Repository: swaywm/sway Branch: master Commit: 82227d610394 Files: 378 Total size: 1.8 MB Directory structure: gitextract_b61p9gre/ ├── .builds/ │ ├── alpine.yml │ ├── archlinux.yml │ └── freebsd.yml ├── .editorconfig ├── .github/ │ └── ISSUE_TEMPLATE/ │ ├── bug_report.md │ ├── config.yml │ ├── enhancement.md │ └── i3_compat.md ├── .gitignore ├── .mailmap ├── CONTRIBUTING.md ├── LICENSE ├── README.ar.md ├── README.az.md ├── README.cs.md ├── README.de.md ├── README.dk.md ├── README.es.md ├── README.fr.md ├── README.ge.md ├── README.gr.md ├── README.hi.md ├── README.hu.md ├── README.ir.md ├── README.it.md ├── README.ja.md ├── README.ko.md ├── README.md ├── README.nl.md ├── README.no.md ├── README.pl.md ├── README.pt.md ├── README.ro.md ├── README.ru.md ├── README.sr.md ├── README.sv.md ├── README.tr.md ├── README.uk.md ├── README.zh-CN.md ├── README.zh-TW.md ├── assets/ │ └── LICENSE ├── client/ │ ├── meson.build │ └── pool-buffer.c ├── common/ │ ├── cairo.c │ ├── gesture.c │ ├── ipc-client.c │ ├── list.c │ ├── log.c │ ├── loop.c │ ├── meson.build │ ├── pango.c │ ├── stringop.c │ └── util.c ├── completions/ │ ├── bash/ │ │ ├── sway │ │ ├── swaybar │ │ └── swaymsg │ ├── fish/ │ │ ├── sway.fish │ │ ├── swaymsg.fish │ │ └── swaynag.fish │ ├── meson.build │ └── zsh/ │ ├── _sway │ ├── _swaybar │ └── _swaymsg ├── config.in ├── include/ │ ├── cairo_util.h │ ├── gesture.h │ ├── ipc-client.h │ ├── ipc.h │ ├── list.h │ ├── log.h │ ├── loop.h │ ├── meson.build │ ├── pango.h │ ├── pool-buffer.h │ ├── stringop.h │ ├── sway/ │ │ ├── commands.h │ │ ├── config.h │ │ ├── criteria.h │ │ ├── decoration.h │ │ ├── desktop/ │ │ │ ├── idle_inhibit_v1.h │ │ │ ├── launcher.h │ │ │ └── transaction.h │ │ ├── input/ │ │ │ ├── cursor.h │ │ │ ├── input-manager.h │ │ │ ├── keyboard.h │ │ │ ├── libinput.h │ │ │ ├── seat.h │ │ │ ├── switch.h │ │ │ ├── tablet.h │ │ │ ├── text_input.h │ │ │ └── text_input_popup.h │ │ ├── ipc-json.h │ │ ├── ipc-server.h │ │ ├── layers.h │ │ ├── lock.h │ │ ├── output.h │ │ ├── scene_descriptor.h │ │ ├── server.h │ │ ├── sway_text_node.h │ │ ├── swaynag.h │ │ ├── tree/ │ │ │ ├── arrange.h │ │ │ ├── container.h │ │ │ ├── node.h │ │ │ ├── root.h │ │ │ ├── view.h │ │ │ └── workspace.h │ │ ├── xdg_decoration.h │ │ └── xwayland.h │ ├── swaybar/ │ │ ├── bar.h │ │ ├── config.h │ │ ├── i3bar.h │ │ ├── image.h │ │ ├── input.h │ │ ├── ipc.h │ │ ├── render.h │ │ ├── status_line.h │ │ └── tray/ │ │ ├── host.h │ │ ├── icon.h │ │ ├── item.h │ │ ├── tray.h │ │ └── watcher.h │ ├── swaynag/ │ │ ├── config.h │ │ ├── render.h │ │ ├── swaynag.h │ │ └── types.h │ └── util.h ├── meson.build ├── meson_options.txt ├── protocols/ │ ├── meson.build │ ├── wlr-layer-shell-unstable-v1.xml │ └── wlr-output-power-management-unstable-v1.xml ├── release.sh ├── sway/ │ ├── commands/ │ │ ├── allow_tearing.c │ │ ├── assign.c │ │ ├── bar/ │ │ │ ├── bind.c │ │ │ ├── binding_mode_indicator.c │ │ │ ├── colors.c │ │ │ ├── font.c │ │ │ ├── gaps.c │ │ │ ├── height.c │ │ │ ├── hidden_state.c │ │ │ ├── icon_theme.c │ │ │ ├── id.c │ │ │ ├── mode.c │ │ │ ├── modifier.c │ │ │ ├── output.c │ │ │ ├── pango_markup.c │ │ │ ├── position.c │ │ │ ├── separator_symbol.c │ │ │ ├── status_command.c │ │ │ ├── status_edge_padding.c │ │ │ ├── status_padding.c │ │ │ ├── strip_workspace_name.c │ │ │ ├── strip_workspace_numbers.c │ │ │ ├── swaybar_command.c │ │ │ ├── tray_bind.c │ │ │ ├── tray_output.c │ │ │ ├── tray_padding.c │ │ │ ├── workspace_buttons.c │ │ │ ├── workspace_min_width.c │ │ │ └── wrap_scroll.c │ │ ├── bar.c │ │ ├── bind.c │ │ ├── border.c │ │ ├── client.c │ │ ├── create_output.c │ │ ├── default_border.c │ │ ├── default_floating_border.c │ │ ├── default_orientation.c │ │ ├── exec.c │ │ ├── exec_always.c │ │ ├── exit.c │ │ ├── floating.c │ │ ├── floating_minmax_size.c │ │ ├── floating_modifier.c │ │ ├── focus.c │ │ ├── focus_follows_mouse.c │ │ ├── focus_on_window_activation.c │ │ ├── focus_wrapping.c │ │ ├── font.c │ │ ├── for_window.c │ │ ├── force_display_urgency_hint.c │ │ ├── force_focus_wrapping.c │ │ ├── fullscreen.c │ │ ├── gaps.c │ │ ├── gesture.c │ │ ├── hide_edge_borders.c │ │ ├── include.c │ │ ├── inhibit_idle.c │ │ ├── input/ │ │ │ ├── accel_profile.c │ │ │ ├── calibration_matrix.c │ │ │ ├── click_method.c │ │ │ ├── clickfinger_button_map.c │ │ │ ├── drag.c │ │ │ ├── drag_lock.c │ │ │ ├── dwt.c │ │ │ ├── dwtp.c │ │ │ ├── events.c │ │ │ ├── left_handed.c │ │ │ ├── map_from_region.c │ │ │ ├── map_to_output.c │ │ │ ├── map_to_region.c │ │ │ ├── middle_emulation.c │ │ │ ├── natural_scroll.c │ │ │ ├── pointer_accel.c │ │ │ ├── repeat_delay.c │ │ │ ├── repeat_rate.c │ │ │ ├── rotation_angle.c │ │ │ ├── scroll_button.c │ │ │ ├── scroll_button_lock.c │ │ │ ├── scroll_factor.c │ │ │ ├── scroll_method.c │ │ │ ├── tap.c │ │ │ ├── tap_button_map.c │ │ │ ├── tool_mode.c │ │ │ ├── xkb_capslock.c │ │ │ ├── xkb_file.c │ │ │ ├── xkb_layout.c │ │ │ ├── xkb_model.c │ │ │ ├── xkb_numlock.c │ │ │ ├── xkb_options.c │ │ │ ├── xkb_rules.c │ │ │ ├── xkb_switch_layout.c │ │ │ └── xkb_variant.c │ │ ├── input.c │ │ ├── kill.c │ │ ├── layout.c │ │ ├── mark.c │ │ ├── max_render_time.c │ │ ├── mode.c │ │ ├── mouse_warping.c │ │ ├── move.c │ │ ├── new_float.c │ │ ├── new_window.c │ │ ├── no_focus.c │ │ ├── nop.c │ │ ├── opacity.c │ │ ├── output/ │ │ │ ├── adaptive_sync.c │ │ │ ├── allow_tearing.c │ │ │ ├── background.c │ │ │ ├── color_profile.c │ │ │ ├── disable.c │ │ │ ├── dpms.c │ │ │ ├── enable.c │ │ │ ├── hdr.c │ │ │ ├── max_render_time.c │ │ │ ├── mode.c │ │ │ ├── position.c │ │ │ ├── power.c │ │ │ ├── render_bit_depth.c │ │ │ ├── scale.c │ │ │ ├── scale_filter.c │ │ │ ├── subpixel.c │ │ │ ├── toggle.c │ │ │ ├── transform.c │ │ │ └── unplug.c │ │ ├── output.c │ │ ├── popup_during_fullscreen.c │ │ ├── primary_selection.c │ │ ├── reload.c │ │ ├── rename.c │ │ ├── resize.c │ │ ├── scratchpad.c │ │ ├── seat/ │ │ │ ├── attach.c │ │ │ ├── cursor.c │ │ │ ├── fallback.c │ │ │ ├── hide_cursor.c │ │ │ ├── idle.c │ │ │ ├── keyboard_grouping.c │ │ │ ├── pointer_constraint.c │ │ │ ├── shortcuts_inhibitor.c │ │ │ └── xcursor_theme.c │ │ ├── seat.c │ │ ├── set.c │ │ ├── shortcuts_inhibitor.c │ │ ├── show_marks.c │ │ ├── smart_borders.c │ │ ├── smart_gaps.c │ │ ├── split.c │ │ ├── sticky.c │ │ ├── swap.c │ │ ├── swaybg_command.c │ │ ├── swaynag_command.c │ │ ├── tiling_drag.c │ │ ├── tiling_drag_threshold.c │ │ ├── title_align.c │ │ ├── title_format.c │ │ ├── titlebar_border_thickness.c │ │ ├── titlebar_padding.c │ │ ├── unmark.c │ │ ├── urgent.c │ │ ├── workspace.c │ │ ├── workspace_layout.c │ │ ├── ws_auto_back_and_forth.c │ │ └── xwayland.c │ ├── commands.c │ ├── config/ │ │ ├── bar.c │ │ ├── input.c │ │ ├── output.c │ │ └── seat.c │ ├── config.c │ ├── criteria.c │ ├── decoration.c │ ├── desktop/ │ │ ├── idle_inhibit_v1.c │ │ ├── launcher.c │ │ ├── layer_shell.c │ │ ├── output.c │ │ ├── tearing.c │ │ ├── transaction.c │ │ ├── xdg_shell.c │ │ └── xwayland.c │ ├── input/ │ │ ├── cursor.c │ │ ├── input-manager.c │ │ ├── keyboard.c │ │ ├── libinput.c │ │ ├── seat.c │ │ ├── seatop_default.c │ │ ├── seatop_down.c │ │ ├── seatop_move_floating.c │ │ ├── seatop_move_tiling.c │ │ ├── seatop_resize_floating.c │ │ ├── seatop_resize_tiling.c │ │ ├── switch.c │ │ ├── tablet.c │ │ └── text_input.c │ ├── ipc-json.c │ ├── ipc-server.c │ ├── lock.c │ ├── main.c │ ├── meson.build │ ├── realtime.c │ ├── scene_descriptor.c │ ├── server.c │ ├── sway-bar.5.scd │ ├── sway-input.5.scd │ ├── sway-ipc.7.scd │ ├── sway-output.5.scd │ ├── sway.1.scd │ ├── sway.5.scd │ ├── sway_text_node.c │ ├── swaynag.c │ ├── tree/ │ │ ├── arrange.c │ │ ├── container.c │ │ ├── node.c │ │ ├── output.c │ │ ├── root.c │ │ ├── view.c │ │ └── workspace.c │ ├── xdg_activation_v1.c │ └── xdg_decoration.c ├── sway.desktop ├── swaybar/ │ ├── bar.c │ ├── config.c │ ├── i3bar.c │ ├── image.c │ ├── input.c │ ├── ipc.c │ ├── main.c │ ├── meson.build │ ├── render.c │ ├── status_line.c │ ├── swaybar-protocol.7.scd │ └── tray/ │ ├── host.c │ ├── icon.c │ ├── item.c │ ├── tray.c │ └── watcher.c ├── swaymsg/ │ ├── main.c │ ├── meson.build │ └── swaymsg.1.scd └── swaynag/ ├── config.c ├── main.c ├── meson.build ├── render.c ├── swaynag.1.scd ├── swaynag.5.scd ├── swaynag.c └── types.c ================================================ FILE CONTENTS ================================================ ================================================ FILE: .builds/alpine.yml ================================================ image: alpine/edge packages: - cairo-dev - eudev-dev - gdk-pixbuf-dev - json-c-dev - lcms2-dev - libdisplay-info-dev - libevdev-dev - libinput-dev - libseat-dev - libxcb-dev - libxkbcommon-dev - mesa-dev - meson - pango-dev - pcre2-dev - pixman-dev - scdoc - wayland-dev - wayland-protocols - xcb-util-image-dev - xcb-util-wm-dev - xwayland-dev - hwdata-dev sources: - https://github.com/swaywm/sway - https://gitlab.freedesktop.org/wlroots/wlroots.git tasks: - wlroots: | cd wlroots meson setup --prefix=/usr build -Dexamples=false ninja -C build sudo ninja -C build install - setup: | cd sway meson setup build --fatal-meson-warnings -Dauto_features=enabled -Dtray=disabled - build: | cd sway ninja -C build - build-no-xwayland: | cd wlroots meson configure build -Dxwayland=disabled ninja -C build sudo ninja -C build install cd ../sway meson configure build --clearcache ninja -C build - build-static: | cd sway mkdir subprojects ln -s ../../wlroots subprojects/wlroots rm -rf build meson setup build --fatal-meson-warnings --default-library=static --force-fallback-for=wlroots ninja -C build ================================================ FILE: .builds/archlinux.yml ================================================ image: archlinux packages: - cairo - gdk-pixbuf2 - json-c - lcms2 - libdisplay-info - libegl - libinput - libxcb - libxkbcommon - meson - pango - pcre2 - scdoc - wayland - wayland-protocols - xcb-util-image - xcb-util-wm - xorg-xwayland - seatd - hwdata sources: - https://github.com/swaywm/sway - https://gitlab.freedesktop.org/wlroots/wlroots.git tasks: - wlroots: | cd wlroots meson setup --prefix=/usr build -Dexamples=false ninja -C build sudo ninja -C build install - setup: | cd sway meson setup build --fatal-meson-warnings -Dauto_features=enabled -Dsd-bus-provider=libsystemd - build: | cd sway ninja -C build ================================================ FILE: .builds/freebsd.yml ================================================ image: freebsd/latest packages: - devel/basu - devel/json-c - devel/libevdev - devel/meson - devel/pcre2 - devel/pkgconf - graphics/cairo - graphics/gdk-pixbuf2 - graphics/lcms2 - graphics/wayland - graphics/wayland-protocols - textproc/scdoc - x11-toolkits/pango - x11/libxcb - x11/libxkbcommon # wlroots dependencies - devel/evdev-proto - devel/libepoll-shim - devel/libudev-devd - graphics/libdrm - graphics/mesa-libs - sysutils/libdisplay-info - sysutils/seatd - x11/libinput - x11/libX11 - x11/pixman - x11/xcb-util-wm - x11-servers/xwayland - misc/hwdata sources: - https://github.com/swaywm/sway - https://gitlab.freedesktop.org/wlroots/wlroots.git tasks: - setup: | cd sway mkdir subprojects cd subprojects ln -s ../../wlroots wlroots cd .. meson setup build --fatal-meson-warnings -Dtray=enabled -Dsd-bus-provider=basu - build: | cd sway ninja -C build ================================================ FILE: .editorconfig ================================================ # For the full list of code style requirements, see CONTRIBUTING.md root = true [*] charset = utf-8 end_of_line = lf [*.{c,h,cmake,txt}] indent_style = tab indent_size = 4 [*.{xml,yml}] indent_style = space indent_size = 2 [config] indent_style = space indent_size = 4 [*.md] trim_trailing_whitespace = false ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bugs about: Crashes and other bugs labels: 'bug' --- ### Please read the following before submitting: - Please do NOT submit bug reports for questions. Ask questions on IRC at #sway on Libera Chat. - Proprietary graphics drivers, including nvidia, are not supported. Please use the open source equivalents, such as nouveau, if you would like to use Sway. - Please do NOT submit issues for information from the github wiki. The github wiki is community maintained and therefore may contain outdated information, scripts that don't work or obsolete workarounds. If you fix a script or find outdated information, don't hesitate to adjust the wiki page. ### Please fill out the following: - **Sway Version:** - `swaymsg -t get_version` or `sway -v` - **Debug Log:** - Run `sway -d 2> ~/sway.log` from a TTY and upload it to a pastebin, such as gist.github.com. - This will record information about sway's activity. Please try to keep the reproduction as brief as possible and exit sway. - Attach the **full** file, do not truncate it. - **Configuration File:** - Please try to produce with the default configuration. - If you cannot reproduce with the default configuration, please try to find the minimal configuration to reproduce. - Upload the config to a pastebin such as gist.github.com. - **Stack Trace:** - This is only needed if sway crashes. - If you use systemd, you should be able to open the coredump of the most recent crash with gdb with `coredumpctl gdb sway` and then `bt full` to obtain the stack trace. - If the lines mentioning sway or wlroots have `??` for the location, your binaries were built without debug symbols. Please compile both sway and wlroots from source and try to reproduce. - **Description:** - The steps you took in plain English to reproduce the problem. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Questions url: "https://libera.chat" about: "Please ask questions on IRC in #sway on Libera Chat" ================================================ FILE: .github/ISSUE_TEMPLATE/enhancement.md ================================================ --- name: Enhancements about: New functionality labels: 'enhancement' --- ### Please read the following before submitting: - We are not accepting any new window management features unless they get implemented by i3. ### Please fill out the following: - **Description:** Please describe in plain English what the enhancement is and what the use case is. ================================================ FILE: .github/ISSUE_TEMPLATE/i3_compat.md ================================================ --- name: i3 Compatibility about: Sway behaves differently from or lacks i3 functionality labels: 'i3-compat' --- ### Please read the following before submitting: - The following either do not make sense for Wayland or we have decided against supporting: - `restart` - `resource` - saving and loading layouts - the i3 sync protocol ### Please fill out the following: - **i3 PR:** - If this is new i3 functionality, please add a link to the i3 pull request. - **Description:** - Please describe in plain English how Sway and i3's behaviors differ or what functionality Sway is lacking. ================================================ FILE: .gitignore ================================================ install_manifest.txt *.swp *.o *.a bin/ test/ build/ build-*/ .cache/ .lvimrc config-debug wayland-*-protocol.* /subprojects/wlroots subprojects ================================================ FILE: .mailmap ================================================ Ronan Pigott ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to sway Contributing just involves sending a pull request. You will probably be more successful with your contribution if you visit #sway-devel on Libera Chat upfront and discuss your plans. Note: rules are made to be broken. Adjust or ignore any/all of these as you see fit, but be prepared to justify it to your peers. ## Scope of future changes to sway **Important**: Sway has completed its core value proposition: it is a fully featured Wayland-compatible replacement for i3. It is not our intention to expand on the scope of what i3 aims to accomplish. Our priorities now are increasing the stability, reliability, and performance of sway within its current scope. For this reason, most new window management feature requests are not accepted, even if accompanied by a patch. ## Pull Requests If you already have your own pull request habits, feel free to use them. If you don't, however, allow me to make a suggestion: feature branches pulled from upstream. Try this: 1. Fork sway 2. `git clone https://github.com/username/sway && cd sway` 3. `git remote add upstream https://github.com/swaywm/sway` You only need to do this once. You're never going to use your fork's master branch. Instead, when you start working on a feature, do this: 1. `git fetch upstream` 2. `git checkout -b add-so-and-so-feature upstream/master` 3. Add and commit your changes 4. `git push -u origin add-so-and-so-feature` 5. Make a pull request from your feature branch When you submit your pull request, your commit log should do most of the talking when it comes to describing your changes and their motivation. In addition to this, your pull request's comments will ideally include a test plan that the reviewers can use to (1) demonstrate the problem on master, if applicable and (2) verify that the problem no longer exists with your changes applied (or that your new features work correctly). Document all of the edge cases you're aware of so we can adequately test them - then verify the test plan yourself before submitting. ## Commit Messages Please strive to write good commit messages. Here's some guidelines to follow: The first line should be limited to 50 characters and should be a sentence that completes the thought [When applied, this commit will...] *"Implement cmd_move"* or *"Fix #742"* or *"Improve performance of arrange_windows on ARM"* or similar. The subsequent lines should be separated from the subject line by a single blank line, and include optional details. In this you can give justification for the change, [reference Github issues](https://help.github.com/articles/closing-issues-via-commit-messages/), or explain some of the subtler details of your patch. This is important because when someone finds a line of code they don't understand later, they can use the `git blame` command to find out what the author was thinking when they wrote it. It's also easier to review your pull requests if they're separated into logical commits that have good commit messages and justify themselves in the extended commit description. As a good rule of thumb, anything you might put into the pull request description on Github is probably fair game for going into the extended commit message as well. See [here](https://chris.beams.io/posts/git-commit/) for more details. ## Code Review When your changes are submitted for review, one or more core committers will look over them. Smaller changes might be merged with little fanfare, but larger changes will typically see review from several people. Be prepared to receive some feedback - you may be asked to make changes to your work. Our code review process is: 1. **Triage** the pull request. Do the commit messages make sense? Is a test plan necessary and/or present? Add anyone as reviewers that you think should be there (using the relevant GitHub feature, if you have the permissions, or with an @mention if necessary). 2. **Review** the code. Look for code style violations, naming convention violations, buffer overflows, memory leaks, logic errors, non-portable code (including GNU-isms), etc. For significant changes to the public API, loop in a couple more people for discussion. 3. **Execute** the test plan, if present. 4. **Merge** the pull request when all reviewers approve. 5. **File** follow-up tickets if appropriate. ## Style Reference Sway is written in C with a style similar to the [kernel style](https://www.kernel.org/doc/Documentation/process/coding-style.rst), but with a few notable differences. Try to keep your code conforming to C11 and POSIX as much as possible, and do not use GNU extensions. ### Brackets Brackets always go on the same line, including in functions. Always include brackets for if/while/for, even if it's a single statement. ```c void function(void) { if (condition1) { do_thing1(); } if (condition2) { do_thing2(); } else { do_thing3(); } } ``` ### Indentation Indentations are a single tab. For long lines that need to be broken, the continuation line should be indented with an additional tab. If the line being broken is opening a new block (functions, if, while, etc.), the continuation line should be indented with two tabs, so they can't be misread as being part of the block. ```c really_long_function(argument1, argument2, ..., argument3, argument4); if (condition1 && condition2 && ... condition3 && condition4) { do_thing(); } ``` Try to break the line in the place which you think is the most appropriate to balance the lines. ### Line Length Try to keep your lines under 80 columns, assuming a tab width equal to 4 spaces, but you can go up to 100 if it improves readability. Don't break lines indiscriminately, try to find nice breaking points so your code is easy to read. ### Names Global function and type names should be prefixed with `sway_submodule_` (e.g. `struct sway_output`, `sway_output_destroy`). For static functions and types local to a file, the names chosen aren't as important. Static functions shouldn't have a `sway_` prefix. For include guards, use the header's filename relative to include. Uppercase all of the characters, and replace any invalid characters with an underscore. ### Construction/Destruction Functions For functions that are responsible for constructing and destructing an object, they should be written as a pair of one of two forms: * `init`/`finish`: These initialize/deinitialize a type, but are **NOT** responsible for allocating it. They should accept a pointer to some pre-allocated memory (e.g. a member of a struct). * `create`/`destroy`: These also initialize/deinitialize, but will return a pointer to a `malloc`ed chunk of memory, and will `free` it in `destroy`. A destruction function should always be able to accept a NULL pointer or a zeroed value and exit cleanly; this simplifies error handling a lot. ### Error Codes For functions not returning a value, they should return a (stdbool.h) bool to indicated if they succeeded or not. ### Macros Keep the use of macros to a minimum, especially if a function can do the job. If you do need to use them, try to keep them close to where they're being used and `#undef` them after. ### Example ```c struct wlr_backend *wlr_backend_autocreate(struct wl_display *display) { struct wlr_backend *backend; if (getenv("WAYLAND_DISPLAY") || getenv("_WAYLAND_DISPLAY")) { backend = attempt_wl_backend(display); if (backend) { return backend; } } const char *x11_display = getenv("DISPLAY"); if (x11_display) { return wlr_x11_backend_create(display, x11_display); } // Attempt DRM+libinput struct wlr_session *session = wlr_session_create(display); if (!session) { wlr_log(WLR_ERROR, "Failed to start a DRM session"); return NULL; } int gpu = wlr_session_find_gpu(session); if (gpu == -1) { wlr_log(WLR_ERROR, "Failed to open DRM device"); goto error_session; } backend = wlr_multi_backend_create(session); if (!backend) { goto error_gpu; } struct wlr_backend *libinput = wlr_libinput_backend_create(display, session); if (!libinput) { goto error_multi; } struct wlr_backend *drm = wlr_drm_backend_create(display, session, gpu); if (!drm) { goto error_libinput; } wlr_multi_backend_add(backend, libinput); wlr_multi_backend_add(backend, drm); return backend; error_libinput: wlr_backend_destroy(libinput); error_multi: wlr_backend_destroy(backend); error_gpu: wlr_session_close_file(session, gpu); error_session: wlr_session_destroy(session); return NULL; } ``` ================================================ FILE: LICENSE ================================================ Copyright (c) 2016-2017 Drew DeVault 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. ================================================ FILE: README.ar.md ================================================ # sway sway هو مدير للمجموعات المركبة لـ[Wayland] متوافق مع [i3] - إقرأ [الأسئلة الشائعة](https://github.com/swaywm/sway/wiki) انضم الى [قناة IRC](https://web.libera.chat/gamja/?channels=#sway) ## تواقيع الإصدار تٌوقع الإصدارات بـواسطة [E88F5E48] و تُنشر على [GitHub](https://github.com/swaywm/sway/releases) ## التثبيت ### بإستخدام الحزم يتوفر Sway للعديد من التوزيعات، حاول تثبيت حزمة "sway" لتوزيعتك ### التجميع من المصدر إطلع على [صفحة الويكي هذه](https://github.com/swaywm/sway/wiki/Development-Setup) إذا أردت بناء الـHEAD من sway و wlroots لأغراض الفحص والتطوير تثبيت اللوازم: * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (optional: system tray) * [scdoc] (optional: man pages) \* * git (optional: version info) \* _\* Compile-time dep_ نفذ هذه الأوامر: meson setup build/ ninja -C build/ sudo ninja -C build/ install ## الإعدادات إذا كنت بالفعل تستخدم i3، فعليك نسخ إعدادات i3 لديك إلى `~/.config/sway/config` وسوف تعمل تلقائياً. و إلا عليك نسخ ملف الإعدادات النموذج إلى `config/sway/config` الموجود عادةً في `/etc/sway/config.` ## التشغيل شغل `sway` بإستخدام أمر TTY. قد يعمل بعض مدراء العرض مع أنهم غير مدعومون من sway (gdm مثلاً يعمل بشكل جيد إلى حد ما) [en]: https://github.com/swaywm/sway#readme [ar]: README.ar.md [cs]: README.cs.md [de]: README.de.md [dk]: README.dk.md [es]: README.es.md [fr]: README.fr.md [ge]: README.ge.md [gr]: README.gr.md [hi]: README.hi.md [hu]: README.hu.md [ir]: README.ir.md [it]: README.it.md [ja]: README.ja.md [ko]: README.ko.md [nl]: README.nl.md [no]: README.no.md [pl]: README.pl.md [pt]: README.pt.md [ro]: README.ro.md [ru]: README.ru.md [sv]: README.sv.md [tr]: README.tr.md [uk]: README.uk.md [zh-CN]: README.zh-CN.md [zh-TW]: README.zh-TW.md [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [FAQ]: https://github.com/swaywm/sway/wiki [IRC channel]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.az.md ================================================ # sway sway [i3]-ə uyğun [Wayland] kompozitorudur. [Tez-tez verilən sualları] oxuyun. [IRC kanalına] qoşulun ("irc.libera.chat"-da #sway). ## Buraxılış İmzaları Buraxılışlar [E88F5E48] ilə imzalanıb və [GitHub-da][GitHub releases] dərc edilib. ## Quraşdırma ### Paketlərdən Sway bir çox distributivdə mövcuddur. Öz distributiviniz üçün "sway" paketini quraşdırmağa çalışın. ### Mənbə kodundan kompilyasiya Test və ya inkişaf üçün sway və wlroots-un HEAD-ini qurmaq istəyirsinizsə, [bu viki səhifəsini][Development setup] nəzərdən keçirin. Asılılıqları quraşdırın: * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (ixtiyari: sistem trayı üçün əlavə şəkil formatları) * [swaybg] (ixtiyari: divar kağızı) * [scdoc] (ixtiyari: man səhifələri) \* * git (ixtiyari: versiya məlumatı) \* _\* Kompilyasiya asılılıqları_ Bu əmrləri icra edin: meson setup build/ ninja -C build/ sudo ninja -C build/ install ## Konfiqurasiya Əgər artıq i3-dən istifadə edirsinizsə, i3 konfiqurasiyanızı `~/.config/sway/config` ünvanına köçürün və o, dərhal işləyəcək. Əks halda, nümunə konfiqurasiya faylını `~/.config/sway/config` ünvanına köçürün. O, adətən `/etc/sway/config` ünvanında yerləşir. Konfiqurasiya haqqında məlumat üçün `man 5 sway` əmrini icra edin. ## İşə Salma TTY-dan `sway`-ı işə salın. Bəzi ekran menecerləri işləyə bilər, lakin sway tərəfindən dəstəklənmir (gdm-in kifayət qədər yaxşı işlədiyi məlumdur). [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [Tez-tez verilən sualları]: https://github.com/swaywm/sway/wiki [IRC kanalına]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [swaybg]: https://github.com/swaywm/swaybg/ [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.cs.md ================================================ # sway sway je [waylandový][Wayland] kompozitor kompatibilní s [i3]. Přečtěte si [FAQ (anglicky)][FAQ]. Připojte se na [IRC kanál (anglicky)][IRC channel] \(#sway na irc.libera.chat). ## Podpisy vydání Vydané verze jsou podepsány klíčem [E88F5E48] a publikovány [na GitHubu][GitHub releases]. ## Instalace ### Z balíků Sway je dostupný v mnoha distribucích. Zkuste v té vaší nainstalovat balík "sway". ### Kompilace ze zdrojových kódů Pokud chcete sestavit HEAD repozitáře sway a wlroots pro testování nebo vývoj, použijte návod na [této stránce na wiki (anglicky)][Development setup]. Nainstalujte závislosti: * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (volitelné: dodatečné formáty ikon pro oznamovací oblast) * [swaybg] (volitelné: tapeta plochy) * [scdoc] (volitelné: man stránky) \* * git (volitelné: informace o verzi) \* _\* Závislost pouze pro kompilaci_ Spusťte tyto příkazy: meson setup build/ ninja -C build/ sudo ninja -C build/ install ## Konfigurace Pokud již používáte i3, zkopírujte svou konfiguraci i3 do `~/.config/sway/config` a ta bude ihned fungovat. Jinak zkopírujte do `~/.config/sway/config` ukázkový konfigurační soubor. Ten se obvykle nachází v `/etc/sway/config`. Pro více informací o konfiguraci spusťte `man 5 sway`. ## Spuštění Spusťte `sway` z TTY nebo ze správce displeje. [en]: https://github.com/swaywm/sway#readme [ar]: README.ar.md [cs]: README.cs.md [de]: README.de.md [dk]: README.dk.md [es]: README.es.md [fr]: README.fr.md [ge]: README.ge.md [gr]: README.gr.md [hi]: README.hi.md [hu]: README.hu.md [ir]: README.ir.md [it]: README.it.md [ja]: README.ja.md [ko]: README.ko.md [nl]: README.nl.md [no]: README.no.md [pl]: README.pl.md [pt]: README.pt.md [ro]: README.ro.md [ru]: README.ru.md [sv]: README.sv.md [tr]: README.tr.md [uk]: README.uk.md [zh-CN]: README.zh-CN.md [zh-TW]: README.zh-TW.md [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [FAQ]: https://github.com/swaywm/sway/wiki [IRC channel]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [swaybg]: https://github.com/swaywm/swaybg/ [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.de.md ================================================ # Sway Sway ist ein [i3]-kompatibler [Wayland]-Compositor. Lies die [FAQ]. Tritt dem [IRC Channel] bei (#sway on irc.libera.chat; Englisch). ## Signaturen Jeder Release wird mit dem PGP-Schlüssel [E88F5E48] signiert und [auf GitHub][GitHub releases] veröffentlicht. ## Installation ### Über die Paketverwaltung Sway kann in vielen Distributionen direkt durch die Paketverwaltung installiert werden. Versuche einfach das Paket "sway" zu installieren. ### Quellcode selbst kompilieren sway benötigt die folgenden Pakete: * meson \* * [wlroots] * wayland * wayland-protocols\* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (Optional, wird für das Benachrichtigungsfeld (System Tray) benötigt) * [swaybg] (Optional, wird für das Setzen von Desktophintergrundbildern benötigt) * [scdoc] (Optional, wird für die Dokumentation (Man Pages) benötigt)\* * git (Optional: Versionsinfo)\* _\*Werden nur für das Kompilieren benötigt_ Führe die folgenden Befehle aus: meson setup build/ ninja -C build/ sudo ninja -C build/ install Schaue in das [Wiki][Development setup] (Englisch) für Informationen, falls du zum Testen oder Entwickeln den neuesten Stand (HEAD) von sway und wlroots kompilieren willst. ## Konfiguration Falls du von i3 migrierst, kannst du deine Konfigurationsdatei nach `~/.config/sway/config` kopieren und die Einstellungen sollten ohne Weiteres funktionieren. Ansonsten kannst du die Beispielkonfiguration, die normalerweise in `/etc/sway/config` liegt, nach `~/.config/sway/config` kopieren. Die Dokumentation zur Konfigurationsdatei findest du in `man 5 sway`. ## Sway starten Sway kann einfach mit dem Befehl `sway` vom TTY oder mithilfe eines Displaymanagers gestartet werden. [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [FAQ]: https://github.com/swaywm/sway/wiki [IRC channel]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [swaybg]: https://github.com/swaywm/swaybg [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.dk.md ================================================ # Sway Sway er en [i3]-kompatibel [Wayland] compositor. Læs [Ofte stillede spørgsmål]. Deltag på [IRC kanalen][IRC kanal] \(#sway på irc.libera.chat). ## Udgivelses Signaturer Udgivelser er signeret med [E88F5E48] og publiceret [på GitHub][GitHub releases]. ## Installation ### Fra pakker Sway er tilgængelig i mange distributioner. Prøv at installere "sway" pakken fra din. Hvis du er interesseret i at pakke Sway til din distribution, kan du tage forbi IRC kanalen eller sende en email til sir@cmpwn.com for rådgivning. ### Kompilering fra kildekode Se [denne wiki-side][Opsætning til udvikling] hvis du vil bygge HEAD af sway og wlroots til test eller udvikling. Installationsafhængigheder: * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (valgfrit: system tray) * [scdoc] (valgfrit: man pages) \* * git \* _\*Kompileringsafhængighed_ Kør følgende kommandoer: meson setup build ninja -C build sudo ninja -C build install ## Konfiguration Hvis du allerede bruger i3 kan du bare kopiere din i3 konfiguration til `~/.config/sway/config`. Ellers skal du kopiere eksempelkonfigurationsfilen til `~/.config/sway/config`. Den er normalt placeret i `/etc/sway/config`. Kør `man 5 sway` for at få oplysninger om konfigurationen. ## Eksekvering Kør `sway` fra en TTY eller fra en display manager. [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [Ofte stillede spørgsmål]: https://github.com/swaywm/sway/wiki [IRC kanal]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Opsætning til udvikling]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.es.md ================================================ # sway sway es un compositor de [Wayland](http://wayland.freedesktop.org/) compatible con [i3](https://i3wm.org/). Lea el [FAQ](https://github.com/swaywm/sway/wiki). Únase al [canal de IRC](https://web.libera.chat/gamja/?channels=#sway) (#sway on irc.libera.chat). ## Firmas de las versiones Las distintas versiones están firmadas con [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) y publicadas [en GitHub](https://github.com/swaywm/sway/releases). ## Instalación ### Usando paquetes Sway está disponible en muchas distribuciones. Pruebe instalando el paquete "sway" desde la suya. Si no está disponible, puede consultar [esta documentación](https://github.com/swaywm/sway/wiki/Unsupported-packages) y así obtener información acerca de como instalarlo. Si está interesado en crear un paquete para su distribución, únase al canal de IRC o escriba un email a sir@cmpwn.com ### Compilando el código fuente Instale las dependencias: * meson \* * [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (optional: system tray) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (optional: man pages) \* * git \* _\*Compile-time dep_ Desde su consola, ejecute las órdenes: meson setup build ninja -C build sudo ninja -C build install ## Configuración Si ya utiliza i3, copie su archivo de configuración de i3 a `~/.config/sway/config` y sway funcionará sin tener que configurar nada más. En otro caso, copie el archivo de configuración básico a `~/.config/sway/config`, normalmente se encuentra en `/etc/sway/config`. Ejecute `man 5 sway` para obtener información sobre la configuración. ## Ejecución Ejecute `sway` desde su consola. Algunos gestores de pantalla pueden funcionar sin estar soportados por `sway` (sabemos que gdm funciona bastante bien). ================================================ FILE: README.fr.md ================================================ # sway Sway est un compositeur [Wayland] compatible avec [i3]. Lisez la [FAQ]. Rejoignez le [canal IRC] (#sway sur irc.libera.chat). ## Aide en français [abdelq] fournit du support en français sur IRC et Github, dans le fuseau horaire UTC-4 (EST). ## Signatures de nouvelles versions Les nouvelles versions sont signées avec [E88F5E48] et publiées [sur GitHub][versions GitHub]. ## Installation ### À partir de paquets Sway est disponible sur beaucoup de distributions. Essayez d'installer le paquet "sway" pour la vôtre. Si vous êtes intéressé à maintenir Sway pour votre distribution, passez sur le canal IRC ou envoyez un e-mail à sir@cmpwn.com (en anglais seulement) pour des conseils. ### Compilation depuis les sources Consultez [cette page wiki][Configuration de développement] si vous souhaitez compiler la révision HEAD de sway et wlroots pour tester ou développer. Installez les dépendances : * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (optionnel : system tray) * [scdoc] (optionnel : requis pour les pages man) \* * git (optionnel : information de version) \* _\* Requis uniquement pour la compilation_ Exécutez ces commandes : meson setup build ninja -C build sudo ninja -C build install ## Configuration Si vous utilisez déjà i3, copiez votre configuration i3 vers `~/.config/sway/config` et sway fonctionnera directement. Sinon, copiez l'exemple de fichier de configuration vers `~/.config/sway/config`. Il se trouve généralement dans `/etc/sway/config`. Exécutez `man 5 sway` pour lire la documentation pour la configuration de sway. ## Exécution Exécutez `sway` à partir d'un TTY ou d'un gestionnaires d'affichage. [Wayland]: http://wayland.freedesktop.org/ [i3]: https://i3wm.org/ [FAQ]: https://github.com/swaywm/sway/wiki [canal IRC]: https://web.libera.chat/gamja/?channels=#sway [abdelq]: https://github.com/abdelq [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [versions GitHub]: https://github.com/swaywm/sway/releases [Configuration de développement]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.ge.md ================================================ # sway sway არის [i3]-თავსებადი [Wayland]-ის კომპოზიტორი. მეტი ინფორმაციისთვის იხილეთ [FAQ]. დაუკავშირდით [IRC არხს][IRC channel] \(#sway irc.libera.chat-ზე). ## გამოშვების ხელმოწერები გამოშვებები ხელმოწერილია [E88F5E48]-ით და გამოქვეყნებულია [GitHub-ზე][GitHub releases]. ## ინსტალაცია ### რეპოზიტორიიდან Sway არის ხელმისაწვდომი ბევრი დისტრიბუტაციისთვის. ცადეთ "sway" პაკეტის ინსტალაცია თქვენთვის. ### კოდის კომპილაცია იხილეთ [ეს ვიკი გვერდი][Development setup] თუ გინდათ რომ ააწყოთ sway და wlroots სატესტოდ ან დეველოპმენტისთვის. დააინსტალირეთ დამოკიდებულებები: * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (ასევე არჩევითია: system tray) * [scdoc] (ასევე არჩევითია: man pages) \* * git (ასევე არჩევითია: version info) \* _\* Compile-time dep_ გაუშვით ეს ბრძანებები: meson setup build/ ninja -C build/ sudo ninja -C build/ install ## კონფიგურაცია თუ უკვე იყენებთ i3-ს, მაშინ დააკოპირე i3 კონფიგურაცია და ჩასვი `~/.config/sway/config` და უპრობლემოდ იმუშავებს პირდაპირ. წინააღმდეგ შემთხვევაში კონფიგურაციის ნიმუში ჩააკოპირეთ აქ: `~/.config/sway/config`. კომპიგურაციის ნიმუში ხშირ შემთხვევაში არის `/etc/sway/config`. გაუშვი `man 5 sway` კონპიგურაციაზე ინფორმაციის მისაღებად. ## გაშვება გაუშვი `sway` TTY-ისთვის. ზოგიერთმა ლოგინ მენეჯერმა შეიძლება იმუშავოს, მაგრამ არ არის მხარდაჭერილი sway-სგან (როგორც წესი კარგად მუშაობს gdm). [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [FAQ]: https://github.com/swaywm/sway/wiki [IRC channel]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.gr.md ================================================ # Sway Το sway ένα [i3]-συμβατό [Wayland] compositor. Διαβάστε το [FAQ]. Μπείτε στο [IRC channel] \(#sway on irc.libera.chat). ## Υπογραφές δημοσιεύσεων Οι εκδόσεις είναι υπογεραμμένες με [E88F5E48] και δημοσιευμένες [στο GitHub][GitHub releases]. ## Εγκατάσταση ### Από πακέτα Το Sway είναι διαθέσιμο σε πολλά distributions. Δοκιμάστε εγκαταστώντας το "sway" package για το δικό σας. Εάν ενδιαφέρεστε για packaging του sway για το distribution σας, να πάτε στο IRC channel ή στείλτε ένα email στο sir@cmpwn.com για συμβουλές. ### Compiling από πηγή Τσεκάρετε [αυτό το wiki page][Development setup] εάμα θέλετε να κάνετε build το HEAD του sway και wlroots γιά τεστάρισμα ή development. Εγκατάσταση των dependencies: * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (προαιρετικό: system tray) * [scdoc] (προαιρετικό: man pages) \* * git (προαιρετικό: πληροφορίες εκδώσεων) \* _\*Compile-time dep_ Τρέξτε αυτά τα commands: meson setup build/ ninja -C build/ sudo ninja -C build/ install ## Configuration Εάν ήδη χρησιμοποιήτε το i3, αντιγράψτε το i3 config σας στο `~/.config/sway/config` και θα δουλέψει out of the box. Αλλιώς, αντιγράψτε το sample configuration αρχείο στο `~/.config/sway/config`. Το οποίο συνήθως βρίσκεται στο `/etc/sway/config`. Κάντε run `man 5 sway` για πληροφορίες τού configuration. ## Τρέχοντας Τρέξτε `sway` από ένα TTY. Μερίκα display managers μπορεί να δουλέψουν αλλά δέν είναι συμβατά με το sway (το gdm γνωρίζεται να δουλέβει σχετικά καλά). [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [FAQ]: https://github.com/swaywm/sway/wiki [IRC channel]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.hi.md ================================================ # sway sway एक [i3](https://i3wm.org/)-अनुकूल [Wayland](https://wayland.freedesktop.org/) Compositor है। [FAQ](https://github.com/swaywm/sway/wiki) पढिये। [IRC Channel](https://web.libera.chat/gamja/?channels=#sway) (irc.libera.chat पर #sway) में भी जुडिये। ## रिलीज हस्ताक्षर रिलीजें [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) से साइन होतें हैं और [Github पर](https://github.com/swaywm/sway/releases) प्रकाशित होते हैं। ## इंस्टौलेशन ### पैकेजों के द्वारा Sway कई distributions में उप्लब्ध है। आप अपने में "sway" नामक पैकेज इंस्टौल करके देख सकते हैं। ### Source से compile करके यदि आप परीक्षण और विकास के लिए sway और wlroots के नवीनतम संस्करण बनाना चाहते हैं, तो [यह विकी पृष्ठ](https://github.com/swaywm/sway/wiki/Development-Setup) देखें। निर्भरताएं: * meson \* * [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf (वैकल्पिक: system tray के लिये) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (वैकल्पिक: man पृष्ठों के लिये) \* * git (वैकल्पिक: संस्करण जानने के लिये) _\* Compilation के समय आवश्यक_ ये commands चलाएं: meson setup build/ ninja -C build/ sudo ninja -C build/ install ## Configuration अगर आप पहले से ही i3 का उपयोग करते हैं तो अपने i3 config को `~/.config/sway/config` में copy कर लीजिये और वह बिना किसी परिवर्तन के काम करेगा। अन्यथा, नमूने configuration file को `~/.config/sway/config` में copy कर लीजिये। यह सामान्यतः `/etc/sway/config` में पाया जाता है। `man 5 sway` से आप configuration के बारे में जानकारी प्राप्त कर सकते हैं। ## चलाना आप एक tty से `sway` को चला सकते हैं। कुछ display managers काम करते हैं परन्तु ये sway के द्वारा समर्थित नहीं है (gdm के बारे में जाना गया है कि वह सही काम करता है)। ================================================ FILE: README.hu.md ================================================ # sway A Sway egy [i3]-kompatibilis [Wayland]-kompozitor. Olvasd el a [Gyarkan Ismételt Kérdéseket][FAQ]. Csatlakozz az [IRC-csatornához][IRC channel] \(`#sway` az `irc.libera.chat`-en). ## Csomagaláírások A kiadott csomagok az [E88F5E48] kulccsal vannak aláírva, és [GitHubon][GitHub releases] publikálva. ## Telepítés ### Csomagból A Sway sok disztribúció csomagkezelőjéből elérhető, próbáld meg a "sway" csomagot telepíteni az általad használt eszközzel. Ha szeretnél csomagot készíteni a saját disztribúciódhoz, ugorj be az IRC- csatornára, vagy küldj levelet a sir@cmpwn.com címre tanácsokért. ### Fordítás forráskódból Olvasd el [ezt a wikioldalt][Development setup], ha szeretnéd tesztelési vagy fejlesztési célokból lefordítani az aktuális (HEAD) állapotát a `sway`-nek és a `wlroots`-nak. Telepítsd a függőségeket: * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (opcionális: system tray) * [scdoc] (opcionális: man pages) \* * git (opcionális: version info) \* _\*Fordításidejű függőség_ Futtasd ezeket a parancsokat: meson setup build ninja -C build sudo ninja -C build install ## Konfiguráció Ha előzőleg i3-at használtál, akkor átmásolhatod az i3-beállításaidat a `~/.config/sway/config` file-ba és ugyanúgy működni fognak. Egyéb esetben másold le kiindulási alapnak a mintát, ami általában az `etc/sway/config` elérési útvonalon található. Futtasd a `man 5 sway` parancsot további információért a konfigurációval kapcsolatban. ## Futtatás Futtasd a `sway` parancsot egy TTY-felületről. Néhány bejelentkezéskezelő (display manager) működhet, de alapvetően nem támogatottak a sway által. (A gdm-ről ismeretes, hogy egész jól működik.) [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [FAQ]: https://github.com/swaywm/sway/wiki [IRC channel]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.ir.md ================================================ # sway ‏sway یک کامپوزیتور الهام گرفته از [i3](https://i3wm.org/) بر روی [Wayland](http://wayland.freedesktop.org/) است. [سوال‌های متداول](https://github.com/swaywm/sway/wiki) را بخوانید. در [کانال IRC](http://web.libera.chat/gamja/?channels=sway&uio=d4) عضو شوید (‎#sway‏ در irc.libera.chat). برای حمایت از تیم توسعه sway به [صفحه Patreon با نام کاربری SirCmpwn](https://patreon.com/sircmpwn) مراجعه کنید. ## امضای نسخه‌ها امضای نسخه‌ها با [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) در [GitHub](https://github.com/swaywm/sway/releases) منتشر می‌شود. ## شیوه نصب ### از بسته‌های رسمی ‏sway در بسته‌های رسمی توزیع‌های مختلف وجود دارد. بسته «sway» را نصب کنید. در صورتی که بسته رسمی وجود نداشت، برای آگاهی بیشتر درباره نصب روی توزیعتان به این [صفحه راهنما](https://github.com/swaywm/sway/wiki/Unsupported-packages) مراجعه کنید. اگر به ایجاد بسته sway برای توزیعتان علاقه‌مند هستید، از کانال IRC استفاده کنید یا به sir@cmpwn.com ایمیل بزنید. ### کامپایل کردن کد چنانچه می‌خواهید آخرین نسخه کد sway و wlroots را برای آزمایش یا توسعه بسازید به این [صفحه راهنما](https://github.com/swaywm/sway/wiki/Development-Setup) مراجعه کنید. بسته‌های مورد نیاز: * meson \* * [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (انتخابی: برای system tray) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (انتخابی: برای صفحه‌های راهنما) \* * git (انتخابی: برای اطلاع در خصوص نسخه‌ها) \* _\*نیازمندی‌های زمان کامپایل برنامه_ این فرمان‌ها را اجرا کنید: meson setup build ninja -C build sudo ninja -C build install ### شخصی سازی و تنظیمات اگر در حال حاضر از i3 استفاده می‌کنید، تنظیمات i3 خودتان را در فایل ‪`~/.config/sway/config`‬ کپی کنید و بدون نیاز به تغییر کار خواهد کرد. در غیر این‌صورت، فایل نمونه تنظیمات را استفاده کنید. این فایل عموما در ‪`/etc/sway/config`‬ قرار دارد. برای آگاهی بیشتر `man 5 sway` را اجرا کنید. ## اجرا در محیط TTY کافیست `sway` را اجرا کنید. ممکن است ابزارهای مدیریت نمایشگری نیز برای این کار وجود داشته باشند اما از طرف sway پشتیبانی نمی‌شوند (gdm عملکرد خوبی در این زمینه دارد). ================================================ FILE: README.it.md ================================================ # sway sway è un compositore di [Wayland] compatibile con [i3]. Leggi le [FAQ]. Unisciti al [canale IRC] \(#sway su irc.libera.chat). ## Firma delle versioni Le versioni sono firmate con la chiave [E88F5E48] e sono pubblicate [su GitHub][GitHub releases]. ## Installazione ### Da un pacchetto Sway è disponibile in molte distribuzioni, prova a installare il pacchetto "sway" per la tua. ### Compilazione dei sorgenti Consulta [questa pagina del wiki][Development setup] se vuoi compilare l'HEAD di sway e wlroots per testarli o contribuire allo sviluppo. Installa le dipendenze: * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre * json-c * pango * cairo * gdk-pixbuf2 (opzionale: area di notifica) * [scdoc] (opzionale: pagine del manuale) \* * git (opzionale: informazioni sulla versione) \* _\* Dipendenza necessaria per la compilazione_ Esegui questi comandi: meson setup build/ ninja -C build/ sudo ninja -C build/ install ## Configurazione Se hai già usato i3, copia il tuo file di configurazione in `~/.config/sway/config` e sway funzionerà immediatamente. Altrimenti, copia il file d'esempio in `~/.config/sway/config`, generalmente è situato in `/etc/sway/config`. Consulta `man 5 sway` per ulteriori informazioni sulla configurazione. ## Esecuzione Lancia `sway` da un TTY o da un display manager. [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [FAQ]: https://github.com/swaywm/sway/wiki [canale IRC]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.ja.md ================================================ # sway Swayは[i3](https://i3wm.org/)互換な[Wayland](http://wayland.freedesktop.org/)コンポジタです。 [FAQ](https://github.com/swaywm/sway/wiki)も合わせてご覧ください。 [IRC チャンネル](https://web.libera.chat/gamja/?channels=#sway) (irc.libera.chatの#sway)もあります。 [![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png) ## 日本語サポート SirCmpwnは、日本語でのサポートをIRCとGitHubで行います。タイムゾーンはUTC-4です。 ## リリースの署名 Swayのリリースは[E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48)で署名され、[GitHub](https://github.com/swaywm/sway/releases)で公開されています。 ## インストール ### パッケージから Swayは沢山のディストリビューションで提供されています。"sway"パッケージのインストールを試してください。パッケージが存在しない場合は、[このページ](https://github.com/swaywm/sway/wiki/Unsupported-packages)で、あなたのディストリビューションでのインストールに関する情報を調べてください。 あなたのディストリビューションにSwayのパッケージを提供したい場合は、SwayのIRCチャンネルを訪れるか、sir@cmpwn.comにメールを送り、相談してください。 ### ソースコードからコンパイル 次の依存パッケージをインストールしてください: * meson \* * [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (任意: システムイコンで必要です) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (任意: manで必要です) \* * git (任意: バージョン情報で必要です) \* _\*コンパイル時の依存_ 次のコマンドを実行してください: meson setup build ninja -C build sudo ninja -C build install ## 設定 既にi3を使用している場合は、i3の設定ファイルを`~/.config/sway/config`にコピーすれば動きます。そうでない場合は、サンプルの設定ファイルを`~/.config/sway/config`にコピーしてください。サンプルの設定ファイルは、通常`/etc/sway/config`にあります。`man 5 sway`を実行することで、設定に関する情報を見ることができます。 ## 実行 `sway`をTTYまたはディスプレイマネージャから実行してください。 ================================================ FILE: README.ko.md ================================================ # sway sway는 [i3](https://i3wm.org/)-호환 [Wayland](http://wayland.freedesktop.org/) 컴포지터입니다. [FAQ](https://github.com/swaywm/sway/wiki)를 읽어보세요. [IRC 채널](https://web.libera.chat/gamja/?channels=#sway) (#sway on irc.libera.chat)도 있습니다. ## 릴리즈 서명 릴리즈는 [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48)에서 서명되고, [GitHub에서](https://github.com/swaywm/sway/releases) 공개되고 있습니다. ## 설치 ### 패키지를 통한 설치 Sway는 많은 배포판에서 이용할 수 있습니다. "sway" 패키지를 설치해 보세요. 만약 없다면, [위키 페이지](https://github.com/swaywm/sway/wiki/Unsupported-packages)를 확인하세요. 해당 배포판에 대한 설치 정보를 확인할 수 있습니다. 당신의 배포판에 sway 패키지를 제공하고 싶다면, IRC 채널을 방문하거나 sir@cmpwn.com으로 이메일을 보내 상담 받으세요. ### 소스를 통한 컴파일 다음 의존 패키지들을 설치해 주세요: * meson \* * [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (선택: system tray) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (선택: man pages) \* * git \* _\*컴파일 떄 필요_ 다음 명령을 실행하세요: meson setup build ninja -C build sudo ninja -C build install ## 설정 i3를 이미 사용 중이라면, i3 config을 `~/.config/sway/config`로 복사하세요. 아니면, 샘플 구성 파일을 '~/.config/sway/config'에 복사할 수도 있습니다. 일반적으로 "/etc/sway/config"에 위치해 있습니다. 설정에 대한 정보를 보려면 "man 5 sway"를 실행하세요. ## 실행 TTY나 display manager에서 `sway`를 실행하세요. ================================================ FILE: README.md ================================================ # sway **[English][en]** - [عربي][ar] - [Azərbaycanca][az] - [Česky][cs] - [Deutsch][de] - [Dansk][dk] - [Español][es] - [Français][fr] - [ქართული][ge] - [Ελληνικά][gr] - [हिन्दी][hi] - [Magyar][hu] - [فارسی][ir] - [Italiano][it] - [日本語][ja] - [한국어][ko] - [Nederlands][nl] - [Norsk][no] - [Polski][pl] - [Português][pt] - [Română][ro] - [Русский][ru] - [Српски][sr] - [Svenska][sv] - [Türkçe][tr] - [Українська][uk] - [中文-简体][zh-CN] - [中文-繁體][zh-TW] sway is an [i3]-compatible [Wayland] compositor. Read the [FAQ]. Join the [IRC channel] \(#sway on irc.libera.chat). ## Release Signatures Releases are signed with [E88F5E48] and published [on GitHub][GitHub releases]. ## Installation ### From Packages Sway is available in many distributions. Try installing the "sway" package for yours. ### Compiling from Source Check out [this wiki page][Development setup] if you want to build the HEAD of sway and wlroots for testing or development. Install dependencies: * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (optional: additional image formats for system tray) * [swaybg] (optional: wallpaper) * [scdoc] (optional: man pages) \* * git (optional: version info) \* _\* Compile-time dep_ Run these commands: meson setup build/ ninja -C build/ sudo ninja -C build/ install ## Configuration If you already use i3, then copy your i3 config to `~/.config/sway/config` and it'll work out of the box. Otherwise, copy the sample configuration file to `~/.config/sway/config`. It is usually located at `/etc/sway/config`. Run `man 5 sway` for information on the configuration. ## Running Run `sway` from a TTY or from a display manager. [en]: https://github.com/swaywm/sway#readme [ar]: README.ar.md [az]: README.az.md [cs]: README.cs.md [de]: README.de.md [dk]: README.dk.md [es]: README.es.md [fr]: README.fr.md [ge]: README.ge.md [gr]: README.gr.md [hi]: README.hi.md [hu]: README.hu.md [ir]: README.ir.md [it]: README.it.md [ja]: README.ja.md [ko]: README.ko.md [nl]: README.nl.md [no]: README.no.md [pl]: README.pl.md [pt]: README.pt.md [ro]: README.ro.md [ru]: README.ru.md [sr]: README.sr.md [sv]: README.sv.md [tr]: README.tr.md [uk]: README.uk.md [zh-CN]: README.zh-CN.md [zh-TW]: README.zh-TW.md [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [FAQ]: https://github.com/swaywm/sway/wiki [IRC channel]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [swaybg]: https://github.com/swaywm/swaybg/ [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.nl.md ================================================ # sway Sway is een [i3](https://i3wm.org/)-compatibele [Wayland](http://wayland.freedesktop.org/) compositor. Lees de [FAQ](https://github.com/swaywm/sway/wiki). Word lid van het [IRC kanaal](https://web.libera.chat/gamja/?channels=#sway) (#sway op irc.libera.chat). ## Releasehandtekeningen Releases worden ondertekend met [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) en gepubliceerd [op GitHub](https://github.com/swaywm/sway/releases). ## Installatie ### Via een pakket Sway is beschikbaar in vele distributies. Probeer het "sway"-pakket te installeren met jouw pakketbeheerapplicatie. Als het niet beschikbaar is, bekijk dan [deze wikipagina](https://github.com/swaywm/sway/wiki/Unsupported-packages) voor informatie over installatie in jouw distributie. Als je geïnteresseerd bent in het maken van pakketten voor je distributie, stuur een bericht in het IRC- kanaal of stuur een e-mail naar sir@cmpwn.com voor advies. ### Compilatie vanuit broncode Afhankelijkheden installeren: * meson \* * [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (optioneel: systeemtray) * [scdoc](https://git.sr.ht/~ircmpwn/scdoc) (optioneel: manpagina's) \* * git \* _\* Compileerafhankelijkheden_ Voer deze opdrachten uit: meson setup build ninja -C build sudo ninja -C build install ## Configuratie Als je al i3 gebruikt, kopieer dan je i3-configuratie naar `~/.config/sway/config` en het zal zonder verdere configuratie werken. Kopieer anders het voorbeeldconfiguratiebestand naar `~/.config/sway/config`. Dit is meestal het bestand: `/etc/sway/config`. Voer `man 5 sway` uit voor informatie over het configureren van sway. ## Uitvoeren Voer `sway` vanaf een TTY uit. Sommige display-managers kunnen werken, maar worden niet ondersteund door sway (van gdm is bekend dat het redelijk goed werkt). ================================================ FILE: README.no.md ================================================ # Sway Sway er en [i3]-kompatibel [Wayland]-compositor. Les [Ofte stilte spørsmål]. Delta på [IRC-kanalen][IRC-kanal] \(#sway på irc.libera.chat). ## Signaturer Utgivelser er signert med [E88F5E48] og publisert [på GitHub][GitHub releases]. ## Installasjon ### Fra systempakker Sway er tilgjengelig i mange distribusjoner. Prøv å installere pakken "sway" fra din distro sine repoer. ### Kompilering fra kildekode Se [denne wiki-siden][Oppsetting for utvikling] hvis du vil bygge fra HEAD-grenen av sway og wlroots for testing eller utvikling. Installer avhengigheter: * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (valgfritt: støtte for ekstra bildeformater i system tray) * [swaybg] (valgfritt: bakgrunnsbilde) * [scdoc] (valgfritt: man pages) \* * git (valgfritt: versjonsinformasjon) \* _\* Kompileringsavhengigheter_ Kjør følgende kommandoer: meson setup build/ ninja -C build/ sudo ninja -C build/ install ## Konfigurasjon Hvis du allerede bruker i3 kan du bare kopiere din i3-konfigurasjon til `~/.config/sway/config`. Ellers skal du kopiere eksempelkonfigurasjonsfilen til `~/.config/sway/config`. Eksempelfilen er normalt plasert i `/etc/sway/config`. Kjør `man 5 sway` for å få opplysninger om konfigurasjonen. ## Kjøring Kjør `sway` fra en TTY eller fra en display manager. [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [Ofte stilte spørsmål]: https://github.com/swaywm/sway/wiki [IRC-kanal]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Oppsetting for utvikling]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [swaybg]: https://github.com/swaywm/swaybg/ [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.pl.md ================================================ # sway sway jest kompozytorem [Wayland](http://wayland.freedesktop.org/) kompatybilnym z [i3](https://i3wm.org/). Przeczytaj [FAQ](https://github.com/swaywm/sway/wiki). Dołącz do [kanału IRC](https://web.libera.chat/gamja/?channels=#sway) (#sway na irc.libera.chat). ## Podpisy cyfrowe wydań Wydania są podpisywane przy pomocy klucza [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) i publikowane [na GitHubie](https://github.com/swaywm/sway/releases). ## Instalacja ### Z pakietów Sway jest dostępny w wielu dystybucjach. Spróbuj zainstalować pakiet "sway" w swoim menedżerze pakietów. Jeśli nie jest dostępny, sprawdź [tę stronę wiki](https://github.com/swaywm/sway/wiki/Unsupported-packages) aby uzyskać informacje dotyczące instalacji w swojej dystrybucji. Jeśli chciałbyś stworzyć pakiet sway dla swojej dystrybucji, odwiedź kanał IRC lub wyślij email na adres sir@cmpwn.com w celu uzyskania wskazówek. ### Kompilacja ze Źródła Zainstaluj zależności: * meson \* * [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (opcjonalnie: system tray) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (opcjonalnie: strony pomocy man) \* * git \* _\*zależności kompilacji_ Wykonaj następujące polecenia: meson setup build ninja -C build sudo ninja -C build install ## Konfiguracja Jeśli już korzystasz z i3, skopiuj swoją konfigurację i3 do katalogu `~/.config/sway/config` i zadziała od ręki. W przeciwnym razie skopiuj przykładowy plik konfiguracyjny do folderu `~/.config/sway/config`; zazwyczaj znajduje się w `/etc/sway/config`. Wykonaj polecenie `man 5 sway` aby uzyskać informacje dotyczące konfiguracji. ## Uruchamianie Wykonaj polecenie `sway` z poziomu TTY lub menedżera wyświetlania. ================================================ FILE: README.pt.md ================================================ # sway O sway é um compositor do [Wayland](http://wayland.freedesktop.org/) compatível com o [i3](https://i3wm.org/). Leia o [FAQ](https://github.com/swaywm/sway/wiki). Junte-se ao [canal do IRC](https://web.libera.chat/gamja/?channels=#sway) (#sway em irc.libera.chat). ## Assinatura das versões As versões são assinadas com [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) e publicadas [no GitHub](https://github.com/swaywm/sway/releases). ## Instalação ### A partir de pacotes O Sway está disponível em várias distribuições. Tente instalar o pacote "sway" na sua. Caso não esteja disponível, verifique [esta wiki](https://github.com/swaywm/sway/wiki/Unsupported-packages) para se informar a sobre a instalação para sua distribuição. Se você está interessado em criar um pacote do sway para a sua distribuição, verifique canal do IRC ou mande um email para sir@cmpwn.com para obter informações. ### Compilando a partir do código-fonte Verifique [essa página da wiki](https://github.com/swaywm/sway/wiki/Development-Setup) se você quer compilar o HEAD do sway e o wlroots para testes ou desenvolvimento. Instale as dependências: * meson \* * [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (opcional: system tray) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (opcional: man pages) \* * git (opcional: informações de versão) \* _\*Dependência de tempo de compilação_ Execute esses comandos: meson setup build ninja -C build sudo ninja -C build install ## Configuração Se você já utiliza o i3, então copie os seus arquivos de configuração para `~/.config/sway/config` e tudo funcionará normalmente. Caso contrário, copie o arquivo de configuração de exemplo para `~/.config/sway/config`. Normalmente, este arquivo está localizado em `/etc/sway/config`. Execute `man 5 sway` para se informar sobre a configuração. ## Execução Execute o comando `sway` de um TTY. Alguns gerenciadores de display (ou gerenciadores de login) podem funcionar mas alguns não são suportados pelo sway (o gdm é conhecido por funcionar bem). ================================================ FILE: README.ro.md ================================================ # sway sway este un compositor pentru [Wayland](http://wayland.freedesktop.org/) compatibil cu [i3](https://i3wm.org/). Citiți [FAQ](https://github.com/swaywm/sway/wiki)-ul. Connectați-vă la canalul nostru [IRC](https://web.libera.chat/gamja/?channels=#sway) (#sway pe irc.libera.chat). ## Semnarea digitală Noile versiuni sunt semnate cu [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) și postate [pe GitHub](https://github.com/swaywm/sway/releases). ## Instalare ### Din pachete (packages) sway este disponibil în multe distribuții. Încercați să instalați pachetul "sway" pe distribuția voastră. Dacă nu este disponibil, uitați-vă în [această pagină wiki](https://github.com/swaywm/sway/wiki/Unsupported-packages) pentru informații a cum puteți să instalați pentru distribuția voastră. Dacă sunteți interesați in a crea pachete pentru distribuția voastră, informați-ne prin IRC sau contactați prin email pe sir@cmpwn.com pentru ajutor. ### Compilare din sursă Dependențe pentru instalare: * meson \* * [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (opțional, dacă doriți să aveți system tray) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (opțional, pentru paginile man) \* * git (opțional, pentru informații de versiune) \* *Dependențe doar pentru compilare* Rulați aceste comenzi: ``` meson setup build ninja -C build sudo ninja -C build install ``` ## Configurare Dacă folosiți deja i3, copiați fișierul de configurare din i3 în `~/.config/sway/config`, și va funcționa fără a necesita nici o modificare. In caz contrar, copiați exemplul de configurare (disponibil de obicei în `/etc/sway/config`) în `~/.config/sway/config`. Folosiți comanda `man 5 sway` pentru informații despre configurare. ## Lansare Folosiți comanda `sway` într-un TTY. Managerii de display nu sunt suportați de către Sway, dar unii pot functiona (se știe că gdm functioneazâ destul de bine). ================================================ FILE: README.ru.md ================================================ # sway sway - это [i3]-совместимый композитор [Wayland]. Больше информации в [FAQ]. Присоединяйтесь к [IRC-каналу][IRC channel] (#sway на irc.libera.chat). ## Подписи релизов Релизы подписываются ключом [E88F5E48] и публикуются [на GitHub][GitHub releases]. ## Установка ### Из репозиториев Sway доступен во многих дистрибутивах. Попробуйте установить пакет "sway". Если вас интересует создание пакета sway для вашего дистрибутива, зайдите на [IRC-канал][IRC channel] или отправьте письмо на sir@cmpwn.com за советом. ### Сборка из исходников Посетите [эту страницу на вики][Development setup], если вы хотите построить последнюю версию sway и wlroots для тестирования или разработки. Установите зависимости: * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (опционально: для работы трея) * [scdoc] (опционально: для man-страниц) \* * git (опционально: для информации о версии) \* _\*Зависимости для сборки_ Выполните эти команды: meson setup build ninja -C build sudo ninja -C build install ## Настройка Если вы уже используете i3, скопируйте ваш конфигурационный файл i3 в `~/.config/sway/config`, и он сразу же заработает. В противном случае, скопируйте образец конфигурационного файла в `~/.config/sway/config`. Обычно он располагается в `/etc/sway/config`. Запустите `man 5 sway` для изучения информации о настройке. ## Запуск Выполните команду `sway` прямо из TTY или дисплейного менеджера. [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [FAQ]: https://github.com/swaywm/sway/wiki [IRC channel]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.sr.md ================================================ # sway sway је [i3]-компатибилан [Wayland] композитор. Прочитајте [FAQ]. Придружите се [IRC каналу] \(#sway на irc.libera.chat). ## Потписи Издања Издања су потписана са [E88F5E48] и објављена [на GitHub-у][GitHub releases]. ## Инсталација ### Из пакета Sway је доступан у многим дистрибуцијама. Покушајте да инсталирате "sway" пакет за вашу. ### Компајлирање из Извора Погледајте [ову вики страницу][Development setup], ако желите да компајлирате HEAD верзију sway-а и wlroots-а за тестирање или развој. Инсталирајте зависности: * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (опционо: додатни формати слика за системску траку) * [swaybg] (опционо: позадина) * [scdoc] (опционо: man странице) \* * git (опционо: информације о верзији) \* _\* Потребно само за компајлирање_ Покрените следеће команде: meson setup build/ ninja -C build/ sudo ninja -C build/ install ## Конфигурација Ако већ користите i3, копирајте вашу i3 конфигурацију у `~/.config/sway/config` и радиће одмах. У супротном, копирајте пример конфигурационе датотеке у `~/.config/sway/config`. Обично се налази у `/etc/sway/config`. Покрените `man 5 sway` за информације о конфигурацији. ## Покретање Покрените `sway` из TTY-a или из менаџера приказа. [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [FAQ]: https://github.com/swaywm/sway/wiki [IRC каналу]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [swaybg]: https://github.com/swaywm/swaybg/ [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.sv.md ================================================ # sway sway är en [i3]-kompatibel [Wayland] compositor. Läs våran [FAQ]-sida. Gå med i vår [IRC-kanal] \(#sway på irc.libera.chat). ## Utgåvosignaturer Utgåvor är signerade med [E88F5E48] och publicerade på [GitHub][GitHub releases]. ## Installering ### Med pakethanterare Sway är tillgänglig i många distributioner. Prova att installera "sway" med din distributions pakethanterare. ### Genom att kompilera från källkod Kolla in [denna wiki-sida][Development setup] om du vill bygga sway och wlroots HEAD för testning eller utveckling. Installera paket som sway behöver: * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (valbar: systembricka) * [scdoc] (valbar: manualer) \* * git (valbar: versioninfo) \* _\* Krav för kompilering_ Kör dessa kommandon: meson setup build/ ninja -C build/ sudo ninja -C build/ install ## Konfiguration Ifall du redan använder i3 så kan du kopiera din konfigurationsfil till `~/.config/sway/config` och det kommer då att fungera som det ska. Kopiera annars exemplarkonfigurationsfilen till `~/.config/sway/config`. Den ligger oftast i `/etc/sway/config`. Kör `man 5 sway` för mer information kring konfigurationen. ## Att köra sway Kör `sway` från en TTY. Vissa inloggningahanterare kan fungera men inte vara stödda av sway (gdm ska fungera hyfsat bra). [en]: https://github.com/swaywm/sway#readme [de]: README.de.md [dk]: README.dk.md [es]: README.es.md [fr]: README.fr.md [sv]: README.sv.md [gr]: README.gr.md [hu]: README.hu.md [ir]: README.ir.md [it]: README.it.md [ja]: README.ja.md [ko]: README.ko.md [nl]: README.nl.md [pl]: README.pl.md [pt]: README.pt.md [ro]: README.ro.md [ru]: README.ru.md [tr]: README.tr.md [uk]: README.uk.md [zh-CN]: README.zh-CN.md [zh-TW]: README.zh-TW.md [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [FAQ]: https://github.com/swaywm/sway/wiki [IRC-kanal]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.tr.md ================================================ # sway Sway, [i3]-uyumlu bir [Wayland] dizgicisidir. [SSS][FAQ]'yi okuyun. [IRC kanalı][IRC channel]na katılın \(irc.libera.chat'te #sway (İngilizce)). ## Sürüm imzaları Sürümler [E88F5E48] ile imzalandı ve [GitHub][GitHub releases]'da yayınlandı. ## Kurulum ### Paketler ile Sway birçok dağıtımda mevcuttur. Sizinki için "sway" paketini yüklemeyi deneyin. Dağıtımınız için sway'i paketlemekle ilgileniyorsanız, IRC kanalına uğrayın veya tavsiye için sir@cmpwn.com adresine bir e-posta gönderin. ### Kaynak koddan derleme Test veya geliştirme için sway ve wlroots'un HEAD'ini oluşturmak istiyorsanız [bu wiki sayfası][Development setup]na göz atın. Aşağıdaki bağımlılıkları yükleyin: * meson \* * [wlroots] * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (isteğe bağlı: system tray) * [scdoc] (isteğe bağlı: man pages) \* * git (isteğe bağlı: version info) \* _\*Derleme-anı bağımlılıkları_ Şu komutları çalıştırın: meson setup build ninja -C build sudo ninja -C build install ## Yapılandırma Zaten i3 kullanıyorsanız, i3 yapılandırmanızı `~/.config/sway/config` konumuna kopyalayın ve kutudan çıktığı gibi çalışacaktır. Aksi takdirde, örnek yapılandırma dosyasını `~/.config/sway/config` konumuna kopyalayın. Genellikle `/etc/sway/config` konumunda bulunur. Yapılandırma hakkında bilgi almak için `man 5 sway` komutunu çalıştırın. ## Çalıştırma TTY'den `sway` çalıştırın. Bazı görüntü yöneticileriyle(display manager) çalışabilir ama Sway tarafından desteklenmez. (gdm'nin oldukça iyi çalıştığı bilinmektedir.) [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [FAQ]: https://github.com/swaywm/sway/wiki [IRC channel]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [scdoc]: https://git.sr.ht/~sircmpwn/scdoc ================================================ FILE: README.uk.md ================================================ # sway Sway це сумісний з [i3](https://i3wm.org/) композитор [Wayland](http://wayland.freedesktop.org/). Ознайомтесь з [ЧаПами](https://github.com/swaywm/sway/wiki). Приєднуйтесь до [спільноти в IRC](https://web.libera.chat/gamja/?channels=#sway) (#sway на irc.libera.chat). ## Підтримка українською мовою Якщо ви хочете отримати підтримку українською мовою, можете звернутись до користувача Hummer12007 у IRC-спільноті. Будьте терплячі, вам обов'язково допоможуть. Наразі переклад Sway українською ще не завершено (він неповний), проте у вас є шанс долучитись, детальніше див. [статус](https://github.com/swaywm/sway/issues/1318#issuecomment-322277382). ## Підписи випусків Випуски підписані ключем [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) та публікуються на сторінці [GitHub](https://github.com/swaywm/sway/releases). ## Встановлення ### З пакунків Sway доступний у багатьох дистрибутивах Linux (а також у FreeBSD). Спробуйте встановити пакунок `sway` у вашому. Якщо він недоступний, перегляньте цю [сторінку Wiki](https://github.com/swaywm/sway/wiki/Unsupported-packages) для інформації щодо встановлення на вашому дистрибутиві. Якщо ви готові та зацікавлені запакувати і підтримувати Sway у вашому дистрибутиві, звертайтесь за порадами до нашого каналу в IRC або пишіть на електронну пошту [sir@cmpwn.com](mailto:sir@cmpwn.com). ### З вихідного коду Встановіть залежності: * meson \* * [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (optional: system tray) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (необов'язково, необхідно для сторінок man) \* * git \* _\*Лише для компіляції_ Виконайте ці команди: meson setup build ninja -C build sudo ninja -C build install ## Налаштування Якщо ви вже використовуєте i3, скопіюйте свій файл налаштувань до `~/.config/sway/config`, він має запрацювати. Інакше, скопіюйте туди файл-зразок (зазвичай знаходиться у `/etc/sway/config`), і налаштуйте під себе. Більше інформації щодо налаштувань можете знайти, виконавши `man 5 sway`. ## Запуск Виконайте `sway` у TTY. Деякі дисплейні менеджери (менеджери сеансу/стільниць) можуть працювати, але офіційно не підтримуються (проте сумісніть із gdm достатньо висока). ================================================ FILE: README.zh-CN.md ================================================ # sway sway 是和 [i3](https://i3wm.org/) 兼容的 [Wayland](http://wayland.freedesktop.org/) compositor。 [查看FAQ](https://github.com/swaywm/sway/wiki)/ [加入IRC频道](https://web.libera.chat/gamja/?channels=#sway) (#sway on irc.libera.chat) ## 发行签名 每个发行版都以 [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) 的密钥签名并发布在 [GitHub](https://github.com/swaywm/sway/releases)上。 ## 安装 ### 从包管理器安装 Sway 在很多发行版中可用。请尝试在你的发行版中安装 `sway` 。 ### 从源码编译 如果想要构建最新版sway和wlroots用以测试和开发,请查看 [此wiki页面](https://github.com/swaywm/sway/wiki/Development-Setup) 安装如下依赖: * meson \* * [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (可选的: system tray) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (可选: man pages) \* * git \* _\*编译时依赖_ 运行如下命令: meson setup build/ ninja -C build/ sudo ninja -C build/ install ## 配置 如果你已经在使用i3,直接复制i3配置文件到 `~/.config/sway/config`,这是开箱即用的。或者,你可以复制配置样例到`~/.config/sway/config`。它通常位于 `/etc/sway/config`。 运行 `man 5 sway` 获取关于配置的更多信息。 ## 运行 从 TTY 中运行 `sway`。 某些显示管理器(Display Manager)也许可以工作但不被 sway 支持。 (已知 gdm 工作得非常好)。 ================================================ FILE: README.zh-TW.md ================================================ # sway sway 是一個與 [i3](https://i3wm.org/) 相容的 [Wayland](http://wayland.freedesktop.org/) compositor。 閱讀 [FAQ](https://github.com/swaywm/sway/wiki)。 加入 [IRC 頻道](https://web.libera.chat/gamja/?channels=#sway) (#sway on irc.libera.chat) ## 發行簽章 所有發行的版本都會以 [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) 簽署 並發佈於 [GitHub](https://github.com/swaywm/sway/releases) ## 安裝 ### 從套件安裝 Sway 在許多發行版都有提供。請自己嘗試於你的發行版安裝 「sway」這個套件。 如果無法取得,請查看 [這個 wiki 頁面](https://github.com/swaywm/sway/wiki/Unsupported-packages) 以取得更多關於如何於你使用的發行版上安裝的資訊。 如果你想要為你使用的發行版包裝 sway,請到 IRC 頻道或是直接寄封信到 sir@cmpwn.com 來取得一些建議。 ### 從原始碼編譯 相依套件: * meson \* * [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) * wayland * wayland-protocols \* * pcre2 * json-c * pango * cairo * gdk-pixbuf2 (選擇性: system tray) * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (選擇性: man pages) \* * git \* _\*編譯時相依_ 執行這些指令: meson setup build ninja -C build sudo ninja -C build install ## 設定檔 如果你已經在使用 i3,你可以直接將你的 i3 設定檔複製到 `~/.config/sway/config` 然後就能直接使用。 或者你也可以把範例設定檔複製到 `~/.config/sway/config`。 它通常會在 `/etc/sway/config`。 執行 `man 5 sway` 來取得更多關於設定檔的資訊。 ## 執行 在 TTY 執行 `sway`。有些 display manager 可能可以運作但 sway 不提供支援 (已知 gdm 運作的很好) ================================================ FILE: assets/LICENSE ================================================ Creative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. ================================================ FILE: client/meson.build ================================================ lib_sway_client = static_library( 'sway-client', files( 'pool-buffer.c', ), dependencies: [ cairo, pango, pangocairo, wayland_client ], link_with: [lib_sway_common], include_directories: sway_inc ) ================================================ FILE: client/pool-buffer.c ================================================ #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "pool-buffer.h" #include "util.h" static int anonymous_shm_open(void) { int retries = 100; do { // try a probably-unique name struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); pid_t pid = getpid(); char name[50]; snprintf(name, sizeof(name), "/sway-%x-%x", (unsigned int)pid, (unsigned int)ts.tv_nsec); // shm_open guarantees that O_CLOEXEC is set int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd >= 0) { shm_unlink(name); return fd; } --retries; } while (retries > 0 && errno == EEXIST); return -1; } static void buffer_release(void *data, struct wl_buffer *wl_buffer) { struct pool_buffer *buffer = data; buffer->busy = false; } static const struct wl_buffer_listener buffer_listener = { .release = buffer_release }; static struct pool_buffer *create_buffer(struct wl_shm *shm, struct pool_buffer *buf, int32_t width, int32_t height, uint32_t format) { uint32_t stride = width * 4; size_t size = stride * height; int fd = anonymous_shm_open(); if (fd == -1) { return NULL; } if (ftruncate(fd, size) < 0) { close(fd); return NULL; } void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); buf->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format); wl_shm_pool_destroy(pool); close(fd); buf->size = size; buf->width = width; buf->height = height; buf->data = data; buf->surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, width, height, stride); buf->cairo = cairo_create(buf->surface); buf->pango = pango_cairo_create_context(buf->cairo); wl_buffer_add_listener(buf->buffer, &buffer_listener, buf); return buf; } void destroy_buffer(struct pool_buffer *buffer) { if (buffer->buffer) { wl_buffer_destroy(buffer->buffer); } if (buffer->cairo) { cairo_destroy(buffer->cairo); } if (buffer->surface) { cairo_surface_destroy(buffer->surface); } if (buffer->pango) { g_object_unref(buffer->pango); } if (buffer->data) { munmap(buffer->data, buffer->size); } memset(buffer, 0, sizeof(struct pool_buffer)); } struct pool_buffer *get_next_buffer(struct wl_shm *shm, struct pool_buffer pool[static 2], uint32_t width, uint32_t height) { struct pool_buffer *buffer = NULL; for (size_t i = 0; i < 2; ++i) { if (pool[i].busy) { continue; } buffer = &pool[i]; } if (!buffer) { return NULL; } if (buffer->width != width || buffer->height != height) { destroy_buffer(buffer); } if (!buffer->buffer) { if (!create_buffer(shm, buffer, width, height, WL_SHM_FORMAT_ARGB8888)) { return NULL; } } buffer->busy = true; return buffer; } ================================================ FILE: common/cairo.c ================================================ #include #include #include "cairo_util.h" void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { cairo_set_source_rgba(cairo, (color >> (3*8) & 0xFF) / 255.0, (color >> (2*8) & 0xFF) / 255.0, (color >> (1*8) & 0xFF) / 255.0, (color >> (0*8) & 0xFF) / 255.0); } cairo_subpixel_order_t to_cairo_subpixel_order(enum wl_output_subpixel subpixel) { switch (subpixel) { case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: return CAIRO_SUBPIXEL_ORDER_RGB; case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: return CAIRO_SUBPIXEL_ORDER_BGR; case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: return CAIRO_SUBPIXEL_ORDER_VRGB; case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: return CAIRO_SUBPIXEL_ORDER_VBGR; default: return CAIRO_SUBPIXEL_ORDER_DEFAULT; } return CAIRO_SUBPIXEL_ORDER_DEFAULT; } cairo_surface_t *cairo_image_surface_scale(cairo_surface_t *image, int width, int height) { int image_width = cairo_image_surface_get_width(image); int image_height = cairo_image_surface_get_height(image); cairo_surface_t *new = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); cairo_t *cairo = cairo_create(new); cairo_scale(cairo, (double)width / image_width, (double)height / image_height); cairo_set_source_surface(cairo, image, 0, 0); cairo_paint(cairo); cairo_destroy(cairo); return new; } ================================================ FILE: common/gesture.c ================================================ #include "gesture.h" #include #include #include #include #include #include "list.h" #include "log.h" #include "stringop.h" const uint8_t GESTURE_FINGERS_ANY = 0; char *gesture_parse(const char *input, struct gesture *output) { // Clear output in case of failure output->type = GESTURE_TYPE_NONE; output->fingers = GESTURE_FINGERS_ANY; output->directions = GESTURE_DIRECTION_NONE; // Split input type, fingers and directions list_t *split = split_string(input, ":"); if (split->length < 1 || split->length > 3) { return format_str( "expected [:][:direction], got %s", input); } // Parse gesture type if (strcmp(split->items[0], "hold") == 0) { output->type = GESTURE_TYPE_HOLD; } else if (strcmp(split->items[0], "pinch") == 0) { output->type = GESTURE_TYPE_PINCH; } else if (strcmp(split->items[0], "swipe") == 0) { output->type = GESTURE_TYPE_SWIPE; } else { return format_str("expected hold|pinch|swipe, got %s", (const char *)split->items[0]); } // Parse optional arguments if (split->length > 1) { char *next = split->items[1]; // Try to parse as finger count (1-9) if (strlen(next) == 1 && '1' <= next[0] && next[0] <= '9') { output->fingers = atoi(next); // Move to next if available next = split->length == 3 ? split->items[2] : NULL; } else if (split->length == 3) { // Fail here if argument can only be finger count return format_str("expected 1-9, got %s", next); } // If there is an argument left, try to parse as direction if (next) { list_t *directions = split_string(next, "+"); for (int i = 0; i < directions->length; ++i) { const char *item = directions->items[i]; if (strcmp(item, "any") == 0) { continue; } else if (strcmp(item, "up") == 0) { output->directions |= GESTURE_DIRECTION_UP; } else if (strcmp(item, "down") == 0) { output->directions |= GESTURE_DIRECTION_DOWN; } else if (strcmp(item, "left") == 0) { output->directions |= GESTURE_DIRECTION_LEFT; } else if (strcmp(item, "right") == 0) { output->directions |= GESTURE_DIRECTION_RIGHT; } else if (strcmp(item, "inward") == 0) { output->directions |= GESTURE_DIRECTION_INWARD; } else if (strcmp(item, "outward") == 0) { output->directions |= GESTURE_DIRECTION_OUTWARD; } else if (strcmp(item, "clockwise") == 0) { output->directions |= GESTURE_DIRECTION_CLOCKWISE; } else if (strcmp(item, "counterclockwise") == 0) { output->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE; } else { return format_str("expected direction, got %s", item); } } list_free_items_and_destroy(directions); } } // if optional args list_free_items_and_destroy(split); return NULL; } const char *gesture_type_string(enum gesture_type type) { switch (type) { case GESTURE_TYPE_NONE: return "none"; case GESTURE_TYPE_HOLD: return "hold"; case GESTURE_TYPE_PINCH: return "pinch"; case GESTURE_TYPE_SWIPE: return "swipe"; } return NULL; } const char *gesture_direction_string(enum gesture_direction direction) { switch (direction) { case GESTURE_DIRECTION_NONE: return "none"; case GESTURE_DIRECTION_UP: return "up"; case GESTURE_DIRECTION_DOWN: return "down"; case GESTURE_DIRECTION_LEFT: return "left"; case GESTURE_DIRECTION_RIGHT: return "right"; case GESTURE_DIRECTION_INWARD: return "inward"; case GESTURE_DIRECTION_OUTWARD: return "outward"; case GESTURE_DIRECTION_CLOCKWISE: return "clockwise"; case GESTURE_DIRECTION_COUNTERCLOCKWISE: return "counterclockwise"; } return NULL; } // Helper to turn combination of directions flags into string representation. static char *gesture_directions_to_string(uint32_t directions) { char *result = NULL; for (uint8_t bit = 0; bit < 32; bit++) { uint32_t masked = directions & (1 << bit); if (masked) { const char *name = gesture_direction_string(masked); if (!name) { name = "unknown"; } if (!result) { result = strdup(name); } else { char *new = format_str("%s+%s", result, name); free(result); result = new; } } } if(!result) { return strdup("any"); } return result; } char *gesture_to_string(struct gesture *gesture) { char *directions = gesture_directions_to_string(gesture->directions); char *result = format_str("%s:%u:%s", gesture_type_string(gesture->type), gesture->fingers, directions); free(directions); return result; } bool gesture_check(struct gesture *target, enum gesture_type type, uint8_t fingers) { // Check that gesture type matches if (target->type != type) { return false; } // Check that finger count matches if (target->fingers != GESTURE_FINGERS_ANY && target->fingers != fingers) { return false; } return true; } bool gesture_match(struct gesture *target, struct gesture *to_match, bool exact) { // Check type and fingers if (!gesture_check(target, to_match->type, to_match->fingers)) { return false; } // Enforce exact matches ... if (exact && target->directions != to_match->directions) { return false; } // ... or ensure all target directions are matched return (target->directions & to_match->directions) == target->directions; } bool gesture_equal(struct gesture *a, struct gesture *b) { return a->type == b->type && a->fingers == b->fingers && a->directions == b->directions; } // Return count of set bits in directions bit field. static uint8_t gesture_directions_count(uint32_t directions) { uint8_t count = 0; for (; directions; directions >>= 1) { count += directions & 1; } return count; } // Compare direction bit count of two direction. static int8_t gesture_directions_compare(uint32_t a, uint32_t b) { return gesture_directions_count(a) - gesture_directions_count(b); } // Compare two direction based on their direction bit count int8_t gesture_compare(struct gesture *a, struct gesture *b) { return gesture_directions_compare(a->directions, b->directions); } void gesture_tracker_begin(struct gesture_tracker *tracker, enum gesture_type type, uint8_t fingers) { tracker->type = type; tracker->fingers = fingers; tracker->dx = 0.0; tracker->dy = 0.0; tracker->scale = 1.0; tracker->rotation = 0.0; sway_log(SWAY_DEBUG, "begin tracking %s:%u:? gesture", gesture_type_string(type), fingers); } bool gesture_tracker_check(struct gesture_tracker *tracker, enum gesture_type type) { return tracker->type == type; } void gesture_tracker_update(struct gesture_tracker *tracker, double dx, double dy, double scale, double rotation) { if (tracker->type == GESTURE_TYPE_HOLD) { sway_assert(false, "hold does not update."); return; } tracker->dx += dx; tracker->dy += dy; if (tracker->type == GESTURE_TYPE_PINCH) { tracker->scale = scale; tracker->rotation += rotation; } sway_log(SWAY_DEBUG, "update tracking %s:%u:? gesture: %f %f %f %f", gesture_type_string(tracker->type), tracker->fingers, tracker->dx, tracker->dy, tracker->scale, tracker->rotation); } void gesture_tracker_cancel(struct gesture_tracker *tracker) { sway_log(SWAY_DEBUG, "cancel tracking %s:%u:? gesture", gesture_type_string(tracker->type), tracker->fingers); tracker->type = GESTURE_TYPE_NONE; } struct gesture *gesture_tracker_end(struct gesture_tracker *tracker) { struct gesture *result = calloc(1, sizeof(struct gesture)); // Ignore gesture under some threshold // TODO: Make configurable const double min_rotation = 5; const double min_scale_delta = 0.1; // Determine direction switch(tracker->type) { // Gestures with scale and rotation case GESTURE_TYPE_PINCH: if (tracker->rotation > min_rotation) { result->directions |= GESTURE_DIRECTION_CLOCKWISE; } if (tracker->rotation < -min_rotation) { result->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE; } if (tracker->scale > (1.0 + min_scale_delta)) { result->directions |= GESTURE_DIRECTION_OUTWARD; } if (tracker->scale < (1.0 - min_scale_delta)) { result->directions |= GESTURE_DIRECTION_INWARD; } __attribute__ ((fallthrough)); // Gestures with dx and dy case GESTURE_TYPE_SWIPE: if (fabs(tracker->dx) > fabs(tracker->dy)) { if (tracker->dx > 0) { result->directions |= GESTURE_DIRECTION_RIGHT; } else { result->directions |= GESTURE_DIRECTION_LEFT; } } else { if (tracker->dy > 0) { result->directions |= GESTURE_DIRECTION_DOWN; } else { result->directions |= GESTURE_DIRECTION_UP; } } // Gesture without any direction case GESTURE_TYPE_HOLD: break; // Not tracking any gesture case GESTURE_TYPE_NONE: sway_assert(false, "Not tracking any gesture."); return result; } result->type = tracker->type; result->fingers = tracker->fingers; char *description = gesture_to_string(result); sway_log(SWAY_DEBUG, "end tracking gesture: %s", description); free(description); tracker->type = GESTURE_TYPE_NONE; return result; } ================================================ FILE: common/ipc-client.c ================================================ #include #include #include #include #include #include #include #include "ipc-client.h" #include "log.h" static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'}; #define IPC_HEADER_SIZE (sizeof(ipc_magic) + 8) char *get_socketpath(void) { const char *swaysock = getenv("SWAYSOCK"); if (swaysock) { return strdup(swaysock); } char *line = NULL; size_t line_size = 0; FILE *fp = popen("sway --get-socketpath 2>/dev/null", "r"); if (fp) { ssize_t nret = getline(&line, &line_size, fp); pclose(fp); if (nret > 0) { // remove trailing newline, if there is one if (line[nret - 1] == '\n') { line[nret - 1] = '\0'; } return line; } } const char *i3sock = getenv("I3SOCK"); if (i3sock) { free(line); return strdup(i3sock); } fp = popen("i3 --get-socketpath 2>/dev/null", "r"); if (fp) { ssize_t nret = getline(&line, &line_size, fp); pclose(fp); if (nret > 0) { // remove trailing newline, if there is one if (line[nret - 1] == '\n') { line[nret - 1] = '\0'; } return line; } } free(line); return NULL; } int ipc_open_socket(const char *socket_path) { struct sockaddr_un addr; int socketfd; if ((socketfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { sway_abort("Unable to open Unix socket"); } addr.sun_family = AF_UNIX; strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); addr.sun_path[sizeof(addr.sun_path) - 1] = 0; int l = sizeof(struct sockaddr_un); if (connect(socketfd, (struct sockaddr *)&addr, l) == -1) { sway_abort("Unable to connect to %s", socket_path); } return socketfd; } bool ipc_set_recv_timeout(int socketfd, struct timeval tv) { if (setsockopt(socketfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) { sway_log_errno(SWAY_ERROR, "Failed to set ipc recv timeout"); return false; } return true; } struct ipc_response *ipc_recv_response(int socketfd) { char data[IPC_HEADER_SIZE]; size_t total = 0; while (total < IPC_HEADER_SIZE) { ssize_t received = recv(socketfd, data + total, IPC_HEADER_SIZE - total, 0); if (received <= 0) { sway_abort("Unable to receive IPC response"); } total += received; } struct ipc_response *response = malloc(sizeof(struct ipc_response)); if (!response) { goto error_1; } memcpy(&response->size, data + sizeof(ipc_magic), sizeof(uint32_t)); memcpy(&response->type, data + sizeof(ipc_magic) + sizeof(uint32_t), sizeof(uint32_t)); char *payload = malloc(response->size + 1); if (!payload) { goto error_2; } total = 0; while (total < response->size) { ssize_t received = recv(socketfd, payload + total, response->size - total, 0); if (received < 0) { sway_abort("Unable to receive IPC response"); } total += received; } payload[response->size] = '\0'; response->payload = payload; return response; error_2: free(response); error_1: sway_log(SWAY_ERROR, "Unable to allocate memory for IPC response"); return NULL; } void free_ipc_response(struct ipc_response *response) { free(response->payload); free(response); } char *ipc_single_command(int socketfd, uint32_t type, const char *payload, uint32_t *len) { char data[IPC_HEADER_SIZE]; memcpy(data, ipc_magic, sizeof(ipc_magic)); memcpy(data + sizeof(ipc_magic), len, sizeof(*len)); memcpy(data + sizeof(ipc_magic) + sizeof(*len), &type, sizeof(type)); if (write(socketfd, data, IPC_HEADER_SIZE) == -1) { sway_abort("Unable to send IPC header"); } if (write(socketfd, payload, *len) == -1) { sway_abort("Unable to send IPC payload"); } struct ipc_response *resp = ipc_recv_response(socketfd); char *response = resp->payload; *len = resp->size; free(resp); return response; } ================================================ FILE: common/list.c ================================================ #include "list.h" #include #include #include #include "log.h" list_t *create_list(void) { list_t *list = malloc(sizeof(list_t)); if (!list) { return NULL; } list->capacity = 10; list->length = 0; list->items = malloc(sizeof(void*) * list->capacity); return list; } static void list_resize(list_t *list) { if (list->length == list->capacity) { list->capacity *= 2; list->items = realloc(list->items, sizeof(void*) * list->capacity); } } void list_free(list_t *list) { if (list == NULL) { return; } free(list->items); free(list); } void list_add(list_t *list, void *item) { list_resize(list); list->items[list->length++] = item; } void list_insert(list_t *list, int index, void *item) { list_resize(list); memmove(&list->items[index + 1], &list->items[index], sizeof(void*) * (list->length - index)); list->length++; list->items[index] = item; } void list_del(list_t *list, int index) { list->length--; memmove(&list->items[index], &list->items[index + 1], sizeof(void*) * (list->length - index)); } void list_cat(list_t *list, list_t *source) { for (int i = 0; i < source->length; ++i) { list_add(list, source->items[i]); } } void list_qsort(list_t *list, int compare(const void *left, const void *right)) { qsort(list->items, list->length, sizeof(void *), compare); } int list_seq_find(list_t *list, int compare(const void *item, const void *data), const void *data) { for (int i = 0; i < list->length; i++) { void *item = list->items[i]; if (compare(item, data) == 0) { return i; } } return -1; } int list_find(list_t *list, const void *item) { for (int i = 0; i < list->length; i++) { if (list->items[i] == item) { return i; } } return -1; } void list_swap(list_t *list, int src, int dest) { void *tmp = list->items[src]; list->items[src] = list->items[dest]; list->items[dest] = tmp; } void list_move_to_end(list_t *list, void *item) { int i; for (i = 0; i < list->length; ++i) { if (list->items[i] == item) { break; } } if (!sway_assert(i < list->length, "Item not found in list")) { return; } list_del(list, i); list_add(list, item); } static void list_rotate(list_t *list, int from, int to) { void *tmp = list->items[to]; while (to > from) { list->items[to] = list->items[to - 1]; to--; } list->items[from] = tmp; } static void list_inplace_merge(list_t *list, int left, int last, int mid, int compare(const void *a, const void *b)) { int right = mid + 1; if (compare(&list->items[mid], &list->items[right]) <= 0) { return; } while (left <= mid && right <= last) { if (compare(&list->items[left], &list->items[right]) <= 0) { left++; } else { list_rotate(list, left, right); left++; mid++; right++; } } } static void list_inplace_sort(list_t *list, int first, int last, int compare(const void *a, const void *b)) { if (first >= last) { return; } else if ((last - first) == 1) { if (compare(&list->items[first], &list->items[last]) > 0) { list_swap(list, first, last); } } else { int mid = (int)((last + first) / 2); list_inplace_sort(list, first, mid, compare); list_inplace_sort(list, mid + 1, last, compare); list_inplace_merge(list, first, last, mid, compare); } } void list_stable_sort(list_t *list, int compare(const void *a, const void *b)) { if (list->length > 1) { list_inplace_sort(list, 0, list->length - 1, compare); } } void list_free_items_and_destroy(list_t *list) { if (!list) { return; } for (int i = 0; i < list->length; ++i) { free(list->items[i]); } list_free(list); } ================================================ FILE: common/log.c ================================================ #include #include #include #include #include #include #include "log.h" static terminate_callback_t log_terminate = exit; void _sway_abort(const char *format, ...) { va_list args; va_start(args, format); _sway_vlog(SWAY_ERROR, format, args); va_end(args); log_terminate(EXIT_FAILURE); } bool _sway_assert(bool condition, const char *format, ...) { if (condition) { return true; } va_list args; va_start(args, format); _sway_vlog(SWAY_ERROR, format, args); va_end(args); #ifndef NDEBUG raise(SIGABRT); #endif return false; } static bool colored = true; static sway_log_importance_t log_importance = SWAY_ERROR; static struct timespec start_time = {-1, -1}; static const char *verbosity_colors[] = { [SWAY_SILENT] = "", [SWAY_ERROR ] = "\x1B[1;31m", [SWAY_INFO ] = "\x1B[1;34m", [SWAY_DEBUG ] = "\x1B[1;90m", }; static const char *verbosity_headers[] = { [SWAY_SILENT] = "", [SWAY_ERROR] = "[ERROR]", [SWAY_INFO] = "[INFO]", [SWAY_DEBUG] = "[DEBUG]", }; static void timespec_sub(struct timespec *r, const struct timespec *a, const struct timespec *b) { const long NSEC_PER_SEC = 1000000000; r->tv_sec = a->tv_sec - b->tv_sec; r->tv_nsec = a->tv_nsec - b->tv_nsec; if (r->tv_nsec < 0) { r->tv_sec--; r->tv_nsec += NSEC_PER_SEC; } } static void init_start_time(void) { if (start_time.tv_sec >= 0) { return; } clock_gettime(CLOCK_MONOTONIC, &start_time); } static void sway_log_stderr(sway_log_importance_t verbosity, const char *fmt, va_list args) { init_start_time(); if (verbosity > log_importance) { return; } struct timespec ts = {0}; clock_gettime(CLOCK_MONOTONIC, &ts); timespec_sub(&ts, &ts, &start_time); fprintf(stderr, "%02d:%02d:%02d.%03ld ", (int)(ts.tv_sec / 60 / 60), (int)(ts.tv_sec / 60 % 60), (int)(ts.tv_sec % 60), ts.tv_nsec / 1000000); unsigned c = (verbosity < SWAY_LOG_IMPORTANCE_LAST) ? verbosity : SWAY_LOG_IMPORTANCE_LAST - 1; if (colored && isatty(STDERR_FILENO)) { fprintf(stderr, "%s", verbosity_colors[c]); } else { fprintf(stderr, "%s ", verbosity_headers[c]); } vfprintf(stderr, fmt, args); if (colored && isatty(STDERR_FILENO)) { fprintf(stderr, "\x1B[0m"); } fprintf(stderr, "\n"); } void sway_log_init(sway_log_importance_t verbosity, terminate_callback_t callback) { init_start_time(); if (verbosity < SWAY_LOG_IMPORTANCE_LAST) { log_importance = verbosity; } if (callback) { log_terminate = callback; } } void _sway_vlog(sway_log_importance_t verbosity, const char *fmt, va_list args) { sway_log_stderr(verbosity, fmt, args); } void _sway_log(sway_log_importance_t verbosity, const char *fmt, ...) { va_list args; va_start(args, fmt); sway_log_stderr(verbosity, fmt, args); va_end(args); } ================================================ FILE: common/loop.c ================================================ #include #include #include #include #include #include #include #include #include "list.h" #include "log.h" #include "loop.h" struct loop_fd_event { void (*callback)(int fd, short mask, void *data); void *data; }; struct loop_timer { void (*callback)(void *data); void *data; struct timespec expiry; }; struct loop { struct pollfd *fds; int fd_length; int fd_capacity; list_t *fd_events; // struct loop_fd_event list_t *timers; // struct loop_timer }; struct loop *loop_create(void) { struct loop *loop = calloc(1, sizeof(struct loop)); if (!loop) { sway_log(SWAY_ERROR, "Unable to allocate memory for loop"); return NULL; } loop->fd_capacity = 10; loop->fds = malloc(sizeof(struct pollfd) * loop->fd_capacity); loop->fd_events = create_list(); loop->timers = create_list(); return loop; } void loop_destroy(struct loop *loop) { list_free_items_and_destroy(loop->fd_events); list_free_items_and_destroy(loop->timers); free(loop->fds); free(loop); } void loop_poll(struct loop *loop) { // Calculate next timer in ms int ms = INT_MAX; if (loop->timers->length) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); for (int i = 0; i < loop->timers->length; ++i) { struct loop_timer *timer = loop->timers->items[i]; int timer_ms = (timer->expiry.tv_sec - now.tv_sec) * 1000; timer_ms += (timer->expiry.tv_nsec - now.tv_nsec) / 1000000; if (timer_ms < ms) { ms = timer_ms; } } } if (ms < 0) { ms = 0; } poll(loop->fds, loop->fd_length, ms); // Dispatch fds for (int i = 0; i < loop->fd_length; ++i) { struct pollfd pfd = loop->fds[i]; struct loop_fd_event *event = loop->fd_events->items[i]; // Always send these events unsigned events = pfd.events | POLLHUP | POLLERR; if (pfd.revents & events) { event->callback(pfd.fd, pfd.revents, event->data); } } // Dispatch timers if (loop->timers->length) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); for (int i = 0; i < loop->timers->length; ++i) { struct loop_timer *timer = loop->timers->items[i]; bool expired = timer->expiry.tv_sec < now.tv_sec || (timer->expiry.tv_sec == now.tv_sec && timer->expiry.tv_nsec < now.tv_nsec); if (expired) { timer->callback(timer->data); loop_remove_timer(loop, timer); --i; } } } } void loop_add_fd(struct loop *loop, int fd, short mask, void (*callback)(int fd, short mask, void *data), void *data) { struct loop_fd_event *event = calloc(1, sizeof(struct loop_fd_event)); if (!event) { sway_log(SWAY_ERROR, "Unable to allocate memory for event"); return; } event->callback = callback; event->data = data; list_add(loop->fd_events, event); struct pollfd pfd = {fd, mask, 0}; if (loop->fd_length == loop->fd_capacity) { int capacity = loop->fd_capacity + 10; struct pollfd *tmp = realloc(loop->fds, sizeof(struct pollfd) * capacity); if (!tmp) { sway_log(SWAY_ERROR, "Unable to allocate memory for pollfd"); return; } loop->fds = tmp; loop->fd_capacity = capacity; } loop->fds[loop->fd_length++] = pfd; } struct loop_timer *loop_add_timer(struct loop *loop, int ms, void (*callback)(void *data), void *data) { struct loop_timer *timer = calloc(1, sizeof(struct loop_timer)); if (!timer) { sway_log(SWAY_ERROR, "Unable to allocate memory for timer"); return NULL; } timer->callback = callback; timer->data = data; clock_gettime(CLOCK_MONOTONIC, &timer->expiry); timer->expiry.tv_sec += ms / 1000; long int nsec = (ms % 1000) * 1000000; if (timer->expiry.tv_nsec + nsec >= 1000000000) { timer->expiry.tv_sec++; nsec -= 1000000000; } timer->expiry.tv_nsec += nsec; list_add(loop->timers, timer); return timer; } bool loop_remove_fd(struct loop *loop, int fd) { for (int i = 0; i < loop->fd_length; ++i) { if (loop->fds[i].fd == fd) { free(loop->fd_events->items[i]); list_del(loop->fd_events, i); loop->fd_length--; memmove(&loop->fds[i], &loop->fds[i + 1], sizeof(struct pollfd) * (loop->fd_length - i)); return true; } } return false; } bool loop_remove_timer(struct loop *loop, struct loop_timer *timer) { for (int i = 0; i < loop->timers->length; ++i) { if (loop->timers->items[i] == timer) { list_del(loop->timers, i); free(timer); return true; } } return false; } ================================================ FILE: common/meson.build ================================================ lib_sway_common = static_library( 'sway-common', files( 'cairo.c', 'gesture.c', 'ipc-client.c', 'log.c', 'loop.c', 'list.c', 'pango.c', 'stringop.c', 'util.c' ), dependencies: [ cairo, pango, pangocairo, wayland_client.partial_dependency(compile_args: true) ], include_directories: sway_inc ) ================================================ FILE: common/pango.c ================================================ #include #include #include #include #include #include #include #include #include "cairo_util.h" #include "log.h" #include "stringop.h" size_t escape_markup_text(const char *src, char *dest) { size_t length = 0; if (dest) { dest[0] = '\0'; } while (src[0]) { switch (src[0]) { case '&': length += 5; lenient_strcat(dest, "&"); break; case '<': length += 4; lenient_strcat(dest, "<"); break; case '>': length += 4; lenient_strcat(dest, ">"); break; case '\'': length += 6; lenient_strcat(dest, "'"); break; case '"': length += 6; lenient_strcat(dest, """); break; default: if (dest) { dest[length] = *src; dest[length + 1] = '\0'; } length += 1; } src++; } return length; } PangoLayout *get_pango_layout(cairo_t *cairo, const PangoFontDescription *desc, const char *text, double scale, bool markup) { PangoLayout *layout = pango_cairo_create_layout(cairo); pango_context_set_round_glyph_positions(pango_layout_get_context(layout), false); PangoAttrList *attrs; if (markup) { char *buf; GError *error = NULL; if (pango_parse_markup(text, -1, 0, &attrs, &buf, NULL, &error)) { pango_layout_set_text(layout, buf, -1); free(buf); } else { sway_log(SWAY_ERROR, "pango_parse_markup '%s' -> error %s", text, error->message); g_error_free(error); markup = false; // fallback to plain text } } if (!markup) { attrs = pango_attr_list_new(); pango_layout_set_text(layout, text, -1); } pango_attr_list_insert(attrs, pango_attr_scale_new(scale)); pango_layout_set_font_description(layout, desc); pango_layout_set_single_paragraph_mode(layout, 1); pango_layout_set_attributes(layout, attrs); pango_attr_list_unref(attrs); return layout; } void get_text_size(cairo_t *cairo, const PangoFontDescription *desc, int *width, int *height, int *baseline, double scale, bool markup, const char *fmt, ...) { if (width) { *width = 0; } if (height) { *height = 0; } if (baseline) { *baseline = 0; } va_list args; va_start(args, fmt); char *buf = vformat_str(fmt, args); va_end(args); if (buf == NULL) { sway_log(SWAY_ERROR, "Failed to format string"); return; } PangoLayout *layout = get_pango_layout(cairo, desc, buf, scale, markup); pango_cairo_update_layout(cairo, layout); cairo_status_t status = cairo_status(cairo); if (status != CAIRO_STATUS_SUCCESS) { sway_log(SWAY_ERROR, "pango_cairo_update_layout() failed: %s", cairo_status_to_string(status)); goto out; } pango_layout_get_pixel_size(layout, width, height); if (baseline) { *baseline = pango_layout_get_baseline(layout) / PANGO_SCALE; } out: g_object_unref(layout); free(buf); } void get_text_metrics(const PangoFontDescription *description, int *height, int *baseline) { cairo_t *cairo = cairo_create(NULL); PangoContext *pango = pango_cairo_create_context(cairo); pango_context_set_round_glyph_positions(pango, false); // When passing NULL as a language, pango uses the current locale. PangoFontMetrics *metrics = pango_context_get_metrics(pango, description, NULL); *baseline = pango_font_metrics_get_ascent(metrics) / PANGO_SCALE; *height = *baseline + pango_font_metrics_get_descent(metrics) / PANGO_SCALE; pango_font_metrics_unref(metrics); g_object_unref(pango); cairo_destroy(cairo); } void render_text(cairo_t *cairo, const PangoFontDescription *desc, double scale, bool markup, const char *fmt, ...) { va_list args; va_start(args, fmt); char *buf = vformat_str(fmt, args); va_end(args); if (buf == NULL) { sway_log(SWAY_ERROR, "Failed to format string"); return; } PangoLayout *layout = get_pango_layout(cairo, desc, buf, scale, markup); cairo_font_options_t *fo = cairo_font_options_create(); cairo_get_font_options(cairo, fo); pango_cairo_context_set_font_options(pango_layout_get_context(layout), fo); cairo_font_options_destroy(fo); pango_cairo_update_layout(cairo, layout); cairo_status_t status = cairo_status(cairo); if (status != CAIRO_STATUS_SUCCESS) { sway_log(SWAY_ERROR, "pango_cairo_update_layout() failed: %s", cairo_status_to_string(status)); goto out; } pango_cairo_show_layout(cairo, layout); out: g_object_unref(layout); free(buf); } ================================================ FILE: common/stringop.c ================================================ #include #include #include #include #include #include #include #include #include "list.h" #include "log.h" #include "stringop.h" static const char whitespace[] = " \f\n\r\t\v"; void strip_whitespace(char *str) { size_t len = strlen(str); size_t start = strspn(str, whitespace); memmove(str, &str[start], len + 1 - start); if (*str) { for (len -= start + 1; isspace(str[len]); --len) {} str[len + 1] = '\0'; } } void strip_quotes(char *str) { bool in_str = false; bool in_chr = false; bool escaped = false; char *end = strchr(str,0); while (*str) { if (*str == '\'' && !in_str && !escaped) { in_chr = !in_chr; goto shift_over; } else if (*str == '\"' && !in_chr && !escaped) { in_str = !in_str; goto shift_over; } else if (*str == '\\') { escaped = !escaped; ++str; continue; } escaped = false; ++str; continue; shift_over: memmove(str, str+1, end-- - str); } *end = '\0'; } char *lenient_strcat(char *dest, const char *src) { if (dest && src) { return strcat(dest, src); } return dest; } char *lenient_strncat(char *dest, const char *src, size_t len) { if (dest && src) { return strncat(dest, src, len); } return dest; } // strcmp that also handles null pointers. int lenient_strcmp(const char *a, const char *b) { if (a == b) { return 0; } else if (!a) { return -1; } else if (!b) { return 1; } else { return strcmp(a, b); } } list_t *split_string(const char *str, const char *delims) { list_t *res = create_list(); char *copy = strdup(str); char *token = strtok(copy, delims); while (token) { list_add(res, strdup(token)); token = strtok(NULL, delims); } free(copy); return res; } char **split_args(const char *start, int *argc) { *argc = 0; int alloc = 2; char **argv = malloc(sizeof(char *) * alloc); bool in_token = false; bool in_string = false; bool in_char = false; bool in_brackets = false; // brackets are used for criteria bool escaped = false; const char *end = start; if (start) { while (*start) { if (!in_token) { start = (end += strspn(end, whitespace)); in_token = true; } if (*end == '"' && !in_char && !escaped) { in_string = !in_string; } else if (*end == '\'' && !in_string && !escaped) { in_char = !in_char; } else if (*end == '[' && !in_string && !in_char && !in_brackets && !escaped) { in_brackets = true; } else if (*end == ']' && !in_string && !in_char && in_brackets && !escaped) { in_brackets = false; } else if (*end == '\\') { escaped = !escaped; } else if (*end == '\0' || (!in_string && !in_char && !in_brackets && !escaped && strchr(whitespace, *end))) { goto add_token; } if (*end != '\\') { escaped = false; } ++end; continue; add_token: if (end - start > 0) { char *token = malloc(end - start + 1); strncpy(token, start, end - start + 1); token[end - start] = '\0'; argv[*argc] = token; if (++*argc + 1 == alloc) { argv = realloc(argv, (alloc *= 2) * sizeof(char *)); } } in_token = false; escaped = false; } } argv[*argc] = NULL; return argv; } void free_argv(int argc, char **argv) { while (argc-- > 0) { free(argv[argc]); } free(argv); } int unescape_string(char *string) { /* TODO: More C string escapes */ int len = strlen(string); int i; for (i = 0; string[i]; ++i) { if (string[i] == '\\') { switch (string[++i]) { case '0': string[i - 1] = '\0'; return i - 1; case 'a': string[i - 1] = '\a'; string[i] = '\0'; break; case 'b': string[i - 1] = '\b'; string[i] = '\0'; break; case 'f': string[i - 1] = '\f'; string[i] = '\0'; break; case 'n': string[i - 1] = '\n'; string[i] = '\0'; break; case 'r': string[i - 1] = '\r'; string[i] = '\0'; break; case 't': string[i - 1] = '\t'; string[i] = '\0'; break; case 'v': string[i - 1] = '\v'; string[i] = '\0'; break; case '\\': string[i] = '\0'; break; case '\'': string[i - 1] = '\''; string[i] = '\0'; break; case '\"': string[i - 1] = '\"'; string[i] = '\0'; break; case '?': string[i - 1] = '?'; string[i] = '\0'; break; case 'x': { unsigned char c = 0; if (string[i+1] >= '0' && string[i+1] <= '9') { c = string[i+1] - '0'; if (string[i+2] >= '0' && string[i+2] <= '9') { c *= 0x10; c += string[i+2] - '0'; string[i+2] = '\0'; } string[i+1] = '\0'; } string[i] = '\0'; string[i - 1] = c; } } } } // Shift characters over nullspaces int shift = 0; for (i = 0; i < len; ++i) { if (string[i] == 0) { shift++; continue; } string[i-shift] = string[i]; } string[len - shift] = 0; return len - shift; } char *join_args(char **argv, int argc) { if (!sway_assert(argc > 0, "argc should be positive")) { return NULL; } int len = 0, i; for (i = 0; i < argc; ++i) { len += strlen(argv[i]) + 1; } char *res = malloc(len); len = 0; for (i = 0; i < argc; ++i) { strcpy(res + len, argv[i]); len += strlen(argv[i]); res[len++] = ' '; } res[len - 1] = '\0'; return res; } static inline char *argsep_next_interesting(const char *src, const char *delim) { char *special = strpbrk(src, "\"'\\"); char *next_delim = strpbrk(src, delim); if (!special) { return next_delim; } if (!next_delim) { return special; } return (next_delim < special) ? next_delim : special; } char *argsep(char **stringp, const char *delim, char *matched) { char *start = *stringp; char *end = start; bool in_string = false; bool in_char = false; bool escaped = false; char *interesting = NULL; while ((interesting = argsep_next_interesting(end, delim))) { if (escaped && interesting != end) { escaped = false; } if (*interesting == '"' && !in_char && !escaped) { in_string = !in_string; end = interesting + 1; } else if (*interesting == '\'' && !in_string && !escaped) { in_char = !in_char; end = interesting + 1; } else if (*interesting == '\\') { escaped = !escaped; end = interesting + 1; } else if (!in_string && !in_char && !escaped) { // We must have matched a separator end = interesting; if (matched) { *matched = *end; } if (end - start) { *(end++) = 0; *stringp = end; break; } else { end = ++start; } } else { end++; } } if (!interesting) { *stringp = NULL; if (matched) { *matched = '\0'; } } return start; } bool expand_path(char **path) { wordexp_t p = {0}; while (strstr(*path, " ")) { *path = realloc(*path, strlen(*path) + 2); char *ptr = strstr(*path, " ") + 1; memmove(ptr + 1, ptr, strlen(ptr) + 1); *ptr = '\\'; } if (wordexp(*path, &p, 0) != 0 || p.we_wordv[0] == NULL) { wordfree(&p); return false; } free(*path); *path = join_args(p.we_wordv, p.we_wordc); wordfree(&p); return true; } char *vformat_str(const char *fmt, va_list args) { char *str = NULL; va_list args_copy; va_copy(args_copy, args); int len = vsnprintf(NULL, 0, fmt, args); if (len < 0) { sway_log_errno(SWAY_ERROR, "vsnprintf(\"%s\") failed", fmt); goto out; } str = malloc(len + 1); if (str == NULL) { sway_log_errno(SWAY_ERROR, "malloc() failed"); goto out; } vsnprintf(str, len + 1, fmt, args_copy); out: va_end(args_copy); return str; } char *format_str(const char *fmt, ...) { va_list args; va_start(args, fmt); char *str = vformat_str(fmt, args); va_end(args); return str; } bool has_prefix(const char *str, const char *prefix) { return strncmp(str, prefix, strlen(prefix)) == 0; } ================================================ FILE: common/util.c ================================================ #include #include #include #include #include #include #include #include #include "log.h" #include "util.h" int wrap(int i, int max) { return ((i % max) + max) % max; } bool parse_color(const char *color, uint32_t *result) { if (color[0] == '#') { ++color; } int len = strlen(color); if ((len != 6 && len != 8) || !isxdigit(color[0]) || !isxdigit(color[1])) { return false; } char *ptr; uint32_t parsed = strtoul(color, &ptr, 16); if (*ptr != '\0') { return false; } *result = len == 6 ? ((parsed << 8) | 0xFF) : parsed; return true; } void color_to_rgba(float dest[static 4], uint32_t color) { dest[0] = ((color >> 24) & 0xff) / 255.0; dest[1] = ((color >> 16) & 0xff) / 255.0; dest[2] = ((color >> 8) & 0xff) / 255.0; dest[3] = (color & 0xff) / 255.0; } bool parse_boolean(const char *boolean, bool current) { if (strcasecmp(boolean, "1") == 0 || strcasecmp(boolean, "yes") == 0 || strcasecmp(boolean, "on") == 0 || strcasecmp(boolean, "true") == 0 || strcasecmp(boolean, "enable") == 0 || strcasecmp(boolean, "enabled") == 0 || strcasecmp(boolean, "active") == 0) { return true; } else if (strcasecmp(boolean, "toggle") == 0) { return !current; } // All other values are false to match i3 return false; } float parse_float(const char *value) { errno = 0; char *end; float flt = strtof(value, &end); if (*end || errno) { sway_log(SWAY_DEBUG, "Invalid float value '%s', defaulting to NAN", value); return NAN; } return flt; } enum movement_unit parse_movement_unit(const char *unit) { if (strcasecmp(unit, "px") == 0) { return MOVEMENT_UNIT_PX; } if (strcasecmp(unit, "ppt") == 0) { return MOVEMENT_UNIT_PPT; } if (strcasecmp(unit, "default") == 0) { return MOVEMENT_UNIT_DEFAULT; } return MOVEMENT_UNIT_INVALID; } int parse_movement_amount(int argc, char **argv, struct movement_amount *amount) { if (!sway_assert(argc > 0, "Expected args in parse_movement_amount")) { amount->amount = 0; amount->unit = MOVEMENT_UNIT_INVALID; return 0; } char *err; amount->amount = (int)strtol(argv[0], &err, 10); if (*err) { // e.g. 10px amount->unit = parse_movement_unit(err); return 1; } if (argc == 1) { amount->unit = MOVEMENT_UNIT_DEFAULT; return 1; } // Try the second argument amount->unit = parse_movement_unit(argv[1]); if (amount->unit == MOVEMENT_UNIT_INVALID) { amount->unit = MOVEMENT_UNIT_DEFAULT; return 1; } return 2; } const char *sway_wl_output_subpixel_to_string(enum wl_output_subpixel subpixel) { switch (subpixel) { case WL_OUTPUT_SUBPIXEL_UNKNOWN: return "unknown"; case WL_OUTPUT_SUBPIXEL_NONE: return "none"; case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: return "rgb"; case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: return "bgr"; case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: return "vrgb"; case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: return "vbgr"; } sway_assert(false, "Unknown value for wl_output_subpixel."); return NULL; } bool sway_set_cloexec(int fd, bool cloexec) { int flags = fcntl(fd, F_GETFD); if (flags == -1) { sway_log_errno(SWAY_ERROR, "fcntl failed"); return false; } if (cloexec) { flags = flags | FD_CLOEXEC; } else { flags = flags & ~FD_CLOEXEC; } if (fcntl(fd, F_SETFD, flags) == -1) { sway_log_errno(SWAY_ERROR, "fcntl failed"); return false; } return true; } uint32_t get_current_time_in_msec(void) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); return now.tv_sec * 1000 + now.tv_nsec / 1000000; } ================================================ FILE: completions/bash/sway ================================================ # sway(1) completion _sway() { local cur prev short long _get_comp_words_by_ref cur prev short=( -h -c -C -d -v -V ) long=( --help --config --validate --debug --version --verbose --get-socketpath ) case $prev in -c|--config) _filedir return ;; esac if [[ $cur == --* ]]; then COMPREPLY=($(compgen -W "${long[*]}" -- "$cur")) elif [[ $cur == -* ]]; then COMPREPLY=($(compgen -W "${short[*]}" -- "$cur")) COMPREPLY+=($(compgen -W "${long[*]}" -- "$cur")) else COMPREPLY=($(compgen -W "${short[*]}" -- "$cur")) COMPREPLY+=($(compgen -W "${long[*]}" -- "$cur")) COMPREPLY+=($(compgen -c -- "$cur")) fi } && complete -F _sway sway ================================================ FILE: completions/bash/swaybar ================================================ # swaybar(1) completion _swaybar() { local cur prev short long _get_comp_words_by_ref cur prev short=( -h -v -s -b -d ) long=( --help --version --socket --bar_id --debug ) case $prev in -s|--socket) _filedir return ;; -b|--bar_id) bars=($(swaymsg -t get_bar_config | jq -r '.[]')) COMPREPLY=($(compgen -W "${bars[*]}" -- "$cur")) return ;; esac if [[ $cur == --* ]]; then COMPREPLY=($(compgen -W "${long[*]}" -- "$cur")) else COMPREPLY=($(compgen -W "${short[*]}" -- "$cur")) COMPREPLY+=($(compgen -W "${long[*]}" -- "$cur")) fi } && complete -F _swaybar swaybar ================================================ FILE: completions/bash/swaymsg ================================================ # swaymsg(1) completion _swaymsg() { local cur prev types short long _get_comp_words_by_ref cur prev types=( 'get_workspaces' 'get_seats' 'get_inputs' 'get_outputs' 'get_tree' 'get_marks' 'get_bar_config' 'get_version' 'get_binding_modes' 'get_binding_state' 'get_config' 'send_tick' 'subscribe' ) short=( -h -m -p -q -r -s -t -v ) long=( --help --monitor --pretty --quiet --raw --socket --type --version ) case $prev in -s|--socket) _filedir return ;; -t|--type) COMPREPLY=($(compgen -W "${types[*]}" -- "$cur")) return ;; esac if [[ $cur == --* ]]; then COMPREPLY=($(compgen -W "${long[*]}" -- "$cur")) else COMPREPLY=($(compgen -W "${short[*]}" -- "$cur")) COMPREPLY+=($(compgen -W "${long[*]}" -- "$cur")) fi } && complete -F _swaymsg swaymsg ================================================ FILE: completions/fish/sway.fish ================================================ # sway(1) completion complete -f -c sway complete -c sway -s h -l help --description "Show help message and quit." complete -c sway -s c -l config --description "Specifies a config file." -r complete -c sway -s C -l validate --description "Check the validity of the config file, then exit." complete -c sway -s d -l debug --description "Enables full logging, including debug information." complete -c sway -s v -l version --description "Show the version number and quit." complete -c sway -s V -l verbose --description "Enables more verbose logging." complete -c sway -l get-socketpath --description "Gets the IPC socket path and prints it, then exits." ================================================ FILE: completions/fish/swaymsg.fish ================================================ # swaymsg(1) completion complete -f -c swaymsg complete -c swaymsg -s h -l help --description "Show help message and quit." complete -c swaymsg -s m -l monitor --description "Monitor subscribed events until killed." complete -c swaymsg -s p -l pretty --description "Use pretty output even when not using a tty." complete -c swaymsg -s q -l quiet --description "Sends the IPC message but does not print the response from sway." complete -c swaymsg -s r -l raw --description "Use raw output even if using tty." complete -c swaymsg -s s -l socket -r --description "Use the specified socket path. Otherwise, swaymsg will ask where the socket is (which is the value of $SWAYSOCK, then of $I3SOCK)." complete -c swaymsg -s v -l version --description "Print the version (of swaymsg) and quit." complete -c swaymsg -s t -l type -fr --description "Specify the type of IPC message." complete -c swaymsg -s t -l type -fra 'get_workspaces' --description "Gets a JSON-encoded list of workspaces and their status." complete -c swaymsg -s t -l type -fra 'get_inputs' --description "Gets a JSON-encoded list of current inputs." complete -c swaymsg -s t -l type -fra 'get_outputs' --description "Gets a JSON-encoded list of current outputs." complete -c swaymsg -s t -l type -fra 'get_tree' --description "Gets a JSON-encoded layout tree of all open windows, containers, outputs, workspaces, and so on." complete -c swaymsg -s t -l type -fra 'get_marks' --description "Get a JSON-encoded list of marks." complete -c swaymsg -s t -l type -fra 'get_bar_config' --description "Get a JSON-encoded configuration for swaybar." complete -c swaymsg -s t -l type -fra 'get_version' --description "Get JSON-encoded version information for the running instance of sway." complete -c swaymsg -s t -l type -fra 'get_binding_modes' --description "Gets a JSON-encoded list of currently configured binding modes." complete -c swaymsg -s t -l type -fra 'get_binding_state' --description "Get JSON-encoded info about the current binding state." complete -c swaymsg -s t -l type -fra 'get_config' --description "Gets a JSON-encoded copy of the current configuration." complete -c swaymsg -s t -l type -fra 'get_seats' --description "Gets a JSON-encoded list of all seats, its properties and all assigned devices." complete -c swaymsg -s t -l type -fra 'send_tick' --description "Sends a tick event to all subscribed clients." complete -c swaymsg -s t -l type -fra 'subscribe' --description "Subscribe to a list of event types." ================================================ FILE: completions/fish/swaynag.fish ================================================ # swaynag complete -f -c swaynag complete -c swaynag -s C -l config -r --description 'The config file to use. Default: $HOME/.swaynag/config, $XDG_CONFIG_HOME/swaynag/config, and SYSCONFDIR/swaynag/config.' complete -c swaynag -s d -l debug --description 'Enable debugging.' complete -c swaynag -s e -l edge -fr --description 'Set the edge to use: top or bottom' complete -c swaynag -s f -l font -r --description 'Set the font to use.' complete -c swaynag -s h -l help --description 'Show help message and quit.' complete -c swaynag -s b -l button -fr --description 'Create a button with a text and an action which is executed when pressed. Multiple buttons can be defined by providing the flag multiple times.' complete -c swaynag -s l -l detailed-message --description 'Read a detailed message from stdin. A button to toggle details will be added. Details are shown in a scrollable multi-line text area.' complete -c swaynag -s L -l detailed-button -fr --description 'Set the text for the button that toggles details. This has no effect if there is not a detailed message. The default is "Toggle details".' complete -c swaynag -s m -l message -fr --description 'Set the message text.' complete -c swaynag -s o -l output -fr --description 'Set the output to use.' complete -c swaynag -s s -l dismiss-button -fr --description 'Sets the text for the dismiss nagbar button. The default is "X".' complete -c swaynag -s t -l type -fr --description 'Set the message type. Two types are created by default "error" and "warning". Custom types can be defined in the config file.' complete -c swaynag -s v -l version --description 'Show the version number and quit.' # Appearance complete -c swaynag -l background -fr --description 'Set the color of the background.' complete -c swaynag -l border -fr --description 'Set the color of the border.' complete -c swaynag -l border-bottom -fr --description 'Set the color of the bottom border.' complete -c swaynag -l button-background -fr --description 'Set the color for the background for buttons.' complete -c swaynag -l text -fr --description 'Set the text color.' complete -c swaynag -l border-bottom-size -fr --description 'Set the thickness of the bottom border.' complete -c swaynag -l message-padding -fr --description 'Set the padding for the message.' complete -c swaynag -l details-border-size -fr --description 'Set the thickness for the details border.' complete -c swaynag -l button-border-size -fr --description 'Set the thickness for the button border.' complete -c swaynag -l button-gap -fr --description 'Set the size of the gap between buttons.' complete -c swaynag -l button-dismiss-gap -fr --description 'Set the size of the gap between the dismiss button and another button.' complete -c swaynag -l button-margin-right -fr --description 'Set the margin from the right of the dismiss button to edge.' complete -c swaynag -l button-padding -fr --description 'Set the padding for the button text.' ================================================ FILE: completions/meson.build ================================================ if get_option('zsh-completions') zsh_files = files( 'zsh/_sway', 'zsh/_swaymsg', ) zsh_install_dir = join_paths(datadir, 'zsh', 'site-functions') install_data(zsh_files, install_dir: zsh_install_dir) endif if get_option('bash-completions') bash_comp = dependency('bash-completion', required: false) bash_files = files( 'bash/sway', 'bash/swaymsg', ) if get_option('swaybar') bash_files += files('bash/swaybar') endif if bash_comp.found() bash_install_dir = bash_comp.get_variable( pkgconfig: 'completionsdir', pkgconfig_define: ['datadir', datadir] ) else bash_install_dir = join_paths(datadir, 'bash-completion', 'completions') endif install_data(bash_files, install_dir: bash_install_dir) endif if get_option('fish-completions') fish_comp = dependency('fish', required: false) fish_files = files( 'fish/sway.fish', 'fish/swaymsg.fish', ) if get_option('swaynag') fish_files += files('fish/swaynag.fish') endif if fish_comp.found() fish_install_dir = fish_comp.get_variable( pkgconfig: 'completionsdir', pkgconfig_define: ['datadir', datadir] ) else fish_install_dir = join_paths(datadir, 'fish', 'vendor_completions.d') endif install_data(fish_files, install_dir: fish_install_dir) endif ================================================ FILE: completions/zsh/_sway ================================================ #compdef sway #------------ # Description # ----------- # # Completion script for the sway window manager (http://swaywm.org) # # --------------------------------------------- # Author # ------- # # * Seth Barberee # # ------------------------------- _arguments -s \ '(-v --version)'{-v,--version}'[Show the version number and quit]' \ '(-h --help)'{-h,--help}'[Show help message and quit]' \ '(-c --config)'{-c,--config}'[Specify a config file]:files:_files' \ '(-C --validate)'{-C,--validate}'[Check validity of the config file, then exit]' \ '(-d --debug)'{-d,--debug}'[Enables full logging, including debug information]' \ '(-V --verbose)'{-V,--verbose}'[Enables more verbose logging]' \ '(--get-socketpath)'--get-socketpath'[Gets the IPC socket path and prints it, then exits]' ================================================ FILE: completions/zsh/_swaybar ================================================ #compdef swaybar # # Completion script for swaybar # local bars=($(swaymsg -t get_bar_config | jq -r '.[]')) _arguments -s \ '(-h --help)'{-h,--help}'[Show help message and quit]' \ '(-v --version)'{-v,--version}'[Show version and quit]' \ '(-s --socket)'{-s,--socket}'[Connect to sway via socket]:filename:_files' \ '(-b --bar_id)'{-b,--bar-id}'[Bar ID for which to get the configuration]:filename:($bars)'\ '(-d --debug)'{-d,--debug}'[Enable debugging]' ================================================ FILE: completions/zsh/_swaymsg ================================================ #compdef swaymsg #----------------- # Description # ----------- # # Completion script for swaymsg in sway wm (http://swaywm.org) # # ------------------------------------------------------ # Author # -------- # # * Seth Barberee # # ------------------------------------------- types=( 'get_workspaces' 'get_seats' 'get_inputs' 'get_outputs' 'get_tree' 'get_marks' 'get_bar_config' 'get_version' 'get_binding_modes' 'get_binding_state' 'get_config' 'send_tick' 'subscribe' ) _arguments -s \ '(-h --help)'{-h,--help}'[Show help message and quit]' \ '(-m --monitor)'{-m,--monitor}'[Monitor until killed (-t SUBSCRIBE only)]' \ '(-p --pretty)'{-p,--pretty}'[Use pretty output even when not using a tty]' \ '(-q --quiet)'{-q,--quiet}'[Be quiet]' \ '(-r --raw)'{-r,--raw}'[Use raw output even if using a tty]' \ '(-s --socket)'{-s,--socket}'[Use the specified socket path]:files:_files' \ '(-t --type)'{-t,--type}'[Specify the message type]:type:{_describe "type" types}' \ '(-v --version)'{-v,--version}'[Show the version number and quit]' ================================================ FILE: config.in ================================================ # Default config for sway # # Copy this to ~/.config/sway/config and edit it to your liking. # # Read `man 5 sway` for a complete reference. ### Variables # # Logo key. Use Mod1 for Alt. set $mod Mod4 # Home row direction keys, like vim set $left h set $down j set $up k set $right l # Your preferred terminal emulator set $term foot # Your preferred application launcher set $menu wmenu-run ### Output configuration # # Default wallpaper (more resolutions are available in @datadir@/backgrounds/sway/) output * bg @datadir@/backgrounds/sway/Sway_Wallpaper_Blue_1920x1080.png fill # # Example configuration: # # output HDMI-A-1 resolution 1920x1080 position 1920,0 # # You can get the names of your outputs by running: swaymsg -t get_outputs ### Idle configuration # # Example configuration: # # exec swayidle -w \ # timeout 300 'swaylock -f -c 000000' \ # timeout 600 'swaymsg "output * power off"' resume 'swaymsg "output * power on"' \ # before-sleep 'swaylock -f -c 000000' # # This will lock your screen after 300 seconds of inactivity, then turn off # your displays after another 300 seconds, and turn your screens back on when # resumed. It will also lock your screen before your computer goes to sleep. ### Input configuration # # Example configuration: # # input type:touchpad { # dwt enabled # tap enabled # natural_scroll enabled # middle_emulation enabled # } # # input type:keyboard { # xkb_layout "eu" # } # # You can also configure each device individually. # Read `man 5 sway-input` for more information about this section. ### Key bindings # # Basics: # # Start a terminal bindsym $mod+Return exec $term # Kill focused window bindsym $mod+Shift+q kill # Start your launcher bindsym $mod+d exec $menu # Drag floating windows by holding down $mod and left mouse button. # Resize them with right mouse button + $mod. # Despite the name, also works for non-floating windows. # Change normal to inverse to use left mouse button for resizing and right # mouse button for dragging. floating_modifier $mod normal # Reload the configuration file bindsym $mod+Shift+c reload # Exit sway (logs you out of your Wayland session) bindsym $mod+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -B 'Yes, exit sway' 'swaymsg exit' # # Moving around: # # Move your focus around bindsym $mod+$left focus left bindsym $mod+$down focus down bindsym $mod+$up focus up bindsym $mod+$right focus right # Or use $mod+[up|down|left|right] bindsym $mod+Left focus left bindsym $mod+Down focus down bindsym $mod+Up focus up bindsym $mod+Right focus right # Move the focused window with the same, but add Shift bindsym $mod+Shift+$left move left bindsym $mod+Shift+$down move down bindsym $mod+Shift+$up move up bindsym $mod+Shift+$right move right # Ditto, with arrow keys bindsym $mod+Shift+Left move left bindsym $mod+Shift+Down move down bindsym $mod+Shift+Up move up bindsym $mod+Shift+Right move right # # Workspaces: # # Switch to workspace bindsym $mod+1 workspace number 1 bindsym $mod+2 workspace number 2 bindsym $mod+3 workspace number 3 bindsym $mod+4 workspace number 4 bindsym $mod+5 workspace number 5 bindsym $mod+6 workspace number 6 bindsym $mod+7 workspace number 7 bindsym $mod+8 workspace number 8 bindsym $mod+9 workspace number 9 bindsym $mod+0 workspace number 10 # Move focused container to workspace bindsym $mod+Shift+1 move container to workspace number 1 bindsym $mod+Shift+2 move container to workspace number 2 bindsym $mod+Shift+3 move container to workspace number 3 bindsym $mod+Shift+4 move container to workspace number 4 bindsym $mod+Shift+5 move container to workspace number 5 bindsym $mod+Shift+6 move container to workspace number 6 bindsym $mod+Shift+7 move container to workspace number 7 bindsym $mod+Shift+8 move container to workspace number 8 bindsym $mod+Shift+9 move container to workspace number 9 bindsym $mod+Shift+0 move container to workspace number 10 # Note: workspaces can have any name you want, not just numbers. # We just use 1-10 as the default. # # Layout stuff: # # You can "split" the current object of your focus with # $mod+b or $mod+v, for horizontal and vertical splits # respectively. bindsym $mod+b splith bindsym $mod+v splitv # Switch the current container between different layout styles bindsym $mod+s layout stacking bindsym $mod+w layout tabbed bindsym $mod+e layout toggle split # Make the current focus fullscreen bindsym $mod+f fullscreen # Toggle the current focus between tiling and floating mode bindsym $mod+Shift+space floating toggle # Swap focus between the tiling area and the floating area bindsym $mod+space focus mode_toggle # Move focus to the parent container bindsym $mod+a focus parent # # Scratchpad: # # Sway has a "scratchpad", which is a bag of holding for windows. # You can send windows there and get them back later. # Move the currently focused window to the scratchpad bindsym $mod+Shift+minus move scratchpad # Show the next scratchpad window or hide the focused scratchpad window. # If there are multiple scratchpad windows, this command cycles through them. bindsym $mod+minus scratchpad show # # Resizing containers: # mode "resize" { # left will shrink the containers width # right will grow the containers width # up will shrink the containers height # down will grow the containers height bindsym $left resize shrink width 10px bindsym $down resize grow height 10px bindsym $up resize shrink height 10px bindsym $right resize grow width 10px # Ditto, with arrow keys bindsym Left resize shrink width 10px bindsym Down resize grow height 10px bindsym Up resize shrink height 10px bindsym Right resize grow width 10px # Return to default mode bindsym Return mode "default" bindsym Escape mode "default" } bindsym $mod+r mode "resize" # # Utilities: # # Special keys to adjust volume via PulseAudio bindsym --locked XF86AudioMute exec pactl set-sink-mute \@DEFAULT_SINK@ toggle bindsym --locked XF86AudioLowerVolume exec pactl set-sink-volume \@DEFAULT_SINK@ -5% bindsym --locked XF86AudioRaiseVolume exec pactl set-sink-volume \@DEFAULT_SINK@ +5% bindsym --locked XF86AudioMicMute exec pactl set-source-mute \@DEFAULT_SOURCE@ toggle # Special keys to control media via playerctl bindsym --locked XF86AudioPlay exec playerctl play-pause bindsym --locked XF86AudioPause exec playerctl play-pause bindsym --locked XF86AudioPrev exec playerctl previous bindsym --locked XF86AudioNext exec playerctl next bindsym --locked XF86AudioStop exec playerctl stop # Special keys to adjust brightness via brightnessctl bindsym --locked XF86MonBrightnessDown exec brightnessctl set 5%- bindsym --locked XF86MonBrightnessUp exec brightnessctl set 5%+ # Special key to take a screenshot with grim bindsym Print exec grim # # Status Bar: # # Read `man 5 sway-bar` for more information about this section. bar { position top # When the status_command prints a new line to stdout, swaybar updates. # The default just shows the current date and time. status_command while date +'%Y-%m-%d %X'; do sleep 1; done colors { statusline #ffffff background #323232 inactive_workspace #32323200 #32323200 #5c5c5c } } include @sysconfdir@/sway/config.d/* ================================================ FILE: include/cairo_util.h ================================================ #ifndef _SWAY_CAIRO_UTIL_H #define _SWAY_CAIRO_UTIL_H #include "config.h" #include #include #include void cairo_set_source_u32(cairo_t *cairo, uint32_t color); cairo_subpixel_order_t to_cairo_subpixel_order(enum wl_output_subpixel subpixel); cairo_surface_t *cairo_image_surface_scale(cairo_surface_t *image, int width, int height); #endif ================================================ FILE: include/gesture.h ================================================ #ifndef _SWAY_GESTURE_H #define _SWAY_GESTURE_H #include #include /** * A gesture type used in binding. */ enum gesture_type { GESTURE_TYPE_NONE = 0, GESTURE_TYPE_HOLD, GESTURE_TYPE_PINCH, GESTURE_TYPE_SWIPE, }; // Turns single type enum value to constant string representation. const char *gesture_type_string(enum gesture_type direction); // Value to use to accept any finger count extern const uint8_t GESTURE_FINGERS_ANY; /** * A gesture direction used in binding. */ enum gesture_direction { GESTURE_DIRECTION_NONE = 0, // Directions based on delta x and y GESTURE_DIRECTION_UP = 1 << 0, GESTURE_DIRECTION_DOWN = 1 << 1, GESTURE_DIRECTION_LEFT = 1 << 2, GESTURE_DIRECTION_RIGHT = 1 << 3, // Directions based on scale GESTURE_DIRECTION_INWARD = 1 << 4, GESTURE_DIRECTION_OUTWARD = 1 << 5, // Directions based on rotation GESTURE_DIRECTION_CLOCKWISE = 1 << 6, GESTURE_DIRECTION_COUNTERCLOCKWISE = 1 << 7, }; // Turns single direction enum value to constant string representation. const char *gesture_direction_string(enum gesture_direction direction); /** * Struct representing a pointer gesture */ struct gesture { enum gesture_type type; uint8_t fingers; uint32_t directions; }; /** * Parses gesture from [:][:] string. * * Return NULL on success, otherwise error message string */ char *gesture_parse(const char *input, struct gesture *output); // Turns gesture into string representation char *gesture_to_string(struct gesture *gesture); // Check if gesture is of certain type and finger count. bool gesture_check(struct gesture *target, enum gesture_type type, uint8_t fingers); // Check if a gesture target/binding is match by other gesture/input bool gesture_match(struct gesture *target, struct gesture *to_match, bool exact); // Returns true if gesture are exactly the same bool gesture_equal(struct gesture *a, struct gesture *b); // Compare distance between two matched target gestures. int8_t gesture_compare(struct gesture *a, struct gesture *b); // Small helper struct to track gestures over time struct gesture_tracker { enum gesture_type type; uint8_t fingers; double dx, dy; double scale; double rotation; }; // Begin gesture tracking void gesture_tracker_begin(struct gesture_tracker *tracker, enum gesture_type type, uint8_t fingers); // Check if the provides type is currently being tracked bool gesture_tracker_check(struct gesture_tracker *tracker, enum gesture_type type); // Update gesture track with new data point void gesture_tracker_update(struct gesture_tracker *tracker, double dx, double dy, double scale, double rotation); // Reset tracker void gesture_tracker_cancel(struct gesture_tracker *tracker); // Reset tracker and return gesture tracked struct gesture *gesture_tracker_end(struct gesture_tracker *tracker); #endif ================================================ FILE: include/ipc-client.h ================================================ #ifndef _SWAY_IPC_CLIENT_H #define _SWAY_IPC_CLIENT_H // arbitrary number, it's probably sufficient, higher number = more memory usage #define JSON_MAX_DEPTH 512 #include #include #include #include "ipc.h" /** * IPC response including type of IPC response, size of payload and the json * encoded payload string. */ struct ipc_response { uint32_t size; uint32_t type; char *payload; }; /** * Gets the path to the IPC socket from sway. */ char *get_socketpath(void); /** * Opens the sway socket. */ int ipc_open_socket(const char *socket_path); /** * Issues a single IPC command and returns the buffer. len will be updated with * the length of the buffer returned from sway. */ char *ipc_single_command(int socketfd, uint32_t type, const char *payload, uint32_t *len); /** * Receives a single IPC response and returns an ipc_response. */ struct ipc_response *ipc_recv_response(int socketfd); /** * Free ipc_response struct */ void free_ipc_response(struct ipc_response *response); /** * Sets the receive timeout for the IPC socket */ bool ipc_set_recv_timeout(int socketfd, struct timeval tv); #endif ================================================ FILE: include/ipc.h ================================================ #ifndef _SWAY_IPC_H #define _SWAY_IPC_H #define event_mask(ev) (1 << (ev & 0x7F)) enum ipc_command_type { // i3 command types - see i3's I3_REPLY_TYPE constants IPC_COMMAND = 0, IPC_GET_WORKSPACES = 1, IPC_SUBSCRIBE = 2, IPC_GET_OUTPUTS = 3, IPC_GET_TREE = 4, IPC_GET_MARKS = 5, IPC_GET_BAR_CONFIG = 6, IPC_GET_VERSION = 7, IPC_GET_BINDING_MODES = 8, IPC_GET_CONFIG = 9, IPC_SEND_TICK = 10, IPC_SYNC = 11, IPC_GET_BINDING_STATE = 12, // sway-specific command types IPC_GET_INPUTS = 100, IPC_GET_SEATS = 101, // Events sent from sway to clients. Events have the highest bits set. IPC_EVENT_WORKSPACE = ((1<<31) | 0), IPC_EVENT_OUTPUT = ((1<<31) | 1), IPC_EVENT_MODE = ((1<<31) | 2), IPC_EVENT_WINDOW = ((1<<31) | 3), IPC_EVENT_BARCONFIG_UPDATE = ((1<<31) | 4), IPC_EVENT_BINDING = ((1<<31) | 5), IPC_EVENT_SHUTDOWN = ((1<<31) | 6), IPC_EVENT_TICK = ((1<<31) | 7), // sway-specific event types IPC_EVENT_BAR_STATE_UPDATE = ((1<<31) | 20), IPC_EVENT_INPUT = ((1<<31) | 21), }; #endif ================================================ FILE: include/list.h ================================================ #ifndef _SWAY_LIST_H #define _SWAY_LIST_H typedef struct { int capacity; int length; void **items; } list_t; list_t *create_list(void); void list_free(list_t *list); void list_add(list_t *list, void *item); void list_insert(list_t *list, int index, void *item); void list_del(list_t *list, int index); void list_cat(list_t *list, list_t *source); // See qsort. Remember to use *_qsort functions as compare functions, // because they dereference the left and right arguments first! void list_qsort(list_t *list, int compare(const void *left, const void *right)); // Return index for first item in list that returns 0 for given compare // function or -1 if none matches. int list_seq_find(list_t *list, int compare(const void *item, const void *cmp_to), const void *cmp_to); int list_find(list_t *list, const void *item); // stable sort since qsort is not guaranteed to be stable void list_stable_sort(list_t *list, int compare(const void *a, const void *b)); // swap two elements in a list void list_swap(list_t *list, int src, int dest); // move item to end of list void list_move_to_end(list_t *list, void *item); /* Calls `free` for each item in the list, then frees the list. * Do not use this to free lists of primitives or items that require more * complicated deallocation code. */ void list_free_items_and_destroy(list_t *list); #endif ================================================ FILE: include/log.h ================================================ #ifndef _SWAY_LOG_H #define _SWAY_LOG_H #include #include #include #include typedef enum { SWAY_SILENT = 0, SWAY_ERROR = 1, SWAY_INFO = 2, SWAY_DEBUG = 3, SWAY_LOG_IMPORTANCE_LAST, } sway_log_importance_t; #ifdef __GNUC__ #define ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end))) #else #define ATTRIB_PRINTF(start, end) #endif void error_handler(int sig); typedef void (*terminate_callback_t)(int exit_code); // Will log all messages less than or equal to `verbosity` // The `terminate` callback is called by `sway_abort` void sway_log_init(sway_log_importance_t verbosity, terminate_callback_t terminate); void _sway_log(sway_log_importance_t verbosity, const char *format, ...) ATTRIB_PRINTF(2, 3); void _sway_vlog(sway_log_importance_t verbosity, const char *format, va_list args) ATTRIB_PRINTF(2, 0); void _sway_abort(const char *filename, ...) ATTRIB_PRINTF(1, 2); bool _sway_assert(bool condition, const char* format, ...) ATTRIB_PRINTF(2, 3); #ifdef SWAY_REL_SRC_DIR // strip prefix from __FILE__, leaving the path relative to the project root #define _SWAY_FILENAME ((const char *)__FILE__ + sizeof(SWAY_REL_SRC_DIR) - 1) #else #define _SWAY_FILENAME __FILE__ #endif #define sway_log(verb, fmt, ...) \ _sway_log(verb, "[%s:%d] " fmt, _SWAY_FILENAME, __LINE__, ##__VA_ARGS__) #define sway_vlog(verb, fmt, args) \ _sway_vlog(verb, "[%s:%d] " fmt, _SWAY_FILENAME, __LINE__, args) #define sway_log_errno(verb, fmt, ...) \ sway_log(verb, fmt ": %s", ##__VA_ARGS__, strerror(errno)) #define sway_abort(FMT, ...) \ _sway_abort("[%s:%d] " FMT, _SWAY_FILENAME, __LINE__, ##__VA_ARGS__) #define sway_assert(COND, FMT, ...) \ _sway_assert(COND, "[%s:%d] %s:" FMT, _SWAY_FILENAME, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__) #endif ================================================ FILE: include/loop.h ================================================ #ifndef _SWAY_LOOP_H #define _SWAY_LOOP_H #include /** * This is an event loop system designed for sway clients, not sway itself. * * The loop consists of file descriptors and timers. Typically the Wayland * display's file descriptor will be one of the fds in the loop. */ struct loop; struct loop_timer; /** * Create an event loop. */ struct loop *loop_create(void); /** * Destroy the event loop (eg. on program termination). */ void loop_destroy(struct loop *loop); /** * Poll the event loop. This will block until one of the fds has data. */ void loop_poll(struct loop *loop); /** * Add a file descriptor to the loop. */ void loop_add_fd(struct loop *loop, int fd, short mask, void (*func)(int fd, short mask, void *data), void *data); /** * Add a timer to the loop. * * When the timer expires, the timer will be removed from the loop and freed. */ struct loop_timer *loop_add_timer(struct loop *loop, int ms, void (*callback)(void *data), void *data); /** * Remove a file descriptor from the loop. */ bool loop_remove_fd(struct loop *loop, int fd); /** * Remove a timer from the loop. */ bool loop_remove_timer(struct loop *loop, struct loop_timer *timer); #endif ================================================ FILE: include/meson.build ================================================ configure_file(output: 'config.h', configuration: conf_data) ================================================ FILE: include/pango.h ================================================ #ifndef _SWAY_PANGO_H #define _SWAY_PANGO_H #include #include #include #include #include #include "stringop.h" /** * Utility function which escape characters a & < > ' ". * * The function returns the length of the escaped string, optionally writing the * escaped string to dest if provided. */ size_t escape_markup_text(const char *src, char *dest); PangoLayout *get_pango_layout(cairo_t *cairo, const PangoFontDescription *desc, const char *text, double scale, bool markup); void get_text_size(cairo_t *cairo, const PangoFontDescription *desc, int *width, int *height, int *baseline, double scale, bool markup, const char *fmt, ...) _SWAY_ATTRIB_PRINTF(8, 9); void get_text_metrics(const PangoFontDescription *desc, int *height, int *baseline); void render_text(cairo_t *cairo, PangoFontDescription *desc, double scale, bool markup, const char *fmt, ...) _SWAY_ATTRIB_PRINTF(5, 6); #endif ================================================ FILE: include/pool-buffer.h ================================================ #ifndef _SWAY_BUFFERS_H #define _SWAY_BUFFERS_H #include #include #include #include #include struct pool_buffer { struct wl_buffer *buffer; cairo_surface_t *surface; cairo_t *cairo; PangoContext *pango; uint32_t width, height; void *data; size_t size; bool busy; }; struct pool_buffer *get_next_buffer(struct wl_shm *shm, struct pool_buffer pool[static 2], uint32_t width, uint32_t height); void destroy_buffer(struct pool_buffer *buffer); #endif ================================================ FILE: include/stringop.h ================================================ #ifndef _SWAY_STRINGOP_H #define _SWAY_STRINGOP_H #include #include #include "list.h" #ifdef __GNUC__ #define _SWAY_ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end))) #else #define _SWAY_ATTRIB_PRINTF(start, end) #endif void strip_whitespace(char *str); void strip_quotes(char *str); // strcat that does nothing if dest or src is NULL char *lenient_strcat(char *dest, const char *src); char *lenient_strncat(char *dest, const char *src, size_t len); // strcmp that also handles null pointers. int lenient_strcmp(const char *a, const char *b); // Simply split a string with delims, free with `list_free_items_and_destroy` list_t *split_string(const char *str, const char *delims); // Splits an argument string, keeping quotes intact char **split_args(const char *str, int *argc); void free_argv(int argc, char **argv); int unescape_string(char *string); char *join_args(char **argv, int argc); // Split string into 2 by delim, handle quotes char *argsep(char **stringp, const char *delim, char *matched_delim); // Expand a path using shell replacements such as $HOME and ~ bool expand_path(char **path); char *vformat_str(const char *fmt, va_list args) _SWAY_ATTRIB_PRINTF(1, 0); char *format_str(const char *fmt, ...) _SWAY_ATTRIB_PRINTF(1, 2); bool has_prefix(const char *str, const char *prefix); #endif ================================================ FILE: include/sway/commands.h ================================================ #ifndef _SWAY_COMMANDS_H #define _SWAY_COMMANDS_H #include #include "config.h" #include "stringop.h" struct sway_container; typedef struct cmd_results *sway_cmd(int argc, char **argv); struct cmd_handler { const char *command; sway_cmd *handle; }; /** * Indicates the result of a command's execution. */ enum cmd_status { CMD_SUCCESS, /**< The command was successful */ CMD_FAILURE, /**< The command resulted in an error */ CMD_INVALID, /**< Unknown command or parser error */ CMD_DEFER, /**< Command execution deferred */ CMD_BLOCK, CMD_BLOCK_COMMANDS, CMD_BLOCK_END }; /** * Stores the result of executing a command. */ struct cmd_results { enum cmd_status status; /** * Human friendly error message, or NULL on success */ char *error; }; enum expected_args { EXPECTED_AT_LEAST, EXPECTED_AT_MOST, EXPECTED_EQUAL_TO }; struct cmd_results *checkarg(int argc, const char *name, enum expected_args type, int val); const struct cmd_handler *find_handler(const char *line, const struct cmd_handler *cmd_handlers, size_t handlers_size); /** * Parse and executes a command. * * If the command string contains criteria then the command will be executed on * all matching containers. Otherwise, it'll run on the `con` container. If * `con` is NULL then it'll run on the currently focused container. */ list_t *execute_command(char *command, struct sway_seat *seat, struct sway_container *con); /** * Parse and handles a command during config file loading. * * Do not use this under normal conditions. */ struct cmd_results *config_command(char *command, char **new_block); /** * Parse and handle a sub command */ struct cmd_results *config_subcommand(char **argv, int argc, const struct cmd_handler *handlers, size_t handlers_size); /* * Parses a command policy rule. */ struct cmd_results *config_commands_command(char *exec); /** * Allocates a cmd_results object. */ struct cmd_results *cmd_results_new(enum cmd_status status, const char *error, ...) _SWAY_ATTRIB_PRINTF(2, 3); /** * Frees a cmd_results object. */ void free_cmd_results(struct cmd_results *results); /** * Serializes a list of cmd_results to a JSON string. * * Free the JSON string later on. */ char *cmd_results_to_json(list_t *res_list); /** * TODO: Move this function and its dependent functions to container.c. */ void container_resize_tiled(struct sway_container *parent, uint32_t axis, int amount); struct sway_container *container_find_resize_parent(struct sway_container *con, uint32_t edge); /** * Handlers shared by exec and exec_always. */ sway_cmd cmd_exec_validate; sway_cmd cmd_exec_process; sway_cmd cmd_allow_tearing; sway_cmd cmd_assign; sway_cmd cmd_bar; sway_cmd cmd_bindcode; sway_cmd cmd_bindgesture; sway_cmd cmd_bindswitch; sway_cmd cmd_bindsym; sway_cmd cmd_border; sway_cmd cmd_client_noop; sway_cmd cmd_client_focused; sway_cmd cmd_client_focused_inactive; sway_cmd cmd_client_focused_tab_title; sway_cmd cmd_client_unfocused; sway_cmd cmd_client_urgent; sway_cmd cmd_client_placeholder; sway_cmd cmd_client_background; sway_cmd cmd_commands; sway_cmd cmd_create_output; sway_cmd cmd_default_border; sway_cmd cmd_default_floating_border; sway_cmd cmd_default_orientation; sway_cmd cmd_exec; sway_cmd cmd_exec_always; sway_cmd cmd_exit; sway_cmd cmd_floating; sway_cmd cmd_floating_maximum_size; sway_cmd cmd_floating_minimum_size; sway_cmd cmd_floating_modifier; sway_cmd cmd_floating_scroll; sway_cmd cmd_focus; sway_cmd cmd_focus_follows_mouse; sway_cmd cmd_focus_on_window_activation; sway_cmd cmd_focus_wrapping; sway_cmd cmd_font; sway_cmd cmd_for_window; sway_cmd cmd_force_display_urgency_hint; sway_cmd cmd_force_focus_wrapping; sway_cmd cmd_fullscreen; sway_cmd cmd_gaps; sway_cmd cmd_hide_edge_borders; sway_cmd cmd_include; sway_cmd cmd_inhibit_idle; sway_cmd cmd_input; sway_cmd cmd_seat; sway_cmd cmd_ipc; sway_cmd cmd_kill; sway_cmd cmd_layout; sway_cmd cmd_log_colors; sway_cmd cmd_mark; sway_cmd cmd_max_render_time; sway_cmd cmd_mode; sway_cmd cmd_mouse_warping; sway_cmd cmd_move; sway_cmd cmd_new_float; sway_cmd cmd_new_window; sway_cmd cmd_nop; sway_cmd cmd_opacity; sway_cmd cmd_no_focus; sway_cmd cmd_output; sway_cmd cmd_permit; sway_cmd cmd_popup_during_fullscreen; sway_cmd cmd_primary_selection; sway_cmd cmd_reject; sway_cmd cmd_reload; sway_cmd cmd_rename; sway_cmd cmd_resize; sway_cmd cmd_scratchpad; sway_cmd cmd_seamless_mouse; sway_cmd cmd_set; sway_cmd cmd_shortcuts_inhibitor; sway_cmd cmd_show_marks; sway_cmd cmd_smart_borders; sway_cmd cmd_smart_gaps; sway_cmd cmd_split; sway_cmd cmd_splith; sway_cmd cmd_splitt; sway_cmd cmd_splitv; sway_cmd cmd_sticky; sway_cmd cmd_swaybg_command; sway_cmd cmd_swaynag_command; sway_cmd cmd_swap; sway_cmd cmd_tiling_drag; sway_cmd cmd_tiling_drag_threshold; sway_cmd cmd_title_align; sway_cmd cmd_title_format; sway_cmd cmd_titlebar_border_thickness; sway_cmd cmd_titlebar_padding; sway_cmd cmd_unbindcode; sway_cmd cmd_unbindswitch; sway_cmd cmd_unbindgesture; sway_cmd cmd_unbindsym; sway_cmd cmd_unmark; sway_cmd cmd_urgent; sway_cmd cmd_workspace; sway_cmd cmd_workspace_layout; sway_cmd cmd_ws_auto_back_and_forth; sway_cmd cmd_xwayland; sway_cmd bar_cmd_bindcode; sway_cmd bar_cmd_binding_mode_indicator; sway_cmd bar_cmd_bindsym; sway_cmd bar_cmd_colors; sway_cmd bar_cmd_font; sway_cmd bar_cmd_gaps; sway_cmd bar_cmd_mode; sway_cmd bar_cmd_modifier; sway_cmd bar_cmd_output; sway_cmd bar_cmd_height; sway_cmd bar_cmd_hidden_state; sway_cmd bar_cmd_icon_theme; sway_cmd bar_cmd_id; sway_cmd bar_cmd_position; sway_cmd bar_cmd_separator_symbol; sway_cmd bar_cmd_status_command; sway_cmd bar_cmd_status_edge_padding; sway_cmd bar_cmd_status_padding; sway_cmd bar_cmd_pango_markup; sway_cmd bar_cmd_strip_workspace_numbers; sway_cmd bar_cmd_strip_workspace_name; sway_cmd bar_cmd_swaybar_command; sway_cmd bar_cmd_tray_bindcode; sway_cmd bar_cmd_tray_bindsym; sway_cmd bar_cmd_tray_output; sway_cmd bar_cmd_tray_padding; sway_cmd bar_cmd_unbindcode; sway_cmd bar_cmd_unbindsym; sway_cmd bar_cmd_wrap_scroll; sway_cmd bar_cmd_workspace_buttons; sway_cmd bar_cmd_workspace_min_width; sway_cmd bar_colors_cmd_active_workspace; sway_cmd bar_colors_cmd_background; sway_cmd bar_colors_cmd_focused_background; sway_cmd bar_colors_cmd_binding_mode; sway_cmd bar_colors_cmd_focused_workspace; sway_cmd bar_colors_cmd_inactive_workspace; sway_cmd bar_colors_cmd_separator; sway_cmd bar_colors_cmd_focused_separator; sway_cmd bar_colors_cmd_statusline; sway_cmd bar_colors_cmd_focused_statusline; sway_cmd bar_colors_cmd_urgent_workspace; sway_cmd input_cmd_seat; sway_cmd input_cmd_accel_profile; sway_cmd input_cmd_calibration_matrix; sway_cmd input_cmd_click_method; sway_cmd input_cmd_clickfinger_button_map; sway_cmd input_cmd_drag; sway_cmd input_cmd_drag_lock; sway_cmd input_cmd_dwt; sway_cmd input_cmd_dwtp; sway_cmd input_cmd_events; sway_cmd input_cmd_left_handed; sway_cmd input_cmd_map_from_region; sway_cmd input_cmd_map_to_output; sway_cmd input_cmd_map_to_region; sway_cmd input_cmd_middle_emulation; sway_cmd input_cmd_natural_scroll; sway_cmd input_cmd_pointer_accel; sway_cmd input_cmd_rotation_angle; sway_cmd input_cmd_scroll_factor; sway_cmd input_cmd_repeat_delay; sway_cmd input_cmd_repeat_rate; sway_cmd input_cmd_scroll_button; sway_cmd input_cmd_scroll_button_lock; sway_cmd input_cmd_scroll_method; sway_cmd input_cmd_tap; sway_cmd input_cmd_tap_button_map; sway_cmd input_cmd_tool_mode; sway_cmd input_cmd_xkb_capslock; sway_cmd input_cmd_xkb_file; sway_cmd input_cmd_xkb_layout; sway_cmd input_cmd_xkb_model; sway_cmd input_cmd_xkb_numlock; sway_cmd input_cmd_xkb_options; sway_cmd input_cmd_xkb_rules; sway_cmd input_cmd_xkb_switch_layout; sway_cmd input_cmd_xkb_variant; sway_cmd output_cmd_adaptive_sync; sway_cmd output_cmd_allow_tearing; sway_cmd output_cmd_background; sway_cmd output_cmd_color_profile; sway_cmd output_cmd_disable; sway_cmd output_cmd_dpms; sway_cmd output_cmd_enable; sway_cmd output_cmd_hdr; sway_cmd output_cmd_max_render_time; sway_cmd output_cmd_mode; sway_cmd output_cmd_modeline; sway_cmd output_cmd_position; sway_cmd output_cmd_power; sway_cmd output_cmd_render_bit_depth; sway_cmd output_cmd_scale; sway_cmd output_cmd_scale_filter; sway_cmd output_cmd_subpixel; sway_cmd output_cmd_toggle; sway_cmd output_cmd_transform; sway_cmd output_cmd_unplug; sway_cmd seat_cmd_attach; sway_cmd seat_cmd_cursor; sway_cmd seat_cmd_fallback; sway_cmd seat_cmd_hide_cursor; sway_cmd seat_cmd_idle_inhibit; sway_cmd seat_cmd_idle_wake; sway_cmd seat_cmd_keyboard_grouping; sway_cmd seat_cmd_pointer_constraint; sway_cmd seat_cmd_shortcuts_inhibitor; sway_cmd seat_cmd_xcursor_theme; sway_cmd cmd_ipc_cmd; sway_cmd cmd_ipc_events; sway_cmd cmd_ipc_event_cmd; #endif ================================================ FILE: include/sway/config.h ================================================ #ifndef _SWAY_CONFIG_H #define _SWAY_CONFIG_H #include #include #include #include #include #include #include #include #include #include #include "../include/config.h" #include "gesture.h" #include "list.h" #include "stringop.h" #include "swaynag.h" #include "tree/container.h" #include "sway/input/tablet.h" #include "sway/tree/root.h" #include "wlr-layer-shell-unstable-v1-protocol.h" #include // TODO: Refactor this shit /** * Describes a variable created via the `set` command. */ struct sway_variable { char *name; char *value; }; enum binding_input_type { BINDING_KEYCODE, BINDING_KEYSYM, BINDING_MOUSECODE, BINDING_MOUSESYM, BINDING_SWITCH, // dummy, only used to call seat_execute_command BINDING_GESTURE // dummy, only used to call seat_execute_command }; enum binding_flags { BINDING_RELEASE = 1 << 0, BINDING_LOCKED = 1 << 1, // keyboard only BINDING_BORDER = 1 << 2, // mouse only; trigger on container border BINDING_CONTENTS = 1 << 3, // mouse only; trigger on container contents BINDING_TITLEBAR = 1 << 4, // mouse only; trigger on container titlebar BINDING_CODE = 1 << 5, // keyboard only; convert keysyms into keycodes BINDING_RELOAD = 1 << 6, // switch only; (re)trigger binding on reload BINDING_INHIBITED = 1 << 7, // keyboard only: ignore shortcut inhibitor BINDING_NOREPEAT = 1 << 8, // keyboard only; do not trigger when repeating a held key BINDING_EXACT = 1 << 9, // gesture only; only trigger on exact match }; /** * A key (or mouse) binding and an associated command. */ struct sway_binding { enum binding_input_type type; int order; char *input; uint32_t flags; list_t *keys; // sorted in ascending order list_t *syms; // sorted in ascending order; NULL if BINDING_CODE is not set uint32_t modifiers; xkb_layout_index_t group; char *command; }; enum sway_switch_trigger { SWAY_SWITCH_TRIGGER_OFF, SWAY_SWITCH_TRIGGER_ON, SWAY_SWITCH_TRIGGER_TOGGLE, }; /** * A laptop switch binding and an associated command. */ struct sway_switch_binding { enum wlr_switch_type type; enum sway_switch_trigger trigger; uint32_t flags; char *command; }; /** * A gesture binding and an associated command. */ struct sway_gesture_binding { char *input; uint32_t flags; struct gesture gesture; char *command; }; /** * Focus on window activation. */ enum sway_fowa { FOWA_SMART, FOWA_URGENT, FOWA_FOCUS, FOWA_NONE, }; /** * A "mode" of keybindings created via the `mode` command. */ struct sway_mode { char *name; list_t *keysym_bindings; list_t *keycode_bindings; list_t *mouse_bindings; list_t *switch_bindings; list_t *gesture_bindings; bool pango; }; struct input_config_mapped_from_region { double x1, y1; double x2, y2; bool mm; }; struct calibration_matrix { bool configured; float matrix[6]; }; enum input_config_mapped_to { MAPPED_TO_DEFAULT, MAPPED_TO_OUTPUT, MAPPED_TO_REGION, }; struct input_config_tool { enum wlr_tablet_tool_type type; enum sway_tablet_tool_mode mode; }; /** * options for input devices */ struct input_config { char *identifier; const char *input_type; int accel_profile; struct calibration_matrix calibration_matrix; int click_method; int clickfinger_button_map; int drag; int drag_lock; int dwt; int dwtp; int left_handed; int middle_emulation; int natural_scroll; float pointer_accel; float rotation_angle; float scroll_factor; int repeat_delay; int repeat_rate; int scroll_button; int scroll_button_lock; int scroll_method; int send_events; int tap; int tap_button_map; char *xkb_layout; char *xkb_model; char *xkb_options; char *xkb_rules; char *xkb_variant; char *xkb_file; bool xkb_file_is_set; int xkb_numlock; int xkb_capslock; struct input_config_mapped_from_region *mapped_from_region; enum input_config_mapped_to mapped_to; char *mapped_to_output; struct wlr_box *mapped_to_region; list_t *tools; bool capturable; struct wlr_box region; }; /** * Options for misc device configurations that happen in the seat block */ struct seat_attachment_config { char *identifier; // TODO other things are configured here for some reason }; enum seat_config_hide_cursor_when_typing { HIDE_WHEN_TYPING_DEFAULT, // the default is currently disabled HIDE_WHEN_TYPING_ENABLE, HIDE_WHEN_TYPING_DISABLE, }; enum seat_config_allow_constrain { CONSTRAIN_DEFAULT, // the default is currently enabled CONSTRAIN_ENABLE, CONSTRAIN_DISABLE, }; enum seat_config_shortcuts_inhibit { SHORTCUTS_INHIBIT_DEFAULT, // the default is currently enabled SHORTCUTS_INHIBIT_ENABLE, SHORTCUTS_INHIBIT_DISABLE, }; enum seat_keyboard_grouping { KEYBOARD_GROUP_DEFAULT, // the default is currently smart KEYBOARD_GROUP_NONE, KEYBOARD_GROUP_SMART, // keymap and repeat info }; enum sway_input_idle_source { IDLE_SOURCE_KEYBOARD = 1 << 0, IDLE_SOURCE_POINTER = 1 << 1, IDLE_SOURCE_TOUCH = 1 << 2, IDLE_SOURCE_TABLET_PAD = 1 << 3, IDLE_SOURCE_TABLET_TOOL = 1 << 4, IDLE_SOURCE_SWITCH = 1 << 5, }; /** * Options for multiseat and other misc device configurations */ struct seat_config { char *name; int fallback; // -1 means not set list_t *attachments; // list of seat_attachment configs int hide_cursor_timeout; enum seat_config_hide_cursor_when_typing hide_cursor_when_typing; enum seat_config_allow_constrain allow_constrain; enum seat_config_shortcuts_inhibit shortcuts_inhibit; enum seat_keyboard_grouping keyboard_grouping; uint32_t idle_inhibit_sources, idle_wake_sources; struct { char *name; int size; } xcursor_theme; }; enum scale_filter_mode { SCALE_FILTER_DEFAULT, // the default is currently smart SCALE_FILTER_LINEAR, SCALE_FILTER_NEAREST, SCALE_FILTER_SMART, }; enum render_bit_depth { RENDER_BIT_DEPTH_DEFAULT, // the default is currently 8 for SDR, 10 for HDR RENDER_BIT_DEPTH_6, RENDER_BIT_DEPTH_8, RENDER_BIT_DEPTH_10, }; enum color_profile { COLOR_PROFILE_DEFAULT, // default is Transform with NULL color_transform COLOR_PROFILE_TRANSFORM, // use color_transform from output_config COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES, // create transform from wlr_output }; /** * Size and position configuration for a particular output. * * This is set via the `output` command. */ struct output_config { char *name; int enabled; int power; int width, height; float refresh_rate; int custom_mode; drmModeModeInfo drm_mode; int x, y; float scale; enum scale_filter_mode scale_filter; int32_t transform; enum wl_output_subpixel subpixel; int max_render_time; // In milliseconds int adaptive_sync; enum render_bit_depth render_bit_depth; enum color_profile color_profile; struct wlr_color_transform *color_transform; int allow_tearing; int hdr; char *background; char *background_option; char *background_fallback; }; /** * Stores size of gaps for each side */ struct side_gaps { int top; int right; int bottom; int left; }; enum smart_gaps_mode { SMART_GAPS_OFF, SMART_GAPS_ON, SMART_GAPS_INVERSE_OUTER, }; /** * Stores configuration for a workspace, regardless of whether the workspace * exists. */ struct workspace_config { char *workspace; list_t *outputs; int gaps_inner; struct side_gaps gaps_outer; }; enum pango_markup_config { PANGO_MARKUP_DISABLED = false, PANGO_MARKUP_ENABLED = true, PANGO_MARKUP_DEFAULT // The default is font dependent ("pango:" prefix) }; struct bar_config { char *swaybar_command; struct wl_client *client; struct wl_listener client_destroy; /** * One of "dock", "hide", "invisible" * * Always visible in dock mode. Visible only when modifier key is held in hide mode. * Never visible in invisible mode. */ char *mode; /** * One of "show" or "hide". * * In "show" mode, it will always be shown on top of the active workspace. */ char *hidden_state; bool visible_by_modifier; // only relevant in "hide" mode /** * Id name used to identify the bar through IPC. * * Defaults to bar-x, where x corresponds to the position of the * embedding bar block in the config file (bar-0, bar-1, ...). */ char *id; uint32_t modifier; list_t *outputs; char *position; list_t *bindings; char *status_command; enum pango_markup_config pango_markup; char *font; int height; // -1 not defined bool workspace_buttons; bool wrap_scroll; char *separator_symbol; bool strip_workspace_numbers; bool strip_workspace_name; bool binding_mode_indicator; bool verbose; struct side_gaps gaps; int status_padding; int status_edge_padding; uint32_t workspace_min_width; struct { char *background; char *statusline; char *separator; char *focused_background; char *focused_statusline; char *focused_separator; char *focused_workspace_border; char *focused_workspace_bg; char *focused_workspace_text; char *active_workspace_border; char *active_workspace_bg; char *active_workspace_text; char *inactive_workspace_border; char *inactive_workspace_bg; char *inactive_workspace_text; char *urgent_workspace_border; char *urgent_workspace_bg; char *urgent_workspace_text; char *binding_mode_border; char *binding_mode_bg; char *binding_mode_text; } colors; #if HAVE_TRAY char *icon_theme; struct wl_list tray_bindings; // struct tray_binding::link list_t *tray_outputs; // char * int tray_padding; #endif }; struct bar_binding { uint32_t button; bool release; char *command; }; #if HAVE_TRAY struct tray_binding { uint32_t button; const char *command; struct wl_list link; // struct tray_binding::link }; #endif struct border_colors { float border[4]; float background[4]; float text[4]; float indicator[4]; float child_border[4]; }; enum edge_border_types { E_NONE, /**< Don't hide edge borders */ E_VERTICAL, /**< hide vertical edge borders */ E_HORIZONTAL, /**< hide horizontal edge borders */ E_BOTH, /**< hide vertical and horizontal edge borders */ }; enum edge_border_smart_types { ESMART_OFF, ESMART_ON, /**< hide edges if precisely one window is present in workspace */ ESMART_NO_GAPS, /**< hide edges if one window and gaps to edge is zero */ }; enum sway_popup_during_fullscreen { POPUP_SMART, POPUP_IGNORE, POPUP_LEAVE, }; enum focus_follows_mouse_mode { FOLLOWS_NO, FOLLOWS_YES, FOLLOWS_ALWAYS, }; enum focus_wrapping_mode { WRAP_NO, WRAP_YES, WRAP_FORCE, WRAP_WORKSPACE, }; enum mouse_warping_mode { WARP_NO, WARP_OUTPUT, WARP_CONTAINER, }; enum alignment { ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT, }; enum xwayland_mode { XWAYLAND_MODE_DISABLED, XWAYLAND_MODE_LAZY, XWAYLAND_MODE_IMMEDIATE, }; /** * The configuration struct. The result of loading a config file. */ struct sway_config { char *swaynag_command; struct swaynag_instance swaynag_config_errors; list_t *symbols; list_t *modes; list_t *bars; list_t *cmd_queue; list_t *workspace_configs; list_t *output_configs; list_t *input_configs; list_t *input_type_configs; list_t *seat_configs; list_t *criteria; list_t *no_focus; list_t *active_bar_modifiers; struct sway_mode *current_mode; struct bar_config *current_bar; uint32_t floating_mod; bool floating_mod_inverse; uint32_t dragging_key; uint32_t resizing_key; char *floating_scroll_up_cmd; char *floating_scroll_down_cmd; char *floating_scroll_left_cmd; char *floating_scroll_right_cmd; enum sway_container_layout default_orientation; enum sway_container_layout default_layout; char *font; // Used for IPC. PangoFontDescription *font_description; // Used internally for rendering and validating. int font_height; int font_baseline; bool pango_markup; int titlebar_border_thickness; int titlebar_h_padding; int titlebar_v_padding; size_t urgent_timeout; enum sway_fowa focus_on_window_activation; enum sway_popup_during_fullscreen popup_during_fullscreen; enum xwayland_mode xwayland; // swaybg char *swaybg_command; struct wl_client *swaybg_client; struct wl_listener swaybg_client_destroy; // Flags enum focus_follows_mouse_mode focus_follows_mouse; enum mouse_warping_mode mouse_warping; enum focus_wrapping_mode focus_wrapping; bool active; bool failed; bool reloading; bool reading; bool validating; bool auto_back_and_forth; bool show_marks; enum alignment title_align; bool primary_selection; bool tiling_drag; int tiling_drag_threshold; enum smart_gaps_mode smart_gaps; int gaps_inner; struct side_gaps gaps_outer; list_t *config_chain; bool user_config_path; const char *current_config_path; const char *current_config; int current_config_line_number; char *current_config_line; enum sway_container_border border; enum sway_container_border floating_border; int border_thickness; int floating_border_thickness; enum edge_border_types hide_edge_borders; enum edge_border_smart_types hide_edge_borders_smart; bool hide_lone_tab; // border colors struct { struct border_colors focused; struct border_colors focused_inactive; struct border_colors focused_tab_title; struct border_colors unfocused; struct border_colors urgent; struct border_colors placeholder; float background[4]; } border_colors; bool has_focused_tab_title; // floating view int32_t floating_maximum_width; int32_t floating_maximum_height; int32_t floating_minimum_width; int32_t floating_minimum_height; // The keysym to keycode translation struct xkb_state *keysym_translation_state; // Context for command handlers struct { struct input_config *input_config; struct output_config *output_config; struct seat_config *seat_config; struct sway_seat *seat; struct sway_node *node; struct sway_container *container; struct sway_workspace *workspace; bool node_overridden; // True if the node is selected by means other than focus struct { int argc; char **argv; } leftovers; } handler_context; }; /** * Loads the main config from the given path. is_active should be true when * reloading the config. */ bool load_main_config(const char *path, bool is_active, bool validating); /** * Loads an included config. Can only be used after load_main_config. */ void load_include_configs(const char *path, struct sway_config *config, struct swaynag_instance *swaynag); /** * Reads the config from the given FILE. */ bool read_config(FILE *file, struct sway_config *config, struct swaynag_instance *swaynag); /** * Run the commands that were deferred when reading the config file. */ void run_deferred_commands(void); /** * Run the binding commands that were deferred when initializing the inputs */ void run_deferred_bindings(void); /** * Adds a warning entry to the swaynag instance used for errors. */ void config_add_swaynag_warning(char *fmt, ...) _SWAY_ATTRIB_PRINTF(1, 2); /** * Free config struct */ void free_config(struct sway_config *config); void free_sway_variable(struct sway_variable *var); /** * Does variable replacement for a string based on the config's currently loaded variables. */ char *do_var_replacement(char *str); int input_identifier_cmp(const void *item, const void *data); struct input_config *new_input_config(const char* identifier); void merge_input_config(struct input_config *dst, struct input_config *src); struct input_config *store_input_config(struct input_config *ic, char **error); void input_config_fill_rule_names(struct input_config *ic, struct xkb_rule_names *rules); void free_input_config(struct input_config *ic); int seat_name_cmp(const void *item, const void *data); struct seat_config *new_seat_config(const char* name); void merge_seat_config(struct seat_config *dst, struct seat_config *src); struct seat_config *copy_seat_config(struct seat_config *seat); void free_seat_config(struct seat_config *ic); struct seat_attachment_config *seat_attachment_config_new(void); struct seat_attachment_config *seat_config_get_attachment( struct seat_config *seat_config, char *identifier); struct seat_config *store_seat_config(struct seat_config *seat); int output_name_cmp(const void *item, const void *data); void output_get_identifier(char *identifier, size_t len, struct sway_output *output); const char *sway_output_scale_filter_to_string(enum scale_filter_mode scale_filter); struct output_config *new_output_config(const char *name); bool apply_output_configs(struct output_config **ocs, size_t ocs_len, bool test_only, bool degrade_to_off); void apply_stored_output_configs(void); /** * store_output_config stores a new output config. An output may be matched by * three different config types, in order of precedence: Identifier, name and * wildcard. When storing a config type of lower precedence, assume that the * user wants the config to take immediate effect by superseding (clearing) the * same values from higher presedence configuration. */ void store_output_config(struct output_config *oc); struct output_config *find_output_config(struct sway_output *output); void free_output_config(struct output_config *oc); void request_modeset(void); void force_modeset(void); bool modeset_is_pending(void); bool spawn_swaybg(void); int workspace_output_cmp_workspace(const void *a, const void *b); void free_sway_binding(struct sway_binding *sb); void free_switch_binding(struct sway_switch_binding *binding); void free_gesture_binding(struct sway_gesture_binding *binding); void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding); void load_swaybar(struct bar_config *bar); void load_swaybars(void); struct bar_config *default_bar_config(void); void free_bar_config(struct bar_config *bar); void free_bar_binding(struct bar_binding *binding); void free_workspace_config(struct workspace_config *wsc); /** * Updates the value of config->font_height based on the metrics for title's * font as reported by pango. * * If the height has changed, all containers will be rearranged to take on the * new size. */ void config_update_font_height(void); /** * Convert bindsym into bindcode using the first configured layout. * Return false in case the conversion is unsuccessful. */ bool translate_binding(struct sway_binding *binding); void translate_keysyms(struct input_config *input_config); void binding_add_translated(struct sway_binding *binding, list_t *bindings); /* Global config singleton. */ extern struct sway_config *config; #endif ================================================ FILE: include/sway/criteria.h ================================================ #ifndef _SWAY_CRITERIA_H #define _SWAY_CRITERIA_H #define PCRE2_CODE_UNIT_WIDTH 8 #include #include "config.h" #include "list.h" #include "tree/view.h" #if WLR_HAS_XWAYLAND #include "sway/xwayland.h" #endif enum criteria_type { CT_COMMAND = 1 << 0, CT_ASSIGN_OUTPUT = 1 << 1, CT_ASSIGN_WORKSPACE = 1 << 2, CT_ASSIGN_WORKSPACE_NUMBER = 1 << 3, CT_NO_FOCUS = 1 << 4, }; enum pattern_type { PATTERN_PCRE2, PATTERN_FOCUSED, }; struct pattern { enum pattern_type match_type; pcre2_code *regex; }; struct criteria { enum criteria_type type; char *raw; // entire criteria string (for logging) char *cmdlist; char *target; // workspace or output name for `assign` criteria struct pattern *title; struct pattern *shell; struct pattern *app_id; struct pattern *con_mark; uint32_t con_id; // internal ID #if WLR_HAS_XWAYLAND struct pattern *class; uint32_t id; // X11 window ID struct pattern *instance; struct pattern *window_role; enum atom_name window_type; #endif bool all; bool floating; bool tiling; char urgent; // 'l' for latest or 'o' for oldest struct pattern *workspace; pid_t pid; struct pattern *sandbox_engine; struct pattern *sandbox_app_id; struct pattern *sandbox_instance_id; struct pattern *tag; }; bool criteria_is_empty(struct criteria *criteria); bool criteria_is_equal(struct criteria *left, struct criteria *right); bool criteria_already_exists(struct criteria *criteria); void criteria_destroy(struct criteria *criteria); /** * Generate a criteria struct from a raw criteria string such as * [class="foo" instance="bar"] (brackets inclusive). * * The error argument is expected to be an address of a null pointer. If an * error is encountered, the function will return NULL and the pointer will be * changed to point to the error string. This string should be freed afterwards. */ struct criteria *criteria_parse(char *raw, char **error); /** * Compile a list of criterias matching the given view. * * Criteria types can be bitwise ORed. */ list_t *criteria_for_view(struct sway_view *view, enum criteria_type types); /** * Compile a list of containers matching the given criteria. */ list_t *criteria_get_containers(struct criteria *criteria); #endif ================================================ FILE: include/sway/decoration.h ================================================ #ifndef _SWAY_DECORATION_H #define _SWAY_DECORATION_H #include struct sway_server_decoration { struct wlr_server_decoration *wlr_server_decoration; struct wl_list link; struct wl_listener destroy; struct wl_listener mode; }; struct sway_server_decoration *decoration_from_surface( struct wlr_surface *surface); #endif ================================================ FILE: include/sway/desktop/idle_inhibit_v1.h ================================================ #ifndef _SWAY_DESKTOP_IDLE_INHIBIT_V1_H #define _SWAY_DESKTOP_IDLE_INHIBIT_V1_H #include enum sway_idle_inhibit_mode { INHIBIT_IDLE_APPLICATION, // Application set inhibitor (when visible) INHIBIT_IDLE_FOCUS, // User set inhibitor when focused INHIBIT_IDLE_FULLSCREEN, // User set inhibitor when fullscreen + visible INHIBIT_IDLE_OPEN, // User set inhibitor while open INHIBIT_IDLE_VISIBLE // User set inhibitor when visible }; struct sway_idle_inhibit_manager_v1 { struct wlr_idle_inhibit_manager_v1 *wlr_manager; struct wl_listener new_idle_inhibitor_v1; struct wl_listener manager_destroy; struct wl_list inhibitors; }; struct sway_idle_inhibitor_v1 { struct wlr_idle_inhibitor_v1 *wlr_inhibitor; struct sway_view *view; enum sway_idle_inhibit_mode mode; struct wl_list link; struct wl_listener destroy; }; bool sway_idle_inhibit_v1_is_active( struct sway_idle_inhibitor_v1 *inhibitor); void sway_idle_inhibit_v1_check_active(void); void sway_idle_inhibit_v1_user_inhibitor_register(struct sway_view *view, enum sway_idle_inhibit_mode mode); struct sway_idle_inhibitor_v1 *sway_idle_inhibit_v1_user_inhibitor_for_view( struct sway_view *view); struct sway_idle_inhibitor_v1 *sway_idle_inhibit_v1_application_inhibitor_for_view( struct sway_view *view); void sway_idle_inhibit_v1_user_inhibitor_destroy( struct sway_idle_inhibitor_v1 *inhibitor); bool sway_idle_inhibit_manager_v1_init(void); #endif ================================================ FILE: include/sway/desktop/launcher.h ================================================ #ifndef _SWAY_LAUNCHER_H #define _SWAY_LAUNCHER_H #include #include #include "sway/input/seat.h" struct launcher_ctx { pid_t pid; char *fallback_name; struct wlr_xdg_activation_token_v1 *token; struct wl_listener token_destroy; struct sway_seat *seat; struct wl_listener seat_destroy; bool activated; bool had_focused_surface; struct sway_node *node; struct wl_listener node_destroy; struct wl_list link; // sway_server::pending_launcher_ctxs }; struct launcher_ctx *launcher_ctx_find_pid(pid_t pid); struct sway_workspace *launcher_ctx_get_workspace(struct launcher_ctx *ctx); void launcher_ctx_consume(struct launcher_ctx *ctx); void launcher_ctx_destroy(struct launcher_ctx *ctx); struct launcher_ctx *launcher_ctx_create_internal(void); struct launcher_ctx *launcher_ctx_create( struct wlr_xdg_activation_token_v1 *token, struct sway_node *node); const char *launcher_ctx_get_token_name(struct launcher_ctx *ctx); #endif ================================================ FILE: include/sway/desktop/transaction.h ================================================ #ifndef _SWAY_TRANSACTION_H #define _SWAY_TRANSACTION_H #include #include #include /** * Transactions enable us to perform atomic layout updates. * * A transaction contains a list of containers and their new state. * A state might contain a new size, or new border settings, or new parent/child * relationships. * * Committing a transaction makes sway notify of all the affected clients with * their new sizes. We then wait for all the views to respond with their new * surface sizes. When all are ready, or when a timeout has passed, we apply the * updates all at the same time. * * When we want to make adjustments to the layout, we change the pending state * in containers, mark them as dirty and call transaction_commit_dirty(). This * create and commits a transaction from the dirty containers. */ struct sway_transaction_instruction; struct sway_view; /** * Find all dirty containers, create and commit a transaction containing them, * and unmark them as dirty. */ void transaction_commit_dirty(void); /* * Same as transaction_commit_dirty, but signalling that this is a * client-initiated change has already taken effect. */ void transaction_commit_dirty_client(void); /** * Notify the transaction system that a view is ready for the new layout. * * When all views in the transaction are ready, the layout will be applied. * * A success boolean is returned denoting that this part of the transaction is * ready. */ bool transaction_notify_view_ready_by_serial(struct sway_view *view, uint32_t serial); /** * Notify the transaction system that a view is ready for the new layout, but * identifying the instruction by geometry rather than by serial. * * This is used by xwayland views, as they don't have serials. * * A success boolean is returned denoting that this part of the transaction is * ready. */ bool transaction_notify_view_ready_by_geometry(struct sway_view *view, double x, double y, int width, int height); void arrange_popups(struct wlr_scene_tree *popups); #endif ================================================ FILE: include/sway/input/cursor.h ================================================ #ifndef _SWAY_INPUT_CURSOR_H #define _SWAY_INPUT_CURSOR_H #include #include #include #include #include #include "sway/input/seat.h" #include "config.h" #define SWAY_CURSOR_PRESSED_BUTTONS_CAP 32 #define SWAY_SCROLL_UP KEY_MAX + 1 #define SWAY_SCROLL_DOWN KEY_MAX + 2 #define SWAY_SCROLL_LEFT KEY_MAX + 3 #define SWAY_SCROLL_RIGHT KEY_MAX + 4 struct sway_cursor { struct sway_seat *seat; struct wlr_cursor *cursor; struct { double x, y; struct sway_node *node; } previous; struct wlr_xcursor_manager *xcursor_manager; struct wl_list tablets; struct wl_list tablet_pads; const char *image; struct wl_client *image_client; struct wlr_surface *image_surface; int hotspot_x, hotspot_y; struct wlr_pointer_constraint_v1 *active_constraint; pixman_region32_t confine; // invalid if active_constraint == NULL bool active_confine_requires_warp; struct wl_listener hold_begin; struct wl_listener hold_end; 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 motion; struct wl_listener motion_absolute; struct wl_listener button; struct wl_listener axis; struct wl_listener frame; struct wl_listener touch_down; struct wl_listener touch_up; struct wl_listener touch_cancel; struct wl_listener touch_motion; struct wl_listener touch_frame; bool simulating_pointer_from_touch; bool pointer_touch_up; int32_t pointer_touch_id; struct wl_listener tool_axis; struct wl_listener tool_tip; struct wl_listener tool_proximity; struct wl_listener tool_button; bool simulating_pointer_from_tool_tip; bool simulating_pointer_from_tool_button; uint32_t tool_buttons; struct wl_listener request_set_cursor; struct wl_listener image_surface_destroy; struct wl_listener constraint_commit; struct wl_event_source *hide_source; bool hidden; // This field is just a cache of the field in seat_config in order to avoid // costly seat_config lookups on every keypress. HIDE_WHEN_TYPING_DEFAULT // indicates that there is no cached value. enum seat_config_hide_cursor_when_typing hide_when_typing; size_t pressed_button_count; }; struct sway_node; struct sway_node *node_at_coords( struct sway_seat *seat, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy); void sway_cursor_destroy(struct sway_cursor *cursor); struct sway_cursor *sway_cursor_create(struct sway_seat *seat); /** * "Rebase" a cursor on top of whatever view is underneath it. * * This chooses a cursor icon and sends a motion event to the surface. */ void cursor_rebase(struct sway_cursor *cursor); void cursor_rebase_all(void); void cursor_update_image(struct sway_cursor *cursor, struct sway_node *node); void cursor_handle_activity_from_idle_source(struct sway_cursor *cursor, enum sway_input_idle_source idle_source); void cursor_handle_activity_from_device(struct sway_cursor *cursor, struct wlr_input_device *device); void cursor_unhide(struct sway_cursor *cursor); int cursor_get_timeout(struct sway_cursor *cursor); void cursor_notify_key_press(struct sway_cursor *cursor); void pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, struct wlr_input_device *device, double dx, double dy, double dx_unaccel, double dy_unaccel); void dispatch_cursor_button(struct sway_cursor *cursor, struct wlr_input_device *device, uint32_t time_msec, uint32_t button, enum wl_pointer_button_state state); void dispatch_cursor_axis(struct sway_cursor *cursor, struct wlr_pointer_axis_event *event); void cursor_set_image(struct sway_cursor *cursor, const char *image, struct wl_client *client); void cursor_set_image_surface(struct sway_cursor *cursor, struct wlr_surface *surface, int32_t hotspot_x, int32_t hotspot_y, struct wl_client *client); void cursor_warp_to_container(struct sway_cursor *cursor, struct sway_container *container, bool force); void cursor_warp_to_workspace(struct sway_cursor *cursor, struct sway_workspace *workspace); void sway_cursor_constrain(struct sway_cursor *cursor, struct wlr_pointer_constraint_v1 *constraint); uint32_t get_mouse_bindsym(const char *name, char **error); uint32_t get_mouse_bindcode(const char *name, char **error); // Considers both bindsym and bindcode uint32_t get_mouse_button(const char *name, char **error); const char *get_mouse_button_name(uint32_t button); void handle_request_set_cursor_shape(struct wl_listener *listener, void *data); #endif ================================================ FILE: include/sway/input/input-manager.h ================================================ #ifndef _SWAY_INPUT_INPUT_MANAGER_H #define _SWAY_INPUT_INPUT_MANAGER_H #include #include #include #include #include #include "sway/config.h" #include "list.h" struct sway_server; struct sway_input_device { char *identifier; struct wlr_input_device *wlr_device; struct wl_list link; struct wl_listener device_destroy; bool is_virtual; }; struct sway_input_manager { struct wl_list devices; struct wl_list seats; struct wlr_keyboard_shortcuts_inhibit_manager_v1 *keyboard_shortcuts_inhibit; struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard; struct wlr_virtual_pointer_manager_v1 *virtual_pointer; struct wlr_pointer_gestures_v1 *pointer_gestures; struct wlr_transient_seat_manager_v1 *transient_seat_manager; struct wl_listener new_input; struct wl_listener inhibit_activate; struct wl_listener inhibit_deactivate; struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor; struct wl_listener virtual_keyboard_new; struct wl_listener virtual_pointer_new; struct wl_listener transient_seat_create; }; struct sway_input_manager *input_manager_create(struct sway_server *server); void input_manager_finish(struct sway_input_manager *input); bool input_manager_has_focus(struct sway_node *node); void input_manager_set_focus(struct sway_node *node); void input_manager_configure_xcursor(void); void input_manager_apply_input_config(struct input_config *input_config); void input_manager_configure_all_input_mappings(void); void input_manager_reset_input(struct sway_input_device *input_device); void input_manager_reset_all_inputs(void); void input_manager_apply_seat_config(struct seat_config *seat_config); struct sway_seat *input_manager_get_default_seat(void); struct sway_seat *input_manager_get_seat(const char *seat_name, bool create); /** * If none of the seat configs have a fallback setting (either true or false), * create the default seat (if needed) and set it as the fallback */ void input_manager_verify_fallback_seat(void); /** * Gets the last seat the user interacted with */ struct sway_seat *input_manager_current_seat(void); struct input_config *input_device_get_config(struct sway_input_device *device); char *input_device_get_identifier(struct wlr_input_device *device); const char *input_device_get_type(struct sway_input_device *device); #endif ================================================ FILE: include/sway/input/keyboard.h ================================================ #ifndef _SWAY_INPUT_KEYBOARD_H #define _SWAY_INPUT_KEYBOARD_H #include "sway/input/seat.h" #define SWAY_KEYBOARD_PRESSED_KEYS_CAP 32 /** * Get modifier mask from modifier name. * * Returns the modifier mask or 0 if the name isn't found. */ uint32_t get_modifier_mask_by_name(const char *name); /** * Get modifier name from modifier mask. * * Returns the modifier name or NULL if it isn't found. */ const char *get_modifier_name_by_mask(uint32_t modifier); /** * Get an array of modifier names from modifier_masks * * Populates the names array and return the number of names added. */ int get_modifier_names(const char **names, uint32_t modifier_masks); struct sway_shortcut_state { /** * A list of pressed key ids (either keysyms or keycodes), * including duplicates when different keycodes produce the same key id. * * Each key id is associated with the keycode (in `pressed_keycodes`) * whose press generated it, so that the key id can be removed on * keycode release without recalculating the transient link between * keycode and key id at the time of the key press. */ uint32_t pressed_keys[SWAY_KEYBOARD_PRESSED_KEYS_CAP]; /** * The list of keycodes associated to currently pressed key ids, * including duplicates when a keycode generates multiple key ids. */ uint32_t pressed_keycodes[SWAY_KEYBOARD_PRESSED_KEYS_CAP]; uint32_t last_keycode; uint32_t last_raw_modifiers; size_t npressed; uint32_t current_key; }; struct sway_keyboard { struct sway_seat_device *seat_device; struct wlr_keyboard *wlr; struct xkb_keymap *keymap; xkb_layout_index_t effective_layout; int32_t repeat_rate; int32_t repeat_delay; struct wl_listener keyboard_key; struct wl_listener keyboard_modifiers; struct sway_shortcut_state state_keysyms_translated; struct sway_shortcut_state state_keysyms_raw; struct sway_shortcut_state state_keycodes; struct sway_shortcut_state state_pressed_sent; struct sway_binding *held_binding; struct wl_event_source *key_repeat_source; struct sway_binding *repeat_binding; }; struct sway_keyboard_group { struct wlr_keyboard_group *wlr_group; struct sway_seat_device *seat_device; struct wl_listener keyboard_key; struct wl_listener keyboard_modifiers; struct wl_listener enter; struct wl_listener leave; struct wl_list link; // sway_seat::keyboard_groups }; struct xkb_keymap *sway_keyboard_compile_keymap(struct input_config *ic, char **error); struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat, struct sway_seat_device *device); void sway_keyboard_configure(struct sway_keyboard *keyboard); void sway_keyboard_destroy(struct sway_keyboard *keyboard); void sway_keyboard_disarm_key_repeat(struct sway_keyboard *keyboard); #endif ================================================ FILE: include/sway/input/libinput.h ================================================ #ifndef _SWAY_INPUT_LIBINPUT_H #define _SWAY_INPUT_LIBINPUT_H #include "sway/input/input-manager.h" bool sway_input_configure_libinput_device(struct sway_input_device *device); void sway_input_configure_libinput_device_send_events( struct sway_input_device *device); void sway_input_reset_libinput_device(struct sway_input_device *device); bool sway_libinput_device_is_builtin(struct sway_input_device *device); #endif ================================================ FILE: include/sway/input/seat.h ================================================ #ifndef _SWAY_INPUT_SEAT_H #define _SWAY_INPUT_SEAT_H #include #include #include #include #include #include #include "sway/config.h" #include "sway/input/input-manager.h" #include "sway/input/tablet.h" #include "sway/input/text_input.h" struct sway_seat; struct sway_seatop_impl { void (*button)(struct sway_seat *seat, uint32_t time_msec, struct wlr_input_device *device, uint32_t button, enum wl_pointer_button_state state); void (*pointer_motion)(struct sway_seat *seat, uint32_t time_msec); void (*pointer_axis)(struct sway_seat *seat, struct wlr_pointer_axis_event *event); void (*hold_begin)(struct sway_seat *seat, struct wlr_pointer_hold_begin_event *event); void (*hold_end)(struct sway_seat *seat, struct wlr_pointer_hold_end_event *event); void (*pinch_begin)(struct sway_seat *seat, struct wlr_pointer_pinch_begin_event *event); void (*pinch_update)(struct sway_seat *seat, struct wlr_pointer_pinch_update_event *event); void (*pinch_end)(struct sway_seat *seat, struct wlr_pointer_pinch_end_event *event); void (*swipe_begin)(struct sway_seat *seat, struct wlr_pointer_swipe_begin_event *event); void (*swipe_update)(struct sway_seat *seat, struct wlr_pointer_swipe_update_event *event); void (*swipe_end)(struct sway_seat *seat, struct wlr_pointer_swipe_end_event *event); void (*rebase)(struct sway_seat *seat, uint32_t time_msec); void (*touch_motion)(struct sway_seat *seat, struct wlr_touch_motion_event *event, double lx, double ly); void (*touch_up)(struct sway_seat *seat, struct wlr_touch_up_event *event); void (*touch_down)(struct sway_seat *seat, struct wlr_touch_down_event *event, double lx, double ly); void (*touch_cancel)(struct sway_seat *seat, struct wlr_touch_cancel_event *event); void (*tablet_tool_motion)(struct sway_seat *seat, struct sway_tablet_tool *tool, uint32_t time_msec); void (*tablet_tool_tip)(struct sway_seat *seat, struct sway_tablet_tool *tool, uint32_t time_msec, enum wlr_tablet_tool_tip_state state); void (*end)(struct sway_seat *seat); void (*unref)(struct sway_seat *seat, struct sway_container *con); bool allow_set_cursor; }; struct sway_seat_device { struct sway_seat *sway_seat; struct sway_input_device *input_device; struct sway_keyboard *keyboard; struct sway_switch *switch_device; struct sway_tablet *tablet; struct sway_tablet_pad *tablet_pad; struct wl_list link; // sway_seat::devices }; struct sway_seat_node { struct sway_seat *seat; struct sway_node *node; struct wl_list link; // sway_seat::focus_stack struct wl_listener destroy; }; struct sway_drag { struct sway_seat *seat; struct wlr_drag *wlr_drag; struct wl_listener destroy; }; struct sway_seat { struct wlr_seat *wlr_seat; struct sway_cursor *cursor; // Seat scene tree structure // - scene_tree // - drag icons // - drag icon 1 // - drag icon 2 // - seatop specific stuff struct wlr_scene_tree *scene_tree; struct wlr_scene_tree *drag_icons; bool has_focus; struct wl_list focus_stack; // list of containers in focus order struct sway_workspace *workspace; char *prev_workspace_name; // for workspace back_and_forth struct wlr_layer_surface_v1 *focused_layer; // If the exclusive layer is set, views cannot receive keyboard focus bool has_exclusive_layer; // Last touch point int32_t touch_id; double touch_x, touch_y; // Seat operations (drag and resize) const struct sway_seatop_impl *seatop_impl; void *seatop_data; uint32_t last_button_serial; uint32_t idle_inhibit_sources, idle_wake_sources; list_t *deferred_bindings; // struct sway_binding struct sway_input_method_relay im_relay; struct wl_listener focus_destroy; struct wl_listener new_node; struct wl_listener request_start_drag; struct wl_listener start_drag; struct wl_listener request_set_selection; struct wl_listener request_set_primary_selection; struct wl_listener destroy; struct wl_list devices; // sway_seat_device::link struct wl_list keyboard_groups; // sway_keyboard_group::link struct wl_list keyboard_shortcuts_inhibitors; // sway_keyboard_shortcuts_inhibitor::link struct wl_list link; // input_manager::seats }; struct sway_pointer_constraint { struct sway_cursor *cursor; struct wlr_pointer_constraint_v1 *constraint; struct wl_listener set_region; struct wl_listener destroy; }; struct sway_keyboard_shortcuts_inhibitor { struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor; struct wl_listener destroy; struct wl_list link; // sway_seat::keyboard_shortcuts_inhibitors }; struct sway_seat *seat_create(const char *seat_name); void seat_destroy(struct sway_seat *seat); void seat_add_device(struct sway_seat *seat, struct sway_input_device *device); void seat_configure_device(struct sway_seat *seat, struct sway_input_device *device); void seat_configure_device_mapping(struct sway_seat *seat, struct sway_input_device *input_device); void seat_reset_device(struct sway_seat *seat, struct sway_input_device *input_device); void seat_remove_device(struct sway_seat *seat, struct sway_input_device *device); void seat_configure_xcursor(struct sway_seat *seat); void seat_set_focus(struct sway_seat *seat, struct sway_node *node); void seat_set_focus_container(struct sway_seat *seat, struct sway_container *con); void seat_set_focus_workspace(struct sway_seat *seat, struct sway_workspace *ws); /** * Manipulate the focus stack without triggering any other behaviour. * * This can be used to set focus_inactive by calling the function a second time * with the real focus. */ void seat_set_raw_focus(struct sway_seat *seat, struct sway_node *node); void seat_set_focus_surface(struct sway_seat *seat, struct wlr_surface *surface, bool unfocus); void seat_set_focus_layer(struct sway_seat *seat, struct wlr_layer_surface_v1 *layer); void seat_unfocus_unless_client(struct sway_seat *seat, struct wl_client *client); struct sway_node *seat_get_focus(struct sway_seat *seat); struct sway_workspace *seat_get_focused_workspace(struct sway_seat *seat); // If a scratchpad container is fullscreen global, this can be used to try to // determine the last focused workspace. Otherwise, this should yield the same // results as seat_get_focused_workspace. struct sway_workspace *seat_get_last_known_workspace(struct sway_seat *seat); struct sway_container *seat_get_focused_container(struct sway_seat *seat); /** * Return the last container to be focused for the seat (or the most recently * opened if no container has received focused) that is a child of the given * container. The focus-inactive container of the root window is the focused * container for the seat (if the seat does have focus). This function can be * used to determine what container gets focused next if the focused container * is destroyed, or focus moves to a container with children and we need to * descend into the next leaf in focus order. */ struct sway_node *seat_get_focus_inactive(struct sway_seat *seat, struct sway_node *node); struct sway_container *seat_get_focus_inactive_tiling(struct sway_seat *seat, struct sway_workspace *workspace); /** * Descend into the focus stack to find the focus-inactive view. Useful for * container placement when they change position in the tree. */ struct sway_container *seat_get_focus_inactive_view(struct sway_seat *seat, struct sway_node *ancestor); /** * Return the immediate child of container which was most recently focused. */ struct sway_node *seat_get_active_tiling_child(struct sway_seat *seat, struct sway_node *parent); /** * Iterate over the focus-inactive children of the container calling the * function on each. */ void seat_for_each_node(struct sway_seat *seat, void (*f)(struct sway_node *node, void *data), void *data); void seat_apply_config(struct sway_seat *seat, struct seat_config *seat_config); struct seat_config *seat_get_config(struct sway_seat *seat); struct seat_config *seat_get_config_by_name(const char *name); void seat_idle_notify_activity(struct sway_seat *seat, enum sway_input_idle_source source); bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface); void drag_icons_update_position(struct sway_seat *seat); enum wlr_edges find_resize_edge(struct sway_container *cont, struct wlr_surface *surface, struct sway_cursor *cursor); void seatop_begin_default(struct sway_seat *seat); void seatop_begin_down(struct sway_seat *seat, struct sway_container *con, double sx, double sy); void seatop_begin_down_on_surface(struct sway_seat *seat, struct wlr_surface *surface, double sx, double sy); void seatop_begin_touch_down(struct sway_seat *seat, struct wlr_surface *surface, struct wlr_touch_down_event *event, double sx, double sy, double lx, double ly); void seatop_begin_move_floating(struct sway_seat *seat, struct sway_container *con); void seatop_begin_move_tiling_threshold(struct sway_seat *seat, struct sway_container *con); void seatop_begin_move_tiling(struct sway_seat *seat, struct sway_container *con); void seatop_begin_resize_floating(struct sway_seat *seat, struct sway_container *con, enum wlr_edges edge); void seatop_begin_resize_tiling(struct sway_seat *seat, struct sway_container *con, enum wlr_edges edge); struct sway_container *seat_get_focus_inactive_floating(struct sway_seat *seat, struct sway_workspace *workspace); void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec, uint32_t button, enum wl_pointer_button_state state); void seat_consider_warp_to_focus(struct sway_seat *seat); void seatop_button(struct sway_seat *seat, uint32_t time_msec, struct wlr_input_device *device, uint32_t button, enum wl_pointer_button_state state); void seatop_pointer_motion(struct sway_seat *seat, uint32_t time_msec); void seatop_pointer_axis(struct sway_seat *seat, struct wlr_pointer_axis_event *event); void seatop_tablet_tool_tip(struct sway_seat *seat, struct sway_tablet_tool *tool, uint32_t time_msec, enum wlr_tablet_tool_tip_state state); void seatop_tablet_tool_motion(struct sway_seat *seat, struct sway_tablet_tool *tool, uint32_t time_msec); void seatop_hold_begin(struct sway_seat *seat, struct wlr_pointer_hold_begin_event *event); void seatop_hold_end(struct sway_seat *seat, struct wlr_pointer_hold_end_event *event); void seatop_pinch_begin(struct sway_seat *seat, struct wlr_pointer_pinch_begin_event *event); void seatop_pinch_update(struct sway_seat *seat, struct wlr_pointer_pinch_update_event *event); void seatop_pinch_end(struct sway_seat *seat, struct wlr_pointer_pinch_end_event *event); void seatop_swipe_begin(struct sway_seat *seat, struct wlr_pointer_swipe_begin_event *event); void seatop_swipe_update(struct sway_seat *seat, struct wlr_pointer_swipe_update_event *event); void seatop_swipe_end(struct sway_seat *seat, struct wlr_pointer_swipe_end_event *event); void seatop_touch_motion(struct sway_seat *seat, struct wlr_touch_motion_event *event, double lx, double ly); void seatop_touch_up(struct sway_seat *seat, struct wlr_touch_up_event *event); void seatop_touch_down(struct sway_seat *seat, struct wlr_touch_down_event *event, double lx, double ly); void seatop_touch_cancel(struct sway_seat *seat, struct wlr_touch_cancel_event *event); void seatop_rebase(struct sway_seat *seat, uint32_t time_msec); /** * End a seatop (ie. free any seatop specific resources). */ void seatop_end(struct sway_seat *seat); /** * Instructs the seatop implementation to drop any references to the given * container (eg. because the container is destroying). * The seatop may choose to abort itself in response to this. */ void seatop_unref(struct sway_seat *seat, struct sway_container *con); bool seatop_allows_set_cursor(struct sway_seat *seat); /** * Returns the keyboard shortcuts inhibitor that applies to the given surface * or NULL if none exists. */ struct sway_keyboard_shortcuts_inhibitor * keyboard_shortcuts_inhibitor_get_for_surface(const struct sway_seat *seat, const struct wlr_surface *surface); /** * Returns the keyboard shortcuts inhibitor that applies to the currently * focused surface of a seat or NULL if none exists. */ struct sway_keyboard_shortcuts_inhibitor * keyboard_shortcuts_inhibitor_get_for_focused_surface(const struct sway_seat *seat); #endif ================================================ FILE: include/sway/input/switch.h ================================================ #ifndef _SWAY_INPUT_SWITCH_H #define _SWAY_INPUT_SWITCH_H #include "sway/input/seat.h" struct sway_switch { struct sway_seat_device *seat_device; struct wlr_switch *wlr; enum wlr_switch_state state; enum wlr_switch_type type; struct wl_listener switch_toggle; }; struct sway_switch *sway_switch_create(struct sway_seat *seat, struct sway_seat_device *device); void sway_switch_configure(struct sway_switch *sway_switch); void sway_switch_destroy(struct sway_switch *sway_switch); void sway_switch_retrigger_bindings_for_all(void); #endif ================================================ FILE: include/sway/input/tablet.h ================================================ #ifndef _SWAY_INPUT_TABLET_H #define _SWAY_INPUT_TABLET_H #include struct sway_seat; struct wlr_tablet_tool; struct sway_tablet { struct wl_list link; struct sway_seat_device *seat_device; struct wlr_tablet_v2_tablet *tablet_v2; }; enum sway_tablet_tool_mode { SWAY_TABLET_TOOL_MODE_ABSOLUTE, SWAY_TABLET_TOOL_MODE_RELATIVE, }; struct sway_tablet_tool { struct sway_seat *seat; struct sway_tablet *tablet; struct wlr_tablet_v2_tablet_tool *tablet_v2_tool; enum sway_tablet_tool_mode mode; double tilt_x, tilt_y; struct wl_listener set_cursor; struct wl_listener tool_destroy; }; struct sway_tablet_pad { struct wl_list link; struct sway_seat_device *seat_device; struct sway_tablet *tablet; struct wlr_tablet_pad *wlr; struct wlr_tablet_v2_tablet_pad *tablet_v2_pad; struct wl_listener attach; struct wl_listener button; struct wl_listener ring; struct wl_listener strip; struct wlr_surface *current_surface; struct wl_listener surface_destroy; struct wl_listener tablet_destroy; }; struct sway_tablet *sway_tablet_create(struct sway_seat *seat, struct sway_seat_device *device); void sway_configure_tablet(struct sway_tablet *tablet); void sway_tablet_destroy(struct sway_tablet *tablet); void sway_tablet_tool_configure(struct sway_tablet *tablet, struct wlr_tablet_tool *wlr_tool); struct sway_tablet_pad *sway_tablet_pad_create(struct sway_seat *seat, struct sway_seat_device *device); void sway_configure_tablet_pad(struct sway_tablet_pad *tablet_pad); void sway_tablet_pad_destroy(struct sway_tablet_pad *tablet_pad); void sway_tablet_pad_set_focus(struct sway_tablet_pad *tablet_pad, struct wlr_surface *surface); #endif ================================================ FILE: include/sway/input/text_input.h ================================================ #ifndef _SWAY_INPUT_TEXT_INPUT_H #define _SWAY_INPUT_TEXT_INPUT_H #include #include #include /** * The relay structure manages the relationship between text-input and * input_method interfaces on a given seat. Multiple text-input interfaces may * be bound to a relay, but at most one will be focused (receiving events) at * a time. At most one input-method interface may be bound to the seat. The * relay manages life cycle of both sides. When both sides are present and * focused, the relay passes messages between them. * * Text input focus is a subset of keyboard focus - if the text-input is * in the focused state, wl_keyboard sent an enter as well. However, having * wl_keyboard focused doesn't mean that text-input will be focused. */ struct sway_input_method_relay { struct sway_seat *seat; struct wl_list text_inputs; // sway_text_input::link struct wl_list input_popups; // sway_input_popup::link struct wlr_input_method_v2 *input_method; // doesn't have to be present struct wl_listener text_input_new; struct wl_listener text_input_manager_destroy; struct wl_listener input_method_new; struct wl_listener input_method_manager_destroy; struct wl_listener input_method_commit; struct wl_listener input_method_new_popup_surface; struct wl_listener input_method_grab_keyboard; struct wl_listener input_method_destroy; struct wl_listener input_method_keyboard_grab_destroy; }; struct sway_text_input { struct sway_input_method_relay *relay; struct wlr_text_input_v3 *input; // The surface getting seat's focus. Stored for when text-input cannot // be sent an enter event immediately after getting focus, e.g. when // there's no input method available. Cleared once text-input is entered. struct wlr_surface *pending_focused_surface; struct wl_list link; struct wl_listener pending_focused_surface_destroy; struct wl_listener text_input_enable; struct wl_listener text_input_commit; struct wl_listener text_input_disable; struct wl_listener text_input_destroy; }; void sway_input_method_relay_init(struct sway_seat *seat, struct sway_input_method_relay *relay); void sway_input_method_relay_finish(struct sway_input_method_relay *relay); // Updates currently focused surface. Surface must belong to the same seat. void sway_input_method_relay_set_focus(struct sway_input_method_relay *relay, struct wlr_surface *surface); struct sway_text_input *sway_text_input_create( struct sway_input_method_relay *relay, struct wlr_text_input_v3 *text_input); #endif ================================================ FILE: include/sway/input/text_input_popup.h ================================================ #ifndef _SWAY_INPUT_TEXT_INPUT_POPUP_H #define _SWAY_INPUT_TEXT_INPUT_POPUP_H #include "sway/tree/view.h" struct sway_input_popup { struct sway_input_method_relay *relay; struct wlr_scene_tree *scene_tree; struct sway_popup_desc desc; struct wlr_input_popup_surface_v2 *popup_surface; struct wlr_output *fixed_output; struct wl_list link; struct wl_listener popup_destroy; struct wl_listener popup_surface_commit; struct wl_listener popup_surface_map; struct wl_listener popup_surface_unmap; struct wl_listener focused_surface_unmap; }; #endif ================================================ FILE: include/sway/ipc-json.h ================================================ #ifndef _SWAY_IPC_JSON_H #define _SWAY_IPC_JSON_H #include #include "sway/output.h" #include "sway/tree/container.h" #include "sway/input/input-manager.h" json_object *ipc_json_get_version(void); json_object *ipc_json_get_binding_mode(void); json_object *ipc_json_describe_disabled_output(struct sway_output *o); json_object *ipc_json_describe_non_desktop_output(struct sway_output_non_desktop *o); json_object *ipc_json_describe_node(struct sway_node *node); json_object *ipc_json_describe_node_recursive(struct sway_node *node); json_object *ipc_json_describe_input(struct sway_input_device *device); json_object *ipc_json_describe_seat(struct sway_seat *seat); json_object *ipc_json_describe_bar_config(struct bar_config *bar); #endif ================================================ FILE: include/sway/ipc-server.h ================================================ #ifndef _SWAY_IPC_SERVER_H #define _SWAY_IPC_SERVER_H #include #include "sway/config.h" #include "sway/input/input-manager.h" #include "sway/tree/container.h" #include "ipc.h" struct sway_server; void ipc_init(struct sway_server *server); struct sockaddr_un *ipc_user_sockaddr(void); void ipc_event_workspace(struct sway_workspace *old, struct sway_workspace *new, const char *change); void ipc_event_window(struct sway_container *window, const char *change); void ipc_event_barconfig_update(struct bar_config *bar); void ipc_event_bar_state_update(struct bar_config *bar); void ipc_event_mode(const char *mode, bool pango); void ipc_event_shutdown(const char *reason); void ipc_event_binding(struct sway_binding *binding); void ipc_event_input(const char *change, struct sway_input_device *device); void ipc_event_output(void); #endif ================================================ FILE: include/sway/layers.h ================================================ #ifndef _SWAY_LAYERS_H #define _SWAY_LAYERS_H #include #include #include #include "sway/tree/view.h" struct sway_layer_surface { struct wl_listener map; struct wl_listener unmap; struct wl_listener surface_commit; struct wl_listener node_destroy; struct wl_listener new_popup; bool mapped; struct wlr_scene_tree *popups; struct sway_popup_desc desc; struct sway_output *output; struct wl_list link; // sway_output.layer_surfaces struct wlr_scene_layer_surface_v1 *scene; struct wlr_scene_tree *tree; struct wlr_layer_surface_v1 *layer_surface; }; struct sway_layer_popup { struct wlr_xdg_popup *wlr_popup; struct wlr_scene_tree *scene; struct sway_layer_surface *toplevel; struct wl_listener destroy; struct wl_listener new_popup; struct wl_listener commit; struct wl_listener reposition; }; struct sway_output; struct wlr_layer_surface_v1 *toplevel_layer_surface_from_surface( struct wlr_surface *surface); void arrange_layers(struct sway_output *output); void destroy_layers(struct sway_output *output); #endif ================================================ FILE: include/sway/lock.h ================================================ #ifndef _SWAY_LOCK_H #define _SWAY_LOCK_H void arrange_locks(void); #endif ================================================ FILE: include/sway/output.h ================================================ #ifndef _SWAY_OUTPUT_H #define _SWAY_OUTPUT_H #include #include #include #include #include #include #include "config.h" #include "sway/tree/node.h" #include "sway/tree/view.h" struct sway_server; struct sway_container; struct sway_output_state { list_t *workspaces; struct sway_workspace *active_workspace; }; struct sway_output { struct sway_node node; struct { struct wlr_scene_tree *shell_background; struct wlr_scene_tree *shell_bottom; struct wlr_scene_tree *tiling; struct wlr_scene_tree *fullscreen; struct wlr_scene_tree *shell_top; struct wlr_scene_tree *shell_overlay; struct wlr_scene_tree *session_lock; } layers; // when a container is fullscreen, in case the fullscreen surface is // translucent (can see behind) we must make sure that the background is a // solid color in order to conform to the wayland protocol. This rect // ensures that when looking through a surface, all that will be seen // is black. struct wlr_scene_rect *fullscreen_background; struct wlr_output *wlr_output; struct wlr_scene_output *scene_output; struct sway_server *server; struct wl_list link; struct wlr_box usable_area; int lx, ly; // layout coords int width, height; // transformed buffer size enum wl_output_subpixel detected_subpixel; enum scale_filter_mode scale_filter; bool enabled; list_t *workspaces; struct wl_list layer_surfaces; // sway_layer_surface.link struct sway_output_state current; struct wl_listener layout_destroy; struct wl_listener destroy; struct wl_listener present; struct wl_listener frame; struct wl_listener request_state; struct wlr_color_transform *color_transform; struct timespec last_presentation; uint32_t refresh_nsec; int max_render_time; // In milliseconds struct wl_event_source *repaint_timer; bool allow_tearing; bool hdr; }; struct sway_output_non_desktop { struct wlr_output *wlr_output; struct wl_listener destroy; }; struct sway_output *output_create(struct wlr_output *wlr_output); void output_destroy(struct sway_output *output); void output_begin_destroy(struct sway_output *output); struct sway_output *output_from_wlr_output(struct wlr_output *output); struct sway_output *output_get_in_direction(struct sway_output *reference, enum wlr_direction direction); void output_configure_scene(struct sway_output *output, struct wlr_scene_node *node, float opacity); void output_add_workspace(struct sway_output *output, struct sway_workspace *workspace); typedef void (*sway_surface_iterator_func_t)(struct sway_output *output, struct sway_view *view, struct wlr_surface *surface, struct wlr_box *box, void *user_data); bool output_match_name_or_id(struct sway_output *output, const char *name_or_id); // this ONLY includes the enabled outputs struct sway_output *output_by_name_or_id(const char *name_or_id); // this includes all the outputs, including disabled ones struct sway_output *all_output_by_name_or_id(const char *name_or_id); void output_sort_workspaces(struct sway_output *output); void output_enable(struct sway_output *output); void output_disable(struct sway_output *output); struct sway_workspace *output_get_active_workspace(struct sway_output *output); void output_for_each_workspace(struct sway_output *output, void (*f)(struct sway_workspace *ws, void *data), void *data); void output_for_each_container(struct sway_output *output, void (*f)(struct sway_container *con, void *data), void *data); struct sway_workspace *output_find_workspace(struct sway_output *output, bool (*test)(struct sway_workspace *ws, void *data), void *data); struct sway_container *output_find_container(struct sway_output *output, bool (*test)(struct sway_container *con, void *data), void *data); void output_get_box(struct sway_output *output, struct wlr_box *box); bool output_supports_hdr(struct wlr_output *output, const char **unsupported_reason_ptr); enum sway_container_layout output_get_default_layout( struct sway_output *output); enum wlr_direction opposite_direction(enum wlr_direction d); void handle_output_manager_apply(struct wl_listener *listener, void *data); void handle_output_manager_test(struct wl_listener *listener, void *data); void handle_output_power_manager_set_mode(struct wl_listener *listener, void *data); struct sway_output_non_desktop *output_non_desktop_create(struct wlr_output *wlr_output); void update_output_manager_config(struct sway_server *server); #endif ================================================ FILE: include/sway/scene_descriptor.h ================================================ /** * Across a wayland compositor, there are multiple shells: It can be * a toplevel, or a layer_shell, or even something more meta like a drag * icon or highlight indicators when dragging windows around. * * This object lets us store values that represent these modes of operation * and keep track of what object is being represented. */ #ifndef _SWAY_SCENE_DESCRIPTOR_H #define _SWAY_SCENE_DESCRIPTOR_H #include enum sway_scene_descriptor_type { SWAY_SCENE_DESC_BUFFER_TIMER, SWAY_SCENE_DESC_NON_INTERACTIVE, SWAY_SCENE_DESC_CONTAINER, SWAY_SCENE_DESC_VIEW, SWAY_SCENE_DESC_LAYER_SHELL, SWAY_SCENE_DESC_XWAYLAND_UNMANAGED, SWAY_SCENE_DESC_POPUP, SWAY_SCENE_DESC_DRAG_ICON, }; bool scene_descriptor_assign(struct wlr_scene_node *node, enum sway_scene_descriptor_type type, void *data); void *scene_descriptor_try_get(struct wlr_scene_node *node, enum sway_scene_descriptor_type type); void scene_descriptor_destroy(struct wlr_scene_node *node, enum sway_scene_descriptor_type type); #endif ================================================ FILE: include/sway/server.h ================================================ #ifndef _SWAY_SERVER_H #define _SWAY_SERVER_H #include #include #include "config.h" #include "list.h" #include "sway/desktop/idle_inhibit_v1.h" #if WLR_HAS_XWAYLAND #include "sway/xwayland.h" #endif struct sway_transaction; struct sway_session_lock { struct wlr_session_lock_v1 *lock; struct wlr_surface *focused; bool abandoned; struct wl_list outputs; // struct sway_session_lock_output // invalid if the session is abandoned struct wl_listener new_surface; struct wl_listener unlock; struct wl_listener destroy; }; struct sway_server { struct wl_display *wl_display; struct wl_event_loop *wl_event_loop; char *socket; struct wlr_backend *backend; struct wlr_session *session; // secondary headless backend used for creating virtual outputs on-the-fly struct wlr_backend *headless_backend; struct wlr_renderer *renderer; struct wlr_allocator *allocator; struct wlr_compositor *compositor; struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1; struct wlr_data_device_manager *data_device_manager; struct sway_input_manager *input; struct wl_listener new_output; struct wl_listener renderer_lost; struct wl_event_source *recreating_renderer; struct wlr_idle_notifier_v1 *idle_notifier_v1; struct sway_idle_inhibit_manager_v1 idle_inhibit_manager_v1; struct wlr_layer_shell_v1 *layer_shell; struct wl_listener layer_shell_surface; struct wlr_xdg_shell *xdg_shell; struct wl_listener xdg_shell_toplevel; struct wlr_tablet_manager_v2 *tablet_v2; #if WLR_HAS_XWAYLAND struct sway_xwayland xwayland; struct wl_listener xwayland_surface; struct wl_listener xwayland_ready; #endif struct wlr_relative_pointer_manager_v1 *relative_pointer_manager; struct wlr_server_decoration_manager *server_decoration_manager; struct wl_listener server_decoration; struct wl_list decorations; // sway_server_decoration::link struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager; struct wl_listener xdg_decoration; struct wl_list xdg_decorations; // sway_xdg_decoration::link struct wlr_drm_lease_v1_manager *drm_lease_manager; struct wl_listener drm_lease_request; struct wlr_pointer_constraints_v1 *pointer_constraints; struct wl_listener pointer_constraint; struct wlr_xdg_output_manager_v1 *xdg_output_manager_v1; struct wlr_output_manager_v1 *output_manager_v1; struct wl_listener output_manager_apply; struct wl_listener output_manager_test; struct wlr_gamma_control_manager_v1 *gamma_control_manager_v1; struct wl_listener gamma_control_set_gamma; struct { struct sway_session_lock *lock; struct wlr_session_lock_manager_v1 *manager; struct wl_listener new_lock; struct wl_listener manager_destroy; } session_lock; struct wlr_output_power_manager_v1 *output_power_manager_v1; struct wl_listener output_power_manager_set_mode; struct wlr_input_method_manager_v2 *input_method; struct wlr_text_input_manager_v3 *text_input; struct wlr_ext_foreign_toplevel_list_v1 *foreign_toplevel_list; struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager; struct wlr_content_type_manager_v1 *content_type_manager_v1; struct wlr_data_control_manager_v1 *wlr_data_control_manager_v1; struct wlr_ext_data_control_manager_v1 *ext_data_control_manager_v1; struct wlr_screencopy_manager_v1 *screencopy_manager_v1; struct wlr_ext_image_copy_capture_manager_v1 *ext_image_copy_capture_manager_v1; struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager_v1; struct wlr_security_context_manager_v1 *security_context_manager_v1; struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *ext_foreign_toplevel_image_capture_source_manager_v1; struct wl_listener new_foreign_toplevel_capture_request; struct wlr_xdg_activation_v1 *xdg_activation_v1; struct wl_listener xdg_activation_v1_request_activate; struct wl_listener xdg_activation_v1_new_token; struct wl_listener xdg_toplevel_tag_manager_v1_set_tag; struct wl_listener request_set_cursor_shape; struct wlr_tearing_control_manager_v1 *tearing_control_v1; struct wl_listener tearing_control_new_object; struct wl_list tearing_controllers; // sway_tearing_controller::link struct wl_list pending_launcher_ctxs; // launcher_ctx::link // The timeout for transactions, after which a transaction is applied // regardless of readiness. size_t txn_timeout_ms; // Stores a transaction after it has been committed, but is waiting for // views to ack the new dimensions before being applied. A queued // transaction is frozen and must not have new instructions added to it. struct sway_transaction *queued_transaction; // Stores a pending transaction that will be committed once the existing // queued transaction is applied and freed. The pending transaction can be // updated with new instructions as needed. struct sway_transaction *pending_transaction; // Stores the nodes that have been marked as "dirty" and will be put into // the pending transaction. list_t *dirty_nodes; struct wl_event_source *delayed_modeset; }; extern struct sway_server server; struct sway_debug { bool noatomic; // Ignore atomic layout updates bool txn_timings; // Log verbose messages about transactions bool txn_wait; // Always wait for the timeout before applying }; extern struct sway_debug debug; extern bool unsupported_gpu_detected; void sway_terminate(int exit_code); bool server_init(struct sway_server *server); void server_fini(struct sway_server *server); bool server_start(struct sway_server *server); void server_run(struct sway_server *server); void handle_new_output(struct wl_listener *listener, void *data); void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data); void handle_layer_shell_surface(struct wl_listener *listener, void *data); void sway_session_lock_init(void); void sway_session_lock_add_output(struct sway_session_lock *lock, struct sway_output *output); bool sway_session_lock_has_surface(struct sway_session_lock *lock, struct wlr_surface *surface); void handle_xdg_shell_toplevel(struct wl_listener *listener, void *data); #if WLR_HAS_XWAYLAND void handle_xwayland_surface(struct wl_listener *listener, void *data); #endif void handle_server_decoration(struct wl_listener *listener, void *data); void handle_xdg_decoration(struct wl_listener *listener, void *data); void handle_pointer_constraint(struct wl_listener *listener, void *data); void xdg_activation_v1_handle_request_activate(struct wl_listener *listener, void *data); void xdg_activation_v1_handle_new_token(struct wl_listener *listener, void *data); void set_rr_scheduling(void); void handle_new_tearing_hint(struct wl_listener *listener, void *data); #endif ================================================ FILE: include/sway/sway_text_node.h ================================================ #ifndef _SWAY_BUFFER_H #define _SWAY_BUFFER_H #include struct sway_text_node { int width; int max_width; int height; int baseline; bool pango_markup; float color[4]; float background[4]; struct wlr_scene_node *node; }; struct sway_text_node *sway_text_node_create(struct wlr_scene_tree *parent, char *text, float color[4], bool pango_markup); void sway_text_node_set_color(struct sway_text_node *node, float color[4]); void sway_text_node_set_text(struct sway_text_node *node, char *text); void sway_text_node_set_max_width(struct sway_text_node *node, int max_width); void sway_text_node_set_background(struct sway_text_node *node, float background[4]); #endif ================================================ FILE: include/sway/swaynag.h ================================================ #ifndef _SWAY_SWAYNAG_H #define _SWAY_SWAYNAG_H #include #include "stringop.h" struct swaynag_instance { struct wl_client *client; struct wl_listener client_destroy; const char *args; int fd[2]; bool detailed; }; // Spawn swaynag. If swaynag->detailed, then swaynag->fd[1] will left open // so it can be written to. Call swaynag_show when done writing. This will // be automatically called by swaynag_log if the instance is not spawned and // swaynag->detailed is true. bool swaynag_spawn(const char *swaynag_command, struct swaynag_instance *swaynag); // Write a log message to swaynag->fd[1]. This will fail when swaynag->detailed // is false. void swaynag_log(const char *swaynag_command, struct swaynag_instance *swaynag, const char *fmt, ...) _SWAY_ATTRIB_PRINTF(3, 4); // If swaynag->detailed, close swaynag->fd[1] so swaynag displays void swaynag_show(struct swaynag_instance *swaynag); #endif ================================================ FILE: include/sway/tree/arrange.h ================================================ #ifndef _SWAY_ARRANGE_H #define _SWAY_ARRANGE_H struct sway_output; struct sway_workspace; struct sway_container; struct sway_node; void arrange_container(struct sway_container *container); void arrange_workspace(struct sway_workspace *workspace); void arrange_output(struct sway_output *output); void arrange_root(void); void arrange_node(struct sway_node *node); #endif ================================================ FILE: include/sway/tree/container.h ================================================ #ifndef _SWAY_CONTAINER_H #define _SWAY_CONTAINER_H #include #include #include #include #include "list.h" #include "sway/tree/node.h" struct sway_view; struct sway_seat; enum sway_container_layout { L_NONE, L_HORIZ, L_VERT, L_STACKED, L_TABBED, }; enum sway_container_border { B_NONE, B_PIXEL, B_NORMAL, B_CSD, }; enum sway_fullscreen_mode { FULLSCREEN_NONE, FULLSCREEN_WORKSPACE, FULLSCREEN_GLOBAL, }; struct sway_root; struct sway_output; struct sway_workspace; struct sway_view; enum wlr_direction; struct sway_container_state { // Container properties enum sway_container_layout layout; double x, y; double width, height; enum sway_fullscreen_mode fullscreen_mode; struct sway_workspace *workspace; // NULL when hidden in the scratchpad struct sway_container *parent; // NULL if container in root of workspace list_t *children; // struct sway_container struct sway_container *focused_inactive_child; bool focused; enum sway_container_border border; int border_thickness; bool border_top; bool border_bottom; bool border_left; bool border_right; // These are in layout coordinates. double content_x, content_y; double content_width, content_height; }; struct sway_container { struct sway_node node; struct sway_view *view; struct wlr_scene_tree *scene_tree; struct { struct wlr_scene_tree *tree; struct wlr_scene_tree *border; struct wlr_scene_tree *background; struct sway_text_node *title_text; struct sway_text_node *marks_text; } title_bar; struct { struct wlr_scene_tree *tree; struct wlr_scene_rect *top; struct wlr_scene_rect *bottom; struct wlr_scene_rect *left; struct wlr_scene_rect *right; } border; struct wlr_scene_tree *content_tree; struct wlr_scene_buffer *output_handler; struct wl_listener outputs_update; struct wl_listener output_handler_destroy; struct sway_container_state current; struct sway_container_state pending; char *title; // The view's title (unformatted) char *formatted_title; // The title displayed in the title bar int title_width; char *title_format; enum sway_container_layout prev_split_layout; // Whether stickiness has been enabled on this container. Use // `container_is_sticky_[or_child]` rather than accessing this field // directly; it'll also check that the container is floating. bool is_sticky; // For C_ROOT, this has no meaning // For other types, this is the position in layout coordinates // Includes borders double saved_x, saved_y; double saved_width, saved_height; // Used when the view changes to CSD unexpectedly. This will be a non-B_CSD // border which we use to restore when the view returns to SSD. enum sway_container_border saved_border; // The share of the space of parent container this container occupies double width_fraction; double height_fraction; // The share of space of the parent container that all children occupy // Used for doing the resize calculations double child_total_width; double child_total_height; // Indicates that the container is a scratchpad container. // Both hidden and visible scratchpad containers have scratchpad=true. // Hidden scratchpad containers have a NULL parent. bool scratchpad; // Stores last output size and position for adjusting coordinates of // scratchpad windows. // Unused for non-scratchpad windows. struct wlr_box transform; float alpha; list_t *marks; // char * struct { struct wl_signal destroy; } events; }; struct sway_container *container_create(struct sway_view *view); void container_destroy(struct sway_container *con); void container_begin_destroy(struct sway_container *con); /** * Search a container's descendants a container based on test criteria. Returns * the first container that passes the test. */ struct sway_container *container_find_child(struct sway_container *container, bool (*test)(struct sway_container *view, void *data), void *data); void container_for_each_child(struct sway_container *container, void (*f)(struct sway_container *container, void *data), void *data); /** * Returns the fullscreen container obstructing this container if it exists. */ struct sway_container *container_obstructing_fullscreen_container(struct sway_container *container); /** * Returns true if the given container is an ancestor of this container. */ bool container_has_ancestor(struct sway_container *container, struct sway_container *ancestor); void container_reap_empty(struct sway_container *con); struct sway_container *container_flatten(struct sway_container *container); void container_update_title_bar(struct sway_container *container); void container_update_marks(struct sway_container *container); size_t parse_title_format(struct sway_container *container, char *buffer); size_t container_build_representation(enum sway_container_layout layout, list_t *children, char *buffer); void container_update_representation(struct sway_container *container); /** * Return the height of a regular title bar. */ size_t container_titlebar_height(void); void floating_calculate_constraints(int *min_width, int *max_width, int *min_height, int *max_height); void floating_fix_coordinates(struct sway_container *con, struct wlr_box *old, struct wlr_box *new); void container_floating_resize_and_center(struct sway_container *con); void container_floating_set_default_size(struct sway_container *con); void container_set_resizing(struct sway_container *con, bool resizing); void container_set_floating(struct sway_container *container, bool enable); void container_set_geometry_from_content(struct sway_container *con); /** * Determine if the given container is itself floating. * This will return false for any descendants of a floating container. * * Uses pending container state. */ bool container_is_floating(struct sway_container *container); /** * Get a container's box in layout coordinates. */ void container_get_box(struct sway_container *container, struct wlr_box *box); /** * Move a floating container by the specified amount. */ void container_floating_translate(struct sway_container *con, double x_amount, double y_amount); /** * Choose an output for the floating container's new position. */ struct sway_output *container_floating_find_output(struct sway_container *con); /** * Move a floating container to a new layout-local position. */ void container_floating_move_to(struct sway_container *con, double lx, double ly); /** * Move a floating container to the center of the workspace. */ void container_floating_move_to_center(struct sway_container *con); bool container_has_urgent_child(struct sway_container *container); /** * If the container is involved in a drag or resize operation via a mouse, this * ends the operation. */ void container_end_mouse_operation(struct sway_container *container); void container_set_fullscreen(struct sway_container *con, enum sway_fullscreen_mode mode); /** * Convenience function. */ void container_fullscreen_disable(struct sway_container *con); /** * Walk up the container tree branch starting at the given container, and return * its earliest ancestor. */ struct sway_container *container_toplevel_ancestor( struct sway_container *container); /** * Return true if the container is floating, or a child of a floating split * container. */ bool container_is_floating_or_child(struct sway_container *container); /** * Return true if the container is fullscreen, or a child of a fullscreen split * container. */ bool container_is_fullscreen_or_child(struct sway_container *container); enum sway_container_layout container_parent_layout(struct sway_container *con); list_t *container_get_siblings(struct sway_container *container); int container_sibling_index(struct sway_container *child); void container_handle_fullscreen_reparent(struct sway_container *con); void container_add_child(struct sway_container *parent, struct sway_container *child); void container_insert_child(struct sway_container *parent, struct sway_container *child, int i); /** * Side should be 0 to add before, or 1 to add after. */ void container_add_sibling(struct sway_container *parent, struct sway_container *child, bool after); void container_detach(struct sway_container *child); void container_replace(struct sway_container *container, struct sway_container *replacement); void container_swap(struct sway_container *con1, struct sway_container *con2); struct sway_container *container_split(struct sway_container *child, enum sway_container_layout layout); bool container_is_transient_for(struct sway_container *child, struct sway_container *ancestor); /** * Find any container that has the given mark and return it. */ struct sway_container *container_find_mark(char *mark); /** * Find any container that has the given mark and remove the mark from the * container. Returns true if it matched a container. */ bool container_find_and_unmark(char *mark); /** * Remove all marks from the container. */ void container_clear_marks(struct sway_container *container); bool container_has_mark(struct sway_container *container, char *mark); void container_add_mark(struct sway_container *container, char *mark); void container_raise_floating(struct sway_container *con); bool container_is_scratchpad_hidden(struct sway_container *con); bool container_is_scratchpad_hidden_or_child(struct sway_container *con); bool container_is_sticky(struct sway_container *con); bool container_is_sticky_or_child(struct sway_container *con); /** * This will destroy pairs of redundant H/V splits * e.g. H[V[H[app app]] app] -> H[app app app] * The middle "V[H[" are eliminated by a call to container_squash * on the V[ con. It's grandchildren are added to its parent. * * This function is roughly equivalent to i3's tree_flatten here: * https://github.com/i3/i3/blob/1f0c628cde40cf87371481041b7197344e0417c6/src/tree.c#L651 * * Returns the number of new containers added to the parent */ int container_squash(struct sway_container *con); void container_arrange_title_bar(struct sway_container *con); void container_update(struct sway_container *con); void container_update_itself_and_parents(struct sway_container *con); #endif ================================================ FILE: include/sway/tree/node.h ================================================ #ifndef _SWAY_NODE_H #define _SWAY_NODE_H #include #include #include #include "list.h" #define MIN_SANE_W 100 #define MIN_SANE_H 60 struct sway_root; struct sway_output; struct sway_workspace; struct sway_container; struct sway_transaction_instruction; struct wlr_box; enum sway_node_type { N_ROOT, N_OUTPUT, N_WORKSPACE, N_CONTAINER, }; struct sway_node { enum sway_node_type type; union { struct sway_root *sway_root; struct sway_output *sway_output; struct sway_workspace *sway_workspace; struct sway_container *sway_container; }; /** * A unique ID to identify this node. * Primarily used in the get_tree JSON output. */ size_t id; struct sway_transaction_instruction *instruction; size_t ntxnrefs; bool destroying; // If true, indicates that the container has pending state that differs from // the current. bool dirty; struct { struct wl_signal destroy; } events; }; void node_init(struct sway_node *node, enum sway_node_type type, void *thing); const char *node_type_to_str(enum sway_node_type type); /** * Mark a node as dirty if it isn't already. Dirty nodes will be included in the * next transaction then unmarked as dirty. */ void node_set_dirty(struct sway_node *node); bool node_is_view(struct sway_node *node); char *node_get_name(struct sway_node *node); void node_get_box(struct sway_node *node, struct wlr_box *box); struct sway_output *node_get_output(struct sway_node *node); enum sway_container_layout node_get_layout(struct sway_node *node); struct sway_node *node_get_parent(struct sway_node *node); list_t *node_get_children(struct sway_node *node); bool node_has_ancestor(struct sway_node *node, struct sway_node *ancestor); // when destroying a sway tree, it's not known which order the tree will be // destroyed. To prevent freeing of scene_nodes recursing up the tree, // let's use this helper function to disown them to the staging node. void scene_node_disown_children(struct wlr_scene_tree *tree); // a helper function used to allocate tree nodes. If an allocation failure // occurs a flag is flipped that can be checked later to destroy a parent // of this scene node preventing memory leaks. struct wlr_scene_tree *alloc_scene_tree(struct wlr_scene_tree *parent, bool *failed); #endif ================================================ FILE: include/sway/tree/root.h ================================================ #ifndef _SWAY_ROOT_H #define _SWAY_ROOT_H #include #include #include #include #include #include #include "sway/tree/container.h" #include "sway/tree/node.h" #include "list.h" extern struct sway_root *root; struct sway_root { struct sway_node node; struct wlr_output_layout *output_layout; // scene node layout: // - root // - staging // - layer shell stuff // - tiling // - floating // - fullscreen stuff // - seat stuff // - ext_session_lock struct wlr_scene *root_scene; // since wlr_scene nodes can't be orphaned and must always // have a parent, use this staging scene_tree so that a // node always have a valid parent. Nothing in this // staging node will be visible. struct wlr_scene_tree *staging; // tree containing all layers the compositor will render. Cursor handling // will end up iterating this tree. struct wlr_scene_tree *layer_tree; struct { struct wlr_scene_tree *shell_background; struct wlr_scene_tree *shell_bottom; struct wlr_scene_tree *tiling; struct wlr_scene_tree *floating; struct wlr_scene_tree *shell_top; struct wlr_scene_tree *fullscreen; struct wlr_scene_tree *fullscreen_global; #if WLR_HAS_XWAYLAND struct wlr_scene_tree *unmanaged; #endif struct wlr_scene_tree *shell_overlay; struct wlr_scene_tree *popup; struct wlr_scene_tree *seat; struct wlr_scene_tree *session_lock; } layers; // Includes disabled outputs struct wl_list all_outputs; // sway_output::link double x, y; double width, height; list_t *outputs; // struct sway_output list_t *non_desktop_outputs; // struct sway_output_non_desktop list_t *scratchpad; // struct sway_container // For when there's no connected outputs struct sway_output *fallback_output; struct sway_container *fullscreen_global; struct { struct wl_signal new_node; } events; }; struct sway_root *root_create(struct wl_display *display); void root_destroy(struct sway_root *root); /** * Move a container to the scratchpad. * If a workspace is passed, the container is assumed to have been in * the scratchpad before and is shown on the workspace. * The ws parameter can safely be NULL. */ void root_scratchpad_add_container(struct sway_container *con, struct sway_workspace *ws); /** * Remove a container from the scratchpad. */ void root_scratchpad_remove_container(struct sway_container *con); /** * Show a single scratchpad container. */ void root_scratchpad_show(struct sway_container *con); /** * Hide a single scratchpad container. */ void root_scratchpad_hide(struct sway_container *con); void root_for_each_workspace(void (*f)(struct sway_workspace *ws, void *data), void *data); void root_for_each_container(void (*f)(struct sway_container *con, void *data), void *data); struct sway_output *root_find_output( bool (*test)(struct sway_output *output, void *data), void *data); struct sway_workspace *root_find_workspace( bool (*test)(struct sway_workspace *ws, void *data), void *data); struct sway_container *root_find_container( bool (*test)(struct sway_container *con, void *data), void *data); void root_get_box(struct sway_root *root, struct wlr_box *box); #endif ================================================ FILE: include/sway/tree/view.h ================================================ #ifndef _SWAY_VIEW_H #define _SWAY_VIEW_H #include #include #include #include #include #include "sway/config.h" #if WLR_HAS_XWAYLAND #include #endif #include "sway/input/input-manager.h" #include "sway/input/seat.h" struct sway_container; struct sway_xdg_decoration; enum sway_view_type { SWAY_VIEW_XDG_SHELL, #if WLR_HAS_XWAYLAND SWAY_VIEW_XWAYLAND, #endif }; enum sway_view_prop { VIEW_PROP_TITLE, VIEW_PROP_APP_ID, VIEW_PROP_TAG, VIEW_PROP_CLASS, VIEW_PROP_INSTANCE, VIEW_PROP_WINDOW_TYPE, VIEW_PROP_WINDOW_ROLE, #if WLR_HAS_XWAYLAND VIEW_PROP_X11_WINDOW_ID, VIEW_PROP_X11_PARENT_ID, #endif }; enum sway_view_tearing_mode { TEARING_OVERRIDE_FALSE, TEARING_OVERRIDE_TRUE, TEARING_WINDOW_HINT, }; struct sway_view_impl { void (*get_constraints)(struct sway_view *view, double *min_width, double *max_width, double *min_height, double *max_height); const char *(*get_string_prop)(struct sway_view *view, enum sway_view_prop prop); uint32_t (*get_int_prop)(struct sway_view *view, enum sway_view_prop prop); uint32_t (*configure)(struct sway_view *view, double lx, double ly, int width, int height); void (*set_activated)(struct sway_view *view, bool activated); void (*set_tiled)(struct sway_view *view, bool tiled); void (*set_fullscreen)(struct sway_view *view, bool fullscreen); void (*set_resizing)(struct sway_view *view, bool resizing); bool (*wants_floating)(struct sway_view *view); bool (*is_transient_for)(struct sway_view *child, struct sway_view *ancestor); void (*close)(struct sway_view *view); void (*close_popups)(struct sway_view *view); void (*destroy)(struct sway_view *view); }; struct sway_view { enum sway_view_type type; const struct sway_view_impl *impl; struct wlr_scene_tree *scene_tree; struct wlr_scene_tree *content_tree; struct wlr_scene_tree *saved_surface_tree; struct wlr_scene *image_capture_scene; struct wlr_ext_image_capture_source_v1 *image_capture_source; struct sway_container *container; // NULL if unmapped and transactions finished struct wlr_surface *surface; // NULL for unmapped views struct sway_xdg_decoration *xdg_decoration; pid_t pid; struct launcher_ctx *ctx; // The size the view would want to be if it weren't tiled. // Used when changing a view from tiled to floating. int natural_width, natural_height; bool using_csd; struct timespec urgent; bool allow_request_urgent; struct wl_event_source *urgent_timer; // The geometry for whatever the client is committing, regardless of // transaction state. Updated on every commit. struct wlr_box geometry; struct wlr_ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel; struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel; struct wl_listener foreign_activate_request; struct wl_listener foreign_fullscreen_request; struct wl_listener foreign_close_request; struct wl_listener foreign_destroy; bool destroying; list_t *executed_criteria; // struct criteria * union { struct wlr_xdg_toplevel *wlr_xdg_toplevel; #if WLR_HAS_XWAYLAND struct wlr_xwayland_surface *wlr_xwayland_surface; #endif }; struct { struct wl_signal unmap; } events; int max_render_time; // In milliseconds enum seat_config_shortcuts_inhibit shortcuts_inhibit; enum sway_view_tearing_mode tearing_mode; enum wp_tearing_control_v1_presentation_hint tearing_hint; }; struct sway_xdg_shell_view { struct sway_view view; struct wlr_scene_tree *image_capture_tree; char *tag; struct wl_listener commit; struct wl_listener request_move; struct wl_listener request_resize; struct wl_listener request_maximize; struct wl_listener request_fullscreen; struct wl_listener set_title; struct wl_listener set_app_id; struct wl_listener new_popup; struct wl_listener map; struct wl_listener unmap; struct wl_listener destroy; }; #if WLR_HAS_XWAYLAND struct sway_xwayland_view { struct sway_view view; struct wlr_scene_tree *surface_tree; struct wlr_scene_surface *image_capture_scene_surface; struct wl_listener commit; struct wl_listener request_move; struct wl_listener request_resize; struct wl_listener request_maximize; struct wl_listener request_minimize; struct wl_listener request_configure; struct wl_listener request_fullscreen; struct wl_listener request_activate; struct wl_listener set_title; struct wl_listener set_class; struct wl_listener set_role; struct wl_listener set_startup_id; struct wl_listener set_window_type; struct wl_listener set_hints; struct wl_listener set_decorations; struct wl_listener associate; struct wl_listener dissociate; struct wl_listener map; struct wl_listener unmap; struct wl_listener destroy; struct wl_listener override_redirect; struct wl_listener surface_tree_destroy; }; struct sway_xwayland_unmanaged { struct wlr_xwayland_surface *wlr_xwayland_surface; struct wlr_scene_surface *surface_scene; struct wl_listener request_activate; struct wl_listener request_configure; struct wl_listener request_fullscreen; struct wl_listener set_geometry; struct wl_listener associate; struct wl_listener dissociate; struct wl_listener map; struct wl_listener unmap; struct wl_listener destroy; struct wl_listener override_redirect; }; #endif struct sway_popup_desc { struct wlr_scene_node *relative; struct sway_view *view; }; struct sway_xdg_popup { struct sway_view *view; struct wlr_xdg_popup *wlr_xdg_popup; struct wlr_scene_tree *scene_tree; struct wlr_scene_tree *xdg_surface_tree; struct wlr_scene_tree *image_capture_tree; struct sway_popup_desc desc; struct wl_listener surface_commit; struct wl_listener new_popup; struct wl_listener reposition; struct wl_listener destroy; }; const char *view_get_title(struct sway_view *view); const char *view_get_app_id(struct sway_view *view); const char *view_get_class(struct sway_view *view); const char *view_get_instance(struct sway_view *view); uint32_t view_get_x11_window_id(struct sway_view *view); uint32_t view_get_x11_parent_id(struct sway_view *view); const char *view_get_window_role(struct sway_view *view); uint32_t view_get_window_type(struct sway_view *view); const char *view_get_sandbox_engine(struct sway_view *view); const char *view_get_sandbox_app_id(struct sway_view *view); const char *view_get_sandbox_instance_id(struct sway_view *view); const char *view_get_tag(struct sway_view *view); const char *view_get_shell(struct sway_view *view); void view_get_constraints(struct sway_view *view, double *min_width, double *max_width, double *min_height, double *max_height); uint32_t view_configure(struct sway_view *view, double lx, double ly, int width, int height); bool view_inhibit_idle(struct sway_view *view); /** * Whether or not this view's most distant ancestor (possibly itself) is the * only visible node in its tree. If the view is tiling, there may be floating * views. If the view is floating, there may be tiling views or views in a * different floating container. */ bool view_ancestor_is_only_visible(struct sway_view *view); /** * Configure the view's position and size based on the container's position and * size, taking borders into consideration. */ void view_autoconfigure(struct sway_view *view); void view_set_activated(struct sway_view *view, bool activated); /** * Called when the view requests to be focused. */ void view_request_activate(struct sway_view *view, struct sway_seat *seat); /* * Called when the view requests urgent state */ void view_request_urgent(struct sway_view *view); /** * If possible, instructs the client to change their decoration mode. */ void view_set_csd_from_server(struct sway_view *view, bool enabled); /** * Updates the view's border setting when the client unexpectedly changes their * decoration mode. */ void view_update_csd_from_client(struct sway_view *view, bool enabled); void view_set_tiled(struct sway_view *view, bool tiled); void view_close(struct sway_view *view); void view_close_popups(struct sway_view *view); // view implementation bool view_init(struct sway_view *view, enum sway_view_type type, const struct sway_view_impl *impl); void view_destroy(struct sway_view *view); void view_begin_destroy(struct sway_view *view); /** * Map a view, ie. make it visible in the tree. * * `fullscreen` should be set to true (and optionally `fullscreen_output` * should be populated) if the view should be made fullscreen immediately. * * `decoration` should be set to true if the client prefers CSD. The client's * preference may be ignored. */ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface, bool fullscreen, struct wlr_output *fullscreen_output, bool decoration); void view_unmap(struct sway_view *view); void view_update_size(struct sway_view *view); void view_center_and_clip_surface(struct sway_view *view); struct sway_view *view_from_wlr_xdg_surface( struct wlr_xdg_surface *xdg_surface); #if WLR_HAS_XWAYLAND struct sway_view *view_from_wlr_xwayland_surface( struct wlr_xwayland_surface *xsurface); #endif struct sway_view *view_from_wlr_surface(struct wlr_surface *surface); void view_update_app_id(struct sway_view *view); /** * Re-read the view's title property and update any relevant title bars. * The force argument makes it recreate the title bars even if the title hasn't * changed. */ void view_update_title(struct sway_view *view, bool force); /** * Run any criteria that match the view and haven't been run on this view * before. */ void view_execute_criteria(struct sway_view *view); /** * Returns true if there's a possibility the view may be rendered on screen. * Intended for damage tracking. */ bool view_is_visible(struct sway_view *view); void view_set_urgent(struct sway_view *view, bool enable); bool view_is_urgent(struct sway_view *view); void view_remove_saved_buffer(struct sway_view *view); void view_save_buffer(struct sway_view *view); bool view_is_transient_for(struct sway_view *child, struct sway_view *ancestor); void view_assign_ctx(struct sway_view *view, struct launcher_ctx *ctx); void view_send_frame_done(struct sway_view *view); bool view_can_tear(struct sway_view *view); void xdg_toplevel_tag_manager_v1_handle_set_tag(struct wl_listener *listener, void *data); #endif ================================================ FILE: include/sway/tree/workspace.h ================================================ #ifndef _SWAY_WORKSPACE_H #define _SWAY_WORKSPACE_H #include #include #include "sway/config.h" #include "sway/tree/container.h" #include "sway/tree/node.h" struct sway_view; struct sway_workspace_state { struct sway_container *fullscreen; double x, y; int width, height; enum sway_container_layout layout; struct sway_output *output; list_t *floating; list_t *tiling; struct sway_container *focused_inactive_child; bool focused; }; struct sway_workspace { struct sway_node node; struct { struct wlr_scene_tree *tiling; struct wlr_scene_tree *fullscreen; } layers; struct sway_container *fullscreen; char *name; char *representation; double x, y; int width, height; enum sway_container_layout layout; enum sway_container_layout prev_split_layout; struct side_gaps current_gaps; int gaps_inner; struct side_gaps gaps_outer; struct sway_output *output; // NULL if no outputs are connected list_t *floating; // struct sway_container list_t *tiling; // struct sway_container list_t *output_priority; bool urgent; struct sway_workspace_state current; }; struct workspace_config *workspace_find_config(const char *ws_name); struct sway_output *workspace_get_initial_output(const char *name); struct sway_workspace *workspace_create(struct sway_output *output, const char *name); void workspace_destroy(struct sway_workspace *workspace); void workspace_begin_destroy(struct sway_workspace *workspace); void workspace_consider_destroy(struct sway_workspace *ws); char *workspace_next_name(const char *output_name); struct sway_workspace *workspace_auto_back_and_forth( struct sway_workspace *workspace); bool workspace_switch(struct sway_workspace *workspace); struct sway_workspace *workspace_by_number(const char* name); struct sway_workspace *workspace_by_name(const char*); struct sway_workspace *workspace_output_next(struct sway_workspace *current); struct sway_workspace *workspace_next(struct sway_workspace *current); struct sway_workspace *workspace_output_prev(struct sway_workspace *current); struct sway_workspace *workspace_prev(struct sway_workspace *current); bool workspace_is_visible(struct sway_workspace *ws); bool workspace_is_empty(struct sway_workspace *ws); void workspace_output_raise_priority(struct sway_workspace *workspace, struct sway_output *old_output, struct sway_output *new_output); void workspace_output_add_priority(struct sway_workspace *workspace, struct sway_output *output); struct sway_output *workspace_output_get_highest_available( struct sway_workspace *ws); void workspace_detect_urgent(struct sway_workspace *workspace); void workspace_for_each_container(struct sway_workspace *ws, void (*f)(struct sway_container *con, void *data), void *data); struct sway_container *workspace_find_container(struct sway_workspace *ws, bool (*test)(struct sway_container *con, void *data), void *data); /** * Wrap the workspace's tiling children in a new container. * The new container will be the only direct tiling child of the workspace. * The new container is returned. */ struct sway_container *workspace_wrap_children(struct sway_workspace *ws); void workspace_unwrap_children(struct sway_workspace *ws, struct sway_container *wrap); void workspace_detach(struct sway_workspace *workspace); struct sway_container *workspace_add_tiling(struct sway_workspace *workspace, struct sway_container *con); void workspace_add_floating(struct sway_workspace *workspace, struct sway_container *con); /** * Adds a tiling container to the workspace without considering * the workspace_layout, so the con will not be split. */ void workspace_insert_tiling_direct(struct sway_workspace *workspace, struct sway_container *con, int index); struct sway_container *workspace_insert_tiling(struct sway_workspace *workspace, struct sway_container *con, int index); void workspace_remove_gaps(struct sway_workspace *ws); void workspace_add_gaps(struct sway_workspace *ws); struct sway_container *workspace_split(struct sway_workspace *workspace, enum sway_container_layout layout); void workspace_update_representation(struct sway_workspace *ws); void workspace_get_box(struct sway_workspace *workspace, struct wlr_box *box); size_t workspace_num_tiling_views(struct sway_workspace *ws); size_t workspace_num_sticky_containers(struct sway_workspace *ws); /** * workspace_squash is container_flatten in the reverse * direction. Instead of eliminating redundant splits that are * parents of the target container, it eliminates pairs of * redundant H/V splits that are children of the workspace. */ void workspace_squash(struct sway_workspace *workspace); #endif ================================================ FILE: include/sway/xdg_decoration.h ================================================ #ifndef _SWAY_XDG_DECORATION_H #define _SWAY_XDG_DECORATION_H #include struct sway_xdg_decoration { struct wlr_xdg_toplevel_decoration_v1 *wlr_xdg_decoration; struct wl_list link; struct sway_view *view; struct wl_listener destroy; struct wl_listener request_mode; }; struct sway_xdg_decoration *xdg_decoration_from_surface( struct wlr_surface *surface); void set_xdg_decoration_mode(struct sway_xdg_decoration *deco); #endif ================================================ FILE: include/sway/xwayland.h ================================================ #ifndef SWAY_XWAYLAND_H #define SWAY_XWAYLAND_H #include #include enum atom_name { NET_WM_WINDOW_TYPE_NORMAL, NET_WM_WINDOW_TYPE_DIALOG, NET_WM_WINDOW_TYPE_UTILITY, NET_WM_WINDOW_TYPE_TOOLBAR, NET_WM_WINDOW_TYPE_SPLASH, NET_WM_WINDOW_TYPE_MENU, NET_WM_WINDOW_TYPE_DROPDOWN_MENU, NET_WM_WINDOW_TYPE_POPUP_MENU, NET_WM_WINDOW_TYPE_TOOLTIP, NET_WM_WINDOW_TYPE_NOTIFICATION, NET_WM_STATE_MODAL, ATOM_LAST, }; struct sway_xwayland { struct wlr_xwayland *wlr_xwayland; struct wlr_xcursor_manager *xcursor_manager; xcb_atom_t atoms[ATOM_LAST]; }; void handle_xwayland_ready(struct wl_listener *listener, void *data); #endif ================================================ FILE: include/swaybar/bar.h ================================================ #ifndef _SWAYBAR_BAR_H #define _SWAYBAR_BAR_H #include #include "config.h" #include "input.h" #include "pool-buffer.h" #include "cursor-shape-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" struct swaybar_config; struct swaybar_output; #if HAVE_TRAY struct swaybar_tray; #endif struct swaybar_workspace; struct loop; struct swaybar { char *id; char *mode; bool mode_pango_markup; // only relevant when bar is in "hide" mode bool visible_by_modifier; bool visible_by_urgency; bool visible_by_mode; bool visible; struct wl_display *display; struct wl_compositor *compositor; struct zwlr_layer_shell_v1 *layer_shell; struct zxdg_output_manager_v1 *xdg_output_manager; struct wp_cursor_shape_manager_v1 *cursor_shape_manager; struct wl_shm *shm; struct swaybar_config *config; struct status_line *status; struct loop *eventloop; int ipc_event_socketfd; int ipc_socketfd; struct wl_list outputs; // swaybar_output::link struct wl_list unused_outputs; // swaybar_output::link struct wl_list seats; // swaybar_seat::link #if HAVE_TRAY struct swaybar_tray *tray; #endif bool running; }; struct swaybar_output { struct wl_list link; // swaybar::outputs struct swaybar *bar; struct wl_output *output; struct zxdg_output_v1 *xdg_output; struct wl_surface *surface; struct zwlr_layer_surface_v1 *layer_surface; uint32_t wl_name; struct wl_list workspaces; // swaybar_workspace::link struct wl_list hotspots; // swaybar_hotspot::link char *name; char *identifier; bool focused; uint32_t width, height; int32_t scale; enum wl_output_subpixel subpixel; struct pool_buffer buffers[2]; struct pool_buffer *current_buffer; bool dirty; bool frame_scheduled; uint32_t output_height, output_width, output_x, output_y; }; struct swaybar_workspace { struct wl_list link; // swaybar_output::workspaces int num; char *name; char *label; bool focused; bool visible; bool urgent; }; bool bar_setup(struct swaybar *bar, const char *socket_path); void bar_run(struct swaybar *bar); void bar_teardown(struct swaybar *bar); void set_bar_dirty(struct swaybar *bar); /* * Determines whether the bar should be visible and changes it to be so. * If the current visibility of the bar is the different to what it should be, * then it adds or destroys the layer surface as required, * as well as sending the cont or stop signal to the status command. * If the current visibility of the bar is already what it should be, * then this function is a no-op, unless moving_layer is true, which occurs * when the bar changes from "hide" to "dock" mode or vice versa, and the bar * needs to be destroyed and re-added in order to change its layer. * * Returns true if the bar is now visible, otherwise false. */ bool determine_bar_visibility(struct swaybar *bar, bool moving_layer); void free_workspaces(struct wl_list *list); void status_in(int fd, short mask, void *data); void destroy_layer_surface(struct swaybar_output *output); #endif ================================================ FILE: include/swaybar/config.h ================================================ #ifndef _SWAYBAR_CONFIG_H #define _SWAYBAR_CONFIG_H #include #include #include #include "../include/config.h" #include "list.h" #include "util.h" #include struct box_colors { uint32_t border; uint32_t background; uint32_t text; }; struct box_size { uint32_t width; uint32_t height; }; struct config_output { struct wl_list link; // swaybar_config::outputs char *name; }; struct swaybar_binding { uint32_t button; char *command; bool release; }; struct swaybar_config { char *status_command; bool pango_markup; uint32_t position; // zwlr_layer_surface_v1_anchor PangoFontDescription *font_description; char *sep_symbol; char *mode; char *hidden_state; char *modifier; bool strip_workspace_numbers; bool strip_workspace_name; bool binding_mode_indicator; bool wrap_scroll; bool workspace_buttons; uint32_t workspace_min_width; list_t *bindings; struct wl_list outputs; // config_output::link int height; int status_padding; int status_edge_padding; struct { int top; int right; int bottom; int left; } gaps; struct { uint32_t background; uint32_t statusline; uint32_t separator; uint32_t focused_background; uint32_t focused_statusline; uint32_t focused_separator; struct box_colors focused_workspace; struct box_colors active_workspace; struct box_colors inactive_workspace; struct box_colors urgent_workspace; struct box_colors binding_mode; } colors; #if HAVE_TRAY char *icon_theme; struct wl_list tray_bindings; // struct tray_binding::link bool tray_hidden; list_t *tray_outputs; // char * int tray_padding; #endif }; #if HAVE_TRAY struct tray_binding { uint32_t button; char *command; struct wl_list link; // struct tray_binding::link }; void free_tray_binding(struct tray_binding *binding); #endif struct swaybar_config *init_config(void); void free_config(struct swaybar_config *config); uint32_t parse_position(const char *position); void free_binding(struct swaybar_binding *binding); #endif ================================================ FILE: include/swaybar/i3bar.h ================================================ #ifndef _SWAYBAR_I3BAR_H #define _SWAYBAR_I3BAR_H #include "input.h" #include "status_line.h" struct i3bar_block { struct wl_list link; // status_link::blocks int ref_count; char *full_text, *short_text, *align, *min_width_str; bool urgent; uint32_t color; bool color_set; int min_width; char *name, *instance; bool separator; int separator_block_width; bool markup; // Airblader features uint32_t background; uint32_t border; bool border_set; int border_top; int border_bottom; int border_left; int border_right; }; void i3bar_block_unref(struct i3bar_block *block); bool i3bar_handle_readable(struct status_line *status); enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, struct i3bar_block *block, double x, double y, double rx, double ry, double w, double h, int scale, uint32_t button, bool released); #endif ================================================ FILE: include/swaybar/image.h ================================================ #ifndef _SWAYBAR_IMAGE_H #define _SWAYBAR_IMAGE_H #include cairo_surface_t *load_image(const char *path); #endif ================================================ FILE: include/swaybar/input.h ================================================ #ifndef _SWAYBAR_INPUT_H #define _SWAYBAR_INPUT_H #include #include #include "list.h" #define SWAY_SCROLL_UP KEY_MAX + 1 #define SWAY_SCROLL_DOWN KEY_MAX + 2 #define SWAY_SCROLL_LEFT KEY_MAX + 3 #define SWAY_SCROLL_RIGHT KEY_MAX + 4 #define SWAY_CONTINUOUS_SCROLL_TIMEOUT 1000 #define SWAY_CONTINUOUS_SCROLL_THRESHOLD 10000 struct swaybar; struct swaybar_output; struct swaybar_pointer { struct wl_pointer *pointer; struct wl_cursor_theme *cursor_theme; struct wl_cursor_image *cursor_image; struct wl_surface *cursor_surface; struct swaybar_output *current; double x, y; uint32_t serial; }; struct touch_slot { int32_t id; uint32_t time; struct swaybar_output *output; double start_x, start_y; double x, y; }; struct swaybar_touch { struct wl_touch *touch; struct touch_slot slots[16]; }; enum hotspot_event_handling { HOTSPOT_IGNORE, HOTSPOT_PROCESS, }; struct swaybar_hotspot { struct wl_list link; // swaybar_output::hotspots int x, y, width, height; enum hotspot_event_handling (*callback)(struct swaybar_output *output, struct swaybar_hotspot *hotspot, double x, double y, uint32_t button, bool released, void *data); void (*destroy)(void *data); void *data; }; struct swaybar_scroll_axis { wl_fixed_t value; uint32_t discrete_steps; uint32_t update_time; }; struct swaybar_seat { struct swaybar *bar; uint32_t wl_name; struct wl_seat *wl_seat; struct swaybar_pointer pointer; struct swaybar_touch touch; struct wl_list link; // swaybar_seat:link struct swaybar_scroll_axis axis[2]; }; extern const struct wl_seat_listener seat_listener; void update_cursor(struct swaybar_seat *seat); uint32_t event_to_x11_button(uint32_t event); void free_hotspots(struct wl_list *list); void swaybar_seat_free(struct swaybar_seat *seat); #endif ================================================ FILE: include/swaybar/ipc.h ================================================ #ifndef _SWAYBAR_IPC_H #define _SWAYBAR_IPC_H #include #include "swaybar/bar.h" bool ipc_initialize(struct swaybar *bar); bool handle_ipc_readable(struct swaybar *bar); bool ipc_get_workspaces(struct swaybar *bar); void ipc_send_workspace_command(struct swaybar *bar, const char *ws); void ipc_execute_binding(struct swaybar *bar, struct swaybar_binding *bind); #endif ================================================ FILE: include/swaybar/render.h ================================================ #ifndef _SWAYBAR_RENDER_H #define _SWAYBAR_RENDER_H struct swaybar_output; void render_frame(struct swaybar_output *output); #endif ================================================ FILE: include/swaybar/status_line.h ================================================ #ifndef _SWAYBAR_STATUS_LINE_H #define _SWAYBAR_STATUS_LINE_H #include #include #include #include #include "bar.h" enum status_protocol { PROTOCOL_UNDEF, PROTOCOL_ERROR, PROTOCOL_TEXT, PROTOCOL_I3BAR, }; struct status_line { struct swaybar *bar; pid_t pid; int read_fd, write_fd; FILE *read, *write; enum status_protocol protocol; const char *text; struct wl_list blocks; // i3bar_block::link int stop_signal; int cont_signal; bool click_events; bool float_event_coords; bool clicked; char *buffer; size_t buffer_size; size_t buffer_index; bool started; bool expecting_comma; json_tokener *tokener; }; struct status_line *status_line_init(char *cmd); void status_error(struct status_line *status, const char *text); bool status_handle_readable(struct status_line *status); void status_line_free(struct status_line *status); #endif ================================================ FILE: include/swaybar/tray/host.h ================================================ #ifndef _SWAYBAR_TRAY_HOST_H #define _SWAYBAR_TRAY_HOST_H #include struct swaybar_tray; struct swaybar_host { struct swaybar_tray *tray; char *service; char *watcher_interface; }; bool init_host(struct swaybar_host *host, char *protocol, struct swaybar_tray *tray); void finish_host(struct swaybar_host *host); #endif ================================================ FILE: include/swaybar/tray/icon.h ================================================ #ifndef _SWAYBAR_TRAY_ICON_H #define _SWAYBAR_TRAY_ICON_H #include "list.h" struct icon_theme_subdir { char *name; int size; enum { THRESHOLD, SCALABLE, FIXED } type; int max_size; int min_size; int threshold; }; struct icon_theme { char *name; char *comment; list_t *inherits; // char * list_t *directories; // char * char *dir; list_t *subdirs; // struct icon_theme_subdir * }; void init_themes(list_t **themes, list_t **basedirs); void finish_themes(list_t *themes, list_t *basedirs); /* * Finds an icon of a specified size given a list of themes and base directories. * If the icon is found, the pointers min_size & max_size are set to minimum & * maximum size that the icon can be scaled to, respectively. * Returns: path of icon (which should be freed), or NULL if the icon is not found. */ char *find_icon(list_t *themes, list_t *basedirs, char *name, int size, char *theme, int *min_size, int *max_size); #endif ================================================ FILE: include/swaybar/tray/item.h ================================================ #ifndef _SWAYBAR_TRAY_ITEM_H #define _SWAYBAR_TRAY_ITEM_H #include #include #include #include #include "swaybar/tray/tray.h" #include "list.h" struct swaybar_output; struct swaybar_pixmap { int size; unsigned char pixels[]; }; struct swaybar_sni_slot { struct wl_list link; // swaybar_sni::slots struct swaybar_sni *sni; const char *prop; const char *type; void *dest; sd_bus_slot *slot; }; struct swaybar_sni { // icon properties struct swaybar_tray *tray; cairo_surface_t *icon; int min_size; int max_size; int target_size; // dbus properties char *watcher_id; char *service; char *path; char *interface; char *status; char *icon_name; list_t *icon_pixmap; // struct swaybar_pixmap * char *attention_icon_name; list_t *attention_icon_pixmap; // struct swaybar_pixmap * bool item_is_menu; char *menu; char *icon_theme_path; // non-standard KDE property struct wl_list slots; // swaybar_sni_slot::link }; struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray); void destroy_sni(struct swaybar_sni *sni); uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x, struct swaybar_sni *sni); #endif ================================================ FILE: include/swaybar/tray/tray.h ================================================ #ifndef _SWAYBAR_TRAY_TRAY_H #define _SWAYBAR_TRAY_TRAY_H #include "config.h" #if HAVE_LIBSYSTEMD #include #elif HAVE_LIBELOGIND #include #elif HAVE_BASU #include #endif #include #include #include "swaybar/tray/host.h" #include "list.h" struct swaybar; struct swaybar_output; struct swaybar_watcher; struct swaybar_tray { struct swaybar *bar; int fd; sd_bus *bus; struct swaybar_host host_xdg; struct swaybar_host host_kde; list_t *items; // struct swaybar_sni * struct swaybar_watcher *watcher_xdg; struct swaybar_watcher *watcher_kde; list_t *basedirs; // char * list_t *themes; // struct swaybar_theme * }; struct swaybar_tray *create_tray(struct swaybar *bar); void destroy_tray(struct swaybar_tray *tray); void tray_in(int fd, short mask, void *data); uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x); #endif ================================================ FILE: include/swaybar/tray/watcher.h ================================================ #ifndef _SWAYBAR_TRAY_WATCHER_H #define _SWAYBAR_TRAY_WATCHER_H #include "swaybar/tray/tray.h" #include "list.h" struct swaybar_watcher { char *interface; sd_bus *bus; list_t *hosts; list_t *items; int version; }; struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus); void destroy_watcher(struct swaybar_watcher *watcher); #endif ================================================ FILE: include/swaynag/config.h ================================================ #ifndef _SWAYNAG_CONFIG_H #define _SWAYNAG_CONFIG_H #include "swaynag/swaynag.h" #include "list.h" int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag, list_t *types, struct swaynag_type *type, char **config, bool *debug); char *swaynag_get_config_path(void); int swaynag_load_config(char *path, struct swaynag *swaynag, list_t *types); #endif ================================================ FILE: include/swaynag/render.h ================================================ #ifndef _SWAYNAG_RENDER_H #define _SWAYNAG_RENDER_H #include "swaynag/swaynag.h" void render_frame(struct swaynag *swaynag); #endif ================================================ FILE: include/swaynag/swaynag.h ================================================ #ifndef _SWAYNAG_SWAYNAG_H #define _SWAYNAG_SWAYNAG_H #include #include #include "list.h" #include "pool-buffer.h" #include "cursor-shape-v1-client-protocol.h" #include "swaynag/types.h" #define SWAYNAG_MAX_HEIGHT 500 struct swaynag; enum swaynag_action_type { SWAYNAG_ACTION_DISMISS, SWAYNAG_ACTION_EXPAND, SWAYNAG_ACTION_COMMAND, }; struct swaynag_pointer { struct wl_pointer *pointer; uint32_t serial; struct wl_cursor_theme *cursor_theme; struct wl_cursor_image *cursor_image; struct wl_surface *cursor_surface; int x; int y; }; struct swaynag_seat { struct wl_seat *wl_seat; uint32_t wl_name; struct swaynag *swaynag; struct swaynag_pointer pointer; struct wl_list link; }; struct swaynag_output { char *name; struct wl_output *wl_output; uint32_t wl_name; uint32_t scale; struct swaynag *swaynag; struct wl_list link; }; struct swaynag_button { char *text; enum swaynag_action_type type; char *action; int x; int y; int width; int height; bool terminal; bool dismiss; }; struct swaynag_details { bool visible; char *message; char *details_text; int x; int y; int width; int height; int offset; int visible_lines; int total_lines; struct swaynag_button *button_details; struct swaynag_button button_up; struct swaynag_button button_down; }; struct swaynag { bool run_display; struct wl_display *display; struct wl_compositor *compositor; struct wl_seat *seat; struct wl_shm *shm; struct wl_list outputs; // swaynag_output::link struct wl_list seats; // swaynag_seat::link struct swaynag_output *output; struct zwlr_layer_shell_v1 *layer_shell; struct zwlr_layer_surface_v1 *layer_surface; struct wp_cursor_shape_manager_v1 *cursor_shape_manager; struct wl_surface *surface; uint32_t width; uint32_t height; int32_t scale; struct pool_buffer buffers[2]; struct pool_buffer *current_buffer; struct swaynag_type *type; char *message; list_t *buttons; struct swaynag_details details; }; void swaynag_setup(struct swaynag *swaynag); void swaynag_run(struct swaynag *swaynag); void swaynag_destroy(struct swaynag *swaynag); #endif ================================================ FILE: include/swaynag/types.h ================================================ #ifndef _SWAYNAG_TYPES_H #define _SWAYNAG_TYPES_H #include #include #include "list.h" struct swaynag_type { char *name; PangoFontDescription *font_description; char *output; uint32_t anchors; int32_t layer; // enum zwlr_layer_shell_v1_layer or -1 if unset // Colors uint32_t button_text; uint32_t button_background; uint32_t details_background; uint32_t background; uint32_t text; uint32_t border; uint32_t border_bottom; // Sizing ssize_t bar_border_thickness; ssize_t message_padding; ssize_t details_border_thickness; ssize_t button_border_thickness; ssize_t button_gap; ssize_t button_gap_close; ssize_t button_margin_right; ssize_t button_padding; }; struct swaynag_type *swaynag_type_new(const char *name); void swaynag_types_add_default(list_t *types); struct swaynag_type *swaynag_type_get(list_t *types, char *name); struct swaynag_type *swaynag_type_clone(struct swaynag_type *type); void swaynag_type_merge(struct swaynag_type *dest, struct swaynag_type *src); void swaynag_type_free(struct swaynag_type *type); void swaynag_types_free(list_t *types); #endif ================================================ FILE: include/util.h ================================================ #ifndef _SWAY_UTIL_H #define _SWAY_UTIL_H #include #include #include enum movement_unit { MOVEMENT_UNIT_PX, MOVEMENT_UNIT_PPT, MOVEMENT_UNIT_DEFAULT, MOVEMENT_UNIT_INVALID, }; struct movement_amount { int amount; enum movement_unit unit; }; /* * Parse units such as "px" or "ppt" */ enum movement_unit parse_movement_unit(const char *unit); /* * Parse arguments such as "10", "10px" or "10 px". * Returns the number of arguments consumed. */ int parse_movement_amount(int argc, char **argv, struct movement_amount *amount); /** * Wrap i into the range [0, max] */ int wrap(int i, int max); /** * Given a string that represents an RGB(A) color, result will be set to a * uint32_t version of the color, as long as it is valid. If it is invalid, * then false will be returned and result will be untouched. */ bool parse_color(const char *color, uint32_t *result); void color_to_rgba(float dest[static 4], uint32_t color); /** * Given a string that represents a boolean, return the boolean value. This * function also takes in the current boolean value to support toggling. If * toggling is not desired, pass in true for current so that toggling values * get parsed as not true. */ bool parse_boolean(const char *boolean, bool current); /** * Given a string that represents a floating point value, return a float. * Returns NAN on error. */ float parse_float(const char *value); const char *sway_wl_output_subpixel_to_string(enum wl_output_subpixel subpixel); bool sway_set_cloexec(int fd, bool cloexec); uint32_t get_current_time_in_msec(void); #endif ================================================ FILE: meson.build ================================================ project( 'sway', 'c', version: '1.12-dev', license: 'MIT', meson_version: '>=1.3', default_options: [ 'c_std=c11', 'warning_level=2', 'werror=true', 'wrap_mode=nodownload', ], ) add_project_arguments( [ '-DWLR_USE_UNSTABLE', '-D_POSIX_C_SOURCE=200809L', '-Wno-unused-parameter', '-Wno-unused-result', '-Wno-missing-braces', '-Wno-format-zero-length', '-Wundef', '-Wvla', ], language: 'c', ) cc = meson.get_compiler('c') is_freebsd = host_machine.system().startswith('freebsd') datadir = get_option('datadir') sysconfdir = get_option('sysconfdir') prefix = get_option('prefix') if is_freebsd add_project_arguments('-D_C11_SOURCE', language: 'c') endif # Execute the wlroots subproject, if any wlroots_version = ['>=0.21.0', '<0.22.0'] subproject( 'wlroots', default_options: ['examples=false'], required: false, version: wlroots_version, ) wlroots = dependency('wlroots-0.21', version: wlroots_version, fallback: 'wlroots') wlroots_features = { 'xwayland': false, 'libinput_backend': false, 'session': false, } foreach name, _ : wlroots_features var_name = 'have_' + name.underscorify() have = wlroots.get_variable(pkgconfig: var_name, internal: var_name) == 'true' wlroots_features += { name: have } endforeach null_dep = dependency('', required: false) jsonc = dependency('json-c', version: '>=0.13') pcre2 = dependency('libpcre2-8') wayland_server = dependency('wayland-server', version: '>=1.21.0') wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols', version: '>=1.41', default_options: ['tests=false']) xkbcommon = dependency('xkbcommon', version: '>=1.5.0') cairo = dependency('cairo') pango = dependency('pango') pangocairo = dependency('pangocairo') gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf')) pixman = dependency('pixman-1') libevdev = dependency('libevdev') libinput = wlroots_features['libinput_backend'] ? dependency('libinput', version: '>=1.26.0') : null_dep xcb = wlroots_features['xwayland'] ? dependency('xcb') : null_dep drm = dependency('libdrm') libudev = wlroots_features['libinput_backend'] ? dependency('libudev') : null_dep math = cc.find_library('m') rt = cc.find_library('rt') xcb_icccm = wlroots_features['xwayland'] ? dependency('xcb-icccm') : null_dep threads = dependency('threads') # for pthread_setschedparam and pthread_atfork if get_option('sd-bus-provider') == 'auto' if not get_option('tray').disabled() assert(get_option('auto_features').auto(), 'sd-bus-provider must not be set to auto since auto_features != auto') endif sdbus = dependency(['libsystemd', 'libelogind'], required: false, version: '>=239', ) if not sdbus.found() sdbus = dependency('basu', required: false) endif else sdbus = dependency(get_option('sd-bus-provider'), required: get_option('tray')) endif tray_deps_found = sdbus.found() if get_option('tray').enabled() and not tray_deps_found error('Building with -Dtray=enabled, but sd-bus has not been not found') endif have_tray = (not get_option('tray').disabled()) and tray_deps_found conf_data = configuration_data() conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found()) conf_data.set10('HAVE_LIBSYSTEMD', sdbus.found() and sdbus.name() == 'libsystemd') conf_data.set10('HAVE_LIBELOGIND', sdbus.found() and sdbus.name() == 'libelogind') conf_data.set10('HAVE_BASU', sdbus.found() and sdbus.name() == 'basu') conf_data.set10('HAVE_TRAY', have_tray) foreach sym : ['LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM', 'LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY', 'LIBINPUT_SWITCH_KEYPAD_SLIDE'] conf_data.set10('HAVE_' + sym, cc.has_header_symbol('libinput.h', sym, dependencies: libinput)) endforeach scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages')) if scdoc.found() scdoc_prog = find_program(scdoc.get_variable(pkgconfig: 'scdoc'), native: true) mandir = get_option('mandir') man_files = [ 'sway/sway.1.scd', 'sway/sway.5.scd', 'sway/sway-bar.5.scd', 'sway/sway-input.5.scd', 'sway/sway-ipc.7.scd', 'sway/sway-output.5.scd', 'swaybar/swaybar-protocol.7.scd', 'swaymsg/swaymsg.1.scd', ] if get_option('swaynag') man_files += [ 'swaynag/swaynag.1.scd', 'swaynag/swaynag.5.scd', ] endif foreach filename : man_files topic = filename.split('.')[-3].split('/')[-1] section = filename.split('.')[-2] output = '@0@.@1@'.format(topic, section) custom_target( output, input: filename, output: output, command: scdoc_prog, install: true, feed: true, capture: true, install_dir: '@0@/man@1@'.format(mandir, section) ) endforeach endif add_project_arguments('-DSYSCONFDIR="/@0@"'.format(join_paths(prefix, sysconfdir)), language : 'c') version = '"@0@"'.format(meson.project_version()) git = find_program('git', native: true, required: false) if git.found() git_commit = run_command([git, '--git-dir=.git', 'rev-parse', '--short', 'HEAD'], check: false) git_branch = run_command([git, '--git-dir=.git', 'rev-parse', '--abbrev-ref', 'HEAD'], check: false) if git_commit.returncode() == 0 and git_branch.returncode() == 0 version = '"@0@-@1@ (" __DATE__ ", branch \'@2@\')"'.format( meson.project_version(), git_commit.stdout().strip(), git_branch.stdout().strip(), ) endif endif add_project_arguments('-DSWAY_VERSION=@0@'.format(version), language: 'c') fs = import('fs') # Strip relative path prefixes from the code if possible, otherwise hide them. relative_dir = fs.relative_to(meson.current_source_dir(), meson.global_build_root()) + '/' if cc.has_argument('-fmacro-prefix-map=/prefix/to/hide=') add_project_arguments( '-fmacro-prefix-map=@0@='.format(relative_dir), language: 'c', ) else add_project_arguments( '-DSWAY_REL_SRC_DIR="@0@"'.format(relative_dir), language: 'c', ) endif sway_inc = include_directories('include') subdir('include') subdir('protocols') subdir('common') subdir('sway') subdir('swaymsg') if get_option('swaybar') or get_option('swaynag') subdir('client') endif if get_option('swaybar') subdir('swaybar') endif if get_option('swaynag') subdir('swaynag') endif config = configuration_data() config.set('datadir', join_paths(prefix, datadir)) config.set('prefix', prefix) config.set('sysconfdir', join_paths(prefix, sysconfdir)) configure_file( configuration: config, input: 'config.in', output: '@BASENAME@', install_dir: join_paths(sysconfdir, 'sway') ) install_data( 'sway.desktop', install_dir: join_paths(datadir, 'wayland-sessions') ) if get_option('default-wallpaper') wallpaper_files = files( 'assets/Sway_Wallpaper_Blue_768x1024.png', 'assets/Sway_Wallpaper_Blue_768x1024_Portrait.png', 'assets/Sway_Wallpaper_Blue_1136x640.png', 'assets/Sway_Wallpaper_Blue_1136x640_Portrait.png', 'assets/Sway_Wallpaper_Blue_1366x768.png', 'assets/Sway_Wallpaper_Blue_1920x1080.png', 'assets/Sway_Wallpaper_Blue_2048x1536.png', 'assets/Sway_Wallpaper_Blue_2048x1536_Portrait.png', ) wallpaper_install_dir = join_paths(datadir, 'backgrounds', 'sway') install_data(wallpaper_files, install_dir: wallpaper_install_dir) endif subdir('completions') summary({ 'gdk-pixbuf': gdk_pixbuf.found(), 'tray': have_tray, 'man-pages': scdoc.found(), }, bool_yn: true) ================================================ FILE: meson_options.txt ================================================ option('default-wallpaper', type: 'boolean', value: true, description: 'Install the default wallpaper.') option('zsh-completions', type: 'boolean', value: true, description: 'Install zsh shell completions.') option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions.') option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.') option('swaybar', type: 'boolean', value: true, description: 'Enable support for swaybar') option('swaynag', type: 'boolean', value: true, description: 'Enable support for swaynag') option('tray', type: 'feature', value: 'auto', description: 'Enable support for swaybar tray') option('gdk-pixbuf', type: 'feature', value: 'auto', description: 'Enable support for more image formats in swaybar tray') option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('sd-bus-provider', type: 'combo', choices: ['auto', 'libsystemd', 'libelogind', 'basu'], value: 'auto', description: 'Provider of the sd-bus library') ================================================ FILE: protocols/meson.build ================================================ wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') wayland_scanner_dep = dependency('wayland-scanner', native: true) wayland_scanner = find_program( wayland_scanner_dep.get_variable('wayland_scanner'), native: true, ) protocols = [ wl_protocol_dir / 'stable/tablet/tablet-v2.xml', wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', wl_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml', 'wlr-output-power-management-unstable-v1.xml', ] wl_protos_src = [] foreach xml : protocols wl_protos_src += custom_target( xml.underscorify() + '_c', input: xml, output: '@BASENAME@-protocol.c', command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], ) wl_protos_src += custom_target( xml.underscorify() + '_server_h', input: xml, output: '@BASENAME@-protocol.h', command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'], ) wl_protos_src += custom_target( xml.underscorify() + '_client_h', input: xml, output: '@BASENAME@-client-protocol.h', command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'], ) endforeach ================================================ FILE: protocols/wlr-layer-shell-unstable-v1.xml ================================================ Copyright © 2017 Drew DeVault Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and rendered with a defined z-depth respective to each other. They may also be anchored to the edges and corners of a screen and specify input handling semantics. This interface should be suitable for the implementation of many desktop shell components, and a broad number of other applications that interact with the desktop. Create a layer surface for an existing surface. This assigns the role of layer_surface, or raises a protocol error if another role is already assigned. Creating a layer surface from a wl_surface which has a buffer attached or committed is a client error, and any attempts by a client to attach or manipulate a buffer prior to the first layer_surface.configure call must also be treated as errors. After creating a layer_surface object and setting it up, the client must perform an initial commit without any buffer attached. The compositor will reply with a layer_surface.configure event. The client must acknowledge it and is then allowed to attach a buffer to map the surface. You may pass NULL for output to allow the compositor to decide which output to use. Generally this will be the one that the user most recently interacted with. Clients can specify a namespace that defines the purpose of the layer surface. These values indicate which layers a surface can be rendered in. They are ordered by z depth, bottom-most first. Traditional shell surfaces will typically be rendered between the bottom and top layers. Fullscreen shell surfaces are typically rendered at the top layer. Multiple surfaces can share a single layer, and ordering within a single layer is undefined. This request indicates that the client will not use the layer_shell object any more. Objects that have been created through this instance are not affected. An interface that may be implemented by a wl_surface, for surfaces that are designed to be rendered as a layer of a stacked desktop-like environment. Layer surface state (layer, size, anchor, exclusive zone, margin, interactivity) is double-buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. Attaching a null buffer to a layer surface unmaps it. Unmapping a layer_surface means that the surface cannot be shown by the compositor until it is explicitly mapped again. The layer_surface returns to the state it had right after layer_shell.get_layer_surface. The client can re-map the surface by performing a commit without any buffer attached, waiting for a configure event and handling it as usual. Sets the size of the surface in surface-local coordinates. The compositor will display the surface centered with respect to its anchors. If you pass 0 for either value, the compositor will assign it and inform you of the assignment in the configure event. You must set your anchor to opposite edges in the dimensions you omit; not doing so is a protocol error. Both values are 0 by default. Size is double-buffered, see wl_surface.commit. Requests that the compositor anchor the surface to the specified edges and corners. If two orthogonal edges are specified (e.g. 'top' and 'left'), then the anchor point will be the intersection of the edges (e.g. the top left corner of the output); otherwise the anchor point will be centered on that edge, or in the center if none is specified. Anchor is double-buffered, see wl_surface.commit. Requests that the compositor avoids occluding an area with other surfaces. The compositor's use of this information is implementation-dependent - do not assume that this region will not actually be occluded. A positive value is only meaningful if the surface is anchored to one edge or an edge and both perpendicular edges. If the surface is not anchored, anchored to only two perpendicular edges (a corner), anchored to only two parallel edges or anchored to all edges, a positive value will be treated the same as zero. A positive zone is the distance from the edge in surface-local coordinates to consider exclusive. Surfaces that do not wish to have an exclusive zone may instead specify how they should interact with surfaces that do. If set to zero, the surface indicates that it would like to be moved to avoid occluding surfaces with a positive exclusive zone. If set to -1, the surface indicates that it would not like to be moved to accommodate for other surfaces, and the compositor should extend it all the way to the edges it is anchored to. For example, a panel might set its exclusive zone to 10, so that maximized shell surfaces are not shown on top of it. A notification might set its exclusive zone to 0, so that it is moved to avoid occluding the panel, but shell surfaces are shown underneath it. A wallpaper or lock screen might set their exclusive zone to -1, so that they stretch below or over the panel. The default value is 0. Exclusive zone is double-buffered, see wl_surface.commit. Requests that the surface be placed some distance away from the anchor point on the output, in surface-local coordinates. Setting this value for edges you are not anchored to has no effect. The exclusive zone includes the margin. Margin is double-buffered, see wl_surface.commit. Types of keyboard interaction possible for layer shell surfaces. The rationale for this is twofold: (1) some applications are not interested in keyboard events and not allowing them to be focused can improve the desktop experience; (2) some applications will want to take exclusive keyboard focus. This value indicates that this surface is not interested in keyboard events and the compositor should never assign it the keyboard focus. This is the default value, set for newly created layer shell surfaces. This is useful for e.g. desktop widgets that display information or only have interaction with non-keyboard input devices. Request exclusive keyboard focus if this surface is above the shell surface layer. For the top and overlay layers, the seat will always give exclusive keyboard focus to the top-most layer which has keyboard interactivity set to exclusive. If this layer contains multiple surfaces with keyboard interactivity set to exclusive, the compositor determines the one receiving keyboard events in an implementation- defined manner. In this case, no guarantee is made when this surface will receive keyboard focus (if ever). For the bottom and background layers, the compositor is allowed to use normal focus semantics. This setting is mainly intended for applications that need to ensure they receive all keyboard events, such as a lock screen or a password prompt. This requests the compositor to allow this surface to be focused and unfocused by the user in an implementation-defined manner. The user should be able to unfocus this surface even regardless of the layer it is on. Typically, the compositor will want to use its normal mechanism to manage keyboard focus between layer shell surfaces with this setting and regular toplevels on the desktop layer (e.g. click to focus). Nevertheless, it is possible for a compositor to require a special interaction to focus or unfocus layer shell surfaces (e.g. requiring a click even if focus follows the mouse normally, or providing a keybinding to switch focus between layers). This setting is mainly intended for desktop shell components (e.g. panels) that allow keyboard interaction. Using this option can allow implementing a desktop shell that can be fully usable without the mouse. Set how keyboard events are delivered to this surface. By default, layer shell surfaces do not receive keyboard events; this request can be used to change this. This setting is inherited by child surfaces set by the get_popup request. Layer surfaces receive pointer, touch, and tablet events normally. If you do not want to receive them, set the input region on your surface to an empty region. Keyboard interactivity is double-buffered, see wl_surface.commit. This assigns an xdg_popup's parent to this layer_surface. This popup should have been created via xdg_surface::get_popup with the parent set to NULL, and this request must be invoked before committing the popup's initial state. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. When a configure event is received, if a client commits the surface in response to the configure event, then the client must make an ack_configure request sometime before the commit request, passing along the serial of the configure event. If the client receives multiple configure events before it can respond to one, it only has to ack the last configure event. A client is not required to commit immediately after sending an ack_configure request - it may even ack_configure several times before its next surface commit. A client may send multiple ack_configure requests before committing, but only the last request sent before a commit indicates which configure event the client really is responding to. This request destroys the layer surface. The configure event asks the client to resize its surface. Clients should arrange their surface for the new states, and then send an ack_configure request with the serial sent in this configure event at some point before committing the new surface. The client is free to dismiss all but the last configure event it received. The width and height arguments specify the size of the window in surface-local coordinates. The size is a hint, in the sense that the client is free to ignore it if it doesn't resize, pick a smaller size (to satisfy aspect ratio or resize in steps of NxM pixels). If the client picks a smaller size and is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the surface will be centered on this axis. If the width or height arguments are zero, it means the client should decide its own window dimension. The closed event is sent by the compositor when the surface will no longer be shown. The output may have been destroyed or the user may have asked for it to be removed. Further changes to the surface will be ignored. The client should destroy the resource after receiving this event, and create a new surface if they so choose. Change the layer that the surface is rendered on. Layer is double-buffered, see wl_surface.commit. Requests an edge for the exclusive zone to apply. The exclusive edge will be automatically deduced from anchor points when possible, but when the surface is anchored to a corner, it will be necessary to set it explicitly to disambiguate, as it is not possible to deduce which one of the two corner edges should be used. The edge must be one the surface is anchored to, otherwise the invalid_exclusive_edge protocol error will be raised. ================================================ FILE: protocols/wlr-output-power-management-unstable-v1.xml ================================================ Copyright © 2019 Purism SPC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol allows clients to control power management modes of outputs that are currently part of the compositor space. The intent is to allow special clients like desktop shells to power down outputs when the system is idle. To modify outputs not currently part of the compositor space see wlr-output-management. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This interface is a manager that allows creating per-output power management mode controls. Create an output power management mode control that can be used to adjust the power management mode for a given output. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object offers requests to set the power management mode of an output. Set an output's power save mode to the given mode. The mode change is effective immediately. If the output does not support the given mode a failed event is sent. Report the power management mode change of an output. The mode event is sent after an output changed its power management mode. The reason can be a client using set_mode or the compositor deciding to change an output's mode. This event is also sent immediately when the object is created so the client is informed about the current power management mode. This event indicates that the output power management mode control is no longer valid. This can happen for a number of reasons, including: - The output doesn't support power management - Another client already has exclusive power management mode control for this output - The output disappeared Upon receiving this event, the client should destroy this object. Destroys the output power management mode control object. ================================================ FILE: release.sh ================================================ #!/bin/sh -eu prev=$(git describe --tags --abbrev=0) next=$(meson rewrite kwargs info project / | jq -r '.kwargs["project#/"].version') case "$next" in *-dev) echo "This is a development version" exit 1 ;; esac if [ "$prev" = "$next" ]; then echo "Version not bumped in meson.build" exit 1 fi if ! git diff-index --quiet HEAD -- meson.build; then echo "meson.build not committed" exit 1 fi shortlog="$(git shortlog --no-merges "$prev..")" (echo "sway $next"; echo ""; echo "$shortlog") | git tag "$next" -ase -F - prefix=sway-$next archive=$prefix.tar.gz git archive --prefix="$prefix/" -o "$archive" "$next" gpg --output "$archive".sig --detach-sig "$archive" git push --follow-tags gh release create "sway $next" -t "$next" -n "" -d "$archive" "$archive.sig" ================================================ FILE: sway/commands/allow_tearing.c ================================================ #include #include "sway/config.h" #include "sway/tree/view.h" #include "util.h" struct cmd_results *cmd_allow_tearing(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "allow_tearing", EXPECTED_AT_LEAST, 1))) { return error; } struct sway_container *container = config->handler_context.container; if (!container || !container->view) { return cmd_results_new(CMD_INVALID, "Tearing can only be allowed on views"); } bool wants_tearing = parse_boolean(argv[0], true); struct sway_view *view = container->view; view->tearing_mode = wants_tearing ? TEARING_OVERRIDE_TRUE : TEARING_OVERRIDE_FALSE; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/assign.c ================================================ #include #include #include "sway/commands.h" #include "sway/criteria.h" #include "list.h" #include "log.h" #include "stringop.h" struct cmd_results *cmd_assign(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "assign", EXPECTED_AT_LEAST, 2))) { return error; } // Create criteria char *err_str = NULL; struct criteria *criteria = criteria_parse(argv[0], &err_str); if (!criteria) { error = cmd_results_new(CMD_INVALID, "%s", err_str); free(err_str); return error; } --argc; ++argv; if (has_prefix(*argv, "→")) { if (argc < 2) { free(criteria); return cmd_results_new(CMD_INVALID, "Missing workspace"); } --argc; ++argv; } if (strcmp(*argv, "output") == 0) { criteria->type = CT_ASSIGN_OUTPUT; --argc; ++argv; } else { if (strcmp(*argv, "workspace") == 0) { --argc; ++argv; } if (strcmp(*argv, "number") == 0) { --argc; ++argv; if (argv[0][0] < '0' || argv[0][0] > '9') { free(criteria); return cmd_results_new(CMD_INVALID, "Invalid workspace number '%s'", argv[0]); } criteria->type = CT_ASSIGN_WORKSPACE_NUMBER; } else { criteria->type = CT_ASSIGN_WORKSPACE; } } criteria->target = join_args(argv, argc); list_add(config->criteria, criteria); sway_log(SWAY_DEBUG, "assign: '%s' -> '%s' added", criteria->raw, criteria->target); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/bind.c ================================================ #include #include #include #include #include "sway/commands.h" #include "sway/config.h" #include "sway/input/cursor.h" #include "list.h" #include "log.h" #include "stringop.h" static struct cmd_results *binding_add(struct bar_binding *binding, list_t *mode_bindings) { const char *name = get_mouse_button_name(binding->button); bool overwritten = false; for (int i = 0; i < mode_bindings->length; i++) { struct bar_binding *other = mode_bindings->items[i]; if (other->button == binding->button && other->release == binding->release) { overwritten = true; mode_bindings->items[i] = binding; free_bar_binding(other); sway_log(SWAY_DEBUG, "[bar %s] Updated binding for %u (%s)%s", config->current_bar->id, binding->button, name, binding->release ? " - release" : ""); break; } } if (!overwritten) { list_add(mode_bindings, binding); sway_log(SWAY_DEBUG, "[bar %s] Added binding for %u (%s)%s", config->current_bar->id, binding->button, name, binding->release ? " - release" : ""); } return cmd_results_new(CMD_SUCCESS, NULL); } static struct cmd_results *binding_remove(struct bar_binding *binding, list_t *mode_bindings) { const char *name = get_mouse_button_name(binding->button); for (int i = 0; i < mode_bindings->length; i++) { struct bar_binding *other = mode_bindings->items[i]; if (other->button == binding->button && other->release == binding->release) { sway_log(SWAY_DEBUG, "[bar %s] Unbound binding for %u (%s)%s", config->current_bar->id, binding->button, name, binding->release ? " - release" : ""); free_bar_binding(other); free_bar_binding(binding); list_del(mode_bindings, i); return cmd_results_new(CMD_SUCCESS, NULL); } } struct cmd_results *error = cmd_results_new(CMD_FAILURE, "Could not " "find binding for [bar %s]" " Button %u (%s)%s", config->current_bar->id, binding->button, name, binding->release ? " - release" : ""); free_bar_binding(binding); return error; } static struct cmd_results *bar_cmd_bind(int argc, char **argv, bool code, bool unbind) { int minargs = 2; const char *command; if (unbind) { minargs--; command = code ? "bar unbindcode" : "bar unbindsym"; } else { command = code ? "bar bindcode" : "bar bindsym"; } struct cmd_results *error = NULL; if ((error = checkarg(argc, command, EXPECTED_AT_LEAST, minargs))) { return error; } struct bar_binding *binding = calloc(1, sizeof(struct bar_binding)); if (!binding) { return cmd_results_new(CMD_FAILURE, "Unable to allocate bar binding"); } binding->release = false; if (strcmp("--release", argv[0]) == 0) { binding->release = true; argv++; argc--; } char *message = NULL; if (code) { binding->button = get_mouse_bindcode(argv[0], &message); } else { binding->button = get_mouse_bindsym(argv[0], &message); } if (message) { free_bar_binding(binding); error = cmd_results_new(CMD_INVALID, "%s", message); free(message); return error; } else if (!binding->button) { free_bar_binding(binding); return cmd_results_new(CMD_INVALID, "Unknown button %s", argv[0]); } list_t *bindings = config->current_bar->bindings; if (unbind) { return binding_remove(binding, bindings); } binding->command = join_args(argv + 1, argc - 1); return binding_add(binding, bindings); } struct cmd_results *bar_cmd_bindcode(int argc, char **argv) { return bar_cmd_bind(argc, argv, true, false); } struct cmd_results *bar_cmd_bindsym(int argc, char **argv) { return bar_cmd_bind(argc, argv, false, false); } struct cmd_results *bar_cmd_unbindcode(int argc, char **argv) { return bar_cmd_bind(argc, argv, true, true); } struct cmd_results *bar_cmd_unbindsym(int argc, char **argv) { return bar_cmd_bind(argc, argv, false, true); } ================================================ FILE: sway/commands/bar/binding_mode_indicator.c ================================================ #include #include #include "sway/commands.h" #include "log.h" #include "util.h" struct cmd_results *bar_cmd_binding_mode_indicator(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "binding_mode_indicator", EXPECTED_EQUAL_TO, 1))) { return error; } config->current_bar->binding_mode_indicator = parse_boolean(argv[0], config->current_bar->binding_mode_indicator); if (config->current_bar->binding_mode_indicator) { sway_log(SWAY_DEBUG, "Enabling binding mode indicator on bar: %s", config->current_bar->id); } else { sway_log(SWAY_DEBUG, "Disabling binding mode indicator on bar: %s", config->current_bar->id); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/colors.c ================================================ #include #include "sway/commands.h" #include "log.h" #include "util.h" // Must be in alphabetical order for bsearch static const struct cmd_handler bar_colors_handlers[] = { { "active_workspace", bar_colors_cmd_active_workspace }, { "background", bar_colors_cmd_background }, { "binding_mode", bar_colors_cmd_binding_mode }, { "focused_background", bar_colors_cmd_focused_background }, { "focused_separator", bar_colors_cmd_focused_separator }, { "focused_statusline", bar_colors_cmd_focused_statusline }, { "focused_workspace", bar_colors_cmd_focused_workspace }, { "inactive_workspace", bar_colors_cmd_inactive_workspace }, { "separator", bar_colors_cmd_separator }, { "statusline", bar_colors_cmd_statusline }, { "urgent_workspace", bar_colors_cmd_urgent_workspace }, }; static char *hex_to_rgba_hex(const char *hex) { uint32_t color; if (!parse_color(hex, &color)) { return NULL; } char *rgba = malloc(10); if (!rgba) { return NULL; } snprintf(rgba, 10, "#%08x", color); return rgba; } static struct cmd_results *parse_single_color(char **color, const char *cmd_name, int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) { return error; } char *rgba = hex_to_rgba_hex(argv[0]); if (!rgba) { return cmd_results_new(CMD_INVALID, "Invalid color: %s", argv[0]); } free(*color); *color = rgba; return cmd_results_new(CMD_SUCCESS, NULL); } static struct cmd_results *parse_three_colors(char ***colors, const char *cmd_name, int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 3))) { return error; } char *rgba[3] = {0}; for (int i = 0; i < 3; i++) { rgba[i] = hex_to_rgba_hex(argv[i]); if (!rgba[i]) { return cmd_results_new(CMD_INVALID, "Invalid color: %s", argv[i]); } } for (int i = 0; i < 3; i++) { free(*colors[i]); *colors[i] = rgba[i]; } return cmd_results_new(CMD_SUCCESS, NULL); } struct cmd_results *bar_cmd_colors(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "colors", EXPECTED_AT_LEAST, 1))) { return error; } return config_subcommand(argv, argc, bar_colors_handlers, sizeof(bar_colors_handlers)); } struct cmd_results *bar_colors_cmd_active_workspace(int argc, char **argv) { char **colors[3] = { &(config->current_bar->colors.active_workspace_border), &(config->current_bar->colors.active_workspace_bg), &(config->current_bar->colors.active_workspace_text) }; return parse_three_colors(colors, "active_workspace", argc, argv); } struct cmd_results *bar_colors_cmd_background(int argc, char **argv) { return parse_single_color(&(config->current_bar->colors.background), "background", argc, argv); } struct cmd_results *bar_colors_cmd_focused_background(int argc, char **argv) { return parse_single_color(&(config->current_bar->colors.focused_background), "focused_background", argc, argv); } struct cmd_results *bar_colors_cmd_binding_mode(int argc, char **argv) { char **colors[3] = { &(config->current_bar->colors.binding_mode_border), &(config->current_bar->colors.binding_mode_bg), &(config->current_bar->colors.binding_mode_text) }; return parse_three_colors(colors, "binding_mode", argc, argv); } struct cmd_results *bar_colors_cmd_focused_workspace(int argc, char **argv) { char **colors[3] = { &(config->current_bar->colors.focused_workspace_border), &(config->current_bar->colors.focused_workspace_bg), &(config->current_bar->colors.focused_workspace_text) }; return parse_three_colors(colors, "focused_workspace", argc, argv); } struct cmd_results *bar_colors_cmd_inactive_workspace(int argc, char **argv) { char **colors[3] = { &(config->current_bar->colors.inactive_workspace_border), &(config->current_bar->colors.inactive_workspace_bg), &(config->current_bar->colors.inactive_workspace_text) }; return parse_three_colors(colors, "inactive_workspace", argc, argv); } struct cmd_results *bar_colors_cmd_separator(int argc, char **argv) { return parse_single_color(&(config->current_bar->colors.separator), "separator", argc, argv); } struct cmd_results *bar_colors_cmd_focused_separator(int argc, char **argv) { return parse_single_color(&(config->current_bar->colors.focused_separator), "focused_separator", argc, argv); } struct cmd_results *bar_colors_cmd_statusline(int argc, char **argv) { return parse_single_color(&(config->current_bar->colors.statusline), "statusline", argc, argv); } struct cmd_results *bar_colors_cmd_focused_statusline(int argc, char **argv) { return parse_single_color(&(config->current_bar->colors.focused_statusline), "focused_statusline", argc, argv); } struct cmd_results *bar_colors_cmd_urgent_workspace(int argc, char **argv) { char **colors[3] = { &(config->current_bar->colors.urgent_workspace_border), &(config->current_bar->colors.urgent_workspace_bg), &(config->current_bar->colors.urgent_workspace_text) }; return parse_three_colors(colors, "urgent_workspace", argc, argv); } ================================================ FILE: sway/commands/bar/font.c ================================================ #include #include "sway/commands.h" #include "log.h" #include "stringop.h" struct cmd_results *bar_cmd_font(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "font", EXPECTED_AT_LEAST, 1))) { return error; } char *font = join_args(argv, argc); free(config->current_bar->font); if (has_prefix(font, "pango:")) { if (config->current_bar->pango_markup == PANGO_MARKUP_DEFAULT) { config->current_bar->pango_markup = true; } config->current_bar->font = strdup(font + 6); } else { if (config->current_bar->pango_markup == PANGO_MARKUP_DEFAULT) { config->current_bar->pango_markup = false; } config->current_bar->font = strdup(font); } free(font); sway_log(SWAY_DEBUG, "Settings font '%s' for bar: %s", config->current_bar->font, config->current_bar->id); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/gaps.c ================================================ #include #include #include #include "sway/commands.h" #include "sway/ipc-server.h" #include "log.h" struct cmd_results *bar_cmd_gaps(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "gaps", EXPECTED_AT_LEAST, 1))) { return error; } if ((error = checkarg(argc, "gaps", EXPECTED_AT_MOST, 4))) { return error; } int top = 0, right = 0, bottom = 0, left = 0; for (int i = 0; i < argc; i++) { char *end; int amount = strtol(argv[i], &end, 10); if (strlen(end) && strcasecmp(end, "px") != 0) { return cmd_results_new(CMD_INVALID, "Expected 'bar [] gaps | " " | '"); } if (i == 0) { top = amount; } if (i == 0 || i == 1) { right = amount; } if (i == 0 || i == 2) { bottom = amount; } if (i == 0 || i == 1 || i == 3) { left = amount; } } config->current_bar->gaps.top = top; config->current_bar->gaps.right = right; config->current_bar->gaps.bottom = bottom; config->current_bar->gaps.left = left; sway_log(SWAY_DEBUG, "Setting bar gaps to %d %d %d %d on bar: %s", config->current_bar->gaps.top, config->current_bar->gaps.right, config->current_bar->gaps.bottom, config->current_bar->gaps.left, config->current_bar->id); if (!config->reading) { ipc_event_barconfig_update(config->current_bar); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/height.c ================================================ #include #include #include "sway/commands.h" #include "log.h" struct cmd_results *bar_cmd_height(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "height", EXPECTED_EQUAL_TO, 1))) { return error; } int height = atoi(argv[0]); if (height < 0) { return cmd_results_new(CMD_INVALID, "Invalid height value: %s", argv[0]); } config->current_bar->height = height; sway_log(SWAY_DEBUG, "Setting bar height to %d on bar: %s", height, config->current_bar->id); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/hidden_state.c ================================================ #include #include #include "sway/commands.h" #include "sway/config.h" #include "sway/ipc-server.h" #include "log.h" static struct cmd_results *bar_set_hidden_state(struct bar_config *bar, const char *hidden_state) { char *old_state = bar->hidden_state; if (strcasecmp("toggle", hidden_state) == 0 && !config->reading) { if (strcasecmp("hide", bar->hidden_state) == 0) { bar->hidden_state = strdup("show"); } else if (strcasecmp("show", bar->hidden_state) == 0) { bar->hidden_state = strdup("hide"); } } else if (strcasecmp("hide", hidden_state) == 0) { bar->hidden_state = strdup("hide"); } else if (strcasecmp("show", hidden_state) == 0) { bar->hidden_state = strdup("show"); } else { return cmd_results_new(CMD_INVALID, "Invalid value %s", hidden_state); } if (strcmp(old_state, bar->hidden_state) != 0) { if (!config->current_bar) { ipc_event_barconfig_update(bar); } sway_log(SWAY_DEBUG, "Setting hidden_state: '%s' for bar: %s", bar->hidden_state, bar->id); } // free old mode free(old_state); return NULL; } struct cmd_results *bar_cmd_hidden_state(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "hidden_state", EXPECTED_AT_LEAST, 1))) { return error; } if ((error = checkarg(argc, "hidden_state", EXPECTED_AT_MOST, 2))) { return error; } if (config->reading && argc > 1) { return cmd_results_new(CMD_INVALID, "Unexpected value %s in config mode", argv[1]); } if (config->current_bar && argc == 2 && strcmp(config->current_bar->id, argv[1]) != 0) { return cmd_results_new(CMD_INVALID, "Conflicting bar ids: %s and %s", config->current_bar->id, argv[1]); } const char *state = argv[0]; if (config->current_bar) { error = bar_set_hidden_state(config->current_bar, state); } else { const char *id = argc == 2 ? argv[1] : NULL; for (int i = 0; i < config->bars->length; ++i) { struct bar_config *bar = config->bars->items[i]; if (id) { if (strcmp(id, bar->id) == 0) { error = bar_set_hidden_state(bar, state); break; } } else if ((error = bar_set_hidden_state(bar, state))) { break; } } } return error ? error : cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/icon_theme.c ================================================ #include #include "config.h" #include "sway/commands.h" #include "sway/config.h" #include "log.h" struct cmd_results *bar_cmd_icon_theme(int argc, char **argv) { #if HAVE_TRAY struct cmd_results *error = NULL; if ((error = checkarg(argc, "icon_theme", EXPECTED_EQUAL_TO, 1))) { return error; } sway_log(SWAY_DEBUG, "[Bar %s] Setting icon theme to %s", config->current_bar->id, argv[0]); free(config->current_bar->icon_theme); config->current_bar->icon_theme = strdup(argv[0]); return cmd_results_new(CMD_SUCCESS, NULL); #else return cmd_results_new(CMD_INVALID, "Sway has been compiled without tray support"); #endif } ================================================ FILE: sway/commands/bar/id.c ================================================ #include #include "sway/commands.h" #include "log.h" struct cmd_results *bar_cmd_id(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "id", EXPECTED_EQUAL_TO, 1))) { return error; } const char *name = argv[0]; const char *oldname = config->current_bar->id; if (strcmp(name, oldname) == 0) { return cmd_results_new(CMD_SUCCESS, NULL); // NOP } else if (strcmp(name, "id") == 0) { return cmd_results_new(CMD_INVALID, "id cannot be 'id'"); } // check if id is used by a previously defined bar for (int i = 0; i < config->bars->length; ++i) { struct bar_config *find = config->bars->items[i]; if (strcmp(name, find->id) == 0 && config->current_bar != find) { return cmd_results_new(CMD_FAILURE, "Id '%s' already defined for another bar. Id unchanged (%s).", name, oldname); } } sway_log(SWAY_DEBUG, "Renaming bar: '%s' to '%s'", oldname, name); // free old bar id free(config->current_bar->id); config->current_bar->id = strdup(name); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/mode.c ================================================ #include #include #include "sway/commands.h" #include "sway/config.h" #include "sway/ipc-server.h" #include "log.h" static struct cmd_results *bar_set_mode(struct bar_config *bar, const char *mode) { char *old_mode = bar->mode; if (strcasecmp("toggle", mode) == 0 && !config->reading) { if (strcasecmp("dock", bar->mode) == 0) { bar->mode = strdup("hide"); } else{ bar->mode = strdup("dock"); } } else if (strcasecmp("dock", mode) == 0) { bar->mode = strdup("dock"); } else if (strcasecmp("hide", mode) == 0) { bar->mode = strdup("hide"); } else if (strcasecmp("invisible", mode) == 0) { bar->mode = strdup("invisible"); } else if (strcasecmp("overlay", mode) == 0) { bar->mode = strdup("overlay"); } else { return cmd_results_new(CMD_INVALID, "Invalid value %s", mode); } if (strcmp(old_mode, bar->mode) != 0) { if (!config->current_bar) { ipc_event_barconfig_update(bar); } sway_log(SWAY_DEBUG, "Setting mode: '%s' for bar: %s", bar->mode, bar->id); } // free old mode free(old_mode); return NULL; } struct cmd_results *bar_cmd_mode(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "mode", EXPECTED_AT_LEAST, 1))) { return error; } if ((error = checkarg(argc, "mode", EXPECTED_AT_MOST, 2))) { return error; } if (config->reading && argc > 1) { return cmd_results_new(CMD_INVALID, "Unexpected value %s in config mode", argv[1]); } if (config->current_bar && argc == 2 && strcmp(config->current_bar->id, argv[1]) != 0) { return cmd_results_new(CMD_INVALID, "Conflicting bar ids: %s and %s", config->current_bar->id, argv[1]); } const char *mode = argv[0]; if (config->current_bar) { error = bar_set_mode(config->current_bar, mode); } else { const char *id = argc == 2 ? argv[1] : NULL; for (int i = 0; i < config->bars->length; ++i) { struct bar_config *bar = config->bars->items[i]; if (id) { if (strcmp(id, bar->id) == 0) { error = bar_set_mode(bar, mode); break; } } else if ((error = bar_set_mode(bar, mode))) { break; } } } return error ? error : cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/modifier.c ================================================ #include #include "sway/commands.h" #include "sway/input/keyboard.h" #include "log.h" #include "stringop.h" struct cmd_results *bar_cmd_modifier(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "modifier", EXPECTED_EQUAL_TO, 1))) { return error; } uint32_t mod = 0; if (strcmp(argv[0], "none") != 0) { list_t *split = split_string(argv[0], "+"); for (int i = 0; i < split->length; ++i) { uint32_t tmp_mod; if ((tmp_mod = get_modifier_mask_by_name(split->items[i])) > 0) { mod |= tmp_mod; } else if (strcmp(split->items[i], "none") == 0) { error = cmd_results_new(CMD_INVALID, "none cannot be used along with other modifiers"); list_free_items_and_destroy(split); return error; } else { error = cmd_results_new(CMD_INVALID, "Unknown modifier '%s'", (char *)split->items[i]); list_free_items_and_destroy(split); return error; } } list_free_items_and_destroy(split); } config->current_bar->modifier = mod; sway_log(SWAY_DEBUG, "Show/Hide the bar when pressing '%s' in hide mode.", argv[0]); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/output.c ================================================ #include #include #include "sway/commands.h" #include "list.h" #include "log.h" struct cmd_results *bar_cmd_output(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "output", EXPECTED_EQUAL_TO, 1))) { return error; } const char *output = argv[0]; list_t *outputs = config->current_bar->outputs; if (!outputs) { outputs = create_list(); config->current_bar->outputs = outputs; } bool add_output = true; if (strcmp("*", output) == 0) { // remove all previous defined outputs and replace with '*' while (outputs->length) { free(outputs->items[0]); list_del(outputs, 0); } } else { // only add output if not already defined, if the list has '*', remove // it, in favor of a manual list for (int i = 0; i < outputs->length; ++i) { const char *find = outputs->items[i]; if (strcmp("*", find) == 0) { free(outputs->items[i]); list_del(outputs, i); } else if (strcmp(output, find) == 0) { add_output = false; break; } } } if (add_output) { list_add(outputs, strdup(output)); sway_log(SWAY_DEBUG, "Adding bar: '%s' to output '%s'", config->current_bar->id, output); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/pango_markup.c ================================================ #include #include #include "sway/commands.h" #include "log.h" #include "util.h" struct cmd_results *bar_cmd_pango_markup(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "pango_markup", EXPECTED_EQUAL_TO, 1))) { return error; } config->current_bar->pango_markup = parse_boolean(argv[0], config->current_bar->pango_markup); if (config->current_bar->pango_markup) { sway_log(SWAY_DEBUG, "Enabling pango markup for bar: %s", config->current_bar->id); } else { sway_log(SWAY_DEBUG, "Disabling pango markup for bar: %s", config->current_bar->id); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/position.c ================================================ #include #include #include "sway/commands.h" #include "log.h" struct cmd_results *bar_cmd_position(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "position", EXPECTED_EQUAL_TO, 1))) { return error; } char *valid[] = { "top", "bottom" }; for (size_t i = 0; i < sizeof(valid) / sizeof(valid[0]); ++i) { if (strcasecmp(valid[i], argv[0]) == 0) { sway_log(SWAY_DEBUG, "Setting bar position '%s' for bar: %s", argv[0], config->current_bar->id); free(config->current_bar->position); config->current_bar->position = strdup(argv[0]); return cmd_results_new(CMD_SUCCESS, NULL); } } return cmd_results_new(CMD_INVALID, "Invalid value %s", argv[0]); } ================================================ FILE: sway/commands/bar/separator_symbol.c ================================================ #include #include "sway/commands.h" #include "log.h" struct cmd_results *bar_cmd_separator_symbol(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "separator_symbol", EXPECTED_EQUAL_TO, 1))) { return error; } free(config->current_bar->separator_symbol); config->current_bar->separator_symbol = strdup(argv[0]); sway_log(SWAY_DEBUG, "Settings separator_symbol '%s' for bar: %s", config->current_bar->separator_symbol, config->current_bar->id); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/status_command.c ================================================ #include #include "sway/commands.h" #include "log.h" #include "stringop.h" struct cmd_results *bar_cmd_status_command(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "status_command", EXPECTED_AT_LEAST, 1))) { return error; } free(config->current_bar->status_command); config->current_bar->status_command = NULL; char *new_command = join_args(argv, argc); if (strcmp(new_command, "-") != 0) { config->current_bar->status_command = new_command; sway_log(SWAY_DEBUG, "Feeding bar with status command: %s", config->current_bar->status_command); } else { free(new_command); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/status_edge_padding.c ================================================ #include #include #include "sway/commands.h" #include "log.h" struct cmd_results *bar_cmd_status_edge_padding(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "status_edge_padding", EXPECTED_EQUAL_TO, 1))) { return error; } char *end; int padding = strtol(argv[0], &end, 10); if (strlen(end) || padding < 0) { return cmd_results_new(CMD_INVALID, "Padding must be a positive integer"); } config->current_bar->status_edge_padding = padding; sway_log(SWAY_DEBUG, "Status edge padding on bar %s: %d", config->current_bar->id, config->current_bar->status_edge_padding); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/status_padding.c ================================================ #include #include #include "sway/commands.h" #include "log.h" struct cmd_results *bar_cmd_status_padding(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "status_padding", EXPECTED_EQUAL_TO, 1))) { return error; } char *end; int padding = strtol(argv[0], &end, 10); if (strlen(end) || padding < 0) { return cmd_results_new(CMD_INVALID, "Padding must be a positive integer"); } config->current_bar->status_padding = padding; sway_log(SWAY_DEBUG, "Status padding on bar %s: %d", config->current_bar->id, config->current_bar->status_padding); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/strip_workspace_name.c ================================================ #include #include #include "sway/commands.h" #include "log.h" #include "util.h" struct cmd_results *bar_cmd_strip_workspace_name(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "strip_workspace_name", EXPECTED_EQUAL_TO, 1))) { return error; } config->current_bar->strip_workspace_name = parse_boolean(argv[0], config->current_bar->strip_workspace_name); if (config->current_bar->strip_workspace_name) { config->current_bar->strip_workspace_numbers = false; sway_log(SWAY_DEBUG, "Stripping workspace name on bar: %s", config->current_bar->id); } else { sway_log(SWAY_DEBUG, "Enabling workspace name on bar: %s", config->current_bar->id); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/strip_workspace_numbers.c ================================================ #include #include #include "sway/commands.h" #include "log.h" #include "util.h" struct cmd_results *bar_cmd_strip_workspace_numbers(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "strip_workspace_numbers", EXPECTED_EQUAL_TO, 1))) { return error; } config->current_bar->strip_workspace_numbers = parse_boolean(argv[0], config->current_bar->strip_workspace_numbers); if (config->current_bar->strip_workspace_numbers) { config->current_bar->strip_workspace_name = false; sway_log(SWAY_DEBUG, "Stripping workspace numbers on bar: %s", config->current_bar->id); } else { sway_log(SWAY_DEBUG, "Enabling workspace numbers on bar: %s", config->current_bar->id); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/swaybar_command.c ================================================ #include #include "sway/commands.h" #include "log.h" #include "stringop.h" struct cmd_results *bar_cmd_swaybar_command(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "swaybar_command", EXPECTED_AT_LEAST, 1))) { return error; } free(config->current_bar->swaybar_command); config->current_bar->swaybar_command = join_args(argv, argc); sway_log(SWAY_DEBUG, "Using custom swaybar command: %s", config->current_bar->swaybar_command); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/tray_bind.c ================================================ #include #include "config.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/input/cursor.h" #include "log.h" static struct cmd_results *tray_bind(int argc, char **argv, bool code) { #if HAVE_TRAY const char *command = code ? "bar tray_bindcode" : "bar tray_bindsym"; struct cmd_results *error = NULL; if ((error = checkarg(argc, command, EXPECTED_EQUAL_TO, 2))) { return error; } struct tray_binding *binding = calloc(1, sizeof(struct tray_binding)); if (!binding) { return cmd_results_new(CMD_FAILURE, "Unable to allocate tray binding"); } char *message = NULL; if (code) { binding->button = get_mouse_bindcode(argv[0], &message); } else { binding->button = get_mouse_bindsym(argv[0], &message); } if (message) { free(binding); error = cmd_results_new(CMD_INVALID, "%s", message); free(message); return error; } else if (!binding->button) { free(binding); return cmd_results_new(CMD_INVALID, "Unknown button %s", argv[0]); } const char *name = get_mouse_button_name(binding->button); static const char *commands[] = { "ContextMenu", "Activate", "SecondaryActivate", "ScrollDown", "ScrollLeft", "ScrollRight", "ScrollUp", "nop" }; for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); ++i) { if (strcasecmp(argv[1], commands[i]) == 0) { binding->command = commands[i]; } } if (!binding->command) { free(binding); return cmd_results_new(CMD_INVALID, "[Bar %s] Invalid tray command %s", config->current_bar->id, argv[1]); } bool overwritten = false; struct tray_binding *other = NULL; wl_list_for_each(other, &config->current_bar->tray_bindings, link) { if (other->button == binding->button) { overwritten = true; other->command = binding->command; free(binding); binding = other; sway_log(SWAY_DEBUG, "[bar %s] Updated tray binding for %u (%s) to %s", config->current_bar->id, binding->button, name, binding->command); break; } } if (!overwritten) { wl_list_insert(&config->current_bar->tray_bindings, &binding->link); sway_log(SWAY_DEBUG, "[bar %s] Added tray binding for %u (%s) to %s", config->current_bar->id, binding->button, name, binding->command); } return cmd_results_new(CMD_SUCCESS, NULL); #else return cmd_results_new(CMD_INVALID, "Sway has been compiled without tray support"); #endif } struct cmd_results *bar_cmd_tray_bindcode(int argc, char **argv) { return tray_bind(argc, argv, true); } struct cmd_results *bar_cmd_tray_bindsym(int argc, char **argv) { return tray_bind(argc, argv, false); } ================================================ FILE: sway/commands/bar/tray_output.c ================================================ #include #include "config.h" #include "sway/commands.h" #include "sway/config.h" #include "list.h" #include "log.h" struct cmd_results *bar_cmd_tray_output(int argc, char **argv) { #if HAVE_TRAY struct cmd_results *error = NULL; if ((error = checkarg(argc, "tray_output", EXPECTED_EQUAL_TO, 1))) { return error; } list_t *outputs = config->current_bar->tray_outputs; if (!outputs) { config->current_bar->tray_outputs = outputs = create_list(); } if (strcmp(argv[0], "none") == 0) { sway_log(SWAY_DEBUG, "Hiding tray on bar: %s", config->current_bar->id); for (int i = 0; i < outputs->length; ++i) { free(outputs->items[i]); } outputs->length = 0; } else if (strcmp(argv[0], "*") == 0) { sway_log(SWAY_DEBUG, "Showing tray on all outputs for bar: %s", config->current_bar->id); while (outputs->length) { free(outputs->items[0]); list_del(outputs, 0); } return cmd_results_new(CMD_SUCCESS, NULL); } else { sway_log(SWAY_DEBUG, "Showing tray on output '%s' for bar: %s", argv[0], config->current_bar->id); if (outputs->length == 1 && strcmp(outputs->items[0], "none") == 0) { free(outputs->items[0]); list_del(outputs, 0); } } list_add(outputs, strdup(argv[0])); return cmd_results_new(CMD_SUCCESS, NULL); #else return cmd_results_new(CMD_INVALID, "Sway has been compiled without tray support"); #endif } ================================================ FILE: sway/commands/bar/tray_padding.c ================================================ #include #include #include "config.h" #include "sway/commands.h" #include "sway/config.h" #include "log.h" struct cmd_results *bar_cmd_tray_padding(int argc, char **argv) { #if HAVE_TRAY struct cmd_results *error = NULL; if ((error = checkarg(argc, "tray_padding", EXPECTED_AT_LEAST, 1))) { return error; } if ((error = checkarg(argc, "tray_padding", EXPECTED_AT_MOST, 2))) { return error; } struct bar_config *bar = config->current_bar; char *end; int padding = strtol(argv[0], &end, 10); if (padding < 0 || (*end != '\0' && strcasecmp(end, "px") != 0)) { return cmd_results_new(CMD_INVALID, "[Bar %s] Invalid tray padding value: %s", bar->id, argv[0]); } if (argc == 2 && strcasecmp(argv[1], "px") != 0) { return cmd_results_new(CMD_INVALID, "Expected 'tray_padding [px]'"); } sway_log(SWAY_DEBUG, "[Bar %s] Setting tray padding to %d", bar->id, padding); config->current_bar->tray_padding = padding; return cmd_results_new(CMD_SUCCESS, NULL); #else return cmd_results_new(CMD_INVALID, "Sway has been compiled without tray support"); #endif } ================================================ FILE: sway/commands/bar/workspace_buttons.c ================================================ #include #include #include "sway/commands.h" #include "log.h" #include "util.h" struct cmd_results *bar_cmd_workspace_buttons(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "workspace_buttons", EXPECTED_EQUAL_TO, 1))) { return error; } config->current_bar->workspace_buttons = parse_boolean(argv[0], config->current_bar->workspace_buttons); if (config->current_bar->workspace_buttons) { sway_log(SWAY_DEBUG, "Enabling workspace buttons on bar: %s", config->current_bar->id); } else { sway_log(SWAY_DEBUG, "Disabling workspace buttons on bar: %s", config->current_bar->id); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/workspace_min_width.c ================================================ #include #include #include "config.h" #include "sway/commands.h" #include "sway/config.h" #include "log.h" struct cmd_results *bar_cmd_workspace_min_width(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "workspace_min_width", EXPECTED_AT_LEAST, 1))) { return error; } struct bar_config *bar = config->current_bar; char *end; int min_width = strtol(argv[0], &end, 10); if (min_width < 0 || (*end != '\0' && strcasecmp(end, "px") != 0)) { return cmd_results_new(CMD_INVALID, "[Bar %s] Invalid minimum workspace button width value: %s", bar->id, argv[0]); } if (argc == 2 && strcasecmp(argv[1], "px") != 0) { return cmd_results_new(CMD_INVALID, "Expected 'workspace_min_width [px]'"); } sway_log(SWAY_DEBUG, "[Bar %s] Setting minimum workspace button width to %d", bar->id, min_width); config->current_bar->workspace_min_width = min_width; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar/wrap_scroll.c ================================================ #include #include #include "sway/commands.h" #include "log.h" #include "util.h" struct cmd_results *bar_cmd_wrap_scroll(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "wrap_scroll", EXPECTED_EQUAL_TO, 1))) { return error; } config->current_bar->wrap_scroll = parse_boolean(argv[0], config->current_bar->wrap_scroll); if (config->current_bar->wrap_scroll) { sway_log(SWAY_DEBUG, "Enabling wrap scroll on bar: %s", config->current_bar->id); } else { sway_log(SWAY_DEBUG, "Disabling wrap scroll on bar: %s", config->current_bar->id); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/bar.c ================================================ #include #include #include #include "sway/commands.h" #include "sway/config.h" #include "sway/ipc-server.h" #include "log.h" // Must be in alphabetical order for bsearch static const struct cmd_handler bar_handlers[] = { { "bindcode", bar_cmd_bindcode }, { "binding_mode_indicator", bar_cmd_binding_mode_indicator }, { "bindsym", bar_cmd_bindsym }, { "colors", bar_cmd_colors }, { "font", bar_cmd_font }, { "gaps", bar_cmd_gaps }, { "height", bar_cmd_height }, { "hidden_state", bar_cmd_hidden_state }, { "icon_theme", bar_cmd_icon_theme }, { "mode", bar_cmd_mode }, { "modifier", bar_cmd_modifier }, { "output", bar_cmd_output }, { "pango_markup", bar_cmd_pango_markup }, { "position", bar_cmd_position }, { "separator_symbol", bar_cmd_separator_symbol }, { "status_command", bar_cmd_status_command }, { "status_edge_padding", bar_cmd_status_edge_padding }, { "status_padding", bar_cmd_status_padding }, { "strip_workspace_name", bar_cmd_strip_workspace_name }, { "strip_workspace_numbers", bar_cmd_strip_workspace_numbers }, { "tray_bindcode", bar_cmd_tray_bindcode }, { "tray_bindsym", bar_cmd_tray_bindsym }, { "tray_output", bar_cmd_tray_output }, { "tray_padding", bar_cmd_tray_padding }, { "unbindcode", bar_cmd_unbindcode }, { "unbindsym", bar_cmd_unbindsym }, { "workspace_buttons", bar_cmd_workspace_buttons }, { "workspace_min_width", bar_cmd_workspace_min_width }, { "wrap_scroll", bar_cmd_wrap_scroll }, }; // Must be in alphabetical order for bsearch static const struct cmd_handler bar_config_handlers[] = { { "id", bar_cmd_id }, { "swaybar_command", bar_cmd_swaybar_command }, }; // Determines whether the subcommand is valid in any bar handler struct static bool is_subcommand(char *name) { return find_handler(name, bar_handlers, sizeof(bar_handlers)) || find_handler(name, bar_config_handlers, sizeof(bar_config_handlers)); } struct cmd_results *cmd_bar(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "bar", EXPECTED_AT_LEAST, 2))) { return error; } char *id = NULL; if (strcmp(argv[0], "id") != 0 && is_subcommand(argv[1])) { for (int i = 0; i < config->bars->length; ++i) { struct bar_config *item = config->bars->items[i]; if (strcmp(item->id, argv[0]) == 0) { sway_log(SWAY_DEBUG, "Selecting bar: %s", argv[0]); config->current_bar = item; break; } } if (!config->current_bar) { id = strdup(argv[0]); } ++argv; --argc; } else if (config->reading && !config->current_bar) { id = format_str("bar-%d", config->bars->length); if (!id) { return cmd_results_new(CMD_FAILURE, "Unable to allocate bar id"); } } else if (!config->reading && strcmp(argv[0], "mode") != 0 && strcmp(argv[0], "hidden_state") != 0) { if (is_subcommand(argv[0])) { return cmd_results_new(CMD_INVALID, "No bar defined."); } else { return cmd_results_new(CMD_INVALID, "Unknown/invalid command '%s'", argv[1]); } } if (id) { sway_log(SWAY_DEBUG, "Creating bar: %s", id); config->current_bar = default_bar_config(); if (!config->current_bar) { free(id); return cmd_results_new(CMD_FAILURE, "Unable to allocate bar config"); } config->current_bar->id = id; } struct cmd_results *res = NULL; if (find_handler(argv[0], bar_config_handlers, sizeof(bar_config_handlers))) { if (config->reading) { res = config_subcommand(argv, argc, bar_config_handlers, sizeof(bar_config_handlers)); } else { res = cmd_results_new(CMD_INVALID, "Can only be used in the config file"); } } else { res = config_subcommand(argv, argc, bar_handlers, sizeof(bar_handlers)); } if (res && res->status != CMD_SUCCESS) { if (id) { free_bar_config(config->current_bar); config->current_bar = NULL; id = NULL; } return res; } if (id) { list_add(config->bars, config->current_bar); } if (!config->reading && config->current_bar) { ipc_event_barconfig_update(config->current_bar); if (id) { load_swaybar(config->current_bar); } config->current_bar = NULL; } return res; } ================================================ FILE: sway/commands/bind.c ================================================ #include #include #include #include #include #include #include #include "sway/commands.h" #include "sway/config.h" #include "sway/desktop/transaction.h" #include "sway/input/cursor.h" #include "sway/input/keyboard.h" #include "sway/ipc-server.h" #include "list.h" #include "log.h" #include "stringop.h" #include "util.h" int binding_order = 0; void free_sway_binding(struct sway_binding *binding) { if (!binding) { return; } list_free_items_and_destroy(binding->keys); list_free_items_and_destroy(binding->syms); free(binding->input); free(binding->command); free(binding); } void free_switch_binding(struct sway_switch_binding *binding) { if (!binding) { return; } free(binding->command); free(binding); } /** * Returns true if the bindings have the same switch type and state combinations. */ static bool binding_switch_compare(struct sway_switch_binding *binding_a, struct sway_switch_binding *binding_b) { if (binding_a->type != binding_b->type) { return false; } if (binding_a->trigger != binding_b->trigger) { return false; } if ((binding_a->flags & BINDING_LOCKED) != (binding_b->flags & BINDING_LOCKED)) { return false; } return true; } /** * Returns true if the bindings have the same key and modifier combinations. * Note that keyboard layout is not considered, so the bindings might actually * not be equivalent on some layouts. */ static bool binding_key_compare(struct sway_binding *binding_a, struct sway_binding *binding_b) { if (strcmp(binding_a->input, binding_b->input) != 0) { return false; } if (binding_a->type != binding_b->type) { return false; } uint32_t conflict_generating_flags = BINDING_RELEASE | BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR | BINDING_LOCKED | BINDING_INHIBITED; if ((binding_a->flags & conflict_generating_flags) != (binding_b->flags & conflict_generating_flags)) { return false; } if (binding_a->group != binding_b->group) { return false; } if (binding_a->modifiers ^ binding_b->modifiers) { return false; } if (binding_a->keys->length != binding_b->keys->length) { return false; } // Keys are sorted int keys_len = binding_a->keys->length; for (int i = 0; i < keys_len; ++i) { uint32_t key_a = *(uint32_t *)binding_a->keys->items[i]; uint32_t key_b = *(uint32_t *)binding_b->keys->items[i]; if (key_a != key_b) { return false; } } return true; } static int key_qsort_cmp(const void *keyp_a, const void *keyp_b) { uint32_t key_a = **(uint32_t **)keyp_a; uint32_t key_b = **(uint32_t **)keyp_b; return (key_a < key_b) ? -1 : ((key_a > key_b) ? 1 : 0); } /** * From a keycode, bindcode, or bindsym name and the most likely binding type, * identify the appropriate numeric value corresponding to the key. Return NULL * and set *key_val if successful, otherwise return a specific error. Change * the value of *type if the initial type guess was incorrect and if this * was the first identified key. */ static struct cmd_results *identify_key(const char* name, bool first_key, uint32_t* key_val, enum binding_input_type* type) { if (*type == BINDING_MOUSECODE) { // check for mouse bindcodes char *message = NULL; uint32_t button = get_mouse_bindcode(name, &message); if (!button) { if (message) { struct cmd_results *error = cmd_results_new(CMD_INVALID, "%s", message); free(message); return error; } else { return cmd_results_new(CMD_INVALID, "Unknown button code %s", name); } } *key_val = button; } else if (*type == BINDING_MOUSESYM) { // check for mouse bindsyms (x11 buttons or event names) char *message = NULL; uint32_t button = get_mouse_bindsym(name, &message); if (!button) { if (message) { struct cmd_results *error = cmd_results_new(CMD_INVALID, "%s", message); free(message); return error; } else { return cmd_results_new(CMD_INVALID, "Unknown button %s", name); } } *key_val = button; } else if (*type == BINDING_KEYCODE) { // check for keycode. If it is the first key, allow mouse bindcodes if (first_key) { char *message = NULL; uint32_t button = get_mouse_bindcode(name, &message); free(message); if (button) { *type = BINDING_MOUSECODE; *key_val = button; return NULL; } } xkb_keycode_t keycode = strtol(name, NULL, 10); if (!xkb_keycode_is_legal_ext(keycode)) { if (first_key) { return cmd_results_new(CMD_INVALID, "Invalid keycode or button code '%s'", name); } else { return cmd_results_new(CMD_INVALID, "Invalid keycode '%s'", name); } } *key_val = keycode; } else { // check for keysym. If it is the first key, allow mouse bindsyms if (first_key) { char *message = NULL; uint32_t button = get_mouse_bindsym(name, &message); if (message) { struct cmd_results *error = cmd_results_new(CMD_INVALID, "%s", message); free(message); return error; } else if (button) { *type = BINDING_MOUSESYM; *key_val = button; return NULL; } } xkb_keysym_t keysym = xkb_keysym_from_name(name, XKB_KEYSYM_CASE_INSENSITIVE); if (!keysym) { if (first_key) { return cmd_results_new(CMD_INVALID, "Unknown key or button '%s'", name); } else { return cmd_results_new(CMD_INVALID, "Unknown key '%s'", name); } } *key_val = keysym; } return NULL; } static struct cmd_results *switch_binding_add( struct sway_switch_binding *binding, const char *bindtype, const char *switchcombo, bool warn) { list_t *mode_bindings = config->current_mode->switch_bindings; // overwrite the binding if it already exists bool overwritten = false; for (int i = 0; i < mode_bindings->length; ++i) { struct sway_switch_binding *config_binding = mode_bindings->items[i]; if (binding_switch_compare(binding, config_binding)) { sway_log(SWAY_INFO, "Overwriting binding '%s' to `%s` from `%s`", switchcombo, binding->command, config_binding->command); if (warn) { config_add_swaynag_warning("Overwriting binding" "'%s' to `%s` from `%s`", switchcombo, binding->command, config_binding->command); } free_switch_binding(config_binding); mode_bindings->items[i] = binding; overwritten = true; } } if (!overwritten) { list_add(mode_bindings, binding); sway_log(SWAY_DEBUG, "%s - Bound %s to command `%s`", bindtype, switchcombo, binding->command); } return cmd_results_new(CMD_SUCCESS, NULL); } static struct cmd_results *switch_binding_remove( struct sway_switch_binding *binding, const char *bindtype, const char *switchcombo) { list_t *mode_bindings = config->current_mode->switch_bindings; for (int i = 0; i < mode_bindings->length; ++i) { struct sway_switch_binding *config_binding = mode_bindings->items[i]; if (binding_switch_compare(binding, config_binding)) { free_switch_binding(config_binding); free_switch_binding(binding); list_del(mode_bindings, i); sway_log(SWAY_DEBUG, "%s - Unbound %s switch", bindtype, switchcombo); return cmd_results_new(CMD_SUCCESS, NULL); } } free_switch_binding(binding); return cmd_results_new(CMD_FAILURE, "Could not find switch binding `%s`", switchcombo); } /** * Insert or update the binding. * Return the binding which has been replaced or NULL. */ static struct sway_binding *binding_upsert(struct sway_binding *binding, list_t *mode_bindings) { for (int i = 0; i < mode_bindings->length; ++i) { struct sway_binding *config_binding = mode_bindings->items[i]; if (binding_key_compare(binding, config_binding)) { mode_bindings->items[i] = binding; return config_binding; } } list_add(mode_bindings, binding); return NULL; } static struct cmd_results *binding_add(struct sway_binding *binding, list_t *mode_bindings, const char *bindtype, const char *keycombo, bool warn) { struct sway_binding *config_binding = binding_upsert(binding, mode_bindings); if (config_binding) { sway_log(SWAY_INFO, "Overwriting binding '%s' for device '%s' " "to `%s` from `%s`", keycombo, binding->input, binding->command, config_binding->command); if (warn) { config_add_swaynag_warning("Overwriting binding" "'%s' for device '%s' to `%s` from `%s`", keycombo, binding->input, binding->command, config_binding->command); } free_sway_binding(config_binding); } else { sway_log(SWAY_DEBUG, "%s - Bound %s to command `%s` for device '%s'", bindtype, keycombo, binding->command, binding->input); } return cmd_results_new(CMD_SUCCESS, NULL); } static struct cmd_results *binding_remove(struct sway_binding *binding, list_t *mode_bindings, const char *bindtype, const char *keycombo) { for (int i = 0; i < mode_bindings->length; ++i) { struct sway_binding *config_binding = mode_bindings->items[i]; if (binding_key_compare(binding, config_binding)) { sway_log(SWAY_DEBUG, "%s - Unbound `%s` from device '%s'", bindtype, keycombo, binding->input); free_sway_binding(config_binding); free_sway_binding(binding); list_del(mode_bindings, i); return cmd_results_new(CMD_SUCCESS, NULL); } } free_sway_binding(binding); return cmd_results_new(CMD_FAILURE, "Could not find binding `%s` " "for the given flags", keycombo); } static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, bool bindcode, bool unbind) { const char *bindtype; int minargs = 2; if (unbind) { bindtype = bindcode ? "unbindcode" : "unbindsym"; minargs--; } else { bindtype = bindcode ? "bindcode": "bindsym"; } struct cmd_results *error = NULL; if ((error = checkarg(argc, bindtype, EXPECTED_AT_LEAST, minargs))) { return error; } struct sway_binding *binding = calloc(1, sizeof(struct sway_binding)); if (!binding) { return cmd_results_new(CMD_FAILURE, "Unable to allocate binding"); } binding->input = strdup("*"); binding->keys = create_list(); binding->group = XKB_LAYOUT_INVALID; binding->modifiers = 0; binding->flags = 0; binding->type = bindcode ? BINDING_KEYCODE : BINDING_KEYSYM; bool exclude_titlebar = false; bool warn = true; while (argc > 0) { if (strcmp("--release", argv[0]) == 0) { binding->flags |= BINDING_RELEASE; } else if (strcmp("--locked", argv[0]) == 0) { binding->flags |= BINDING_LOCKED; } else if (strcmp("--inhibited", argv[0]) == 0) { binding->flags |= BINDING_INHIBITED; } else if (strcmp("--whole-window", argv[0]) == 0) { binding->flags |= BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR; } else if (strcmp("--border", argv[0]) == 0) { binding->flags |= BINDING_BORDER; } else if (strcmp("--to-code", argv[0]) == 0) { if (!bindcode) { binding->flags |= BINDING_CODE; } } else if (strcmp("--exclude-titlebar", argv[0]) == 0) { exclude_titlebar = true; } else if (has_prefix(argv[0], "--input-device=")) { free(binding->input); binding->input = strdup(argv[0] + strlen("--input-device=")); strip_quotes(binding->input); } else if (strcmp("--no-warn", argv[0]) == 0) { warn = false; } else if (strcmp("--no-repeat", argv[0]) == 0) { binding->flags |= BINDING_NOREPEAT; } else { break; } argv++; argc--; } if (binding->flags & (BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR) || exclude_titlebar) { binding->type = binding->type == BINDING_KEYCODE ? BINDING_MOUSECODE : BINDING_MOUSESYM; } if (argc < minargs) { free_sway_binding(binding); return cmd_results_new(CMD_FAILURE, "Invalid %s command " "(expected at least %d non-option arguments, got %d)", bindtype, minargs, argc); } list_t *split = split_string(argv[0], "+"); for (int i = 0; i < split->length; ++i) { // Check for group if (has_prefix(split->items[i], "Group")) { if (binding->group != XKB_LAYOUT_INVALID) { free_sway_binding(binding); list_free_items_and_destroy(split); return cmd_results_new(CMD_FAILURE, "Only one group can be specified"); } char *end; int group = strtol(split->items[i] + strlen("Group"), &end, 10); if (group < 1 || group > 4 || end[0] != '\0') { free_sway_binding(binding); list_free_items_and_destroy(split); return cmd_results_new(CMD_FAILURE, "Invalid group"); } binding->group = group - 1; continue; } else if (strcmp(split->items[i], "Mode_switch") == 0) { // For full i3 compatibility, Mode_switch is an alias for Group2 if (binding->group != XKB_LAYOUT_INVALID) { free_sway_binding(binding); list_free_items_and_destroy(split); return cmd_results_new(CMD_FAILURE, "Only one group can be specified"); } binding->group = 1; } // Check for a modifier key uint32_t mod; if ((mod = get_modifier_mask_by_name(split->items[i])) > 0) { binding->modifiers |= mod; continue; } // Identify the key and possibly change binding->type uint32_t key_val = 0; error = identify_key(split->items[i], binding->keys->length == 0, &key_val, &binding->type); if (error) { free_sway_binding(binding); list_free(split); return error; } uint32_t *key = calloc(1, sizeof(uint32_t)); if (!key) { free_sway_binding(binding); list_free_items_and_destroy(split); return cmd_results_new(CMD_FAILURE, "Unable to allocate binding key"); } *key = key_val; list_add(binding->keys, key); } list_free_items_and_destroy(split); // refine region of interest for mouse binding once we are certain // that this is one if (exclude_titlebar) { binding->flags &= ~BINDING_TITLEBAR; } else if (binding->type == BINDING_MOUSECODE || binding->type == BINDING_MOUSESYM) { binding->flags |= BINDING_TITLEBAR; } // sort ascending list_qsort(binding->keys, key_qsort_cmp); // translate keysyms into keycodes if (!translate_binding(binding)) { sway_log(SWAY_INFO, "Unable to translate bindsym into bindcode: %s", argv[0]); } list_t *mode_bindings; if (binding->type == BINDING_KEYCODE) { mode_bindings = config->current_mode->keycode_bindings; } else if (binding->type == BINDING_KEYSYM) { mode_bindings = config->current_mode->keysym_bindings; } else { mode_bindings = config->current_mode->mouse_bindings; } if (unbind) { return binding_remove(binding, mode_bindings, bindtype, argv[0]); } binding->command = join_args(argv + 1, argc - 1); binding->order = binding_order++; return binding_add(binding, mode_bindings, bindtype, argv[0], warn); } struct cmd_results *cmd_bind_or_unbind_switch(int argc, char **argv, bool unbind) { int minargs = 2; char *bindtype = "bindswitch"; if (unbind) { minargs--; bindtype = "unbindswitch"; } struct cmd_results *error = NULL; if ((error = checkarg(argc, bindtype, EXPECTED_AT_LEAST, minargs))) { return error; } struct sway_switch_binding *binding = calloc(1, sizeof(struct sway_switch_binding)); if (!binding) { return cmd_results_new(CMD_FAILURE, "Unable to allocate binding"); } bool warn = true; // Handle flags while (argc > 0) { if (strcmp("--locked", argv[0]) == 0) { binding->flags |= BINDING_LOCKED; } else if (strcmp("--no-warn", argv[0]) == 0) { warn = false; } else if (strcmp("--reload", argv[0]) == 0) { binding->flags |= BINDING_RELOAD; } else { break; } argv++; argc--; } if (argc < minargs) { free(binding); return cmd_results_new(CMD_FAILURE, "Invalid %s command (expected at least %d " "non-option arguments, got %d)", bindtype, minargs, argc); } list_t *split = split_string(argv[0], ":"); if (split->length != 2) { free_switch_binding(binding); return cmd_results_new(CMD_FAILURE, "Invalid %s command (expected binding with the form " ":)", bindtype); } if (strcmp(split->items[0], "tablet") == 0) { binding->type = WLR_SWITCH_TYPE_TABLET_MODE; } else if (strcmp(split->items[0], "lid") == 0) { binding->type = WLR_SWITCH_TYPE_LID; #if HAVE_LIBINPUT_SWITCH_KEYPAD_SLIDE } else if (strcmp(split->items[0], "keypad_slide") == 0) { binding->type = WLR_SWITCH_TYPE_KEYPAD_SLIDE; #endif } else { free_switch_binding(binding); return cmd_results_new(CMD_FAILURE, "Invalid %s command (expected switch binding: " "unknown switch %s)", bindtype, (const char *)split->items[0]); } if (strcmp(split->items[1], "on") == 0) { binding->trigger = SWAY_SWITCH_TRIGGER_ON; } else if (strcmp(split->items[1], "off") == 0) { binding->trigger = SWAY_SWITCH_TRIGGER_OFF; } else if (strcmp(split->items[1], "toggle") == 0) { binding->trigger = SWAY_SWITCH_TRIGGER_TOGGLE; } else { free_switch_binding(binding); return cmd_results_new(CMD_FAILURE, "Invalid %s command " "(expected switch state: unknown state %s)", bindtype, (const char *)split->items[1]); } list_free_items_and_destroy(split); if (unbind) { return switch_binding_remove(binding, bindtype, argv[0]); } binding->command = join_args(argv + 1, argc - 1); return switch_binding_add(binding, bindtype, argv[0], warn); } struct cmd_results *cmd_bindsym(int argc, char **argv) { return cmd_bindsym_or_bindcode(argc, argv, false, false); } struct cmd_results *cmd_bindcode(int argc, char **argv) { return cmd_bindsym_or_bindcode(argc, argv, true, false); } struct cmd_results *cmd_unbindsym(int argc, char **argv) { return cmd_bindsym_or_bindcode(argc, argv, false, true); } struct cmd_results *cmd_unbindcode(int argc, char **argv) { return cmd_bindsym_or_bindcode(argc, argv, true, true); } struct cmd_results *cmd_bindswitch(int argc, char **argv) { return cmd_bind_or_unbind_switch(argc, argv, false); } struct cmd_results *cmd_unbindswitch(int argc, char **argv) { return cmd_bind_or_unbind_switch(argc, argv, true); } /** * Execute the command associated to a binding */ void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding) { if (!config->active) { sway_log(SWAY_DEBUG, "deferring command for binding: %s", binding->command); struct sway_binding *deferred = calloc(1, sizeof(struct sway_binding)); if (!deferred) { sway_log(SWAY_ERROR, "Failed to allocate deferred binding"); return; } memcpy(deferred, binding, sizeof(struct sway_binding)); deferred->command = binding->command ? strdup(binding->command) : NULL; list_add(seat->deferred_bindings, deferred); return; } sway_log(SWAY_DEBUG, "running command for binding: %s", binding->command); struct sway_container *con = NULL; if (binding->type == BINDING_MOUSESYM || binding->type == BINDING_MOUSECODE) { struct wlr_surface *surface = NULL; double sx, sy; struct sway_node *node = node_at_coords(seat, seat->cursor->cursor->x, seat->cursor->cursor->y, &surface, &sx, &sy); if (node && node->type == N_CONTAINER) { con = node->sway_container; } } list_t *res_list = execute_command(binding->command, seat, con); bool success = true; for (int i = 0; i < res_list->length; ++i) { struct cmd_results *results = res_list->items[i]; if (results->status != CMD_SUCCESS) { sway_log(SWAY_DEBUG, "could not run command for binding: %s (%s)", binding->command, results->error); success = false; } free_cmd_results(results); } list_free(res_list); if (success) { ipc_event_binding(binding); } transaction_commit_dirty(); } /** * The last found keycode associated with the keysym * and the total count of matches. */ struct keycode_matches { xkb_keysym_t keysym; xkb_keycode_t keycode; int count; }; /** * Iterate through keycodes in the keymap to find ones matching * the specified keysym. */ static void find_keycode(struct xkb_keymap *keymap, xkb_keycode_t keycode, void *data) { xkb_keysym_t keysym = xkb_state_key_get_one_sym( config->keysym_translation_state, keycode); if (keysym == XKB_KEY_NoSymbol) { return; } struct keycode_matches *matches = data; if (matches->keysym == keysym) { matches->keycode = keycode; matches->count++; } } /** * Return the keycode for the specified keysym. */ static struct keycode_matches get_keycode_for_keysym(xkb_keysym_t keysym) { struct keycode_matches matches = { .keysym = keysym, .keycode = XKB_KEYCODE_INVALID, .count = 0, }; xkb_keymap_key_for_each( xkb_state_get_keymap(config->keysym_translation_state), find_keycode, &matches); return matches; } bool translate_binding(struct sway_binding *binding) { if ((binding->flags & BINDING_CODE) == 0) { return true; } switch (binding->type) { // a bindsym to translate case BINDING_KEYSYM: binding->syms = binding->keys; binding->keys = create_list(); break; // a bindsym to re-translate case BINDING_KEYCODE: list_free_items_and_destroy(binding->keys); binding->keys = create_list(); break; default: return true; } for (int i = 0; i < binding->syms->length; ++i) { xkb_keysym_t *keysym = binding->syms->items[i]; struct keycode_matches matches = get_keycode_for_keysym(*keysym); if (matches.count != 1) { sway_log(SWAY_INFO, "Unable to convert keysym %" PRIu32 " into" " a single keycode (found %d matches)", *keysym, matches.count); goto error; } xkb_keycode_t *keycode = malloc(sizeof(xkb_keycode_t)); if (!keycode) { sway_log(SWAY_ERROR, "Unable to allocate memory for a keycode"); goto error; } *keycode = matches.keycode; list_add(binding->keys, keycode); } list_qsort(binding->keys, key_qsort_cmp); binding->type = BINDING_KEYCODE; return true; error: list_free_items_and_destroy(binding->keys); binding->type = BINDING_KEYSYM; binding->keys = binding->syms; binding->syms = NULL; return false; } void binding_add_translated(struct sway_binding *binding, list_t *mode_bindings) { struct sway_binding *config_binding = binding_upsert(binding, mode_bindings); if (config_binding) { sway_log(SWAY_INFO, "Overwriting binding for device '%s' " "to `%s` from `%s`", binding->input, binding->command, config_binding->command); free_sway_binding(config_binding); } } ================================================ FILE: sway/commands/border.c ================================================ #include "log.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/input/cursor.h" #include "sway/input/input-manager.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/view.h" // A couple of things here: // - view->border should never be B_CSD when the view is tiled, even when CSD is // in use (we set using_csd instead and render a sway border). // - view->saved_border should be the last applied border when switching to CSD. // - view->using_csd should always reflect whether CSD is applied or not. static void set_border(struct sway_container *con, enum sway_container_border new_border) { if (con->view) { if (con->view->using_csd && new_border != B_CSD) { view_set_csd_from_server(con->view, false); } else if (!con->view->using_csd && new_border == B_CSD) { view_set_csd_from_server(con->view, true); con->saved_border = con->pending.border; } } if (new_border != B_CSD || container_is_floating(con)) { con->pending.border = new_border; } if (con->view) { con->view->using_csd = new_border == B_CSD; } } static void border_toggle(struct sway_container *con) { if (con->view && con->view->using_csd) { set_border(con, B_NONE); return; } switch (con->pending.border) { case B_NONE: set_border(con, B_PIXEL); break; case B_PIXEL: set_border(con, B_NORMAL); break; case B_NORMAL: if (con->view && con->view->xdg_decoration) { set_border(con, B_CSD); } else { set_border(con, B_NONE); } break; case B_CSD: // view->using_csd should be true so it would have returned above sway_assert(false, "Unreachable"); break; } } struct cmd_results *cmd_border(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "border", EXPECTED_AT_LEAST, 1))) { return error; } struct sway_container *container = config->handler_context.container; if (!container || !container->view) { return cmd_results_new(CMD_INVALID, "Only views can have borders"); } struct sway_view *view = container->view; if (strcmp(argv[0], "none") == 0) { set_border(container, B_NONE); } else if (strcmp(argv[0], "normal") == 0) { set_border(container, B_NORMAL); } else if (strcmp(argv[0], "pixel") == 0) { set_border(container, B_PIXEL); } else if (strcmp(argv[0], "csd") == 0) { if (!view->xdg_decoration) { return cmd_results_new(CMD_INVALID, "This window doesn't support client side decorations"); } set_border(container, B_CSD); } else if (strcmp(argv[0], "toggle") == 0) { border_toggle(container); } else { return cmd_results_new(CMD_INVALID, "Expected 'border ' " "or 'border pixel '"); } if (argc == 2) { container->pending.border_thickness = atoi(argv[1]); } if (container_is_floating(container)) { container_set_geometry_from_content(container); } arrange_container(container); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/client.c ================================================ #include "log.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/output.h" #include "sway/tree/container.h" #include "util.h" static void container_update_iterator(struct sway_container *con, void *data) { container_update(con); } static struct cmd_results *handle_command(int argc, char **argv, char *cmd_name, struct border_colors *class, const char *default_indicator) { struct cmd_results *error = NULL; if ((error = checkarg(argc, cmd_name, EXPECTED_AT_LEAST, 3)) || (error = checkarg(argc, cmd_name, EXPECTED_AT_MOST, 5))) { return error; } if (argc > 3 && strcmp(cmd_name, "client.focused_tab_title") == 0) { sway_log(SWAY_ERROR, "Warning: indicator and child_border colors have no effect for %s", cmd_name); } struct border_colors colors = {0}; const char *ind_hex = argc > 3 ? argv[3] : default_indicator; const char *child_hex = argc > 4 ? argv[4] : argv[1]; // def to background struct { const char *name; const char *hex; float *rgba[4]; } properties[] = { { "border", argv[0], colors.border }, { "background", argv[1], colors.background }, { "text", argv[2], colors.text }, { "indicator", ind_hex, colors.indicator }, { "child_border", child_hex, colors.child_border } }; for (size_t i = 0; i < sizeof(properties) / sizeof(properties[0]); i++) { uint32_t color; if (!parse_color(properties[i].hex, &color)) { return cmd_results_new(CMD_INVALID, "Invalid %s color %s", properties[i].name, properties[i].hex); } color_to_rgba(*properties[i].rgba, color); } memcpy(class, &colors, sizeof(struct border_colors)); if (config->active) { root_for_each_container(container_update_iterator, NULL); } return cmd_results_new(CMD_SUCCESS, NULL); } struct cmd_results *cmd_client_focused(int argc, char **argv) { return handle_command(argc, argv, "client.focused", &config->border_colors.focused, "#2e9ef4ff"); } struct cmd_results *cmd_client_focused_inactive(int argc, char **argv) { return handle_command(argc, argv, "client.focused_inactive", &config->border_colors.focused_inactive, "#484e50ff"); } struct cmd_results *cmd_client_unfocused(int argc, char **argv) { return handle_command(argc, argv, "client.unfocused", &config->border_colors.unfocused, "#292d2eff"); } struct cmd_results *cmd_client_urgent(int argc, char **argv) { return handle_command(argc, argv, "client.urgent", &config->border_colors.urgent, "#900000ff"); } struct cmd_results *cmd_client_noop(int argc, char **argv) { sway_log(SWAY_INFO, "Warning: %s is ignored by sway", argv[-1]); return cmd_results_new(CMD_SUCCESS, NULL); } struct cmd_results *cmd_client_focused_tab_title(int argc, char **argv) { struct cmd_results *result = handle_command(argc, argv, "client.focused_tab_title", &config->border_colors.focused_tab_title, "#2e9ef4ff"); if (result && result->status == CMD_SUCCESS) { config->has_focused_tab_title = true; } return result; } ================================================ FILE: sway/commands/create_output.c ================================================ #include #include #include #include #if WLR_HAS_X11_BACKEND #include #endif #include "sway/commands.h" #include "sway/server.h" #include "log.h" static void create_output(struct wlr_backend *backend, void *data) { bool *done = data; if (*done) { return; } if (wlr_backend_is_wl(backend)) { wlr_wl_output_create(backend); *done = true; } else if (wlr_backend_is_headless(backend)) { wlr_headless_add_output(backend, 1920, 1080); *done = true; } #if WLR_HAS_X11_BACKEND else if (wlr_backend_is_x11(backend)) { wlr_x11_output_create(backend); *done = true; } #endif } /** * This command is intended for developer use only. */ struct cmd_results *cmd_create_output(int argc, char **argv) { sway_assert(wlr_backend_is_multi(server.backend), "Expected a multi backend"); bool done = false; wlr_multi_for_each_backend(server.backend, create_output, &done); if (!done) { return cmd_results_new(CMD_INVALID, "Can only create outputs for Wayland, X11 or headless backends"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/default_border.c ================================================ #include "log.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/container.h" struct cmd_results *cmd_default_border(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "default_border", EXPECTED_AT_LEAST, 1))) { return error; } if (strcmp(argv[0], "none") == 0) { config->border = B_NONE; } else if (strcmp(argv[0], "normal") == 0) { config->border = B_NORMAL; } else if (strcmp(argv[0], "pixel") == 0) { config->border = B_PIXEL; } else { return cmd_results_new(CMD_INVALID, "Expected 'default_border ' or 'default_border '"); } if (argc == 2) { config->border_thickness = atoi(argv[1]); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/default_floating_border.c ================================================ #include "log.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/container.h" struct cmd_results *cmd_default_floating_border(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "default_floating_border", EXPECTED_AT_LEAST, 1))) { return error; } if (strcmp(argv[0], "none") == 0) { config->floating_border = B_NONE; } else if (strcmp(argv[0], "normal") == 0) { config->floating_border = B_NORMAL; } else if (strcmp(argv[0], "pixel") == 0) { config->floating_border = B_PIXEL; } else { return cmd_results_new(CMD_INVALID, "Expected 'default_floating_border ' " "or 'default_floating_border '"); } if (argc == 2) { config->floating_border_thickness = atoi(argv[1]); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/default_orientation.c ================================================ #include #include #include "sway/commands.h" struct cmd_results *cmd_default_orientation(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "default_orientation", EXPECTED_EQUAL_TO, 1))) { return error; } if (strcasecmp(argv[0], "horizontal") == 0) { config->default_orientation = L_HORIZ; } else if (strcasecmp(argv[0], "vertical") == 0) { config->default_orientation = L_VERT; } else if (strcasecmp(argv[0], "auto") == 0) { // Do nothing } else { return cmd_results_new(CMD_INVALID, "Expected 'orientation '"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/exec.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "log.h" #include "stringop.h" struct cmd_results *cmd_exec(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = cmd_exec_validate(argc, argv))) { return error; } if (config->reloading) { char *args = join_args(argv, argc); sway_log(SWAY_DEBUG, "Ignoring 'exec %s' due to reload", args); free(args); return cmd_results_new(CMD_SUCCESS, NULL); } return cmd_exec_process(argc, argv); } ================================================ FILE: sway/commands/exec_always.c ================================================ #include #include #include #include #include #include #include "sway/commands.h" #include "sway/config.h" #include "sway/server.h" #include "sway/desktop/launcher.h" #include "sway/tree/container.h" #include "sway/tree/root.h" #include "sway/tree/workspace.h" #include "log.h" #include "stringop.h" struct cmd_results *cmd_exec_validate(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, argv[-1], EXPECTED_AT_LEAST, 1))) { return error; } if (!config->active || config->validating) { return cmd_results_new(CMD_DEFER, NULL); } return error; } struct cmd_results *cmd_exec_process(int argc, char **argv) { struct cmd_results *error = NULL; char *cmd = NULL; bool no_startup_id = false; if (strcmp(argv[0], "--no-startup-id") == 0) { no_startup_id = true; --argc; ++argv; if ((error = checkarg(argc, argv[-1], EXPECTED_AT_LEAST, 1))) { return error; } } if (argc == 1 && (argv[0][0] == '\'' || argv[0][0] == '"')) { cmd = strdup(argv[0]); strip_quotes(cmd); } else { cmd = join_args(argv, argc); } sway_log(SWAY_DEBUG, "Executing %s", cmd); struct launcher_ctx *ctx = launcher_ctx_create_internal(); // Fork process pid_t child = fork(); if (child == 0) { setsid(); if (ctx) { const char *token = launcher_ctx_get_token_name(ctx); setenv("XDG_ACTIVATION_TOKEN", token, 1); if (!no_startup_id) { setenv("DESKTOP_STARTUP_ID", token, 1); } } execlp("sh", "sh", "-c", cmd, (void*)NULL); sway_log_errno(SWAY_ERROR, "execve failed"); _exit(0); // Close child process } else if (child < 0) { launcher_ctx_destroy(ctx); free(cmd); return cmd_results_new(CMD_FAILURE, "fork() failed"); } sway_log(SWAY_DEBUG, "Child process created with pid %d", child); if (ctx != NULL) { sway_log(SWAY_DEBUG, "Recording workspace for process %d", child); ctx->pid = child; } free(cmd); return cmd_results_new(CMD_SUCCESS, NULL); } struct cmd_results *cmd_exec_always(int argc, char **argv) { struct cmd_results *error; if ((error = cmd_exec_validate(argc, argv))) { return error; } return cmd_exec_process(argc, argv); } ================================================ FILE: sway/commands/exit.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/server.h" struct cmd_results *cmd_exit(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "exit", EXPECTED_EQUAL_TO, 0))) { return error; } sway_terminate(0); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/floating.c ================================================ #include #include #include "sway/commands.h" #include "sway/input/seat.h" #include "sway/ipc-server.h" #include "sway/output.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "list.h" #include "util.h" struct cmd_results *cmd_floating(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "floating", EXPECTED_EQUAL_TO, 1))) { return error; } if (!root->outputs->length) { return cmd_results_new(CMD_INVALID, "Can't run this command while there's no outputs connected."); } struct sway_container *container = config->handler_context.container; struct sway_workspace *workspace = config->handler_context.workspace; if (!container && workspace->tiling->length == 0) { return cmd_results_new(CMD_INVALID, "Can't float an empty workspace"); } if (!container) { // Wrap the workspace's children in a container so we can float it container = workspace_wrap_children(workspace); workspace->layout = L_HORIZ; seat_set_focus_container(config->handler_context.seat, container); } if (container_is_scratchpad_hidden(container)) { return cmd_results_new(CMD_INVALID, "Can't change floating on hidden scratchpad container"); } // If the container is in a floating split container, // operate on the split container instead of the child. if (container_is_floating_or_child(container)) { while (container->pending.parent) { container = container->pending.parent; } } bool wants_floating = parse_boolean(argv[0], container_is_floating(container)); container_set_floating(container, wants_floating); // Floating containers in the scratchpad should be ignored if (container->pending.workspace) { arrange_workspace(container->pending.workspace); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/floating_minmax_size.c ================================================ #include #include #include #include #include #include #include "sway/commands.h" #include "log.h" static const char min_usage[] = "Expected 'floating_minimum_size x '"; static const char max_usage[] = "Expected 'floating_maximum_size x '"; static struct cmd_results *handle_command(int argc, char **argv, char *cmd_name, const char *usage, int *config_width, int *config_height) { struct cmd_results *error; if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 3))) { return error; } char *err; int width = (int)strtol(argv[0], &err, 10); if (*err) { return cmd_results_new(CMD_INVALID, "%s", usage); } if (strcmp(argv[1], "x") != 0) { return cmd_results_new(CMD_INVALID, "%s", usage); } int height = (int)strtol(argv[2], &err, 10); if (*err) { return cmd_results_new(CMD_INVALID, "%s", usage); } *config_width = width; *config_height = height; return cmd_results_new(CMD_SUCCESS, NULL); } struct cmd_results *cmd_floating_minimum_size(int argc, char **argv) { return handle_command(argc, argv, "floating_minimum_size", min_usage, &config->floating_minimum_width, &config->floating_minimum_height); } struct cmd_results *cmd_floating_maximum_size(int argc, char **argv) { return handle_command(argc, argv, "floating_maximum_size", max_usage, &config->floating_maximum_width, &config->floating_maximum_height); } ================================================ FILE: sway/commands/floating_modifier.c ================================================ #include "strings.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/input/keyboard.h" struct cmd_results *cmd_floating_modifier(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "floating_modifier", EXPECTED_AT_LEAST, 1))) { return error; } if (strcasecmp(argv[0], "none") == 0) { config->floating_mod = 0; return cmd_results_new(CMD_SUCCESS, NULL); } uint32_t mod = get_modifier_mask_by_name(argv[0]); if (!mod) { return cmd_results_new(CMD_INVALID, "Invalid modifier"); } if (argc == 1 || strcasecmp(argv[1], "normal") == 0) { config->floating_mod_inverse = false; } else if (strcasecmp(argv[1], "inverse") == 0) { config->floating_mod_inverse = true; } else { return cmd_results_new(CMD_INVALID, "Usage: floating_modifier [inverse|normal]"); } config->floating_mod = mod; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/focus.c ================================================ #include #include #include #include "log.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "sway/input/cursor.h" #include "sway/input/seat.h" #include "sway/output.h" #include "sway/tree/arrange.h" #include "sway/tree/root.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "stringop.h" #include "util.h" static bool get_direction_from_next_prev(struct sway_container *container, struct sway_seat *seat, const char *name, enum wlr_direction *out) { enum sway_container_layout parent_layout = L_NONE; if (container) { parent_layout = container_parent_layout(container); } if (strcasecmp(name, "prev") == 0) { switch (parent_layout) { case L_HORIZ: case L_TABBED: *out = WLR_DIRECTION_LEFT; break; case L_VERT: case L_STACKED: *out = WLR_DIRECTION_UP; break; case L_NONE: return true; default: return false; } } else if (strcasecmp(name, "next") == 0) { switch (parent_layout) { case L_HORIZ: case L_TABBED: *out = WLR_DIRECTION_RIGHT; break; case L_VERT: case L_STACKED: *out = WLR_DIRECTION_DOWN; break; case L_NONE: return true; default: return false; } } else { return false; } return true; } static bool parse_direction(const char *name, enum wlr_direction *out) { if (strcasecmp(name, "left") == 0) { *out = WLR_DIRECTION_LEFT; } else if (strcasecmp(name, "right") == 0) { *out = WLR_DIRECTION_RIGHT; } else if (strcasecmp(name, "up") == 0) { *out = WLR_DIRECTION_UP; } else if (strcasecmp(name, "down") == 0) { *out = WLR_DIRECTION_DOWN; } else { return false; } return true; } /** * Get node in the direction of newly entered output. */ static struct sway_node *get_node_in_output_direction( struct sway_output *output, enum wlr_direction dir) { struct sway_seat *seat = config->handler_context.seat; struct sway_workspace *ws = output_get_active_workspace(output); if (!sway_assert(ws, "Expected output to have a workspace")) { return NULL; } if (ws->fullscreen) { return seat_get_focus_inactive(seat, &ws->fullscreen->node); } struct sway_container *container = NULL; if (ws->tiling->length > 0) { switch (dir) { case WLR_DIRECTION_LEFT: if (ws->layout == L_HORIZ || ws->layout == L_TABBED) { // get most right child of new output container = ws->tiling->items[ws->tiling->length-1]; } else { container = seat_get_focus_inactive_tiling(seat, ws); } break; case WLR_DIRECTION_RIGHT: if (ws->layout == L_HORIZ || ws->layout == L_TABBED) { // get most left child of new output container = ws->tiling->items[0]; } else { container = seat_get_focus_inactive_tiling(seat, ws); } break; case WLR_DIRECTION_UP: if (ws->layout == L_VERT || ws->layout == L_STACKED) { // get most bottom child of new output container = ws->tiling->items[ws->tiling->length-1]; } else { container = seat_get_focus_inactive_tiling(seat, ws); } break; case WLR_DIRECTION_DOWN: if (ws->layout == L_VERT || ws->layout == L_STACKED) { // get most top child of new output container = ws->tiling->items[0]; } else { container = seat_get_focus_inactive_tiling(seat, ws); } break; } } if (container) { container = seat_get_focus_inactive_view(seat, &container->node); return &container->node; } return &ws->node; } static struct sway_node *node_get_in_direction_tiling( struct sway_container *container, struct sway_seat *seat, enum wlr_direction dir, bool descend) { struct sway_container *wrap_candidate = NULL; struct sway_container *current = container; while (current) { if (current->pending.fullscreen_mode == FULLSCREEN_WORKSPACE) { // Fullscreen container with a direction - go straight to outputs struct sway_output *output = current->pending.workspace->output; struct sway_output *new_output = output_get_in_direction(output, dir); if (!new_output) { return NULL; } return get_node_in_output_direction(new_output, dir); } if (current->pending.fullscreen_mode == FULLSCREEN_GLOBAL) { return NULL; } bool can_move = false; int desired; int idx = container_sibling_index(current); enum sway_container_layout parent_layout = container_parent_layout(current); list_t *siblings = container_get_siblings(current); if (dir == WLR_DIRECTION_LEFT || dir == WLR_DIRECTION_RIGHT) { if (parent_layout == L_HORIZ || parent_layout == L_TABBED) { can_move = true; desired = idx + (dir == WLR_DIRECTION_LEFT ? -1 : 1); } } else { if (parent_layout == L_VERT || parent_layout == L_STACKED) { can_move = true; desired = idx + (dir == WLR_DIRECTION_UP ? -1 : 1); } } if (can_move) { if (desired < 0 || desired >= siblings->length) { int len = siblings->length; if (config->focus_wrapping != WRAP_NO && !wrap_candidate && len > 1) { if (desired < 0) { wrap_candidate = siblings->items[len-1]; } else { wrap_candidate = siblings->items[0]; } if (config->focus_wrapping == WRAP_FORCE) { struct sway_container *c = seat_get_focus_inactive_view( seat, &wrap_candidate->node); return &c->node; } } } else { struct sway_container *desired_con = siblings->items[desired]; if (!descend) { return &desired_con->node; } else { struct sway_container *c = seat_get_focus_inactive_view( seat, &desired_con->node); return &c->node; } } } current = current->pending.parent; } // Check a different output struct sway_output *output = container->pending.workspace->output; struct sway_output *new_output = output_get_in_direction(output, dir); if ((config->focus_wrapping != WRAP_WORKSPACE || container->node.type == N_WORKSPACE) && new_output) { return get_node_in_output_direction(new_output, dir); } // If there is a wrap candidate, return its focus inactive view if (wrap_candidate) { struct sway_container *wrap_inactive = seat_get_focus_inactive_view( seat, &wrap_candidate->node); return &wrap_inactive->node; } return NULL; } static struct sway_node *node_get_in_direction_floating( struct sway_container *con, struct sway_seat *seat, enum wlr_direction dir) { double ref_lx = con->pending.x + con->pending.width / 2; double ref_ly = con->pending.y + con->pending.height / 2; double closest_distance = DBL_MAX; struct sway_container *closest_con = NULL; if (!con->pending.workspace) { return NULL; } for (int i = 0; i < con->pending.workspace->floating->length; i++) { struct sway_container *floater = con->pending.workspace->floating->items[i]; if (floater == con) { continue; } float distance = dir == WLR_DIRECTION_LEFT || dir == WLR_DIRECTION_RIGHT ? (floater->pending.x + floater->pending.width / 2) - ref_lx : (floater->pending.y + floater->pending.height / 2) - ref_ly; if (dir == WLR_DIRECTION_LEFT || dir == WLR_DIRECTION_UP) { distance = -distance; } if (distance < 0) { continue; } if (distance < closest_distance) { closest_distance = distance; closest_con = floater; } } return closest_con ? &closest_con->node : NULL; } static struct cmd_results *focus_mode(struct sway_workspace *ws, struct sway_seat *seat, bool floating) { struct sway_container *new_focus = NULL; if (floating) { new_focus = seat_get_focus_inactive_floating(seat, ws); } else { new_focus = seat_get_focus_inactive_tiling(seat, ws); } if (new_focus) { struct sway_container *new_focus_view = seat_get_focus_inactive_view(seat, &new_focus->node); if (new_focus_view) { new_focus = new_focus_view; } seat_set_focus_container(seat, new_focus); // If we're on the floating layer and the floating container area // overlaps the position on the tiling layer that would be warped to, // `seat_consider_warp_to_focus` would decide not to warp, but we need // to anyway. if (config->mouse_warping == WARP_CONTAINER) { cursor_warp_to_container(seat->cursor, new_focus, true); } else { seat_consider_warp_to_focus(seat); } } else { return cmd_results_new(CMD_FAILURE, "Failed to find a %s container in workspace.", floating ? "floating" : "tiling"); } return cmd_results_new(CMD_SUCCESS, NULL); } static struct cmd_results *focus_output(struct sway_seat *seat, int argc, char **argv) { if (!argc) { return cmd_results_new(CMD_INVALID, "Expected 'focus output '."); } char *identifier = join_args(argv, argc); struct sway_output *output = output_by_name_or_id(identifier); if (!output) { enum wlr_direction direction; if (!parse_direction(identifier, &direction)) { free(identifier); return cmd_results_new(CMD_INVALID, "There is no output with that name."); } struct sway_workspace *ws = seat_get_focused_workspace(seat); if (!ws) { free(identifier); return cmd_results_new(CMD_FAILURE, "No focused workspace to base directions off of."); } output = output_get_in_direction(ws->output, direction); if (!output) { int center_lx = ws->output->lx + ws->output->width / 2; int center_ly = ws->output->ly + ws->output->height / 2; struct wlr_output *target = wlr_output_layout_farthest_output( root->output_layout, opposite_direction(direction), ws->output->wlr_output, center_lx, center_ly); if (target) { output = output_from_wlr_output(target); } } } free(identifier); if (output) { seat_set_focus(seat, seat_get_focus_inactive(seat, &output->node)); seat_consider_warp_to_focus(seat); } return cmd_results_new(CMD_SUCCESS, NULL); } static struct cmd_results *focus_parent(void) { struct sway_seat *seat = config->handler_context.seat; struct sway_container *con = config->handler_context.container; if (!con || con->pending.fullscreen_mode) { return cmd_results_new(CMD_SUCCESS, NULL); } struct sway_node *parent = node_get_parent(&con->node); if (parent) { seat_set_focus(seat, parent); seat_consider_warp_to_focus(seat); } return cmd_results_new(CMD_SUCCESS, NULL); } static struct cmd_results *focus_child(void) { struct sway_seat *seat = config->handler_context.seat; struct sway_node *node = config->handler_context.node; struct sway_node *focus = seat_get_active_tiling_child(seat, node); if (focus) { seat_set_focus(seat, focus); seat_consider_warp_to_focus(seat); } return cmd_results_new(CMD_SUCCESS, NULL); } struct cmd_results *cmd_focus(int argc, char **argv) { if (config->reading || !config->active) { return cmd_results_new(CMD_DEFER, NULL); } if (!root->outputs->length) { return cmd_results_new(CMD_INVALID, "Can't run this command while there's no outputs connected."); } struct sway_node *node = config->handler_context.node; struct sway_container *container = config->handler_context.container; struct sway_workspace *workspace = config->handler_context.workspace; struct sway_seat *seat = config->handler_context.seat; if (node->type < N_WORKSPACE) { return cmd_results_new(CMD_FAILURE, "Command 'focus' cannot be used above the workspace level."); } if (argc == 0) { if (!container) { return cmd_results_new(CMD_FAILURE, "No container to focus was specified."); } if (container_is_scratchpad_hidden_or_child(container)) { root_scratchpad_show(container); } // if we are switching to a container under a fullscreen window, we first // need to exit fullscreen so that the newly focused container becomes visible struct sway_container *obstructing = container_obstructing_fullscreen_container(container); if (obstructing) { container_fullscreen_disable(obstructing); arrange_root(); } seat_set_focus_container(seat, container); seat_consider_warp_to_focus(seat); container_raise_floating(container); return cmd_results_new(CMD_SUCCESS, NULL); } if (strcmp(argv[0], "floating") == 0) { return focus_mode(workspace, seat, true); } else if (strcmp(argv[0], "tiling") == 0) { return focus_mode(workspace, seat, false); } else if (strcmp(argv[0], "mode_toggle") == 0) { bool floating = container && container_is_floating_or_child(container); return focus_mode(workspace, seat, !floating); } if (strcmp(argv[0], "output") == 0) { argc--; argv++; return focus_output(seat, argc, argv); } if (strcasecmp(argv[0], "parent") == 0) { return focus_parent(); } if (strcasecmp(argv[0], "child") == 0) { return focus_child(); } enum wlr_direction direction = 0; bool descend = true; if (!parse_direction(argv[0], &direction)) { if (!get_direction_from_next_prev(container, seat, argv[0], &direction)) { return cmd_results_new(CMD_INVALID, "Expected 'focus ' " "or 'focus output '"); } else if (argc == 2 && strcasecmp(argv[1], "sibling") == 0) { descend = false; } } if (!direction) { return cmd_results_new(CMD_SUCCESS, NULL); } if (node->type == N_WORKSPACE) { // Jump to the next output struct sway_output *new_output = output_get_in_direction(workspace->output, direction); if (!new_output) { return cmd_results_new(CMD_SUCCESS, NULL); } struct sway_node *node = get_node_in_output_direction(new_output, direction); seat_set_focus(seat, node); seat_consider_warp_to_focus(seat); return cmd_results_new(CMD_SUCCESS, NULL); } if (!sway_assert(container, "Expected container to be non null")) { return cmd_results_new(CMD_FAILURE, ""); } struct sway_node *next_focus = NULL; if (container_is_floating(container) && container->pending.fullscreen_mode == FULLSCREEN_NONE) { next_focus = node_get_in_direction_floating(container, seat, direction); } else { next_focus = node_get_in_direction_tiling(container, seat, direction, descend); } if (next_focus) { seat_set_focus(seat, next_focus); seat_consider_warp_to_focus(seat); if (next_focus->type == N_CONTAINER) { container_raise_floating(next_focus->sway_container); } } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/focus_follows_mouse.c ================================================ #include #include #include "sway/commands.h" #include "util.h" struct cmd_results *cmd_focus_follows_mouse(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "focus_follows_mouse", EXPECTED_EQUAL_TO, 1))) { return error; } else if(strcmp(argv[0], "no") == 0) { config->focus_follows_mouse = FOLLOWS_NO; } else if(strcmp(argv[0], "yes") == 0) { config->focus_follows_mouse = FOLLOWS_YES; } else if(strcmp(argv[0], "always") == 0) { config->focus_follows_mouse = FOLLOWS_ALWAYS; } else { return cmd_results_new(CMD_FAILURE, "Expected 'focus_follows_mouse no|yes|always'"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/focus_on_window_activation.c ================================================ #include "sway/commands.h" struct cmd_results *cmd_focus_on_window_activation(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "focus_on_window_activation", EXPECTED_EQUAL_TO, 1))) { return error; } if (strcmp(argv[0], "smart") == 0) { config->focus_on_window_activation = FOWA_SMART; } else if (strcmp(argv[0], "urgent") == 0) { config->focus_on_window_activation = FOWA_URGENT; } else if (strcmp(argv[0], "focus") == 0) { config->focus_on_window_activation = FOWA_FOCUS; } else if (strcmp(argv[0], "none") == 0) { config->focus_on_window_activation = FOWA_NONE; } else { return cmd_results_new(CMD_INVALID, "Expected " "'focus_on_window_activation smart|urgent|focus|none'"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/focus_wrapping.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "util.h" struct cmd_results *cmd_focus_wrapping(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "focus_wrapping", EXPECTED_EQUAL_TO, 1))) { return error; } if (strcasecmp(argv[0], "force") == 0) { config->focus_wrapping = WRAP_FORCE; } else if (strcasecmp(argv[0], "workspace") == 0) { config->focus_wrapping = WRAP_WORKSPACE; } else if (parse_boolean(argv[0], config->focus_wrapping == WRAP_YES)) { config->focus_wrapping = WRAP_YES; } else { config->focus_wrapping = WRAP_NO; } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/font.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "log.h" #include "stringop.h" #include struct cmd_results *cmd_font(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "font", EXPECTED_AT_LEAST, 1))) { return error; } char *font = join_args(argv, argc); free(config->font); if (has_prefix(font, "pango:")) { config->pango_markup = true; config->font = strdup(font + strlen("pango:")); free(font); } else { config->pango_markup = false; config->font = font; } // Parse the font early so we can reject it if it's not valid for pango. // Also avoids re-parsing each time we render text. PangoFontDescription *font_description = pango_font_description_from_string(config->font); const char *family = pango_font_description_get_family(font_description); if (family == NULL) { pango_font_description_free(font_description); return cmd_results_new(CMD_FAILURE, "Invalid font family."); } const gint size = pango_font_description_get_size(font_description); if (size == 0) { pango_font_description_free(font_description); return cmd_results_new(CMD_FAILURE, "Invalid font size."); } if (config->font_description != NULL) { pango_font_description_free(config->font_description); } config->font_description = font_description; config_update_font_height(); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/for_window.c ================================================ #include #include "sway/commands.h" #include "sway/criteria.h" #include "list.h" #include "log.h" #include "stringop.h" struct cmd_results *cmd_for_window(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "for_window", EXPECTED_AT_LEAST, 2))) { return error; } char *err_str = NULL; struct criteria *criteria = criteria_parse(argv[0], &err_str); if (!criteria) { error = cmd_results_new(CMD_INVALID, "%s", err_str); free(err_str); return error; } criteria->type = CT_COMMAND; criteria->cmdlist = join_args(argv + 1, argc - 1); // Check if it already exists if (criteria_already_exists(criteria)) { sway_log(SWAY_DEBUG, "for_window already exists: '%s' -> '%s'", criteria->raw, criteria->cmdlist); criteria_destroy(criteria); return cmd_results_new(CMD_SUCCESS, NULL); } list_add(config->criteria, criteria); sway_log(SWAY_DEBUG, "for_window: '%s' -> '%s' added", criteria->raw, criteria->cmdlist); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/force_display_urgency_hint.c ================================================ #include "sway/commands.h" #include "sway/config.h" #include struct cmd_results *cmd_force_display_urgency_hint(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "force_display_urgency_hint", EXPECTED_AT_LEAST, 1))) { return error; } errno = 0; char *end; int timeout = (int)strtol(argv[0], &end, 10); if (errno || end == argv[0] || (*end && strcmp(end, "ms") != 0)) { return cmd_results_new(CMD_INVALID, "timeout integer invalid"); } if (argc > 1 && strcmp(argv[1], "ms") != 0) { return cmd_results_new(CMD_INVALID, "Expected 'force_display_urgency_hint [ms]'"); } config->urgent_timeout = timeout > 0 ? timeout : 0; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/force_focus_wrapping.c ================================================ #include "sway/commands.h" #include "sway/config.h" #include "log.h" #include "util.h" struct cmd_results *cmd_force_focus_wrapping(int argc, char **argv) { sway_log(SWAY_INFO, "Warning: force_focus_wrapping is deprecated. " "Use focus_wrapping instead."); if (config->reading) { config_add_swaynag_warning("force_focus_wrapping is deprecated. " "Use focus_wrapping instead."); } struct cmd_results *error = checkarg(argc, "force_focus_wrapping", EXPECTED_EQUAL_TO, 1); if (error) { return error; } if (parse_boolean(argv[0], config->focus_wrapping == WRAP_FORCE)) { config->focus_wrapping = WRAP_FORCE; } else { config->focus_wrapping = WRAP_YES; } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/fullscreen.c ================================================ #include #include "log.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "util.h" // fullscreen [enable|disable|toggle] [global] struct cmd_results *cmd_fullscreen(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "fullscreen", EXPECTED_AT_MOST, 2))) { return error; } if (!root->outputs->length) { return cmd_results_new(CMD_FAILURE, "Can't run this command while there's no outputs connected."); } struct sway_container *container = config->handler_context.container; if (!container) { // If the focus is not a container, do nothing successfully return cmd_results_new(CMD_SUCCESS, NULL); } else if (!container->pending.workspace) { // If in the scratchpad, operate on the highest container while (container->pending.parent) { container = container->pending.parent; } } bool is_fullscreen = container->pending.fullscreen_mode != FULLSCREEN_NONE; bool global = false; bool enable = !is_fullscreen; if (argc >= 1) { if (strcasecmp(argv[0], "global") == 0) { global = true; } else { enable = parse_boolean(argv[0], is_fullscreen); } } if (argc >= 2) { global = strcasecmp(argv[1], "global") == 0; } enum sway_fullscreen_mode mode = FULLSCREEN_NONE; if (enable) { mode = global ? FULLSCREEN_GLOBAL : FULLSCREEN_WORKSPACE; } container_set_fullscreen(container, mode); arrange_root(); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/gaps.c ================================================ #include #include #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/arrange.h" #include "sway/tree/workspace.h" #include "log.h" #include "stringop.h" #include enum gaps_op { GAPS_OP_SET, GAPS_OP_ADD, GAPS_OP_SUBTRACT, GAPS_OP_TOGGLE }; struct gaps_data { bool inner; struct { bool top; bool right; bool bottom; bool left; } outer; enum gaps_op operation; int amount; }; // Prevent negative outer gaps from moving windows out of the workspace. static void prevent_invalid_outer_gaps(void) { if (config->gaps_outer.top < -config->gaps_inner) { config->gaps_outer.top = -config->gaps_inner; } if (config->gaps_outer.right < -config->gaps_inner) { config->gaps_outer.right = -config->gaps_inner; } if (config->gaps_outer.bottom < -config->gaps_inner) { config->gaps_outer.bottom = -config->gaps_inner; } if (config->gaps_outer.left < -config->gaps_inner) { config->gaps_outer.left = -config->gaps_inner; } } // gaps inner|outer|horizontal|vertical|top|right|bottom|left static const char expected_defaults[] = "'gaps inner|outer|horizontal|vertical|top|right|bottom|left '"; static struct cmd_results *gaps_set_defaults(int argc, char **argv) { struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_EQUAL_TO, 2); if (error) { return error; } char *end; int amount = strtol(argv[1], &end, 10); if (strlen(end) && strcasecmp(end, "px") != 0) { return cmd_results_new(CMD_INVALID, "Expected %s", expected_defaults); } bool valid = false; if (!strcasecmp(argv[0], "inner")) { valid = true; config->gaps_inner = (amount >= 0) ? amount : 0; } else { if (!strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "vertical") || !strcasecmp(argv[0], "top")) { valid = true; config->gaps_outer.top = amount; } if (!strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "horizontal") || !strcasecmp(argv[0], "right")) { valid = true; config->gaps_outer.right = amount; } if (!strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "vertical") || !strcasecmp(argv[0], "bottom")) { valid = true; config->gaps_outer.bottom = amount; } if (!strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "horizontal") || !strcasecmp(argv[0], "left")) { valid = true; config->gaps_outer.left = amount; } } if (!valid) { return cmd_results_new(CMD_INVALID, "Expected %s", expected_defaults); } prevent_invalid_outer_gaps(); return cmd_results_new(CMD_SUCCESS, NULL); } static void apply_gaps_op(int *prop, enum gaps_op op, int amount) { switch (op) { case GAPS_OP_SET: *prop = amount; break; case GAPS_OP_ADD: *prop += amount; break; case GAPS_OP_SUBTRACT: *prop -= amount; break; case GAPS_OP_TOGGLE: *prop = *prop ? 0 : amount; break; } } static void configure_gaps(struct sway_workspace *ws, void *_data) { // Apply operation to gaps struct gaps_data *data = _data; if (data->inner) { apply_gaps_op(&ws->gaps_inner, data->operation, data->amount); } if (data->outer.top) { apply_gaps_op(&(ws->gaps_outer.top), data->operation, data->amount); } if (data->outer.right) { apply_gaps_op(&(ws->gaps_outer.right), data->operation, data->amount); } if (data->outer.bottom) { apply_gaps_op(&(ws->gaps_outer.bottom), data->operation, data->amount); } if (data->outer.left) { apply_gaps_op(&(ws->gaps_outer.left), data->operation, data->amount); } // Prevent invalid gaps configurations. if (ws->gaps_inner < 0) { ws->gaps_inner = 0; } prevent_invalid_outer_gaps(); arrange_workspace(ws); } // gaps inner|outer|horizontal|vertical|top|right|bottom|left current|all // set|plus|minus|toggle static const char expected_runtime[] = "'gaps inner|outer|horizontal|vertical|" "top|right|bottom|left current|all set|plus|minus|toggle '"; static struct cmd_results *gaps_set_runtime(int argc, char **argv) { struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_EQUAL_TO, 4); if (error) { return error; } if (!root->outputs->length) { return cmd_results_new(CMD_INVALID, "Can't run this command while there's no outputs connected."); } struct gaps_data data = {0}; if (strcasecmp(argv[0], "inner") == 0) { data.inner = true; } else { data.outer.top = !strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "vertical") || !strcasecmp(argv[0], "top"); data.outer.right = !strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "horizontal") || !strcasecmp(argv[0], "right"); data.outer.bottom = !strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "vertical") || !strcasecmp(argv[0], "bottom"); data.outer.left = !strcasecmp(argv[0], "outer") || !strcasecmp(argv[0], "horizontal") || !strcasecmp(argv[0], "left"); } if (!data.inner && !data.outer.top && !data.outer.right && !data.outer.bottom && !data.outer.left) { return cmd_results_new(CMD_INVALID, "Expected %s", expected_runtime); } bool all; if (strcasecmp(argv[1], "current") == 0) { all = false; } else if (strcasecmp(argv[1], "all") == 0) { all = true; } else { return cmd_results_new(CMD_INVALID, "Expected %s", expected_runtime); } if (strcasecmp(argv[2], "set") == 0) { data.operation = GAPS_OP_SET; } else if (strcasecmp(argv[2], "plus") == 0) { data.operation = GAPS_OP_ADD; } else if (strcasecmp(argv[2], "minus") == 0) { data.operation = GAPS_OP_SUBTRACT; } else if (strcasecmp(argv[2], "toggle") == 0) { data.operation = GAPS_OP_TOGGLE; } else { return cmd_results_new(CMD_INVALID, "Expected %s", expected_runtime); } char *end; data.amount = strtol(argv[3], &end, 10); if (strlen(end) && strcasecmp(end, "px") != 0) { return cmd_results_new(CMD_INVALID, "Expected %s", expected_runtime); } if (all) { root_for_each_workspace(configure_gaps, &data); } else { configure_gaps(config->handler_context.workspace, &data); } return cmd_results_new(CMD_SUCCESS, NULL); } // gaps inner|outer|| - sets defaults for workspaces // gaps inner|outer|| current|all set|plus|minus|toggle - runtime only // = horizontal|vertical // = top|right|bottom|left struct cmd_results *cmd_gaps(int argc, char **argv) { struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_AT_LEAST, 2); if (error) { return error; } if (argc == 2) { return gaps_set_defaults(argc, argv); } if (argc == 4 && !config->reading) { return gaps_set_runtime(argc, argv); } if (config->reading) { return cmd_results_new(CMD_INVALID, "Expected %s", expected_defaults); } return cmd_results_new(CMD_INVALID, "Expected %s or %s", expected_runtime, expected_defaults); } ================================================ FILE: sway/commands/gesture.c ================================================ #include "sway/config.h" #include "gesture.h" #include "log.h" #include "stringop.h" #include "sway/commands.h" void free_gesture_binding(struct sway_gesture_binding *binding) { if (!binding) { return; } free(binding->input); free(binding->command); free(binding); } /** * Returns true if the bindings have the same gesture type, direction, etc */ static bool binding_gesture_equal(struct sway_gesture_binding *binding_a, struct sway_gesture_binding *binding_b) { if (strcmp(binding_a->input, binding_b->input) != 0) { return false; } if (!gesture_equal(&binding_a->gesture, &binding_b->gesture)) { return false; } if ((binding_a->flags & BINDING_EXACT) != (binding_b->flags & BINDING_EXACT)) { return false; } return true; } /** * Add gesture binding to config */ static struct cmd_results *gesture_binding_add( struct sway_gesture_binding *binding, const char *gesturecombo, bool warn) { list_t *mode_bindings = config->current_mode->gesture_bindings; // overwrite the binding if it already exists bool overwritten = false; for (int i = 0; i < mode_bindings->length; ++i) { struct sway_gesture_binding *config_binding = mode_bindings->items[i]; if (binding_gesture_equal(binding, config_binding)) { sway_log(SWAY_INFO, "Overwriting binding '%s' to `%s` from `%s`", gesturecombo, binding->command, config_binding->command); if (warn) { config_add_swaynag_warning("Overwriting binding" "'%s' to `%s` from `%s`", gesturecombo, binding->command, config_binding->command); } free_gesture_binding(config_binding); mode_bindings->items[i] = binding; overwritten = true; } } if (!overwritten) { list_add(mode_bindings, binding); sway_log(SWAY_DEBUG, "bindgesture - Bound %s to command `%s`", gesturecombo, binding->command); } return cmd_results_new(CMD_SUCCESS, NULL); } /** * Remove gesture binding from config */ static struct cmd_results *gesture_binding_remove( struct sway_gesture_binding *binding, const char *gesturecombo) { list_t *mode_bindings = config->current_mode->gesture_bindings; for (int i = 0; i < mode_bindings->length; ++i) { struct sway_gesture_binding *config_binding = mode_bindings->items[i]; if (binding_gesture_equal(binding, config_binding)) { free_gesture_binding(config_binding); free_gesture_binding(binding); list_del(mode_bindings, i); sway_log(SWAY_DEBUG, "unbindgesture - Unbound %s gesture", gesturecombo); return cmd_results_new(CMD_SUCCESS, NULL); } } free_gesture_binding(binding); return cmd_results_new(CMD_FAILURE, "Could not find gesture binding `%s`", gesturecombo); } /** * Parse and execute bindgesture or unbindgesture command. */ static struct cmd_results *cmd_bind_or_unbind_gesture(int argc, char **argv, bool unbind) { int minargs = 2; char *bindtype = "bindgesture"; if (unbind) { minargs--; bindtype = "unbindgesture"; } struct cmd_results *error = NULL; if ((error = checkarg(argc, bindtype, EXPECTED_AT_LEAST, minargs))) { return error; } struct sway_gesture_binding *binding = calloc(1, sizeof(struct sway_gesture_binding)); if (!binding) { return cmd_results_new(CMD_FAILURE, "Unable to allocate binding"); } binding->input = strdup("*"); bool warn = true; // Handle flags while (argc > 0) { if (strcmp("--exact", argv[0]) == 0) { binding->flags |= BINDING_EXACT; } else if (strcmp("--no-warn", argv[0]) == 0) { warn = false; } else if (has_prefix(argv[0], "--input-device=")) { free(binding->input); binding->input = strdup(argv[0] + strlen("--input-device=")); } else { break; } argv++; argc--; } if (argc < minargs) { free(binding); return cmd_results_new(CMD_FAILURE, "Invalid %s command (expected at least %d " "non-option arguments, got %d)", bindtype, minargs, argc); } char* errmsg = NULL; if ((errmsg = gesture_parse(argv[0], &binding->gesture))) { free(binding); struct cmd_results *final = cmd_results_new(CMD_FAILURE, "Invalid %s command (%s)", bindtype, errmsg); free(errmsg); return final; } if (unbind) { return gesture_binding_remove(binding, argv[0]); } binding->command = join_args(argv + 1, argc - 1); return gesture_binding_add(binding, argv[0], warn); } struct cmd_results *cmd_bindgesture(int argc, char **argv) { return cmd_bind_or_unbind_gesture(argc, argv, false); } struct cmd_results *cmd_unbindgesture(int argc, char **argv) { return cmd_bind_or_unbind_gesture(argc, argv, true); } ================================================ FILE: sway/commands/hide_edge_borders.c ================================================ #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/arrange.h" #include "sway/tree/view.h" struct cmd_results *cmd_hide_edge_borders(int argc, char **argv) { const char *expected_syntax = "Expected 'hide_edge_borders [--i3] " "none|vertical|horizontal|both|smart|smart_no_gaps"; struct cmd_results *error = NULL; if ((error = checkarg(argc, "hide_edge_borders", EXPECTED_AT_LEAST, 1))) { return error; } bool hide_lone_tab = false; if (strcmp(*argv, "--i3") == 0) { hide_lone_tab = true; ++argv; --argc; } if (!argc) { return cmd_results_new(CMD_INVALID, "%s", expected_syntax); } if (strcmp(argv[0], "none") == 0) { config->hide_edge_borders = E_NONE; } else if (strcmp(argv[0], "vertical") == 0) { config->hide_edge_borders = E_VERTICAL; } else if (strcmp(argv[0], "horizontal") == 0) { config->hide_edge_borders = E_HORIZONTAL; } else if (strcmp(argv[0], "both") == 0) { config->hide_edge_borders = E_BOTH; } else if (strcmp(argv[0], "smart") == 0) { config->hide_edge_borders = E_NONE; config->hide_edge_borders_smart = ESMART_ON; } else if (strcmp(argv[0], "smart_no_gaps") == 0) { config->hide_edge_borders = E_NONE; config->hide_edge_borders_smart = ESMART_NO_GAPS; } else { return cmd_results_new(CMD_INVALID, "%s", expected_syntax); } config->hide_lone_tab = hide_lone_tab; arrange_root(); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/include.c ================================================ #include "sway/commands.h" #include "sway/config.h" struct cmd_results *cmd_include(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "include", EXPECTED_AT_LEAST, 1))) { return error; } char *files = join_args(argv, argc); // We don't care if the included config(s) fails to load. load_include_configs(files, config, &config->swaynag_config_errors); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/inhibit_idle.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/desktop/idle_inhibit_v1.h" #include "sway/tree/container.h" #include "sway/tree/view.h" struct cmd_results *cmd_inhibit_idle(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "inhibit_idle", EXPECTED_EQUAL_TO, 1))) { return error; } struct sway_container *con = config->handler_context.container; if (!con || !con->view) { return cmd_results_new(CMD_INVALID, "Only views can have idle inhibitors"); } bool clear = false; enum sway_idle_inhibit_mode mode; if (strcmp(argv[0], "focus") == 0) { mode = INHIBIT_IDLE_FOCUS; } else if (strcmp(argv[0], "fullscreen") == 0) { mode = INHIBIT_IDLE_FULLSCREEN; } else if (strcmp(argv[0], "open") == 0) { mode = INHIBIT_IDLE_OPEN; } else if (strcmp(argv[0], "none") == 0) { clear = true; } else if (strcmp(argv[0], "visible") == 0) { mode = INHIBIT_IDLE_VISIBLE; } else { return cmd_results_new(CMD_INVALID, "Expected `inhibit_idle focus|fullscreen|open|none|visible`"); } struct sway_idle_inhibitor_v1 *inhibitor = sway_idle_inhibit_v1_user_inhibitor_for_view(con->view); if (inhibitor) { if (clear) { sway_idle_inhibit_v1_user_inhibitor_destroy(inhibitor); } else { inhibitor->mode = mode; sway_idle_inhibit_v1_check_active(); } } else if (!clear) { sway_idle_inhibit_v1_user_inhibitor_register(con->view, mode); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/accel_profile.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" struct cmd_results *input_cmd_accel_profile(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "accel_profile", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (strcasecmp(argv[0], "adaptive") == 0) { ic->accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; } else if (strcasecmp(argv[0], "flat") == 0) { ic->accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; } else { return cmd_results_new(CMD_INVALID, "Expected 'accel_profile '"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/calibration_matrix.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "log.h" #include "stringop.h" #include "util.h" struct cmd_results *input_cmd_calibration_matrix(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "calibration_matrix", EXPECTED_EQUAL_TO, 6))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } float parsed[6]; for (int i = 0; i < argc; ++i) { char *item = argv[i]; float x = parse_float(item); if (isnan(x)) { return cmd_results_new(CMD_FAILURE, "calibration_matrix: unable to parse float: %s", item); } parsed[i] = x; } ic->calibration_matrix.configured = true; memcpy(ic->calibration_matrix.matrix, parsed, sizeof(ic->calibration_matrix.matrix)); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/click_method.c ================================================ #include #include #include "sway/commands.h" #include "sway/config.h" #include "sway/input/input-manager.h" #include "log.h" struct cmd_results *input_cmd_click_method(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "click_method", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (strcasecmp(argv[0], "none") == 0) { ic->click_method = LIBINPUT_CONFIG_CLICK_METHOD_NONE; } else if (strcasecmp(argv[0], "button_areas") == 0) { ic->click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; } else if (strcasecmp(argv[0], "clickfinger") == 0) { ic->click_method = LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER; } else { return cmd_results_new(CMD_INVALID, "Expected 'click_method '"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/clickfinger_button_map.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" struct cmd_results *input_cmd_clickfinger_button_map(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "clickfinger_button_map", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (strcasecmp(argv[0], "lrm") == 0) { ic->clickfinger_button_map = LIBINPUT_CONFIG_CLICKFINGER_MAP_LRM; } else if (strcasecmp(argv[0], "lmr") == 0) { ic->clickfinger_button_map = LIBINPUT_CONFIG_CLICKFINGER_MAP_LMR; } else { return cmd_results_new(CMD_INVALID, "Expected 'clickfinger_button_map '"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/drag.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "util.h" struct cmd_results *input_cmd_drag(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "drag", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (parse_boolean(argv[0], true)) { ic->drag = LIBINPUT_CONFIG_DRAG_ENABLED; } else { ic->drag = LIBINPUT_CONFIG_DRAG_DISABLED; } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/drag_lock.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "util.h" struct cmd_results *input_cmd_drag_lock(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "drag_lock", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } #if HAVE_LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY if (strcmp(argv[0], "enabled_sticky") == 0) { ic->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY; } else #endif if (parse_boolean(argv[0], true)) { ic->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_ENABLED; } else { ic->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/dwt.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "util.h" struct cmd_results *input_cmd_dwt(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "dwt", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (parse_boolean(argv[0], true)) { ic->dwt = LIBINPUT_CONFIG_DWT_ENABLED; } else { ic->dwt = LIBINPUT_CONFIG_DWT_DISABLED; } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/dwtp.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "util.h" struct cmd_results *input_cmd_dwtp(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "dwtp", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (parse_boolean(argv[0], true)) { ic->dwtp = LIBINPUT_CONFIG_DWTP_ENABLED; } else { ic->dwtp = LIBINPUT_CONFIG_DWTP_DISABLED; } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/events.c ================================================ #include #include #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "sway/server.h" #include "log.h" #if WLR_HAS_LIBINPUT_BACKEND #include #endif static void toggle_supported_send_events_for_device(struct input_config *ic, struct sway_input_device *input_device) { #if WLR_HAS_LIBINPUT_BACKEND struct wlr_input_device *wlr_device = input_device->wlr_device; if (!wlr_input_device_is_libinput(wlr_device)) { return; } struct libinput_device *libinput_dev = wlr_libinput_get_device_handle(wlr_device); enum libinput_config_send_events_mode mode = libinput_device_config_send_events_get_mode(libinput_dev); uint32_t possible = libinput_device_config_send_events_get_modes(libinput_dev); switch (mode) { case LIBINPUT_CONFIG_SEND_EVENTS_ENABLED: mode = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; if (possible & mode) { break; } // fall through case LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE: mode = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; if (possible & mode) { break; } // fall through case LIBINPUT_CONFIG_SEND_EVENTS_DISABLED: default: mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; break; } ic->send_events = mode; #endif } static int mode_for_name(const char *name) { if (!strcmp(name, "enabled")) { return LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; } else if (!strcmp(name, "disabled_on_external_mouse")) { return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; } else if (!strcmp(name, "disabled")) { return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; } return -1; } static void toggle_select_send_events_for_device(struct input_config *ic, struct sway_input_device *input_device, int argc, char **argv) { #if WLR_HAS_LIBINPUT_BACKEND if (!wlr_input_device_is_libinput(input_device->wlr_device)) { return; } // Get the currently set event mode since ic is a new config that will be // merged on the existing later. It should be set to INT_MIN before this. ic->send_events = libinput_device_config_send_events_get_mode( wlr_libinput_get_device_handle(input_device->wlr_device)); int index; for (index = 0; index < argc; ++index) { if (mode_for_name(argv[index]) == ic->send_events) { ++index; break; } } ic->send_events = mode_for_name(argv[index % argc]); #endif } static void toggle_send_events(int argc, char **argv) { struct input_config *ic = config->handler_context.input_config; bool wildcard = strcmp(ic->identifier, "*") == 0; const char *type = has_prefix(ic->identifier, "type:") ? ic->identifier + strlen("type:") : NULL; struct sway_input_device *device = NULL; wl_list_for_each(device, &server.input->devices, link) { if (wildcard || type) { ic = new_input_config(device->identifier); if (!ic) { continue; } if (type && strcmp(input_device_get_type(device), type) != 0) { continue; } } else if (strcmp(ic->identifier, device->identifier) != 0) { continue; } if (argc) { toggle_select_send_events_for_device(ic, device, argc, argv); } else { toggle_supported_send_events_for_device(ic, device); } if (wildcard || type) { store_input_config(ic, NULL); } else { return; } } } struct cmd_results *input_cmd_events(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "events", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (strcasecmp(argv[0], "enabled") == 0) { ic->send_events = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; } else if (strcasecmp(argv[0], "disabled") == 0) { ic->send_events = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; } else if (strcasecmp(argv[0], "disabled_on_external_mouse") == 0) { ic->send_events = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; } else if (config->reading) { return cmd_results_new(CMD_INVALID, "Expected 'events '"); } else if (strcasecmp(argv[0], "toggle") == 0) { for (int i = 1; i < argc; ++i) { if (mode_for_name(argv[i]) == -1) { return cmd_results_new(CMD_INVALID, "Invalid toggle mode %s", argv[i]); } } toggle_send_events(argc - 1, argv + 1); if (strcmp(ic->identifier, "*") == 0 || has_prefix(ic->identifier, "type:")) { // Update the device input configs and then reset the type/wildcard // config send events mode so that is does not override the device // ones. The device ones will be applied when attempting to apply // the type/wildcard config ic->send_events = INT_MIN; } } else { return cmd_results_new(CMD_INVALID, "Expected 'events '"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/left_handed.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "util.h" struct cmd_results *input_cmd_left_handed(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "left_handed", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } ic->left_handed = parse_boolean(argv[0], true); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/map_from_region.c ================================================ #include #include #include #include "log.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/input/input-manager.h" static bool parse_coords(const char *str, double *x, double *y, bool *mm) { *mm = false; char *end; // Check for "0x" prefix to avoid strtod treating the string as hex if (str[0] == '0' && str[1] == 'x') { if (strlen(str) < 3) { return false; } *x = 0; end = (char *)str + 2; } else { *x = strtod(str, &end); if (end[0] != 'x') { return false; } ++end; } *y = strtod(end, &end); if (end[0] == 'm') { // Expect mm if (end[1] != 'm') { return false; } *mm = true; end = &end[2]; } if (end[0] != '\0') { return false; } return true; } struct cmd_results *input_cmd_map_from_region(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "map_from_region", EXPECTED_EQUAL_TO, 2))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined"); } ic->mapped_from_region = calloc(1, sizeof(struct input_config_mapped_from_region)); bool mm1, mm2; if (!parse_coords(argv[0], &ic->mapped_from_region->x1, &ic->mapped_from_region->y1, &mm1)) { free(ic->mapped_from_region); ic->mapped_from_region = NULL; return cmd_results_new(CMD_FAILURE, "Invalid top-left coordinates"); } if (!parse_coords(argv[1], &ic->mapped_from_region->x2, &ic->mapped_from_region->y2, &mm2)) { free(ic->mapped_from_region); ic->mapped_from_region = NULL; return cmd_results_new(CMD_FAILURE, "Invalid bottom-right coordinates"); } if (ic->mapped_from_region->x1 > ic->mapped_from_region->x2 || ic->mapped_from_region->y1 > ic->mapped_from_region->y2) { free(ic->mapped_from_region); ic->mapped_from_region = NULL; return cmd_results_new(CMD_FAILURE, "Invalid rectangle"); } if (mm1 != mm2) { free(ic->mapped_from_region); ic->mapped_from_region = NULL; return cmd_results_new(CMD_FAILURE, "Both coordinates must be in the same unit"); } ic->mapped_from_region->mm = mm1; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/map_to_output.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "log.h" struct cmd_results *input_cmd_map_to_output(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "map_to_output", EXPECTED_EQUAL_TO, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } ic->mapped_to = MAPPED_TO_OUTPUT; ic->mapped_to_output = strdup(argv[0]); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/map_to_region.c ================================================ #include #include #include "sway/commands.h" #include "sway/config.h" struct cmd_results *input_cmd_map_to_region(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "map_to_region", EXPECTED_EQUAL_TO, 4))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined"); } ic->mapped_to = MAPPED_TO_REGION; ic->mapped_to_region = calloc(1, sizeof(struct wlr_box)); const char *errstr; char *end; ic->mapped_to_region->x = strtol(argv[0], &end, 10); if (end[0] != '\0') { errstr = "Invalid X coordinate"; goto error; } ic->mapped_to_region->y = strtol(argv[1], &end, 10); if (end[0] != '\0') { errstr = "Invalid Y coordinate"; goto error; } ic->mapped_to_region->width = strtol(argv[2], &end, 10); if (end[0] != '\0' || ic->mapped_to_region->width < 1) { errstr = "Invalid width"; goto error; } ic->mapped_to_region->height = strtol(argv[3], &end, 10); if (end[0] != '\0' || ic->mapped_to_region->height < 1) { errstr = "Invalid height"; goto error; } return cmd_results_new(CMD_SUCCESS, NULL); error: free(ic->mapped_to_region); ic->mapped_to_region = NULL; return cmd_results_new(CMD_FAILURE, "%s", errstr); } ================================================ FILE: sway/commands/input/middle_emulation.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "util.h" struct cmd_results *input_cmd_middle_emulation(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "middle_emulation", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (parse_boolean(argv[0], true)) { ic->middle_emulation = LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED; } else { ic->middle_emulation = LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/natural_scroll.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "util.h" struct cmd_results *input_cmd_natural_scroll(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "natural_scroll", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } ic->natural_scroll = parse_boolean(argv[0], true); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/pointer_accel.c ================================================ #include #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "util.h" struct cmd_results *input_cmd_pointer_accel(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "pointer_accel", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } float pointer_accel = parse_float(argv[0]); if (isnan(pointer_accel)) { return cmd_results_new(CMD_INVALID, "Invalid pointer accel; expected float."); } if (pointer_accel < -1 || pointer_accel > 1) { return cmd_results_new(CMD_INVALID, "Input out of range [-1, 1]"); } ic->pointer_accel = pointer_accel; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/repeat_delay.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" struct cmd_results *input_cmd_repeat_delay(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "repeat_delay", EXPECTED_EQUAL_TO, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } int repeat_delay = atoi(argv[0]); if (repeat_delay < 0) { return cmd_results_new(CMD_INVALID, "Repeat delay cannot be negative"); } ic->repeat_delay = repeat_delay; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/repeat_rate.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" struct cmd_results *input_cmd_repeat_rate(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "repeat_rate", EXPECTED_EQUAL_TO, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } int repeat_rate = atoi(argv[0]); if (repeat_rate < 0) { return cmd_results_new(CMD_INVALID, "Repeat rate cannot be negative"); } ic->repeat_rate = repeat_rate; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/rotation_angle.c ================================================ #include #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "util.h" struct cmd_results *input_cmd_rotation_angle(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "rotation_angle", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } float rotation_angle = parse_float(argv[0]); if (isnan(rotation_angle)) { return cmd_results_new(CMD_INVALID, "Invalid rotation_angle; expected float."); } if (rotation_angle < 0 || rotation_angle > 360) { return cmd_results_new(CMD_INVALID, "Input out of range [0, 360)"); } ic->rotation_angle = rotation_angle; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/scroll_button.c ================================================ #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/cursor.h" struct cmd_results *input_cmd_scroll_button(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "scroll_button", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (strcmp(*argv, "disable") == 0) { ic->scroll_button = 0; return cmd_results_new(CMD_SUCCESS, NULL); } char *message = NULL; uint32_t button = get_mouse_button(*argv, &message); if (message) { error = cmd_results_new(CMD_INVALID, "%s", message); free(message); return error; } else if (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_DOWN || button == SWAY_SCROLL_LEFT || button == SWAY_SCROLL_RIGHT) { return cmd_results_new(CMD_INVALID, "X11 axis buttons are not supported for scroll_button"); } else if (!button) { return cmd_results_new(CMD_INVALID, "Unknown button %s", *argv); } ic->scroll_button = button; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/scroll_button_lock.c ================================================ #include #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "util.h" struct cmd_results *input_cmd_scroll_button_lock(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "scroll_button_lock", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (parse_boolean(argv[0], true)) { ic->scroll_button_lock = LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED; } else { ic->scroll_button_lock = LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED; } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/scroll_factor.c ================================================ #include #include #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "util.h" struct cmd_results *input_cmd_scroll_factor(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "scroll_factor", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } float scroll_factor = parse_float(argv[0]); if (isnan(scroll_factor)) { return cmd_results_new(CMD_INVALID, "Invalid scroll factor; expected float."); } else if (scroll_factor < 0) { return cmd_results_new(CMD_INVALID, "Scroll factor cannot be negative."); } ic->scroll_factor = scroll_factor; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/scroll_method.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" struct cmd_results *input_cmd_scroll_method(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "scroll_method", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (strcasecmp(argv[0], "none") == 0) { ic->scroll_method = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; } else if (strcasecmp(argv[0], "two_finger") == 0) { ic->scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; } else if (strcasecmp(argv[0], "edge") == 0) { ic->scroll_method = LIBINPUT_CONFIG_SCROLL_EDGE; } else if (strcasecmp(argv[0], "on_button_down") == 0) { ic->scroll_method = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; } else { return cmd_results_new(CMD_INVALID, "Expected 'scroll_method '"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/tap.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "log.h" #include "util.h" struct cmd_results *input_cmd_tap(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "tap", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (parse_boolean(argv[0], true)) { ic->tap = LIBINPUT_CONFIG_TAP_ENABLED; } else { ic->tap = LIBINPUT_CONFIG_TAP_DISABLED; } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/tap_button_map.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" struct cmd_results *input_cmd_tap_button_map(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "tap_button_map", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (strcasecmp(argv[0], "lrm") == 0) { ic->tap_button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; } else if (strcasecmp(argv[0], "lmr") == 0) { ic->tap_button_map = LIBINPUT_CONFIG_TAP_MAP_LMR; } else { return cmd_results_new(CMD_INVALID, "Expected 'tap_button_map '"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/tool_mode.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" static void set_tool_mode(struct input_config *ic, enum wlr_tablet_tool_type type, enum sway_tablet_tool_mode mode) { for (int i = 0; i < ic->tools->length; i++) { struct input_config_tool *tool = ic->tools->items[i]; if (tool->type == type) { tool->mode = mode; return; } } struct input_config_tool *tool = calloc(1, sizeof(*tool)); if (tool) { tool->type = type; tool->mode = mode; list_add(ic->tools, tool); } } struct cmd_results *input_cmd_tool_mode(int argc, char **argv) { struct cmd_results *error; if ((error = checkarg(argc, "tool_mode", EXPECTED_AT_LEAST, 2))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } enum sway_tablet_tool_mode tool_mode; if (!strcasecmp(argv[1], "absolute")) { tool_mode = SWAY_TABLET_TOOL_MODE_ABSOLUTE; } else if (!strcasecmp(argv[1], "relative")) { tool_mode = SWAY_TABLET_TOOL_MODE_RELATIVE; } else { goto invalid_command; } if (!strcasecmp(argv[0], "*")) { set_tool_mode(ic, WLR_TABLET_TOOL_TYPE_PEN, tool_mode); set_tool_mode(ic, WLR_TABLET_TOOL_TYPE_ERASER, tool_mode); set_tool_mode(ic, WLR_TABLET_TOOL_TYPE_BRUSH, tool_mode); set_tool_mode(ic, WLR_TABLET_TOOL_TYPE_PENCIL, tool_mode); set_tool_mode(ic, WLR_TABLET_TOOL_TYPE_AIRBRUSH, tool_mode); } else { enum wlr_tablet_tool_type tool_type; if (!strcasecmp(argv[0], "pen")) { tool_type = WLR_TABLET_TOOL_TYPE_PEN; } else if (!strcasecmp(argv[0], "eraser")) { tool_type = WLR_TABLET_TOOL_TYPE_ERASER; } else if (!strcasecmp(argv[0], "brush")) { tool_type = WLR_TABLET_TOOL_TYPE_BRUSH; } else if (!strcasecmp(argv[0], "pencil")) { tool_type = WLR_TABLET_TOOL_TYPE_PENCIL; } else if (!strcasecmp(argv[0], "airbrush")) { tool_type = WLR_TABLET_TOOL_TYPE_AIRBRUSH; } else { goto invalid_command; } set_tool_mode(ic, tool_type, tool_mode); } return cmd_results_new(CMD_SUCCESS, NULL); invalid_command: return cmd_results_new(CMD_INVALID, "Expected 'tool_mode '"); } ================================================ FILE: sway/commands/input/xkb_capslock.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "util.h" struct cmd_results *input_cmd_xkb_capslock(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "xkb_capslock", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } ic->xkb_capslock = parse_boolean(argv[0], false); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/xkb_file.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "log.h" #include "stringop.h" struct cmd_results *input_cmd_xkb_file(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "xkb_file", EXPECTED_EQUAL_TO, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (strcmp(argv[0], "-") == 0) { free(ic->xkb_file); ic->xkb_file = NULL; } else { ic->xkb_file = strdup(argv[0]); if (!expand_path(&ic->xkb_file)) { error = cmd_results_new(CMD_INVALID, "Invalid syntax (%s)", ic->xkb_file); free(ic->xkb_file); ic->xkb_file = NULL; return error; } if (!ic->xkb_file) { sway_log(SWAY_ERROR, "Failed to allocate expanded path"); return cmd_results_new(CMD_FAILURE, "Unable to allocate resource"); } bool can_access = access(ic->xkb_file, F_OK) != -1; if (!can_access) { sway_log_errno(SWAY_ERROR, "Unable to access xkb file '%s'", ic->xkb_file); config_add_swaynag_warning("Unable to access xkb file '%s'", ic->xkb_file); } } ic->xkb_file_is_set = true; sway_log(SWAY_DEBUG, "set-xkb_file for config: %s file: %s", ic->identifier, ic->xkb_file); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/xkb_layout.c ================================================ #include "sway/config.h" #include "sway/commands.h" #include "log.h" struct cmd_results *input_cmd_xkb_layout(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "xkb_layout", EXPECTED_EQUAL_TO, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } ic->xkb_layout = strdup(argv[0]); sway_log(SWAY_DEBUG, "set-xkb_layout for config: %s layout: %s", ic->identifier, ic->xkb_layout); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/xkb_model.c ================================================ #include "sway/config.h" #include "sway/commands.h" #include "log.h" struct cmd_results *input_cmd_xkb_model(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "xkb_model", EXPECTED_EQUAL_TO, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } ic->xkb_model = strdup(argv[0]); sway_log(SWAY_DEBUG, "set-xkb_model for config: %s model: %s", ic->identifier, ic->xkb_model); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/xkb_numlock.c ================================================ #include "sway/config.h" #include "sway/commands.h" #include "util.h" struct cmd_results *input_cmd_xkb_numlock(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "xkb_numlock", EXPECTED_AT_LEAST, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } ic->xkb_numlock = parse_boolean(argv[0], false); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/xkb_options.c ================================================ #include "sway/config.h" #include "sway/commands.h" #include "log.h" struct cmd_results *input_cmd_xkb_options(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "xkb_options", EXPECTED_EQUAL_TO, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } ic->xkb_options = strdup(argv[0]); sway_log(SWAY_DEBUG, "set-xkb_options for config: %s options: %s", ic->identifier, ic->xkb_options); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/xkb_rules.c ================================================ #include "sway/config.h" #include "sway/commands.h" #include "log.h" struct cmd_results *input_cmd_xkb_rules(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "xkb_rules", EXPECTED_EQUAL_TO, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } ic->xkb_rules = strdup(argv[0]); sway_log(SWAY_DEBUG, "set-xkb_rules for config: %s rules: %s", ic->identifier, ic->xkb_rules); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/xkb_switch_layout.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" #include "sway/server.h" #include "log.h" struct xkb_switch_layout_action { struct wlr_keyboard *keyboard; xkb_layout_index_t layout; }; static void switch_layout(struct wlr_keyboard *kbd, xkb_layout_index_t idx) { xkb_layout_index_t num_layouts = xkb_keymap_num_layouts(kbd->keymap); if (idx >= num_layouts) { return; } wlr_keyboard_notify_modifiers(kbd, kbd->modifiers.depressed, kbd->modifiers.latched, kbd->modifiers.locked, idx); } static xkb_layout_index_t get_current_layout_index(struct wlr_keyboard *kbd) { xkb_layout_index_t num_layouts = xkb_keymap_num_layouts(kbd->keymap); assert(num_layouts > 0); xkb_layout_index_t layout_idx; for (layout_idx = 0; layout_idx < num_layouts; layout_idx++) { if (xkb_state_layout_index_is_active(kbd->xkb_state, layout_idx, XKB_STATE_LAYOUT_EFFECTIVE)) { break; } } return layout_idx; } static xkb_layout_index_t get_layout_relative(struct wlr_keyboard *kbd, int dir) { xkb_layout_index_t num_layouts = xkb_keymap_num_layouts(kbd->keymap); xkb_layout_index_t idx = get_current_layout_index(kbd); return (idx + num_layouts + dir) % num_layouts; } struct cmd_results *input_cmd_xkb_switch_layout(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "xkb_switch_layout", EXPECTED_EQUAL_TO, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } if (config->reading || !config->active) { return cmd_results_new(CMD_DEFER, NULL); } const char *layout_str = argv[0]; int relative, layout; if (strcmp(layout_str, "next") == 0) { relative = 1; } else if (strcmp(layout_str, "prev") == 0) { relative = -1; } else { char *end; layout = strtol(layout_str, &end, 10); if (layout_str[0] == '\0' || end[0] != '\0') { return cmd_results_new(CMD_FAILURE, "Invalid argument."); } else if (layout < 0) { return cmd_results_new(CMD_FAILURE, "Invalid layout index."); } relative = 0; } struct xkb_switch_layout_action *actions = calloc( wl_list_length(&server.input->devices), sizeof(struct xkb_switch_layout_action)); size_t actions_len = 0; if (!actions) { return cmd_results_new(CMD_FAILURE, "Unable to allocate actions"); } /* Calculate new indexes first because switching a layout in one keyboard may result in a change on other keyboards as well because of keyboard groups. */ struct sway_input_device *dev; wl_list_for_each(dev, &server.input->devices, link) { if (strcmp(ic->identifier, "*") != 0 && strcmp(ic->identifier, "type:keyboard") != 0 && strcmp(ic->identifier, dev->identifier) != 0) { continue; } if (dev->wlr_device->type != WLR_INPUT_DEVICE_KEYBOARD) { continue; } struct wlr_keyboard *keyboard = wlr_keyboard_from_input_device(dev->wlr_device); if (keyboard->keymap == NULL && dev->is_virtual) { // The `sway_keyboard_set_layout` function is by default skipped // when configuring virtual keyboards. continue; } struct xkb_switch_layout_action *action = &actions[actions_len++]; action->keyboard = keyboard; if (relative) { action->layout = get_layout_relative(action->keyboard, relative); } else { action->layout = layout; } } for (size_t i = 0; i < actions_len; i++) { switch_layout(actions[i].keyboard, actions[i].layout); } free(actions); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input/xkb_variant.c ================================================ #include "sway/config.h" #include "sway/commands.h" #include "log.h" struct cmd_results *input_cmd_xkb_variant(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "xkb_variant", EXPECTED_EQUAL_TO, 1))) { return error; } struct input_config *ic = config->handler_context.input_config; if (!ic) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } ic->xkb_variant = strdup(argv[0]); sway_log(SWAY_DEBUG, "set-xkb_variant for config: %s variant: %s", ic->identifier, ic->xkb_variant); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/input.c ================================================ #include #include #include "sway/commands.h" #include "sway/input/input-manager.h" #include "sway/input/keyboard.h" #include "log.h" #include "stringop.h" // must be in order for the bsearch static const struct cmd_handler input_handlers[] = { { "accel_profile", input_cmd_accel_profile }, { "calibration_matrix", input_cmd_calibration_matrix }, { "click_method", input_cmd_click_method }, { "clickfinger_button_map", input_cmd_clickfinger_button_map }, { "drag", input_cmd_drag }, { "drag_lock", input_cmd_drag_lock }, { "dwt", input_cmd_dwt }, { "dwtp", input_cmd_dwtp }, { "events", input_cmd_events }, { "left_handed", input_cmd_left_handed }, { "map_from_region", input_cmd_map_from_region }, { "map_to_output", input_cmd_map_to_output }, { "map_to_region", input_cmd_map_to_region }, { "middle_emulation", input_cmd_middle_emulation }, { "natural_scroll", input_cmd_natural_scroll }, { "pointer_accel", input_cmd_pointer_accel }, { "repeat_delay", input_cmd_repeat_delay }, { "repeat_rate", input_cmd_repeat_rate }, { "rotation_angle", input_cmd_rotation_angle }, { "scroll_button", input_cmd_scroll_button }, { "scroll_button_lock", input_cmd_scroll_button_lock }, { "scroll_factor", input_cmd_scroll_factor }, { "scroll_method", input_cmd_scroll_method }, { "tap", input_cmd_tap }, { "tap_button_map", input_cmd_tap_button_map }, { "tool_mode", input_cmd_tool_mode }, { "xkb_file", input_cmd_xkb_file }, { "xkb_layout", input_cmd_xkb_layout }, { "xkb_model", input_cmd_xkb_model }, { "xkb_options", input_cmd_xkb_options }, { "xkb_rules", input_cmd_xkb_rules }, { "xkb_switch_layout", input_cmd_xkb_switch_layout }, { "xkb_variant", input_cmd_xkb_variant }, }; // must be in order for the bsearch static const struct cmd_handler input_config_handlers[] = { { "xkb_capslock", input_cmd_xkb_capslock }, { "xkb_numlock", input_cmd_xkb_numlock }, }; struct cmd_results *cmd_input(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "input", EXPECTED_AT_LEAST, 2))) { return error; } sway_log(SWAY_DEBUG, "entering input block: %s", argv[0]); config->handler_context.input_config = new_input_config(argv[0]); if (!config->handler_context.input_config) { return cmd_results_new(CMD_FAILURE, "Couldn't allocate config"); } struct cmd_results *res; if (find_handler(argv[1], input_config_handlers, sizeof(input_config_handlers))) { if (config->reading) { res = config_subcommand(argv + 1, argc - 1, input_config_handlers, sizeof(input_config_handlers)); } else { res = cmd_results_new(CMD_FAILURE, "Can only be used in config file."); } } else { res = config_subcommand(argv + 1, argc - 1, input_handlers, sizeof(input_handlers)); } if ((!res || res->status == CMD_SUCCESS) && strcmp(argv[1], "xkb_switch_layout") != 0) { char *error = NULL; struct input_config *ic = store_input_config(config->handler_context.input_config, &error); if (!ic) { free_input_config(config->handler_context.input_config); if (res) { free_cmd_results(res); } res = cmd_results_new(CMD_FAILURE, "Failed to compile keymap: %s", error ? error : "(details unavailable)"); free(error); return res; } if (!config->reading) { input_manager_apply_input_config(ic); } } else { free_input_config(config->handler_context.input_config); } config->handler_context.input_config = NULL; return res; } ================================================ FILE: sway/commands/kill.c ================================================ #include "log.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" #include "sway/tree/container.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "sway/commands.h" static void close_container_iterator(struct sway_container *con, void *data) { if (con->view) { view_close(con->view); } } struct cmd_results *cmd_kill(int argc, char **argv) { if (!root->outputs->length) { return cmd_results_new(CMD_INVALID, "Can't run this command while there's no outputs connected."); } struct sway_container *con = config->handler_context.container; struct sway_workspace *ws = config->handler_context.workspace; if (con) { close_container_iterator(con, NULL); container_for_each_child(con, close_container_iterator, NULL); } else { workspace_for_each_container(ws, close_container_iterator, NULL); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/layout.c ================================================ #include #include #include #include "sway/commands.h" #include "sway/output.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/workspace.h" #include "log.h" static enum sway_container_layout parse_layout_string(char *s) { if (strcasecmp(s, "splith") == 0) { return L_HORIZ; } else if (strcasecmp(s, "splitv") == 0) { return L_VERT; } else if (strcasecmp(s, "tabbed") == 0) { return L_TABBED; } else if (strcasecmp(s, "stacking") == 0) { return L_STACKED; } return L_NONE; } static const char expected_syntax[] = "Expected 'layout default|tabbed|stacking|splitv|splith' or " "'layout toggle [split|all]' or " "'layout toggle [split|tabbed|stacking|splitv|splith] [split|tabbed|stacking|splitv|splith]...'"; static enum sway_container_layout toggle_split_layout( enum sway_container_layout layout, enum sway_container_layout prev_split_layout, struct sway_output *output) { if (layout == L_HORIZ) { return L_VERT; } else if (layout == L_VERT) { return L_HORIZ; } else if (prev_split_layout != L_NONE) { return prev_split_layout; } else if (config->default_orientation != L_NONE) { return config->default_orientation; } else if (output->height > output->width) { return L_VERT; } return L_HORIZ; } static enum sway_container_layout get_layout_toggle(int argc, char **argv, enum sway_container_layout layout, enum sway_container_layout prev_split_layout, struct sway_output *output) { // "layout toggle" if (argc == 1) { return toggle_split_layout(layout, prev_split_layout, output); } if (argc == 2) { // "layout toggle split" (same as "layout toggle") if (strcasecmp(argv[1], "split") == 0) { return toggle_split_layout(layout, prev_split_layout, output); } // "layout toggle all" if (strcasecmp(argv[1], "all") == 0) { return layout == L_HORIZ ? L_VERT : layout == L_VERT ? L_STACKED : layout == L_STACKED ? L_TABBED : L_HORIZ; } return L_NONE; } enum sway_container_layout parsed; int curr = 1; for (; curr < argc; curr++) { parsed = parse_layout_string(argv[curr]); if (parsed == layout || (strcmp(argv[curr], "split") == 0 && (layout == L_VERT || layout == L_HORIZ))) { break; } } for (int i = curr + 1; i != curr; ++i) { // cycle round to find next valid layout if (i >= argc) { i = 1; } parsed = parse_layout_string(argv[i]); if (parsed != L_NONE) { return parsed; } if (strcmp(argv[i], "split") == 0) { return toggle_split_layout(layout, prev_split_layout, output); } // invalid layout strings are silently ignored } return L_NONE; } static enum sway_container_layout get_layout(int argc, char **argv, enum sway_container_layout layout, enum sway_container_layout prev_split_layout, struct sway_output *output) { // Check if assigned directly enum sway_container_layout parsed = parse_layout_string(argv[0]); if (parsed != L_NONE) { return parsed; } if (strcasecmp(argv[0], "default") == 0) { return prev_split_layout; } if (strcasecmp(argv[0], "toggle") == 0) { return get_layout_toggle(argc, argv, layout, prev_split_layout, output); } return L_NONE; } struct cmd_results *cmd_layout(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "layout", EXPECTED_AT_LEAST, 1))) { return error; } if (!root->outputs->length) { return cmd_results_new(CMD_INVALID, "Can't run this command while there's no outputs connected."); } struct sway_container *container = config->handler_context.container; struct sway_workspace *workspace = config->handler_context.workspace; if (container && container_is_floating(container)) { return cmd_results_new(CMD_FAILURE, "Unable to change layout of floating windows"); } // Operate on parent container, like i3. if (container) { container = container->pending.parent; // If parent has only a singe child operate on its parent and // flatten once, like i3 if (container && container->pending.children->length == 1) { // Also check grandparent to avoid restricting layouts struct sway_container *parent = container->pending.parent; if (parent && parent->pending.children->length == 1) { struct sway_container *child = container->pending.children->items[0]; struct sway_container *parent = container->pending.parent; container_replace(container, child); container_begin_destroy(container); container = parent; } } } // We could be working with a container OR a workspace. These are different // structures, so we set up pointers to they layouts so we can refer them in // an abstract way. enum sway_container_layout new_layout = L_NONE; enum sway_container_layout old_layout = L_NONE; if (container) { old_layout = container->pending.layout; new_layout = get_layout(argc, argv, container->pending.layout, container->prev_split_layout, container->pending.workspace->output); } else { old_layout = workspace->layout; new_layout = get_layout(argc, argv, workspace->layout, workspace->prev_split_layout, workspace->output); } if (new_layout == L_NONE) { return cmd_results_new(CMD_INVALID, "%s", expected_syntax); } if (new_layout != old_layout) { if (container) { if (old_layout != L_TABBED && old_layout != L_STACKED) { container->prev_split_layout = old_layout; } container->pending.layout = new_layout; container_update_representation(container); } else if (config->handler_context.container) { // i3 avoids changing workspace layouts with a new container // https://github.com/i3/i3/blob/3cd1c45eba6de073bc4300eebb4e1cc1a0c4479a/src/con.c#L1817 container = workspace_wrap_children(workspace); container->pending.layout = new_layout; container_update_representation(container); } else { if (old_layout != L_TABBED && old_layout != L_STACKED) { workspace->prev_split_layout = old_layout; } workspace->layout = new_layout; workspace_update_representation(workspace); } if (root->fullscreen_global) { arrange_root(); } else { arrange_workspace(workspace); } } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/mark.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/view.h" #include "list.h" #include "log.h" #include "stringop.h" // mark foo Same as mark --replace foo // mark --add foo Add this mark to view's list // mark --replace foo Replace view's marks with this single one // mark --add --toggle foo Toggle current mark and persist other marks // mark --replace --toggle foo Toggle current mark and remove other marks struct cmd_results *cmd_mark(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "mark", EXPECTED_AT_LEAST, 1))) { return error; } struct sway_container *container = config->handler_context.container; if (!container) { return cmd_results_new(CMD_INVALID, "Only containers can have marks"); } bool add = false, toggle = false; while (argc > 0 && has_prefix(*argv, "--")) { if (strcmp(*argv, "--add") == 0) { add = true; } else if (strcmp(*argv, "--replace") == 0) { add = false; } else if (strcmp(*argv, "--toggle") == 0) { toggle = true; } else { return cmd_results_new(CMD_INVALID, "Unrecognized argument '%s'", *argv); } ++argv; --argc; } if (!argc) { return cmd_results_new(CMD_INVALID, "Expected '[--add|--replace] [--toggle] '"); } char *mark = join_args(argv, argc); bool had_mark = container_has_mark(container, mark); if (!add) { // Replacing container_clear_marks(container); } container_find_and_unmark(mark); if (!toggle || !had_mark) { container_add_mark(container, mark); } free(mark); container_update_marks(container); if (container->view) { view_execute_criteria(container->view); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/max_render_time.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/view.h" struct cmd_results *cmd_max_render_time(int argc, char **argv) { if (!argc) { return cmd_results_new(CMD_INVALID, "Missing max render time argument."); } int max_render_time; if (!strcmp(*argv, "off")) { max_render_time = 0; } else { char *end; max_render_time = strtol(*argv, &end, 10); if (*end || max_render_time <= 0) { return cmd_results_new(CMD_INVALID, "Invalid max render time."); } } struct sway_container *container = config->handler_context.container; if (!container || !container->view) { return cmd_results_new(CMD_INVALID, "Only views can have a max_render_time"); } struct sway_view *view = container->view; view->max_render_time = max_render_time; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/mode.c ================================================ #include #include #include "sway/commands.h" #include "sway/config.h" #include "sway/ipc-server.h" #include "list.h" #include "log.h" #include "stringop.h" // Must be in order for the bsearch static const struct cmd_handler mode_handlers[] = { { "bindcode", cmd_bindcode }, { "bindgesture", cmd_bindgesture }, { "bindswitch", cmd_bindswitch }, { "bindsym", cmd_bindsym }, { "set", cmd_set }, { "unbindcode", cmd_unbindcode }, { "unbindgesture", cmd_unbindgesture }, { "unbindswitch", cmd_unbindswitch }, { "unbindsym", cmd_unbindsym }, }; struct cmd_results *cmd_mode(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "mode", EXPECTED_AT_LEAST, 1))) { return error; } bool pango = strcmp(*argv, "--pango_markup") == 0; if (pango) { argc--; argv++; if (argc == 0) { return cmd_results_new(CMD_FAILURE, "Mode name is missing"); } } if (config->reading && argc == 1) { return cmd_results_new(CMD_DEFER, NULL); } char *mode_name = *argv; strip_quotes(mode_name); struct sway_mode *mode = NULL; // Find mode for (int i = 0; i < config->modes->length; ++i) { struct sway_mode *test = config->modes->items[i]; if (strcmp(test->name, mode_name) == 0) { mode = test; break; } } // Create mode if it doesn't exist if (!mode && argc > 1) { mode = calloc(1, sizeof(struct sway_mode)); if (!mode) { return cmd_results_new(CMD_FAILURE, "Unable to allocate mode"); } mode->name = strdup(mode_name); mode->keysym_bindings = create_list(); mode->keycode_bindings = create_list(); mode->mouse_bindings = create_list(); mode->switch_bindings = create_list(); mode->gesture_bindings = create_list(); mode->pango = pango; list_add(config->modes, mode); } if (!mode) { error = cmd_results_new(CMD_INVALID, "Unknown mode `%s'", mode_name); return error; } // Set current mode struct sway_mode *stored_mode = config->current_mode; config->current_mode = mode; if (argc == 1) { // trigger IPC mode event sway_log(SWAY_DEBUG, "Switching to mode `%s' (pango=%d)", mode->name, mode->pango); ipc_event_mode(config->current_mode->name, config->current_mode->pango); return cmd_results_new(CMD_SUCCESS, NULL); } // Create binding struct cmd_results *result = config_subcommand(argv + 1, argc - 1, mode_handlers, sizeof(mode_handlers)); config->current_mode = stored_mode; return result; } ================================================ FILE: sway/commands/mouse_warping.c ================================================ #include #include #include "sway/commands.h" struct cmd_results *cmd_mouse_warping(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "mouse_warping", EXPECTED_EQUAL_TO, 1))) { return error; } else if (strcasecmp(argv[0], "container") == 0) { config->mouse_warping = WARP_CONTAINER; } else if (strcasecmp(argv[0], "output") == 0) { config->mouse_warping = WARP_OUTPUT; } else if (strcasecmp(argv[0], "none") == 0) { config->mouse_warping = WARP_NO; } else { return cmd_results_new(CMD_FAILURE, "Expected 'mouse_warping output|container|none'"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/move.c ================================================ #include #include #include #include #include #include #include #include #include "sway/commands.h" #include "sway/input/cursor.h" #include "sway/input/seat.h" #include "sway/ipc-server.h" #include "sway/output.h" #include "sway/server.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/root.h" #include "sway/tree/workspace.h" #include "stringop.h" #include "list.h" #include "log.h" #include "util.h" static const char expected_syntax[] = "Expected 'move <[px] px>' or " "'move [--no-auto-back-and-forth] [to] workspace ' or " "'move [to] output ' or " "'move [to] mark '"; static struct sway_output *output_in_direction(const char *direction_string, struct sway_output *reference, int ref_lx, int ref_ly) { if (strcasecmp(direction_string, "current") == 0) { struct sway_workspace *active_ws = seat_get_focused_workspace(config->handler_context.seat); if (!active_ws) { return NULL; } return active_ws->output; } struct { char *name; enum wlr_direction direction; } names[] = { { "up", WLR_DIRECTION_UP }, { "down", WLR_DIRECTION_DOWN }, { "left", WLR_DIRECTION_LEFT }, { "right", WLR_DIRECTION_RIGHT }, }; enum wlr_direction direction = 0; for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); ++i) { if (strcasecmp(names[i].name, direction_string) == 0) { direction = names[i].direction; break; } } if (reference && direction) { struct wlr_output *target = wlr_output_layout_adjacent_output( root->output_layout, direction, reference->wlr_output, ref_lx, ref_ly); if (!target) { target = wlr_output_layout_farthest_output( root->output_layout, opposite_direction(direction), reference->wlr_output, ref_lx, ref_ly); } if (target) { return target->data; } } return output_by_name_or_id(direction_string); } static bool is_parallel(enum sway_container_layout layout, enum wlr_direction dir) { switch (layout) { case L_TABBED: case L_HORIZ: return dir == WLR_DIRECTION_LEFT || dir == WLR_DIRECTION_RIGHT; case L_STACKED: case L_VERT: return dir == WLR_DIRECTION_UP || dir == WLR_DIRECTION_DOWN; default: return false; } } /** * Ensures all seats focus the fullscreen container if needed. */ static void workspace_focus_fullscreen(struct sway_workspace *workspace) { if (!workspace->fullscreen) { return; } struct sway_seat *seat; struct sway_workspace *focus_ws; wl_list_for_each(seat, &server.input->seats, link) { focus_ws = seat_get_focused_workspace(seat); if (focus_ws == workspace) { struct sway_node *new_focus = seat_get_focus_inactive(seat, &workspace->fullscreen->node); seat_set_raw_focus(seat, new_focus); } } } static void container_move_to_container_from_direction( struct sway_container *container, struct sway_container *destination, enum wlr_direction move_dir) { if (destination->view) { if (destination->pending.parent == container->pending.parent && destination->pending.workspace == container->pending.workspace) { sway_log(SWAY_DEBUG, "Swapping siblings"); list_t *siblings = container_get_siblings(container); int container_index = list_find(siblings, container); int destination_index = list_find(siblings, destination); list_swap(siblings, container_index, destination_index); container_update_representation(container); } else { sway_log(SWAY_DEBUG, "Promoting to sibling of cousin"); int offset = move_dir == WLR_DIRECTION_LEFT || move_dir == WLR_DIRECTION_UP; int index = container_sibling_index(destination) + offset; if (destination->pending.parent) { container_insert_child(destination->pending.parent, container, index); } else { workspace_insert_tiling(destination->pending.workspace, container, index); } container->pending.width = container->pending.height = 0; container->width_fraction = container->height_fraction = 0; workspace_squash(destination->pending.workspace); } return; } if (is_parallel(destination->pending.layout, move_dir)) { sway_log(SWAY_DEBUG, "Reparenting container (parallel)"); int index = move_dir == WLR_DIRECTION_RIGHT || move_dir == WLR_DIRECTION_DOWN ? 0 : destination->pending.children->length; container_insert_child(destination, container, index); container->pending.width = container->pending.height = 0; container->width_fraction = container->height_fraction = 0; workspace_squash(destination->pending.workspace); return; } sway_log(SWAY_DEBUG, "Reparenting container (perpendicular)"); struct sway_node *focus_inactive = seat_get_active_tiling_child( config->handler_context.seat, &destination->node); if (!focus_inactive || focus_inactive == &destination->node) { // The container has no children container_add_child(destination, container); return; } // Try again but with the child container_move_to_container_from_direction(container, focus_inactive->sway_container, move_dir); } static void container_move_to_workspace_from_direction( struct sway_container *container, struct sway_workspace *workspace, enum wlr_direction move_dir) { container->pending.width = container->pending.height = 0; container->width_fraction = container->height_fraction = 0; if (is_parallel(workspace->layout, move_dir)) { sway_log(SWAY_DEBUG, "Reparenting container (parallel)"); int index = move_dir == WLR_DIRECTION_RIGHT || move_dir == WLR_DIRECTION_DOWN ? 0 : workspace->tiling->length; workspace_insert_tiling(workspace, container, index); return; } sway_log(SWAY_DEBUG, "Reparenting container (perpendicular)"); struct sway_container *focus_inactive = seat_get_focus_inactive_tiling( config->handler_context.seat, workspace); if (!focus_inactive) { // The workspace has no tiling children workspace_add_tiling(workspace, container); return; } while (focus_inactive->pending.parent) { focus_inactive = focus_inactive->pending.parent; } container_move_to_container_from_direction(container, focus_inactive, move_dir); } static void container_move_to_workspace(struct sway_container *container, struct sway_workspace *workspace) { if (container->pending.workspace == workspace) { return; } struct sway_workspace *old_workspace = container->pending.workspace; if (container_is_floating(container)) { struct sway_output *old_output = container->pending.workspace->output; container_detach(container); workspace_add_floating(workspace, container); container_handle_fullscreen_reparent(container); // If changing output, adjust the coordinates of the window. if (old_output != workspace->output && !container->pending.fullscreen_mode) { struct wlr_box workspace_box, old_workspace_box; workspace_get_box(workspace, &workspace_box); workspace_get_box(old_workspace, &old_workspace_box); floating_fix_coordinates(container, &old_workspace_box, &workspace_box); if (container->scratchpad && workspace->output) { struct wlr_box output_box; output_get_box(workspace->output, &output_box); container->transform = workspace_box; } } } else { container_detach(container); if (workspace_is_empty(workspace) && container->pending.children) { workspace_unwrap_children(workspace, container); container_reap_empty(container); } else { container->pending.width = container->pending.height = 0; container->width_fraction = container->height_fraction = 0; workspace_add_tiling(workspace, container); } container_update_representation(container); } if (container->view) { ipc_event_window(container, "move"); } workspace_detect_urgent(old_workspace); workspace_detect_urgent(workspace); workspace_focus_fullscreen(workspace); } static void container_move_to_container(struct sway_container *container, struct sway_container *destination) { if (container == destination || container_has_ancestor(destination, container)) { return; } if (container_is_floating(container)) { container_move_to_workspace(container, destination->pending.workspace); return; } struct sway_workspace *old_workspace = container->pending.workspace; container_detach(container); container->pending.width = container->pending.height = 0; container->width_fraction = container->height_fraction = 0; if (destination->view) { container_add_sibling(destination, container, 1); } else { container_add_child(destination, container); } if (container->view) { ipc_event_window(container, "move"); } if (destination->pending.workspace) { workspace_focus_fullscreen(destination->pending.workspace); workspace_detect_urgent(destination->pending.workspace); } if (old_workspace && old_workspace != destination->pending.workspace) { workspace_detect_urgent(old_workspace); } } static bool container_move_to_next_output(struct sway_container *container, struct sway_output *output, enum wlr_direction move_dir) { struct sway_output *next_output = output_get_in_direction(output, move_dir); if (next_output) { struct sway_workspace *ws = output_get_active_workspace(next_output); if (!sway_assert(ws, "Expected output to have a workspace")) { return false; } switch (container->pending.fullscreen_mode) { case FULLSCREEN_NONE: container_move_to_workspace_from_direction(container, ws, move_dir); return true; case FULLSCREEN_WORKSPACE: container_move_to_workspace(container, ws); return true; case FULLSCREEN_GLOBAL: return false; } } return false; } // Returns true if moved static bool container_move_in_direction(struct sway_container *container, enum wlr_direction move_dir) { // If moving a fullscreen view, only consider outputs switch (container->pending.fullscreen_mode) { case FULLSCREEN_NONE: break; case FULLSCREEN_WORKSPACE: return container_move_to_next_output(container, container->pending.workspace->output, move_dir); case FULLSCREEN_GLOBAL: return false; } int offs = move_dir == WLR_DIRECTION_LEFT || move_dir == WLR_DIRECTION_UP ? -1 : 1; int index = -1; int desired = -1; list_t *siblings = NULL; struct sway_container *target = NULL; // Look for a suitable ancestor of the container to move within struct sway_container *ancestor = NULL; struct sway_container *current = container; bool wrapped = false; while (!ancestor) { // Don't allow containers to move out of their // fullscreen or floating parent if (current->pending.fullscreen_mode || container_is_floating(current)) { return false; } enum sway_container_layout parent_layout = container_parent_layout(current); if (!is_parallel(parent_layout, move_dir)) { if (!current->pending.parent) { // No parallel parent, so we reorient the workspace current = workspace_wrap_children(current->pending.workspace); current->pending.workspace->layout = move_dir == WLR_DIRECTION_LEFT || move_dir == WLR_DIRECTION_RIGHT ? L_HORIZ : L_VERT; container->pending.height = container->pending.width = 0; container->height_fraction = container->width_fraction = 0; workspace_update_representation(current->pending.workspace); wrapped = true; } else { // Keep looking for a parallel parent current = current->pending.parent; } continue; } // Only scratchpad hidden containers don't have siblings // so siblings != NULL here siblings = container_get_siblings(current); index = list_find(siblings, current); desired = index + offs; target = desired == -1 || desired == siblings->length ? NULL : siblings->items[desired]; // If the move is simple we can complete it here early if (current == container) { if (target) { // Container will swap with or descend into its neighbor container_move_to_container_from_direction(container, target, move_dir); return true; } else if (!container->pending.parent) { // Container is at workspace level so we move it to the // next workspace if possible return container_move_to_next_output(container, current->pending.workspace->output, move_dir); } else { // Container has escaped its immediate parallel parent current = current->pending.parent; continue; } } // We found a suitable ancestor, the loop will end ancestor = current; } if (target) { // Container will move in with its cousin container_move_to_container_from_direction(container, target, move_dir); return true; } else if (!wrapped && !container->pending.parent->pending.parent && container->pending.parent->pending.children->length == 1) { // Treat singleton children as if they are at workspace level like i3 // https://github.com/i3/i3/blob/1d9160f2d247dbaa83fb62f02fd7041dec767fc2/src/move.c#L367 return container_move_to_next_output(container, ancestor->pending.workspace->output, move_dir); } else { // Container will be promoted struct sway_container *old_parent = container->pending.parent; if (ancestor->pending.parent) { // Container will move in with its parent container_insert_child(ancestor->pending.parent, container, index + (offs < 0 ? 0 : 1)); } else { // Container will move to workspace level, // may be re-split by workspace_layout workspace_insert_tiling(ancestor->pending.workspace, container, index + (offs < 0 ? 0 : 1)); } ancestor->pending.height = ancestor->pending.width = 0; ancestor->height_fraction = ancestor->width_fraction = 0; if (old_parent) { container_reap_empty(old_parent); } workspace_squash(container->pending.workspace); return true; } } static struct cmd_results *cmd_move_to_scratchpad(void); static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth, int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "move container/window", EXPECTED_AT_LEAST, 2))) { return error; } struct sway_node *node = config->handler_context.node; struct sway_workspace *workspace = config->handler_context.workspace; struct sway_container *container = config->handler_context.container; if (node->type == N_WORKSPACE) { if (workspace->tiling->length == 0) { return cmd_results_new(CMD_FAILURE, "Can't move an empty workspace"); } container = workspace_wrap_children(workspace); } if (container->pending.fullscreen_mode == FULLSCREEN_GLOBAL) { return cmd_results_new(CMD_FAILURE, "Can't move fullscreen global container"); } struct sway_seat *seat = config->handler_context.seat; struct sway_container *old_parent = container->pending.parent; struct sway_workspace *old_ws = container->pending.workspace; struct sway_output *old_output = old_ws ? old_ws->output : NULL; struct sway_node *destination = NULL; // determine destination if (strcasecmp(argv[0], "workspace") == 0) { // move container to workspace x struct sway_workspace *ws = NULL; char *ws_name = NULL; if (strcasecmp(argv[1], "next") == 0 || strcasecmp(argv[1], "prev") == 0 || strcasecmp(argv[1], "next_on_output") == 0 || strcasecmp(argv[1], "prev_on_output") == 0 || strcasecmp(argv[1], "current") == 0) { ws = workspace_by_name(argv[1]); } else if (strcasecmp(argv[1], "back_and_forth") == 0) { if (!(ws = workspace_by_name(argv[1]))) { if (seat->prev_workspace_name) { ws_name = strdup(seat->prev_workspace_name); } else { return cmd_results_new(CMD_FAILURE, "No workspace was previously active."); } } } else { if (strcasecmp(argv[1], "number") == 0) { // move [window|container] [to] "workspace number x" if (argc < 3) { return cmd_results_new(CMD_INVALID, "%s", expected_syntax); } if (!isdigit(argv[2][0])) { return cmd_results_new(CMD_INVALID, "Invalid workspace number '%s'", argv[2]); } ws_name = join_args(argv + 2, argc - 2); ws = workspace_by_number(ws_name); } else { ws_name = join_args(argv + 1, argc - 1); ws = workspace_by_name(ws_name); } if (!no_auto_back_and_forth && config->auto_back_and_forth && seat->prev_workspace_name) { // auto back and forth move if (old_ws && old_ws->name && strcmp(old_ws->name, ws_name) == 0) { // if target workspace is the current one free(ws_name); ws_name = strdup(seat->prev_workspace_name); ws = workspace_by_name(ws_name); } } } if (!ws) { // We have to create the workspace, but if the container is // sticky and the workspace is going to be created on the same // output, we'll bail out first. if (container_is_sticky_or_child(container)) { struct sway_output *new_output = workspace_get_initial_output(ws_name); if (old_output == new_output) { free(ws_name); return cmd_results_new(CMD_FAILURE, "Can't move sticky container to another workspace " "on the same output"); } } ws = workspace_create(NULL, ws_name); arrange_workspace(ws); } free(ws_name); struct sway_container *dst = seat_get_focus_inactive_tiling(seat, ws); destination = dst ? &dst->node : &ws->node; } else if (strcasecmp(argv[0], "output") == 0) { struct sway_output *new_output = output_in_direction(argv[1], old_output, container->pending.x, container->pending.y); if (!new_output) { return cmd_results_new(CMD_FAILURE, "Can't find output with name/direction '%s'", argv[1]); } destination = seat_get_focus_inactive(seat, &new_output->node); } else if (strcasecmp(argv[0], "mark") == 0) { struct sway_container *dest_con = container_find_mark(argv[1]); if (dest_con == NULL) { return cmd_results_new(CMD_FAILURE, "Mark '%s' not found", argv[1]); } destination = &dest_con->node; } else { return cmd_results_new(CMD_INVALID, "%s", expected_syntax); } if (destination->type == N_CONTAINER && container_is_scratchpad_hidden(destination->sway_container)) { return cmd_move_to_scratchpad(); } if (container_is_sticky_or_child(container) && old_output && node_has_ancestor(destination, &old_output->node)) { return cmd_results_new(CMD_FAILURE, "Can't move sticky " "container to another workspace on the same output"); } struct sway_output *new_output = node_get_output(destination); struct sway_workspace *new_output_last_ws = NULL; if (new_output && old_output != new_output) { new_output_last_ws = output_get_active_workspace(new_output); } // save focus, in case it needs to be restored struct sway_node *focus = seat_get_focus(seat); // move container if (container_is_scratchpad_hidden_or_child(container)) { container_detach(container); root_scratchpad_show(container); } switch (destination->type) { case N_WORKSPACE: container_move_to_workspace(container, destination->sway_workspace); break; case N_OUTPUT: { struct sway_output *output = destination->sway_output; struct sway_workspace *ws = output_get_active_workspace(output); if (!sway_assert(ws, "Expected output to have a workspace")) { return cmd_results_new(CMD_FAILURE, "Expected output to have a workspace"); } container_move_to_workspace(container, ws); } break; case N_CONTAINER: container_move_to_container(container, destination->sway_container); break; case N_ROOT: break; } // restore focus on destination output back to its last active workspace struct sway_workspace *new_workspace = new_output ? output_get_active_workspace(new_output) : NULL; if (new_output && !sway_assert(new_workspace, "Expected output to have a workspace")) { return cmd_results_new(CMD_FAILURE, "Expected output to have a workspace"); } if (new_output_last_ws && new_output_last_ws != new_workspace) { struct sway_node *new_output_last_focus = seat_get_focus_inactive(seat, &new_output_last_ws->node); seat_set_raw_focus(seat, new_output_last_focus); } // restore focus if (focus == &container->node) { focus = NULL; if (old_parent) { focus = seat_get_focus_inactive(seat, &old_parent->node); } if (!focus && old_ws) { focus = seat_get_focus_inactive(seat, &old_ws->node); } } seat_set_focus(seat, focus); // clean-up, destroying parents if the container was the last child if (old_parent) { container_reap_empty(old_parent); } else if (old_ws) { workspace_consider_destroy(old_ws); } // arrange windows if (root->fullscreen_global) { arrange_root(); } else { if (old_ws && !old_ws->node.destroying) { arrange_workspace(old_ws); } arrange_node(node_get_parent(destination)); } return cmd_results_new(CMD_SUCCESS, NULL); } static void workspace_move_to_output(struct sway_workspace *workspace, struct sway_output *output) { if (workspace->output == output) { return; } struct sway_output *old_output = workspace->output; workspace_detach(workspace); struct sway_workspace *new_output_old_ws = output_get_active_workspace(output); if (!sway_assert(new_output_old_ws, "Expected output to have a workspace")) { return; } output_add_workspace(output, workspace); // If moving the last workspace from the old output, create a new workspace // on the old output struct sway_seat *seat = config->handler_context.seat; if (old_output->workspaces->length == 0) { char *ws_name = workspace_next_name(old_output->wlr_output->name); struct sway_workspace *ws = workspace_create(old_output, ws_name); free(ws_name); seat_set_raw_focus(seat, &ws->node); } workspace_consider_destroy(new_output_old_ws); output_sort_workspaces(output); struct sway_node *focus = seat_get_focus_inactive(seat, &workspace->node); seat_set_focus(seat, focus); workspace_output_raise_priority(workspace, old_output, output); ipc_event_workspace(NULL, workspace, "move"); } static struct cmd_results *cmd_move_workspace(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "move workspace", EXPECTED_AT_LEAST, 1))) { return error; } if (strcasecmp(argv[0], "output") == 0) { --argc; ++argv; } if (!argc) { return cmd_results_new(CMD_INVALID, "Expected 'move workspace to [output] '"); } struct sway_workspace *workspace = config->handler_context.workspace; if (!workspace) { return cmd_results_new(CMD_FAILURE, "No workspace to move"); } struct sway_output *old_output = workspace->output; int center_x = workspace->width / 2 + workspace->x, center_y = workspace->height / 2 + workspace->y; struct sway_output *new_output = output_in_direction(argv[0], old_output, center_x, center_y); if (!new_output) { return cmd_results_new(CMD_FAILURE, "Can't find output with name/direction '%s'", argv[0]); } workspace_move_to_output(workspace, new_output); arrange_output(old_output); arrange_output(new_output); struct sway_seat *seat = config->handler_context.seat; seat_consider_warp_to_focus(seat); return cmd_results_new(CMD_SUCCESS, NULL); } static struct cmd_results *cmd_move_in_direction( enum wlr_direction direction, int argc, char **argv) { int move_amt = 10; if (argc) { char *inv; move_amt = (int)strtol(argv[0], &inv, 10); if (*inv != '\0' && strcasecmp(inv, "px") != 0) { return cmd_results_new(CMD_FAILURE, "Invalid distance specified"); } } struct sway_container *container = config->handler_context.container; if (!container) { return cmd_results_new(CMD_FAILURE, "Cannot move workspaces in a direction"); } if (container_is_floating(container)) { if (container->pending.fullscreen_mode) { return cmd_results_new(CMD_FAILURE, "Cannot move fullscreen floating container"); } double lx = container->pending.x; double ly = container->pending.y; switch (direction) { case WLR_DIRECTION_LEFT: lx -= move_amt; break; case WLR_DIRECTION_RIGHT: lx += move_amt; break; case WLR_DIRECTION_UP: ly -= move_amt; break; case WLR_DIRECTION_DOWN: ly += move_amt; break; } container_floating_move_to(container, lx, ly); return cmd_results_new(CMD_SUCCESS, NULL); } struct sway_workspace *old_ws = container->pending.workspace; struct sway_container *old_parent = container->pending.parent; if (!container_move_in_direction(container, direction)) { // Container didn't move return cmd_results_new(CMD_SUCCESS, NULL); } // clean-up, destroying parents if the container was the last child if (old_parent) { container_reap_empty(old_parent); } else if (old_ws) { workspace_consider_destroy(old_ws); } struct sway_workspace *new_ws = container->pending.workspace; if (root->fullscreen_global) { arrange_root(); } else { arrange_workspace(old_ws); if (new_ws != old_ws) { arrange_workspace(new_ws); } } if (container->view) { ipc_event_window(container, "move"); } container_end_mouse_operation(container); return cmd_results_new(CMD_SUCCESS, NULL); } static struct cmd_results *cmd_move_to_position_pointer( struct sway_container *container) { struct sway_seat *seat = config->handler_context.seat; if (!seat->cursor) { return cmd_results_new(CMD_FAILURE, "No cursor device"); } struct wlr_cursor *cursor = seat->cursor->cursor; /* Determine where to put the window. */ double lx = cursor->x - container->pending.width / 2; double ly = cursor->y - container->pending.height / 2; /* Correct target coordinates to be in bounds (on screen). */ struct wlr_output *output = wlr_output_layout_output_at( root->output_layout, cursor->x, cursor->y); if (output) { struct wlr_box box; wlr_output_layout_get_box(root->output_layout, output, &box); lx = fmax(lx, box.x); ly = fmax(ly, box.y); if (lx + container->pending.width > box.x + box.width) { lx = box.x + box.width - container->pending.width; } if (ly + container->pending.height > box.y + box.height) { ly = box.y + box.height - container->pending.height; } } /* Actually move the container. */ container_floating_move_to(container, lx, ly); return cmd_results_new(CMD_SUCCESS, NULL); } static const char expected_position_syntax[] = "Expected 'move [absolute] position [px] [px]' or " "'move [absolute] position center' or " "'move position cursor|mouse|pointer'"; static struct cmd_results *cmd_move_to_position(int argc, char **argv) { struct sway_container *container = config->handler_context.container; if (!container || !container_is_floating(container)) { return cmd_results_new(CMD_FAILURE, "Only floating containers " "can be moved to an absolute position"); } if (!argc) { return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax); } bool absolute = false; if (strcmp(argv[0], "absolute") == 0) { absolute = true; --argc; ++argv; } if (!argc) { return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax); } if (strcmp(argv[0], "position") == 0) { --argc; ++argv; } if (!argc) { return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax); } if (strcmp(argv[0], "cursor") == 0 || strcmp(argv[0], "mouse") == 0 || strcmp(argv[0], "pointer") == 0) { if (absolute) { return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax); } return cmd_move_to_position_pointer(container); } else if (strcmp(argv[0], "center") == 0) { double lx, ly; if (absolute) { lx = root->x + (root->width - container->pending.width) / 2; ly = root->y + (root->height - container->pending.height) / 2; } else { struct sway_workspace *ws = container->pending.workspace; if (!ws) { struct sway_seat *seat = config->handler_context.seat; ws = seat_get_focused_workspace(seat); } lx = ws->x + (ws->width - container->pending.width) / 2; ly = ws->y + (ws->height - container->pending.height) / 2; } container_floating_move_to(container, lx, ly); return cmd_results_new(CMD_SUCCESS, NULL); } if (argc < 2) { return cmd_results_new(CMD_FAILURE, "%s", expected_position_syntax); } struct movement_amount lx = { .amount = 0, .unit = MOVEMENT_UNIT_INVALID }; // X direction int num_consumed_args = parse_movement_amount(argc, argv, &lx); argc -= num_consumed_args; argv += num_consumed_args; if (lx.unit == MOVEMENT_UNIT_INVALID) { return cmd_results_new(CMD_INVALID, "Invalid x position specified"); } if (argc < 1) { return cmd_results_new(CMD_FAILURE, "%s", expected_position_syntax); } struct movement_amount ly = { .amount = 0, .unit = MOVEMENT_UNIT_INVALID }; // Y direction num_consumed_args = parse_movement_amount(argc, argv, &ly); argc -= num_consumed_args; argv += num_consumed_args; if (argc > 0) { return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax); } if (ly.unit == MOVEMENT_UNIT_INVALID) { return cmd_results_new(CMD_INVALID, "Invalid y position specified"); } struct sway_workspace *ws = container->pending.workspace; if (!ws) { struct sway_seat *seat = config->handler_context.seat; ws = seat_get_focused_workspace(seat); } switch (lx.unit) { case MOVEMENT_UNIT_PPT: if (container_is_scratchpad_hidden(container)) { return cmd_results_new(CMD_FAILURE, "Cannot move a hidden scratchpad container by ppt"); } if (absolute) { return cmd_results_new(CMD_FAILURE, "Cannot move to absolute positions by ppt"); } // Convert to px lx.amount = ws->width * lx.amount / 100; lx.unit = MOVEMENT_UNIT_PX; // Falls through case MOVEMENT_UNIT_PX: case MOVEMENT_UNIT_DEFAULT: break; case MOVEMENT_UNIT_INVALID: sway_assert(false, "invalid x unit"); break; } switch (ly.unit) { case MOVEMENT_UNIT_PPT: if (container_is_scratchpad_hidden(container)) { return cmd_results_new(CMD_FAILURE, "Cannot move a hidden scratchpad container by ppt"); } if (absolute) { return cmd_results_new(CMD_FAILURE, "Cannot move to absolute positions by ppt"); } // Convert to px ly.amount = ws->height * ly.amount / 100; ly.unit = MOVEMENT_UNIT_PX; // Falls through case MOVEMENT_UNIT_PX: case MOVEMENT_UNIT_DEFAULT: break; case MOVEMENT_UNIT_INVALID: sway_assert(false, "invalid y unit"); break; } if (!absolute) { lx.amount += ws->x; ly.amount += ws->y; } container_floating_move_to(container, lx.amount, ly.amount); return cmd_results_new(CMD_SUCCESS, NULL); } static struct cmd_results *cmd_move_to_scratchpad(void) { struct sway_node *node = config->handler_context.node; struct sway_container *con = config->handler_context.container; struct sway_workspace *ws = config->handler_context.workspace; if (node->type == N_WORKSPACE && ws->tiling->length == 0) { return cmd_results_new(CMD_INVALID, "Can't move an empty workspace to the scratchpad"); } if (node->type == N_WORKSPACE) { // Wrap the workspace's children in a container con = workspace_wrap_children(ws); ws->layout = L_HORIZ; } // If the container is in a floating split container, // operate on the split container instead of the child. if (container_is_floating_or_child(con)) { while (con->pending.parent) { con = con->pending.parent; } } if (!con->scratchpad) { root_scratchpad_add_container(con, NULL); } else if (con->pending.workspace) { root_scratchpad_hide(con); } return cmd_results_new(CMD_SUCCESS, NULL); } static const char expected_full_syntax[] = "Expected " "'move left|right|up|down [ [px]]'" " or 'move [--no-auto-back-and-forth] [window|container] [to] workspace" " |next|prev|next_on_output|prev_on_output|current|(number )'" " or 'move [window|container] [to] output |left|right|up|down'" " or 'move [window|container] [to] mark '" " or 'move [window|container] [to] scratchpad'" " or 'move workspace to [output] |left|right|up|down'" " or 'move [window|container] [to] [absolute] position [px] [px]'" " or 'move [window|container] [to] [absolute] position center'" " or 'move [window|container] [to] position mouse|cursor|pointer'"; struct cmd_results *cmd_move(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "move", EXPECTED_AT_LEAST, 1))) { return error; } if (!root->outputs->length) { return cmd_results_new(CMD_INVALID, "Can't run this command while there's no outputs connected."); } if (strcasecmp(argv[0], "left") == 0) { return cmd_move_in_direction(WLR_DIRECTION_LEFT, --argc, ++argv); } else if (strcasecmp(argv[0], "right") == 0) { return cmd_move_in_direction(WLR_DIRECTION_RIGHT, --argc, ++argv); } else if (strcasecmp(argv[0], "up") == 0) { return cmd_move_in_direction(WLR_DIRECTION_UP, --argc, ++argv); } else if (strcasecmp(argv[0], "down") == 0) { return cmd_move_in_direction(WLR_DIRECTION_DOWN, --argc, ++argv); } else if (strcasecmp(argv[0], "workspace") == 0 && argc >= 2 && (strcasecmp(argv[1], "to") == 0 || strcasecmp(argv[1], "output") == 0)) { argc -= 2; argv += 2; return cmd_move_workspace(argc, argv); } bool no_auto_back_and_forth = false; if (strcasecmp(argv[0], "--no-auto-back-and-forth") == 0) { no_auto_back_and_forth = true; --argc; ++argv; } if (argc > 0 && (strcasecmp(argv[0], "window") == 0 || strcasecmp(argv[0], "container") == 0)) { --argc; ++argv; } if (argc > 0 && strcasecmp(argv[0], "to") == 0) { --argc; ++argv; } if (!argc) { return cmd_results_new(CMD_INVALID, "%s", expected_full_syntax); } // Only `move [window|container] [to] workspace` supports // `--no-auto-back-and-forth` so treat others as invalid syntax if (no_auto_back_and_forth && strcasecmp(argv[0], "workspace") != 0) { return cmd_results_new(CMD_INVALID, "%s", expected_full_syntax); } if (strcasecmp(argv[0], "workspace") == 0 || strcasecmp(argv[0], "output") == 0 || strcasecmp(argv[0], "mark") == 0) { return cmd_move_container(no_auto_back_and_forth, argc, argv); } else if (strcasecmp(argv[0], "scratchpad") == 0) { return cmd_move_to_scratchpad(); } else if (strcasecmp(argv[0], "position") == 0 || (argc > 1 && strcasecmp(argv[0], "absolute") == 0 && strcasecmp(argv[1], "position") == 0)) { return cmd_move_to_position(argc, argv); } return cmd_results_new(CMD_INVALID, "%s", expected_full_syntax); } ================================================ FILE: sway/commands/new_float.c ================================================ #include "log.h" #include "sway/commands.h" #include "sway/config.h" struct cmd_results *cmd_new_float(int argc, char **argv) { sway_log(SWAY_INFO, "Warning: new_float is deprecated. " "Use default_floating_border instead."); if (config->reading) { config_add_swaynag_warning("new_float is deprecated. " "Use default_floating_border instead."); } return cmd_default_floating_border(argc, argv); } ================================================ FILE: sway/commands/new_window.c ================================================ #include "log.h" #include "sway/commands.h" #include "sway/config.h" struct cmd_results *cmd_new_window(int argc, char **argv) { sway_log(SWAY_INFO, "Warning: new_window is deprecated. " "Use default_border instead."); if (config->reading) { config_add_swaynag_warning("new_window is deprecated. " "Use default_border instead."); } return cmd_default_border(argc, argv); } ================================================ FILE: sway/commands/no_focus.c ================================================ #include #include "sway/commands.h" #include "sway/criteria.h" #include "list.h" #include "log.h" struct cmd_results *cmd_no_focus(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "no_focus", EXPECTED_AT_LEAST, 1))) { return error; } char *err_str = NULL; struct criteria *criteria = criteria_parse(argv[0], &err_str); if (!criteria) { error = cmd_results_new(CMD_INVALID, "%s", err_str); free(err_str); return error; } criteria->type = CT_NO_FOCUS; // Check if it already exists if (criteria_already_exists(criteria)) { sway_log(SWAY_DEBUG, "no_focus already exists: '%s'", criteria->raw); criteria_destroy(criteria); return cmd_results_new(CMD_SUCCESS, NULL); } list_add(config->criteria, criteria); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/nop.c ================================================ #include "sway/commands.h" struct cmd_results *cmd_nop(int argc, char **argv) { return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/opacity.c ================================================ #include #include #include #include "sway/commands.h" #include "sway/tree/container.h" #include "sway/output.h" #include "log.h" struct cmd_results *cmd_opacity(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "opacity", EXPECTED_AT_LEAST, 1))) { return error; } struct sway_container *con = config->handler_context.container; if (con == NULL) { return cmd_results_new(CMD_FAILURE, "No current container"); } char *err; float val = strtof(argc == 1 ? argv[0] : argv[1], &err); if (*err) { return cmd_results_new(CMD_INVALID, "opacity float invalid"); } if (!strcasecmp(argv[0], "plus")) { val = con->alpha + val; } else if (!strcasecmp(argv[0], "minus")) { val = con->alpha - val; } else if (argc > 1 && strcasecmp(argv[0], "set")) { return cmd_results_new(CMD_INVALID, "Expected: set|plus|minus <0..1>: %s", argv[0]); } if (val < 0 || val > 1) { return cmd_results_new(CMD_FAILURE, "opacity value out of bounds"); } con->alpha = val; output_configure_scene(NULL, &con->scene_tree->node, 1); container_update(con); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/output/adaptive_sync.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/output.h" #include "util.h" struct cmd_results *output_cmd_adaptive_sync(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } if (argc == 0) { return cmd_results_new(CMD_INVALID, "Missing adaptive_sync argument"); } bool current_value = true; if (strcasecmp(argv[0], "toggle") == 0) { const char *oc_name = config->handler_context.output_config->name; if (strcmp(oc_name, "*") == 0) { return cmd_results_new(CMD_INVALID, "Cannot apply toggle to all outputs"); } struct sway_output *sway_output = all_output_by_name_or_id(oc_name); if (!sway_output || !sway_output->wlr_output) { return cmd_results_new(CMD_FAILURE, "Cannot apply toggle to unknown output %s", oc_name); } current_value = sway_output->wlr_output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED; } config->handler_context.output_config->adaptive_sync = parse_boolean(argv[0], current_value); config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; return NULL; } ================================================ FILE: sway/commands/output/allow_tearing.c ================================================ #include "sway/commands.h" #include "sway/config.h" #include "util.h" struct cmd_results *output_cmd_allow_tearing(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } if (argc == 0) { return cmd_results_new(CMD_INVALID, "Missing allow_tearing argument"); } if (parse_boolean(argv[0], (config->handler_context.output_config->allow_tearing == 1))) { config->handler_context.output_config->allow_tearing = 1; } else { config->handler_context.output_config->allow_tearing = 0; } config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; return NULL; } ================================================ FILE: sway/commands/output/background.c ================================================ #include #include #include #include #include #include "sway/commands.h" #include "sway/config.h" #include "log.h" #include "stringop.h" static const char *bg_options[] = { "stretch", "center", "fill", "fit", "tile", }; static bool validate_color(const char *color) { if (strlen(color) != 7 || color[0] != '#') { return false; } char *ptr = NULL; strtol(&color[1], &ptr, 16); return *ptr == '\0'; } struct cmd_results *output_cmd_background(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } if (!argc) { return cmd_results_new(CMD_INVALID, "Missing background file or color specification."); } if (argc < 2) { return cmd_results_new(CMD_INVALID, "Missing background scaling mode or `solid_color`."); } struct output_config *output = config->handler_context.output_config; char *src = NULL; if (strcasecmp(argv[1], "solid_color") == 0) { if (!validate_color(argv[0])) { return cmd_results_new(CMD_INVALID, "Colors should be of the form #RRGGBB"); } if (!(output->background = strdup(argv[0]))) goto cleanup; if (!(output->background_option = strdup("solid_color"))) goto cleanup; output->background_fallback = NULL; argc -= 2; argv += 2; } else { bool valid = false; char *mode; size_t j; for (j = 0; j < (size_t)argc; ++j) { mode = argv[j]; size_t n = sizeof(bg_options) / sizeof(char *); for (size_t k = 0; k < n; ++k) { if (strcasecmp(mode, bg_options[k]) == 0) { valid = true; break; } } if (valid) { break; } } if (!valid) { return cmd_results_new(CMD_INVALID, "Missing background scaling mode."); } if (j == 0) { return cmd_results_new(CMD_INVALID, "Missing background file"); } if (!(src = join_args(argv, j))) goto cleanup; if (!expand_path(&src)) { struct cmd_results *cmd_res = cmd_results_new(CMD_INVALID, "Invalid syntax (%s)", src); free(src); return cmd_res; } if (config->reading && *src != '/') { // src file is inside configuration dir char *conf = strdup(config->current_config_path); if (!conf) goto cleanup; char *conf_path = dirname(conf); char *real_src = malloc(strlen(conf_path) + strlen(src) + 2); if (!real_src) { free(conf); goto cleanup; } snprintf(real_src, strlen(conf_path) + strlen(src) + 2, "%s/%s", conf_path, src); free(src); free(conf); src = real_src; } bool can_access = access(src, F_OK) != -1; argc -= j + 1; argv += j + 1; free(output->background_option); free(output->background_fallback); free(output->background); output->background = output->background_option = output->background_fallback = NULL; char *fallback = NULL; if (argc && *argv[0] == '#') { if (validate_color(argv[0])) { if (!(fallback = strdup(argv[0]))) goto cleanup; output->background_fallback = fallback; } else { sway_log(SWAY_ERROR, "fallback '%s' should be of the form #RRGGBB", argv[0]); config_add_swaynag_warning("fallback '%s' should be of the form #RRGGBB\n", argv[0]); } argc--; argv++; } if (!can_access) { if (!fallback) { sway_log(SWAY_ERROR, "Unable to access background file '%s' " "and no valid fallback provided", src); struct cmd_results *res = cmd_results_new(CMD_FAILURE, "Unable to access " "background file '%s' and no valid fallback provided", src); free(src); return res; } sway_log(SWAY_DEBUG, "Cannot access file '%s', using fallback '%s'", src, fallback); output->background = fallback; if (!(output->background_option = strdup("solid_color"))) goto cleanup; output->background_fallback = NULL; } else { output->background = src; if (!(output->background_option = strdup(mode))) goto cleanup; } } config->handler_context.leftovers.argc = argc; config->handler_context.leftovers.argv = argv; return NULL; cleanup: free(src); sway_log(SWAY_ERROR, "Failed to allocate resources"); return cmd_results_new(CMD_FAILURE, "Unable to allocate resources"); } ================================================ FILE: sway/commands/output/color_profile.c ================================================ #include #include #include #include #include #include "sway/commands.h" #include "sway/config.h" #include "stringop.h" static bool read_file_into_buf(const char *path, void **buf, size_t *size) { /* Why not use fopen/fread directly? glibc will succesfully open directories, * not just files, and supports seeking on them. Instead, we directly * work with file descriptors and use the more consistent open/fstat/read. */ int fd = open(path, O_RDONLY | O_NOCTTY | O_CLOEXEC); if (fd == -1) { return false; } char *b = NULL; struct stat info; if (fstat(fd, &info) == -1) { goto fail; } // only regular files, to avoid issues with e.g. opening pipes if (!S_ISREG(info.st_mode)) { goto fail; } off_t s = info.st_size; if (s <= 0) { goto fail; } b = calloc(1, s); if (!b) { goto fail; } size_t nread = 0; while (nread < (size_t)s) { size_t to_read = (size_t)s - nread; ssize_t r = read(fd, b + nread, to_read); if ((r == -1 && errno != EINTR) || r == 0) { goto fail; } nread += (size_t)r; } close(fd); *buf = b; *size = (size_t)s; return true; // success fail: free(b); close(fd); return false; } struct cmd_results *output_cmd_color_profile(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } enum color_profile new_mode = COLOR_PROFILE_TRANSFORM; if (argc >= 2 && strcmp(*argv, "--device-primaries") == 0) { new_mode = COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES; argc--; argv++; } if (!argc) { return cmd_results_new(CMD_INVALID, "Missing color_profile first argument."); } if (strcmp(*argv, "gamma22") == 0) { wlr_color_transform_unref(config->handler_context.output_config->color_transform); config->handler_context.output_config->color_transform = NULL; config->handler_context.output_config->color_profile = new_mode; config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; } else if (strcmp(*argv, "srgb") == 0) { wlr_color_transform_unref(config->handler_context.output_config->color_transform); config->handler_context.output_config->color_transform = wlr_color_transform_init_linear_to_inverse_eotf(WLR_COLOR_TRANSFER_FUNCTION_SRGB); config->handler_context.output_config->color_profile = new_mode; config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; } else if (strcmp(*argv, "icc") == 0) { if (argc < 2) { return cmd_results_new(CMD_INVALID, "Invalid color profile specification: icc type requires a file"); } if (new_mode != COLOR_PROFILE_TRANSFORM) { return cmd_results_new(CMD_INVALID, "Invalid color profile specification: --device-primaries cannot be used with icc"); } char *icc_path = strdup(argv[1]); if (!expand_path(&icc_path)) { struct cmd_results *cmd_res = cmd_results_new(CMD_INVALID, "Invalid color profile specification: invalid file path"); free(icc_path); return cmd_res; } void *data = NULL; size_t size = 0; if (!read_file_into_buf(icc_path, &data, &size)) { free(icc_path); return cmd_results_new(CMD_FAILURE, "Failed to load color profile: could not read ICC file"); } free(icc_path); struct wlr_color_transform *tmp = wlr_color_transform_init_linear_to_icc(data, size); if (!tmp) { free(data); return cmd_results_new(CMD_FAILURE, "Failed to load color profile: failed to initialize transform from ICC"); } free(data); wlr_color_transform_unref(config->handler_context.output_config->color_transform); config->handler_context.output_config->color_transform = tmp; config->handler_context.output_config->color_profile = COLOR_PROFILE_TRANSFORM; config->handler_context.leftovers.argc = argc - 2; config->handler_context.leftovers.argv = argv + 2; } else { return cmd_results_new(CMD_INVALID, "Invalid color profile specification: " "first argument should be gamma22|icc|srgb"); } return NULL; } ================================================ FILE: sway/commands/output/disable.c ================================================ #include "sway/commands.h" #include "sway/config.h" struct cmd_results *output_cmd_disable(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } config->handler_context.output_config->enabled = 0; config->handler_context.leftovers.argc = argc; config->handler_context.leftovers.argv = argv; return NULL; } ================================================ FILE: sway/commands/output/dpms.c ================================================ #include "log.h" #include "sway/commands.h" struct cmd_results *output_cmd_dpms(int argc, char **argv) { sway_log(SWAY_INFO, "The \"output dpms\" command is deprecated, " "use \"output power\" instead"); return output_cmd_power(argc, argv); } ================================================ FILE: sway/commands/output/enable.c ================================================ #include "sway/commands.h" #include "sway/config.h" struct cmd_results *output_cmd_enable(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } config->handler_context.output_config->enabled = 1; config->handler_context.leftovers.argc = argc; config->handler_context.leftovers.argv = argv; return NULL; } ================================================ FILE: sway/commands/output/hdr.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/output.h" #include "util.h" struct cmd_results *output_cmd_hdr(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } if (argc == 0) { return cmd_results_new(CMD_INVALID, "Missing hdr argument"); } bool current = false; if (strcasecmp(argv[0], "toggle") == 0) { const char *oc_name = config->handler_context.output_config->name; if (strcmp(oc_name, "*") == 0) { return cmd_results_new(CMD_INVALID, "Cannot apply toggle to all outputs"); } struct sway_output *output = all_output_by_name_or_id(oc_name); if (!output) { return cmd_results_new(CMD_FAILURE, "Cannot apply toggle to unknown output %s", oc_name); } current = output->hdr; } config->handler_context.output_config->hdr = parse_boolean(argv[0], current); config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; return NULL; } ================================================ FILE: sway/commands/output/max_render_time.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" struct cmd_results *output_cmd_max_render_time(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } if (!argc) { return cmd_results_new(CMD_INVALID, "Missing max render time argument."); } int max_render_time; if (!strcmp(*argv, "off")) { max_render_time = 0; } else { char *end; max_render_time = strtol(*argv, &end, 10); if (*end || max_render_time <= 0) { return cmd_results_new(CMD_INVALID, "Invalid max render time."); } } config->handler_context.output_config->max_render_time = max_render_time; config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; return NULL; } ================================================ FILE: sway/commands/output/mode.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" struct cmd_results *output_cmd_mode(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } if (!argc) { return cmd_results_new(CMD_INVALID, "Missing mode argument."); } struct output_config *output = config->handler_context.output_config; if (strcmp(argv[0], "--custom") == 0) { argv++; argc--; output->custom_mode = 1; } else { output->custom_mode = 0; } // Reset custom modeline, if any output->drm_mode.type = 0; char *end; output->width = strtol(*argv, &end, 10); if (*end) { // Format is 1234x4321 if (*end != 'x') { return cmd_results_new(CMD_INVALID, "Invalid mode width."); } ++end; output->height = strtol(end, &end, 10); if (*end) { if (*end != '@') { return cmd_results_new(CMD_INVALID, "Invalid mode height."); } ++end; output->refresh_rate = strtof(end, &end); if (strcasecmp("Hz", end) != 0) { return cmd_results_new(CMD_INVALID, "Invalid mode refresh rate."); } } } else { // Format is 1234 4321 argc--; argv++; if (!argc) { return cmd_results_new(CMD_INVALID, "Missing mode argument (height)."); } output->height = strtol(*argv, &end, 10); if (*end) { return cmd_results_new(CMD_INVALID, "Invalid mode height."); } } config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; return NULL; } static bool parse_modeline(char **argv, drmModeModeInfo *mode) { mode->type = DRM_MODE_TYPE_USERDEF; mode->clock = strtof(argv[0], NULL) * 1000; mode->hdisplay = strtol(argv[1], NULL, 10); mode->hsync_start = strtol(argv[2], NULL, 10); mode->hsync_end = strtol(argv[3], NULL, 10); mode->htotal = strtol(argv[4], NULL, 10); mode->vdisplay = strtol(argv[5], NULL, 10); mode->vsync_start = strtol(argv[6], NULL, 10); mode->vsync_end = strtol(argv[7], NULL, 10); mode->vtotal = strtol(argv[8], NULL, 10); mode->vrefresh = mode->clock * 1000.0 * 1000.0 / mode->htotal / mode->vtotal; if (strcasecmp(argv[9], "+hsync") == 0) { mode->flags |= DRM_MODE_FLAG_PHSYNC; } else if (strcasecmp(argv[9], "-hsync") == 0) { mode->flags |= DRM_MODE_FLAG_NHSYNC; } else { return false; } if (strcasecmp(argv[10], "+vsync") == 0) { mode->flags |= DRM_MODE_FLAG_PVSYNC; } else if (strcasecmp(argv[10], "-vsync") == 0) { mode->flags |= DRM_MODE_FLAG_NVSYNC; } else { return false; } snprintf(mode->name, sizeof(mode->name), "%dx%d@%d", mode->hdisplay, mode->vdisplay, mode->vrefresh / 1000); return true; } struct cmd_results *output_cmd_modeline(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } if (!argc) { return cmd_results_new(CMD_INVALID, "Missing modeline argument."); } struct output_config *output = config->handler_context.output_config; if (argc != 11 || !parse_modeline(argv, &output->drm_mode)) { return cmd_results_new(CMD_INVALID, "Invalid modeline"); } config->handler_context.leftovers.argc = argc - 12; config->handler_context.leftovers.argv = argv + 12; return NULL; } ================================================ FILE: sway/commands/output/position.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" struct cmd_results *output_cmd_position(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } if (!argc) { return cmd_results_new(CMD_INVALID, "Missing position argument."); } char *end; config->handler_context.output_config->x = strtol(*argv, &end, 10); if (*end) { // Format is 1234,4321 if (*end != ',') { return cmd_results_new(CMD_INVALID, "Invalid position x."); } ++end; config->handler_context.output_config->y = strtol(end, &end, 10); if (*end) { return cmd_results_new(CMD_INVALID, "Invalid position y."); } } else { // Format is 1234 4321 (legacy) argc--; argv++; if (!argc) { return cmd_results_new(CMD_INVALID, "Missing position argument (y)."); } config->handler_context.output_config->y = strtol(*argv, &end, 10); if (*end) { return cmd_results_new(CMD_INVALID, "Invalid position y."); } } config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; return NULL; } ================================================ FILE: sway/commands/output/power.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/output.h" #include "util.h" struct cmd_results *output_cmd_power(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } if (argc == 0) { return cmd_results_new(CMD_INVALID, "Missing power argument"); } bool current = true; if (strcasecmp(argv[0], "toggle") == 0) { const char *oc_name = config->handler_context.output_config->name; if (strcmp(oc_name, "*") == 0) { return cmd_results_new(CMD_INVALID, "Cannot apply toggle to all outputs"); } struct sway_output *sway_output = all_output_by_name_or_id(oc_name); if (!sway_output || !sway_output->wlr_output) { return cmd_results_new(CMD_FAILURE, "Cannot apply toggle to unknown output %s", oc_name); } if (sway_output->enabled && !sway_output->wlr_output->enabled) { current = false; } } if (parse_boolean(argv[0], current)) { config->handler_context.output_config->power = 1; } else { config->handler_context.output_config->power = 0; } config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; return NULL; } ================================================ FILE: sway/commands/output/render_bit_depth.c ================================================ #include #include #include "sway/commands.h" #include "sway/config.h" struct cmd_results *output_cmd_render_bit_depth(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } if (!argc) { return cmd_results_new(CMD_INVALID, "Missing bit depth argument."); } if (strcmp(*argv, "6") == 0) { config->handler_context.output_config->render_bit_depth = RENDER_BIT_DEPTH_6; } else if (strcmp(*argv, "8") == 0) { config->handler_context.output_config->render_bit_depth = RENDER_BIT_DEPTH_8; } else if (strcmp(*argv, "10") == 0) { config->handler_context.output_config->render_bit_depth = RENDER_BIT_DEPTH_10; } else { return cmd_results_new(CMD_INVALID, "Invalid bit depth. Must be a value in (6|8|10)."); } config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; return NULL; } ================================================ FILE: sway/commands/output/scale.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" struct cmd_results *output_cmd_scale(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } if (!argc) { return cmd_results_new(CMD_INVALID, "Missing scale argument."); } char *end; config->handler_context.output_config->scale = strtof(*argv, &end); if (*end) { return cmd_results_new(CMD_INVALID, "Invalid scale."); } config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; return NULL; } ================================================ FILE: sway/commands/output/scale_filter.c ================================================ #include #include "log.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/output.h" struct cmd_results *output_cmd_scale_filter(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } if (!argc) { return cmd_results_new(CMD_INVALID, "Missing scale_filter argument."); } enum scale_filter_mode scale_filter; if (strcmp(*argv, "linear") == 0) { scale_filter = SCALE_FILTER_LINEAR; } else if (strcmp(*argv, "nearest") == 0) { scale_filter = SCALE_FILTER_NEAREST; } else if (strcmp(*argv, "smart") == 0) { scale_filter = SCALE_FILTER_SMART; } else { return cmd_results_new(CMD_INVALID, "Invalid output scale_filter."); } struct output_config *oc = config->handler_context.output_config; config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; oc->scale_filter = scale_filter; return NULL; } ================================================ FILE: sway/commands/output/subpixel.c ================================================ #include #include "log.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/output.h" struct cmd_results *output_cmd_subpixel(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } if (!argc) { return cmd_results_new(CMD_INVALID, "Missing subpixel argument."); } enum wl_output_subpixel subpixel; if (strcmp(*argv, "rgb") == 0) { subpixel = WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB; } else if (strcmp(*argv, "bgr") == 0) { subpixel = WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR; } else if (strcmp(*argv, "vrgb") == 0) { subpixel = WL_OUTPUT_SUBPIXEL_VERTICAL_RGB; } else if (strcmp(*argv, "vbgr") == 0) { subpixel = WL_OUTPUT_SUBPIXEL_VERTICAL_BGR; } else if (strcmp(*argv, "none") == 0) { subpixel = WL_OUTPUT_SUBPIXEL_NONE; } else { return cmd_results_new(CMD_INVALID, "Invalid output subpixel."); } struct output_config *oc = config->handler_context.output_config; config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; oc->subpixel = subpixel; return NULL; } ================================================ FILE: sway/commands/output/toggle.c ================================================ #include "sway/commands.h" #include "sway/config.h" #include "sway/output.h" struct cmd_results *output_cmd_toggle(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } struct output_config *oc = config->handler_context.output_config; if (strcmp(oc->name, "*") == 0) { return cmd_results_new(CMD_INVALID, "Cannot apply toggle to all outputs."); } struct sway_output *sway_output = all_output_by_name_or_id(oc->name); if (sway_output == NULL) { return cmd_results_new(CMD_FAILURE, "Cannot apply toggle to unknown output %s", oc->name); } oc = find_output_config(sway_output); if (!oc || oc->enabled != 0) { config->handler_context.output_config->enabled = 0; } else { config->handler_context.output_config->enabled = 1; } free_output_config(oc); config->handler_context.leftovers.argc = argc; config->handler_context.leftovers.argv = argv; return NULL; } ================================================ FILE: sway/commands/output/transform.c ================================================ #include #include #include "sway/commands.h" #include "sway/config.h" #include "log.h" #include "sway/output.h" static enum wl_output_transform invert_rotation_direction( enum wl_output_transform t) { switch (t) { case WL_OUTPUT_TRANSFORM_90: return WL_OUTPUT_TRANSFORM_270; case WL_OUTPUT_TRANSFORM_270: return WL_OUTPUT_TRANSFORM_90; case WL_OUTPUT_TRANSFORM_FLIPPED_90: return WL_OUTPUT_TRANSFORM_FLIPPED_270; case WL_OUTPUT_TRANSFORM_FLIPPED_270: return WL_OUTPUT_TRANSFORM_FLIPPED_90; default: return t; } } struct cmd_results *output_cmd_transform(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } if (!argc) { return cmd_results_new(CMD_INVALID, "Missing transform argument."); } enum wl_output_transform transform; if (strcmp(*argv, "normal") == 0 || strcmp(*argv, "0") == 0) { transform = WL_OUTPUT_TRANSFORM_NORMAL; } else if (strcmp(*argv, "90") == 0) { transform = WL_OUTPUT_TRANSFORM_90; } else if (strcmp(*argv, "180") == 0) { transform = WL_OUTPUT_TRANSFORM_180; } else if (strcmp(*argv, "270") == 0) { transform = WL_OUTPUT_TRANSFORM_270; } else if (strcmp(*argv, "flipped") == 0) { transform = WL_OUTPUT_TRANSFORM_FLIPPED; } else if (strcmp(*argv, "flipped-90") == 0) { transform = WL_OUTPUT_TRANSFORM_FLIPPED_90; } else if (strcmp(*argv, "flipped-180") == 0) { transform = WL_OUTPUT_TRANSFORM_FLIPPED_180; } else if (strcmp(*argv, "flipped-270") == 0) { transform = WL_OUTPUT_TRANSFORM_FLIPPED_270; } else { return cmd_results_new(CMD_INVALID, "Invalid output transform."); } // Sway uses clockwise transforms, while WL_OUTPUT_TRANSFORM_* describe // anti-clockwise transforms transform = invert_rotation_direction(transform); struct output_config *output = config->handler_context.output_config; config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; if (argc > 1 && (strcmp(argv[1], "clockwise") == 0 || strcmp(argv[1], "anticlockwise") == 0)) { if (config->reloading) { return cmd_results_new(CMD_INVALID, "Relative transforms cannot be used in the configuration file"); } if (!sway_assert(output->name != NULL, "Output config name not set")) { return NULL; } if (strcmp(output->name, "*") == 0) { return cmd_results_new(CMD_INVALID, "Cannot apply relative transform to all outputs."); } struct sway_output *s_output = output_by_name_or_id(output->name); if (s_output == NULL) { return cmd_results_new(CMD_INVALID, "Cannot apply relative transform to unknown output %s", output->name); } if (strcmp(argv[1], "anticlockwise") == 0) { transform = invert_rotation_direction(transform); } struct wlr_output *w_output = s_output->wlr_output; transform = wlr_output_transform_compose(w_output->transform, transform); config->handler_context.leftovers.argv += 1; config->handler_context.leftovers.argc -= 1; } output->transform = transform; return NULL; } ================================================ FILE: sway/commands/output/unplug.c ================================================ #include #include #include #include #if WLR_HAS_X11_BACKEND #include #endif #include "sway/commands.h" #include "sway/config.h" #include "sway/output.h" static bool is_backend_allowed(struct wlr_backend *backend) { if (wlr_backend_is_headless(backend)) { return true; } if (wlr_backend_is_wl(backend)) { return true; } #if WLR_HAS_X11_BACKEND if (wlr_backend_is_x11(backend)) { return true; } #endif return false; } /** * This command is intended for developer use only. */ struct cmd_results *output_cmd_unplug(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } const char *oc_name = config->handler_context.output_config->name; if (strcmp(oc_name, "*") == 0) { return cmd_results_new(CMD_INVALID, "Won't unplug all outputs"); } struct sway_output *sway_output = all_output_by_name_or_id(oc_name); if (!sway_output) { return cmd_results_new(CMD_INVALID, "Cannot unplug unknown output %s", oc_name); } if (!is_backend_allowed(sway_output->wlr_output->backend)) { return cmd_results_new(CMD_INVALID, "Can only unplug outputs with headless, wayland or x11 backend"); } wlr_output_destroy(sway_output->wlr_output); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/output.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/output.h" #include "list.h" #include "log.h" // must be in order for the bsearch static const struct cmd_handler output_handlers[] = { { "adaptive_sync", output_cmd_adaptive_sync }, { "allow_tearing", output_cmd_allow_tearing }, { "background", output_cmd_background }, { "bg", output_cmd_background }, { "color_profile", output_cmd_color_profile }, { "disable", output_cmd_disable }, { "dpms", output_cmd_dpms }, { "enable", output_cmd_enable }, { "hdr", output_cmd_hdr }, { "max_render_time", output_cmd_max_render_time }, { "mode", output_cmd_mode }, { "modeline", output_cmd_modeline }, { "pos", output_cmd_position }, { "position", output_cmd_position }, { "power", output_cmd_power }, { "render_bit_depth", output_cmd_render_bit_depth }, { "res", output_cmd_mode }, { "resolution", output_cmd_mode }, { "scale", output_cmd_scale }, { "scale_filter", output_cmd_scale_filter }, { "subpixel", output_cmd_subpixel }, { "toggle", output_cmd_toggle }, { "transform", output_cmd_transform }, { "unplug", output_cmd_unplug }, }; struct cmd_results *cmd_output(int argc, char **argv) { struct cmd_results *error = checkarg(argc, "output", EXPECTED_AT_LEAST, 1); if (error != NULL) { return error; } // The HEADLESS-1 output is a dummy output used when there's no outputs // connected. It should never be configured. if (strcasecmp(argv[0], root->fallback_output->wlr_output->name) == 0) { return cmd_results_new(CMD_FAILURE, "Refusing to configure the no op output"); } struct output_config *output = NULL; if (strcmp(argv[0], "-") == 0 || strcmp(argv[0], "--") == 0) { if (config->reading) { return cmd_results_new(CMD_FAILURE, "Current output alias (%s) cannot be used in the config", argv[0]); } struct sway_output *sway_output = config->handler_context.node ? node_get_output(config->handler_context.node) : NULL; if (!sway_output) { return cmd_results_new(CMD_FAILURE, "Unknown output"); } if (sway_output == root->fallback_output) { return cmd_results_new(CMD_FAILURE, "Refusing to configure the no op output"); } if (strcmp(argv[0], "-") == 0) { output = new_output_config(sway_output->wlr_output->name); } else { char identifier[128]; output_get_identifier(identifier, 128, sway_output); output = new_output_config(identifier); } } else { output = new_output_config(argv[0]); } if (!output) { sway_log(SWAY_ERROR, "Failed to allocate output config"); return NULL; } argc--; argv++; config->handler_context.output_config = output; while (argc > 0) { config->handler_context.leftovers.argc = 0; config->handler_context.leftovers.argv = NULL; if (find_handler(*argv, output_handlers, sizeof(output_handlers))) { error = config_subcommand(argv, argc, output_handlers, sizeof(output_handlers)); } else { error = cmd_results_new(CMD_INVALID, "Invalid output subcommand: %s.", *argv); } if (error != NULL) { goto fail; } argc = config->handler_context.leftovers.argc; argv = config->handler_context.leftovers.argv; } config->handler_context.output_config = NULL; config->handler_context.leftovers.argc = 0; config->handler_context.leftovers.argv = NULL; bool background = output->background; store_output_config(output); if (config->reading) { // When reading the config file, we wait till the end to do a single // modeset and swaybg spawn. return cmd_results_new(CMD_SUCCESS, NULL); } request_modeset(); if (background && !spawn_swaybg()) { return cmd_results_new(CMD_FAILURE, "Failed to apply background configuration"); } return cmd_results_new(CMD_SUCCESS, NULL); fail: config->handler_context.output_config = NULL; free_output_config(output); return error; } ================================================ FILE: sway/commands/popup_during_fullscreen.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" struct cmd_results *cmd_popup_during_fullscreen(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "popup_during_fullscreen", EXPECTED_EQUAL_TO, 1))) { return error; } if (strcasecmp(argv[0], "smart") == 0) { config->popup_during_fullscreen = POPUP_SMART; } else if (strcasecmp(argv[0], "ignore") == 0) { config->popup_during_fullscreen = POPUP_IGNORE; } else if (strcasecmp(argv[0], "leave_fullscreen") == 0) { config->popup_during_fullscreen = POPUP_LEAVE; } else { return cmd_results_new(CMD_INVALID, "Expected " "'popup_during_fullscreen smart|ignore|leave_fullscreen'"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/primary_selection.c ================================================ #include #include #include "sway/config.h" #include "sway/commands.h" #include "util.h" struct cmd_results *cmd_primary_selection(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "primary_selection", EXPECTED_EQUAL_TO, 1))) { return error; } bool primary_selection = parse_boolean(argv[0], true); // config->primary_selection is reset to the previous value on reload in // load_main_config() if (config->reloading && config->primary_selection != primary_selection) { return cmd_results_new(CMD_FAILURE, "primary_selection can only be enabled/disabled at launch"); } config->primary_selection = primary_selection; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/reload.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/ipc-server.h" #include "sway/server.h" #include "sway/tree/arrange.h" #include "sway/tree/view.h" #include "list.h" #include "log.h" static void title_bar_update_iterator(struct sway_container *con, void *data) { container_update_title_bar(con); } static void do_reload(void *data) { // store bar ids to check against new bars for barconfig_update events list_t *bar_ids = create_list(); for (int i = 0; i < config->bars->length; ++i) { struct bar_config *bar = config->bars->items[i]; list_add(bar_ids, strdup(bar->id)); } const char *path = NULL; if (config->user_config_path) { path = config->current_config_path; } if (!load_main_config(path, true, false)) { sway_log(SWAY_ERROR, "Error(s) reloading config"); list_free_items_and_destroy(bar_ids); return; } ipc_event_workspace(NULL, NULL, "reload"); load_swaybars(); for (int i = 0; i < config->bars->length; ++i) { struct bar_config *bar = config->bars->items[i]; for (int j = 0; j < bar_ids->length; ++j) { if (strcmp(bar->id, bar_ids->items[j]) == 0) { ipc_event_barconfig_update(bar); break; } } } list_free_items_and_destroy(bar_ids); root_for_each_container(title_bar_update_iterator, NULL); arrange_root(); } struct cmd_results *cmd_reload(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "reload", EXPECTED_EQUAL_TO, 0))) { return error; } const char *path = NULL; if (config->user_config_path) { path = config->current_config_path; } if (!load_main_config(path, true, true)) { return cmd_results_new(CMD_FAILURE, "Error(s) reloading config."); } // The reload command frees a lot of stuff, so to avoid use-after-frees // we schedule the reload to happen using an idle event. wl_event_loop_add_idle(server.wl_event_loop, do_reload, NULL); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/rename.c ================================================ #include #include #include #include "log.h" #include "stringop.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/ipc-server.h" #include "sway/output.h" #include "sway/desktop/launcher.h" #include "sway/tree/container.h" #include "sway/tree/workspace.h" #include "sway/tree/root.h" static const char expected_syntax[] = "Expected 'rename workspace to ' or " "'rename workspace to '"; struct cmd_results *cmd_rename(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "rename", EXPECTED_AT_LEAST, 3))) { return error; } if (!root->outputs->length) { return cmd_results_new(CMD_INVALID, "Can't run this command while there's no outputs connected."); } if (strcasecmp(argv[0], "workspace") != 0) { return cmd_results_new(CMD_INVALID, "%s", expected_syntax); } int argn = 1; struct sway_workspace *workspace = NULL; if (strcasecmp(argv[1], "to") == 0) { // 'rename workspace to new_name' workspace = config->handler_context.workspace; } else if (strcasecmp(argv[1], "number") == 0) { // 'rename workspace number x to new_name' if (!isdigit(argv[2][0])) { return cmd_results_new(CMD_INVALID, "Invalid workspace number '%s'", argv[2]); } workspace = workspace_by_number(argv[2]); while (argn < argc && strcasecmp(argv[argn], "to") != 0) { ++argn; } } else { // 'rename workspace old_name to new_name' int end = argn; while (end < argc && strcasecmp(argv[end], "to") != 0) { ++end; } char *old_name = join_args(argv + argn, end - argn); workspace = workspace_by_name(old_name); free(old_name); argn = end; } if (!workspace) { return cmd_results_new(CMD_INVALID, "There is no workspace with that name"); } ++argn; // move past "to" if (argn >= argc) { return cmd_results_new(CMD_INVALID, "%s", expected_syntax); } char *new_name = join_args(argv + argn, argc - argn); if (strcasecmp(new_name, "next") == 0 || strcasecmp(new_name, "prev") == 0 || strcasecmp(new_name, "next_on_output") == 0 || strcasecmp(new_name, "prev_on_output") == 0 || strcasecmp(new_name, "back_and_forth") == 0 || strcasecmp(new_name, "current") == 0 || strcasecmp(new_name, "number") == 0) { free(new_name); return cmd_results_new(CMD_INVALID, "Cannot use special workspace name '%s'", argv[argn]); } struct sway_workspace *tmp_workspace = workspace_by_name(new_name); if (tmp_workspace) { free(new_name); if (tmp_workspace == workspace) { return cmd_results_new(CMD_SUCCESS, NULL); } else { return cmd_results_new(CMD_INVALID, "Workspace already exists"); } } sway_log(SWAY_DEBUG, "renaming workspace '%s' to '%s'", workspace->name, new_name); free(workspace->name); workspace->name = new_name; output_sort_workspaces(workspace->output); ipc_event_workspace(NULL, workspace, "rename"); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/resize.c ================================================ #include #include #include #include #include #include #include #include #include "sway/commands.h" #include "sway/tree/arrange.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "log.h" #include "util.h" #define AXIS_HORIZONTAL (WLR_EDGE_LEFT | WLR_EDGE_RIGHT) #define AXIS_VERTICAL (WLR_EDGE_TOP | WLR_EDGE_BOTTOM) static uint32_t parse_resize_axis(const char *axis) { if (strcasecmp(axis, "width") == 0 || strcasecmp(axis, "horizontal") == 0) { return AXIS_HORIZONTAL; } if (strcasecmp(axis, "height") == 0 || strcasecmp(axis, "vertical") == 0) { return AXIS_VERTICAL; } if (strcasecmp(axis, "up") == 0) { return WLR_EDGE_TOP; } if (strcasecmp(axis, "down") == 0) { return WLR_EDGE_BOTTOM; } if (strcasecmp(axis, "left") == 0) { return WLR_EDGE_LEFT; } if (strcasecmp(axis, "right") == 0) { return WLR_EDGE_RIGHT; } return WLR_EDGE_NONE; } static bool is_horizontal(uint32_t axis) { return axis & (WLR_EDGE_LEFT | WLR_EDGE_RIGHT); } struct sway_container *container_find_resize_parent(struct sway_container *con, uint32_t axis) { enum sway_container_layout parallel_layout = is_horizontal(axis) ? L_HORIZ : L_VERT; bool allow_first = axis != WLR_EDGE_TOP && axis != WLR_EDGE_LEFT; bool allow_last = axis != WLR_EDGE_RIGHT && axis != WLR_EDGE_BOTTOM; while (con) { list_t *siblings = container_get_siblings(con); int index = container_sibling_index(con); if (container_parent_layout(con) == parallel_layout && siblings->length > 1 && (allow_first || index > 0) && (allow_last || index < siblings->length - 1)) { return con; } con = con->pending.parent; } return NULL; } void container_resize_tiled(struct sway_container *con, uint32_t axis, int amount) { if (!con) { return; } con = container_find_resize_parent(con, axis); if (!con) { // Can't resize in this direction return; } if (container_is_scratchpad_hidden_or_child(con)) { return; } // For HORIZONTAL or VERTICAL, we are growing in two directions so select // all adjacent siblings. For RIGHT or DOWN or LEFT or UP select just the // previous or next sibling. list_t *resize = create_list(); list_t *siblings = container_get_siblings(con); int index = container_sibling_index(con); if (axis == AXIS_HORIZONTAL || axis == AXIS_VERTICAL) { list_cat(resize, siblings); } else if (axis == WLR_EDGE_TOP || axis == WLR_EDGE_LEFT) { if (!sway_assert(index > 0, "Didn't expect first child")) { goto cleanup; } list_add(resize, siblings->items[index - 1]); list_add(resize, con); } else { if (!sway_assert(index < siblings->length - 1, "Didn't expect last child")) { goto cleanup; } list_add(resize, con); list_add(resize, siblings->items[index + 1]); } // Apply new dimensions int sibling_amount = ceil((double)amount / (double)(resize->length - 1)); if (is_horizontal(axis)) { for (int i = 0; i < resize->length; i++) { struct sway_container *sibling = resize->items[i]; double change = sibling == con ? amount : -sibling_amount; if (sibling->pending.width + change < MIN_SANE_W) { goto cleanup; } } if (con->child_total_width <= 0) { goto cleanup; } // We're going to resize so snap all the width fractions to full pixels // to avoid rounding issues for (int i = 0; i < siblings->length; ++i) { struct sway_container *con = siblings->items[i]; con->width_fraction = con->pending.width / con->child_total_width; } double amount_fraction = (double)amount / con->child_total_width; double sibling_amount_fraction = amount_fraction / (double)(resize->length - 1); for (int i = 0; i < resize->length; i++) { struct sway_container *sibling = resize->items[i]; sibling->width_fraction += sibling == con ? amount_fraction : -sibling_amount_fraction; } } else { for (int i = 0; i < resize->length; i++) { struct sway_container *sibling = resize->items[i]; double change = sibling == con ? amount : -sibling_amount; if (sibling->pending.height + change < MIN_SANE_H) { goto cleanup; } } if (con->child_total_height <= 0) { goto cleanup; } // We're going to resize so snap all the height fractions to full pixels // to avoid rounding issues for (int i = 0; i < siblings->length; ++i) { struct sway_container *con = siblings->items[i]; con->height_fraction = con->pending.height / con->child_total_height; } double amount_fraction = (double)amount / con->child_total_height; double sibling_amount_fraction = amount_fraction / (double)(resize->length - 1); for (int i = 0; i < resize->length; i++) { struct sway_container *sibling = resize->items[i]; sibling->height_fraction += sibling == con ? amount_fraction : -sibling_amount_fraction; } } if (con->pending.parent) { arrange_container(con->pending.parent); } else { arrange_workspace(con->pending.workspace); } cleanup: list_free(resize); } /** * Implement `resize ` for a floating container. */ static struct cmd_results *resize_adjust_floating(uint32_t axis, struct movement_amount *amount) { struct sway_container *con = config->handler_context.container; int grow_width = 0, grow_height = 0; if (is_horizontal(axis)) { grow_width = amount->amount; } else { grow_height = amount->amount; } // Make sure we're not adjusting beyond floating min/max size int min_width, max_width, min_height, max_height; floating_calculate_constraints(&min_width, &max_width, &min_height, &max_height); if (con->pending.width + grow_width < min_width) { grow_width = min_width - con->pending.width; } else if (con->pending.width + grow_width > max_width) { grow_width = max_width - con->pending.width; } if (con->pending.height + grow_height < min_height) { grow_height = min_height - con->pending.height; } else if (con->pending.height + grow_height > max_height) { grow_height = max_height - con->pending.height; } int grow_x = 0, grow_y = 0; if (axis == AXIS_HORIZONTAL) { grow_x = -grow_width / 2; } else if (axis == AXIS_VERTICAL) { grow_y = -grow_height / 2; } else if (axis == WLR_EDGE_TOP) { grow_y = -grow_height; } else if (axis == WLR_EDGE_LEFT) { grow_x = -grow_width; } if (grow_width == 0 && grow_height == 0) { return cmd_results_new(CMD_INVALID, "Cannot resize any further"); } con->pending.x += grow_x; con->pending.y += grow_y; con->pending.width += grow_width; con->pending.height += grow_height; con->pending.content_x += grow_x; con->pending.content_y += grow_y; con->pending.content_width += grow_width; con->pending.content_height += grow_height; arrange_container(con); return cmd_results_new(CMD_SUCCESS, NULL); } /** * Implement `resize ` for a tiled container. */ static struct cmd_results *resize_adjust_tiled(uint32_t axis, struct movement_amount *amount) { struct sway_container *current = config->handler_context.container; if (container_is_scratchpad_hidden_or_child(current)) { return cmd_results_new(CMD_FAILURE, "Cannot resize a hidden scratchpad container"); } if (amount->unit == MOVEMENT_UNIT_DEFAULT) { amount->unit = MOVEMENT_UNIT_PPT; } if (amount->unit == MOVEMENT_UNIT_PPT) { struct sway_container *parent = current->pending.parent; float pct = amount->amount / 100.0f; if (is_horizontal(axis)) { while (parent && parent->pending.layout != L_HORIZ) { parent = parent->pending.parent; } if (parent) { amount->amount = (float)parent->pending.width * pct; } else { amount->amount = (float)current->pending.workspace->width * pct; } } else { while (parent && parent->pending.layout != L_VERT) { parent = parent->pending.parent; } if (parent) { amount->amount = (float)parent->pending.height * pct; } else { amount->amount = (float)current->pending.workspace->height * pct; } } } double old_width = current->width_fraction; double old_height = current->height_fraction; container_resize_tiled(current, axis, amount->amount); if (current->width_fraction == old_width && current->height_fraction == old_height) { return cmd_results_new(CMD_INVALID, "Cannot resize any further"); } return cmd_results_new(CMD_SUCCESS, NULL); } /** * Implement `resize set` for a tiled container. */ static struct cmd_results *resize_set_tiled(struct sway_container *con, struct movement_amount *width, struct movement_amount *height) { if (container_is_scratchpad_hidden_or_child(con)) { return cmd_results_new(CMD_FAILURE, "Cannot resize a hidden scratchpad container"); } if (width->amount) { if (width->unit == MOVEMENT_UNIT_PPT || width->unit == MOVEMENT_UNIT_DEFAULT) { // Convert to px struct sway_container *parent = con->pending.parent; while (parent && parent->pending.layout != L_HORIZ) { parent = parent->pending.parent; } if (parent) { width->amount = parent->pending.width * width->amount / 100; } else { width->amount = con->pending.workspace->width * width->amount / 100; } width->unit = MOVEMENT_UNIT_PX; } if (width->unit == MOVEMENT_UNIT_PX) { container_resize_tiled(con, AXIS_HORIZONTAL, width->amount - con->pending.width); } } if (height->amount) { if (height->unit == MOVEMENT_UNIT_PPT || height->unit == MOVEMENT_UNIT_DEFAULT) { // Convert to px struct sway_container *parent = con->pending.parent; while (parent && parent->pending.layout != L_VERT) { parent = parent->pending.parent; } if (parent) { height->amount = parent->pending.height * height->amount / 100; } else { height->amount = con->pending.workspace->height * height->amount / 100; } height->unit = MOVEMENT_UNIT_PX; } if (height->unit == MOVEMENT_UNIT_PX) { container_resize_tiled(con, AXIS_VERTICAL, height->amount - con->pending.height); } } return cmd_results_new(CMD_SUCCESS, NULL); } /** * Implement `resize set` for a floating container. */ static struct cmd_results *resize_set_floating(struct sway_container *con, struct movement_amount *width, struct movement_amount *height) { int min_width, max_width, min_height, max_height, grow_width = 0, grow_height = 0; floating_calculate_constraints(&min_width, &max_width, &min_height, &max_height); if (width->amount) { switch (width->unit) { case MOVEMENT_UNIT_PPT: if (container_is_scratchpad_hidden(con)) { return cmd_results_new(CMD_FAILURE, "Cannot resize a hidden scratchpad container by ppt"); } // Convert to px width->amount = con->pending.workspace->width * width->amount / 100; width->unit = MOVEMENT_UNIT_PX; // Falls through case MOVEMENT_UNIT_PX: case MOVEMENT_UNIT_DEFAULT: width->amount = fmax(min_width, fmin(width->amount, max_width)); grow_width = width->amount - con->pending.width; con->pending.x -= grow_width / 2; con->pending.width = width->amount; break; case MOVEMENT_UNIT_INVALID: sway_assert(false, "invalid width unit"); break; } } if (height->amount) { switch (height->unit) { case MOVEMENT_UNIT_PPT: if (container_is_scratchpad_hidden(con)) { return cmd_results_new(CMD_FAILURE, "Cannot resize a hidden scratchpad container by ppt"); } // Convert to px height->amount = con->pending.workspace->height * height->amount / 100; height->unit = MOVEMENT_UNIT_PX; // Falls through case MOVEMENT_UNIT_PX: case MOVEMENT_UNIT_DEFAULT: height->amount = fmax(min_height, fmin(height->amount, max_height)); grow_height = height->amount - con->pending.height; con->pending.y -= grow_height / 2; con->pending.height = height->amount; break; case MOVEMENT_UNIT_INVALID: sway_assert(false, "invalid height unit"); break; } } con->pending.content_x -= grow_width / 2; con->pending.content_y -= grow_height / 2; con->pending.content_width += grow_width; con->pending.content_height += grow_height; arrange_container(con); return cmd_results_new(CMD_SUCCESS, NULL); } /** * resize set * * args: [width] [px|ppt] * : height [px|ppt] * : [width] [px|ppt] [height] [px|ppt] */ static struct cmd_results *cmd_resize_set(int argc, char **argv) { struct cmd_results *error; if ((error = checkarg(argc, "resize", EXPECTED_AT_LEAST, 1))) { return error; } const char usage[] = "Expected 'resize set [width] [px|ppt]' or " "'resize set height [px|ppt]' or " "'resize set [width] [px|ppt] [height] [px|ppt]'"; // Width struct movement_amount width = {0}; if (argc >= 2 && !strcmp(argv[0], "width") && strcmp(argv[1], "height")) { argc--; argv++; } if (strcmp(argv[0], "height")) { int num_consumed_args = parse_movement_amount(argc, argv, &width); argc -= num_consumed_args; argv += num_consumed_args; if (width.unit == MOVEMENT_UNIT_INVALID) { return cmd_results_new(CMD_INVALID, "%s", usage); } } // Height struct movement_amount height = {0}; if (argc) { if (argc >= 2 && !strcmp(argv[0], "height")) { argc--; argv++; } int num_consumed_args = parse_movement_amount(argc, argv, &height); if (argc > num_consumed_args) { return cmd_results_new(CMD_INVALID, "%s", usage); } if (height.unit == MOVEMENT_UNIT_INVALID) { return cmd_results_new(CMD_INVALID, "%s", usage); } } // If 0, don't resize that dimension struct sway_container *con = config->handler_context.container; if (width.amount <= 0) { width.amount = con->pending.width; } if (height.amount <= 0) { height.amount = con->pending.height; } if (container_is_floating(con)) { return resize_set_floating(con, &width, &height); } return resize_set_tiled(con, &width, &height); } /** * resize * * args: * args: * args: or */ static struct cmd_results *cmd_resize_adjust(int argc, char **argv, int multiplier) { const char usage[] = "Expected 'resize grow|shrink " "[ px|ppt [or px|ppt]]'"; uint32_t axis = parse_resize_axis(*argv); if (axis == WLR_EDGE_NONE) { return cmd_results_new(CMD_INVALID, "%s", usage); } --argc; ++argv; // First amount struct movement_amount first_amount; if (argc) { int num_consumed_args = parse_movement_amount(argc, argv, &first_amount); argc -= num_consumed_args; argv += num_consumed_args; if (first_amount.unit == MOVEMENT_UNIT_INVALID) { return cmd_results_new(CMD_INVALID, "%s", usage); } } else { first_amount.amount = 10; first_amount.unit = MOVEMENT_UNIT_DEFAULT; } // "or" if (argc) { if (strcmp(*argv, "or") != 0) { return cmd_results_new(CMD_INVALID, "%s", usage); } --argc; ++argv; } // Second amount struct movement_amount second_amount; if (argc) { int num_consumed_args = parse_movement_amount(argc, argv, &second_amount); if (argc > num_consumed_args) { return cmd_results_new(CMD_INVALID, "%s", usage); } if (second_amount.unit == MOVEMENT_UNIT_INVALID) { return cmd_results_new(CMD_INVALID, "%s", usage); } } else { second_amount.amount = 0; second_amount.unit = MOVEMENT_UNIT_INVALID; } first_amount.amount *= multiplier; second_amount.amount *= multiplier; struct sway_container *con = config->handler_context.container; if (container_is_floating(con)) { // Floating containers can only resize in px. Choose an amount which // uses px, with fallback to an amount that specified no unit. if (first_amount.unit == MOVEMENT_UNIT_PX) { return resize_adjust_floating(axis, &first_amount); } else if (second_amount.unit == MOVEMENT_UNIT_PX) { return resize_adjust_floating(axis, &second_amount); } else if (first_amount.unit == MOVEMENT_UNIT_DEFAULT) { return resize_adjust_floating(axis, &first_amount); } else if (second_amount.unit == MOVEMENT_UNIT_DEFAULT) { return resize_adjust_floating(axis, &second_amount); } else { return cmd_results_new(CMD_INVALID, "Floating containers cannot use ppt measurements"); } } // For tiling, prefer ppt -> default -> px if (first_amount.unit == MOVEMENT_UNIT_PPT) { return resize_adjust_tiled(axis, &first_amount); } else if (second_amount.unit == MOVEMENT_UNIT_PPT) { return resize_adjust_tiled(axis, &second_amount); } else if (first_amount.unit == MOVEMENT_UNIT_DEFAULT) { return resize_adjust_tiled(axis, &first_amount); } else if (second_amount.unit == MOVEMENT_UNIT_DEFAULT) { return resize_adjust_tiled(axis, &second_amount); } else { return resize_adjust_tiled(axis, &first_amount); } } struct cmd_results *cmd_resize(int argc, char **argv) { if (!root->outputs->length) { return cmd_results_new(CMD_INVALID, "Can't run this command while there's no outputs connected."); } struct sway_container *current = config->handler_context.container; if (!current) { return cmd_results_new(CMD_INVALID, "Cannot resize nothing"); } struct cmd_results *error; if ((error = checkarg(argc, "resize", EXPECTED_AT_LEAST, 2))) { return error; } if (strcasecmp(argv[0], "set") == 0) { return cmd_resize_set(argc - 1, &argv[1]); } if (strcasecmp(argv[0], "grow") == 0) { return cmd_resize_adjust(argc - 1, &argv[1], 1); } if (strcasecmp(argv[0], "shrink") == 0) { return cmd_resize_adjust(argc - 1, &argv[1], -1); } const char usage[] = "Expected 'resize " " [] [px|ppt]'"; return cmd_results_new(CMD_INVALID, "%s", usage); } ================================================ FILE: sway/commands/scratchpad.c ================================================ #include "log.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" #include "sway/ipc-server.h" #include "sway/tree/container.h" #include "sway/tree/root.h" #include "sway/tree/workspace.h" static void scratchpad_toggle_auto(void) { struct sway_seat *seat = input_manager_current_seat(); struct sway_container *focus = seat_get_focused_container(seat); struct sway_workspace *ws = seat_get_focused_workspace(seat); if (!ws) { sway_log(SWAY_DEBUG, "No focused workspace to toggle scratchpad windows on"); return; } // If the focus is in a floating split container, // operate on the split container instead of the child. if (focus && container_is_floating_or_child(focus)) { while (focus->pending.parent) { focus = focus->pending.parent; } } // Check if the currently focused window is a scratchpad window and should // be hidden again. if (focus && focus->scratchpad) { sway_log(SWAY_DEBUG, "Focus is a scratchpad window - hiding %s", focus->title); root_scratchpad_hide(focus); return; } // Check if there is an unfocused scratchpad window on the current workspace // and focus it. for (int i = 0; i < ws->floating->length; ++i) { struct sway_container *floater = ws->floating->items[i]; if (floater->scratchpad && focus != floater) { sway_log(SWAY_DEBUG, "Focusing other scratchpad window (%s) in this workspace", floater->title); root_scratchpad_show(floater); return; } } // Check if there is a visible scratchpad window on another workspace. // In this case we move it to the current workspace. for (int i = 0; i < root->scratchpad->length; ++i) { struct sway_container *con = root->scratchpad->items[i]; if (con->pending.parent) { sway_log(SWAY_DEBUG, "Moving a visible scratchpad window (%s) to this workspace", con->title); root_scratchpad_show(con); ipc_event_window(con, "move"); return; } } // Take the container at the bottom of the scratchpad list if (!sway_assert(root->scratchpad->length, "Scratchpad is empty")) { return; } struct sway_container *con = root->scratchpad->items[0]; sway_log(SWAY_DEBUG, "Showing %s from list", con->title); root_scratchpad_show(con); ipc_event_window(con, "move"); } static void scratchpad_toggle_container(struct sway_container *con) { if (!sway_assert(con->scratchpad, "Container isn't in the scratchpad")) { return; } struct sway_seat *seat = input_manager_current_seat(); struct sway_workspace *ws = seat_get_focused_workspace(seat); // Check if it matches a currently visible scratchpad window and hide it. if (con->pending.workspace && ws == con->pending.workspace) { root_scratchpad_hide(con); return; } root_scratchpad_show(con); ipc_event_window(con, "move"); } struct cmd_results *cmd_scratchpad(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "scratchpad", EXPECTED_EQUAL_TO, 1))) { return error; } if (strcmp(argv[0], "show") != 0) { return cmd_results_new(CMD_INVALID, "Expected 'scratchpad show'"); } if (!root->outputs->length) { return cmd_results_new(CMD_INVALID, "Can't run this command while there's no outputs connected."); } if (!root->scratchpad->length) { return cmd_results_new(CMD_INVALID, "Scratchpad is empty"); } if (config->handler_context.node_overridden) { struct sway_container *con = config->handler_context.container; // If the container is in a floating split container, // operate on the split container instead of the child. if (con && container_is_floating_or_child(con)) { while (con->pending.parent) { con = con->pending.parent; } } // If using criteria, this command is executed for every container which // matches the criteria. If this container isn't in the scratchpad, // we'll return an error. The same is true if the // overridden node is not a container. if (!con || !con->scratchpad) { return cmd_results_new(CMD_INVALID, "Container is not in scratchpad."); } scratchpad_toggle_container(con); } else { scratchpad_toggle_auto(); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/seat/attach.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "stringop.h" struct cmd_results *seat_cmd_attach(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "attach", EXPECTED_AT_LEAST, 1))) { return error; } if (!config->handler_context.seat_config) { return cmd_results_new(CMD_FAILURE, "No seat defined"); } if (!config->active) { return cmd_results_new(CMD_DEFER, NULL); } struct seat_attachment_config *attachment = seat_attachment_config_new(); if (!attachment) { return cmd_results_new(CMD_FAILURE, "Failed to allocate seat attachment config"); } attachment->identifier = strdup(argv[0]); list_add(config->handler_context.seat_config->attachments, attachment); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/seat/cursor.c ================================================ #include #include #include #include #include "sway/commands.h" #include "sway/input/cursor.h" #include "sway/server.h" static struct cmd_results *press_or_release(struct sway_cursor *cursor, char *action, char *button_str); static const char expected_syntax[] = "Expected 'cursor ' or " "'cursor ' or " "'cursor '"; static struct cmd_results *handle_command(struct sway_cursor *cursor, int argc, char **argv) { if (strcasecmp(argv[0], "move") == 0) { if (argc < 3) { return cmd_results_new(CMD_INVALID, "%s", expected_syntax); } int delta_x = strtol(argv[1], NULL, 10); int delta_y = strtol(argv[2], NULL, 10); wlr_cursor_move(cursor->cursor, NULL, delta_x, delta_y); cursor_rebase(cursor); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); } else if (strcasecmp(argv[0], "set") == 0) { if (argc < 3) { return cmd_results_new(CMD_INVALID, "%s", expected_syntax); } // map absolute coords (0..1,0..1) to root container coords float x = strtof(argv[1], NULL) / root->width; float y = strtof(argv[2], NULL) / root->height; wlr_cursor_warp_absolute(cursor->cursor, NULL, x, y); cursor_rebase(cursor); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); } else { if (argc < 2) { return cmd_results_new(CMD_INVALID, "%s", expected_syntax); } struct cmd_results *error = NULL; if ((error = press_or_release(cursor, argv[0], argv[1]))) { return error; } } cursor_handle_activity_from_idle_source(cursor, IDLE_SOURCE_POINTER); return cmd_results_new(CMD_SUCCESS, NULL); } struct cmd_results *seat_cmd_cursor(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "cursor", EXPECTED_AT_LEAST, 2))) { return error; } struct seat_config *sc = config->handler_context.seat_config; if (!sc) { return cmd_results_new(CMD_FAILURE, "No seat defined"); } if (config->reading || !config->active) { return cmd_results_new(CMD_DEFER, NULL); } if (strcmp(sc->name, "*") != 0) { struct sway_seat *seat = input_manager_get_seat(sc->name, false); if (!seat) { return cmd_results_new(CMD_FAILURE, "Seat %s does not exist", sc->name); } error = handle_command(seat->cursor, argc, argv); } else { struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { error = handle_command(seat->cursor, argc, argv); if (error && error->status != CMD_SUCCESS) { break; } } } return error ? error : cmd_results_new(CMD_SUCCESS, NULL); } static struct cmd_results *press_or_release(struct sway_cursor *cursor, char *action, char *button_str) { enum wl_pointer_button_state state; uint32_t button; if (strcasecmp(action, "press") == 0) { state = WL_POINTER_BUTTON_STATE_PRESSED; } else if (strcasecmp(action, "release") == 0) { state = WL_POINTER_BUTTON_STATE_RELEASED; } else { return cmd_results_new(CMD_INVALID, "%s", expected_syntax); } char *message = NULL; button = get_mouse_button(button_str, &message); if (message) { struct cmd_results *error = cmd_results_new(CMD_INVALID, "%s", message); free(message); return error; } else if (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_DOWN || button == SWAY_SCROLL_LEFT || button == SWAY_SCROLL_RIGHT) { // Dispatch axis event enum wl_pointer_axis orientation = (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_DOWN) ? WL_POINTER_AXIS_VERTICAL_SCROLL : WL_POINTER_AXIS_HORIZONTAL_SCROLL; double delta = (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_LEFT) ? -1 : 1; struct wlr_pointer_axis_event event = { .pointer = NULL, .time_msec = 0, .source = WL_POINTER_AXIS_SOURCE_WHEEL, .orientation = orientation, .delta = delta * 15, .delta_discrete = delta }; dispatch_cursor_axis(cursor, &event); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); return cmd_results_new(CMD_SUCCESS, NULL); } else if (!button) { return cmd_results_new(CMD_INVALID, "Unknown button %s", button_str); } dispatch_cursor_button(cursor, NULL, 0, button, state); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/seat/fallback.c ================================================ #include "sway/config.h" #include "sway/commands.h" #include "util.h" struct cmd_results *seat_cmd_fallback(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "fallback", EXPECTED_EQUAL_TO, 1))) { return error; } if (!config->handler_context.seat_config) { return cmd_results_new(CMD_FAILURE, "No seat defined"); } config->handler_context.seat_config->fallback = parse_boolean(argv[0], false); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/seat/hide_cursor.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/input/seat.h" #include "sway/input/cursor.h" #include "sway/server.h" #include "stringop.h" #include "util.h" struct cmd_results *seat_cmd_hide_cursor(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "hide_cursor", EXPECTED_AT_LEAST, 1))) { return error; } if ((error = checkarg(argc, "hide_cursor", EXPECTED_AT_MOST, 2))) { return error; } struct seat_config *seat_config = config->handler_context.seat_config; if (!seat_config) { return cmd_results_new(CMD_FAILURE, "No seat defined"); } if (argc == 1) { char *end; int timeout = strtol(argv[0], &end, 10); if (*end) { return cmd_results_new(CMD_INVALID, "Expected an integer timeout"); } if (timeout < 100 && timeout != 0) { timeout = 100; } seat_config->hide_cursor_timeout = timeout; } else { if (strcmp(argv[0], "when-typing") != 0) { return cmd_results_new(CMD_INVALID, "Expected 'hide_cursor |when-typing [enable|disable]'"); } seat_config->hide_cursor_when_typing = parse_boolean(argv[1], true) ? HIDE_WHEN_TYPING_ENABLE : HIDE_WHEN_TYPING_DISABLE; // Invalidate all the caches for this config struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { seat->cursor->hide_when_typing = HIDE_WHEN_TYPING_DEFAULT; } } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/seat/idle.c ================================================ #include #include #include #include #include "log.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/input/seat.h" static const struct { const char *name; uint32_t value; } idle_source_strings[] = { { "keyboard", IDLE_SOURCE_KEYBOARD }, { "pointer", IDLE_SOURCE_POINTER }, { "touch", IDLE_SOURCE_TOUCH }, { "tablet_pad", IDLE_SOURCE_TABLET_PAD }, { "tablet_tool", IDLE_SOURCE_TABLET_TOOL }, { "switch", IDLE_SOURCE_SWITCH }, }; static uint32_t parse_sources(int argc, char **argv) { uint32_t sources = 0; for (int i = 0; i < argc; ++i) { uint32_t value = 0; for (size_t j = 0; j < sizeof(idle_source_strings) / sizeof(idle_source_strings[0]); ++j) { if (strcasecmp(idle_source_strings[j].name, argv[i]) == 0) { value = idle_source_strings[j].value; break; } } if (value == 0) { return UINT32_MAX; } sources |= value; } return sources; } struct cmd_results *seat_cmd_idle_inhibit(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "idle_inhibit", EXPECTED_AT_LEAST, 1))) { return error; } if (!config->handler_context.seat_config) { return cmd_results_new(CMD_FAILURE, "No seat defined"); } uint32_t sources = parse_sources(argc, argv); if (sources == UINT32_MAX) { return cmd_results_new(CMD_FAILURE, "Invalid idle source"); } config->handler_context.seat_config->idle_inhibit_sources = sources; return cmd_results_new(CMD_SUCCESS, NULL); } struct cmd_results *seat_cmd_idle_wake(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "idle_wake", EXPECTED_AT_LEAST, 1))) { return error; } if (!config->handler_context.seat_config) { return cmd_results_new(CMD_FAILURE, "No seat defined"); } uint32_t sources = parse_sources(argc, argv); if (sources == UINT32_MAX) { return cmd_results_new(CMD_FAILURE, "Invalid idle source"); } config->handler_context.seat_config->idle_wake_sources = sources; sway_log(SWAY_INFO, "Warning: seat idle_wake is deprecated"); if (config->reading) { config_add_swaynag_warning("seat idle_wake is deprecated. " "Only seat idle_inhibit is supported."); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/seat/keyboard_grouping.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "stringop.h" struct cmd_results *seat_cmd_keyboard_grouping(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "keyboard_grouping", EXPECTED_EQUAL_TO, 1))) { return error; } if (!config->handler_context.seat_config) { return cmd_results_new(CMD_INVALID, "No seat defined"); } struct seat_config *seat_config = config->handler_context.seat_config; if (strcmp(argv[0], "none") == 0) { seat_config->keyboard_grouping = KEYBOARD_GROUP_NONE; } else if (strcmp(argv[0], "smart") == 0) { seat_config->keyboard_grouping = KEYBOARD_GROUP_SMART; } else { return cmd_results_new(CMD_INVALID, "Expected syntax `keyboard_grouping none|smart`"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/seat/pointer_constraint.c ================================================ #include #include #include "sway/commands.h" #include "sway/config.h" #include "sway/input/cursor.h" #include "sway/input/seat.h" #include "sway/server.h" enum operation { OP_ENABLE, OP_DISABLE, OP_ESCAPE, }; // pointer_constraint [enable|disable|escape] struct cmd_results *seat_cmd_pointer_constraint(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "pointer_constraint", EXPECTED_EQUAL_TO, 1))) { return error; } if (!config->handler_context.seat_config) { return cmd_results_new(CMD_FAILURE, "No seat defined"); } enum operation op; if (strcmp(argv[0], "enable") == 0) { op = OP_ENABLE; } else if (strcmp(argv[0], "disable") == 0) { op = OP_DISABLE; } else if (strcmp(argv[0], "escape") == 0) { op = OP_ESCAPE; } else { return cmd_results_new(CMD_FAILURE, "Expected enable|disable|escape"); } if (op == OP_ESCAPE && config->reading) { return cmd_results_new(CMD_FAILURE, "Can only escape at runtime."); } struct seat_config *seat_config = config->handler_context.seat_config; switch (op) { case OP_ENABLE: seat_config->allow_constrain = CONSTRAIN_ENABLE; break; case OP_DISABLE: seat_config->allow_constrain = CONSTRAIN_DISABLE; /* fallthrough */ case OP_ESCAPE:; bool wildcard = !strcmp(seat_config->name, "*"); struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { if (wildcard || !strcmp(seat->wlr_seat->name, seat_config->name)) { sway_cursor_constrain(seat->cursor, NULL); } } break; } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/seat/shortcuts_inhibitor.c ================================================ #include "log.h" #include "sway/commands.h" #include "sway/input/seat.h" #include "sway/input/input-manager.h" #include "sway/server.h" #include "util.h" static struct cmd_results *handle_action(struct seat_config *sc, struct sway_seat *seat, const char *action) { struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor = NULL; if (strcmp(action, "disable") == 0) { sc->shortcuts_inhibit = SHORTCUTS_INHIBIT_DISABLE; wl_list_for_each(sway_inhibitor, &seat->keyboard_shortcuts_inhibitors, link) { wlr_keyboard_shortcuts_inhibitor_v1_deactivate( sway_inhibitor->inhibitor); } sway_log(SWAY_DEBUG, "Deactivated all keyboard shortcuts inhibitors"); } else { sway_inhibitor = keyboard_shortcuts_inhibitor_get_for_focused_surface(seat); if (!sway_inhibitor) { return cmd_results_new(CMD_FAILURE, "No inhibitor found for focused surface"); } struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = sway_inhibitor->inhibitor; bool inhibit; if (strcmp(action, "activate") == 0) { inhibit = true; } else if (strcmp(action, "deactivate") == 0) { inhibit = false; } else if (strcmp(action, "toggle") == 0) { inhibit = !inhibitor->active; } else { return cmd_results_new(CMD_INVALID, "Expected enable|" "disable|activate|deactivate|toggle"); } if (inhibit) { wlr_keyboard_shortcuts_inhibitor_v1_activate(inhibitor); } else { wlr_keyboard_shortcuts_inhibitor_v1_deactivate(inhibitor); } sway_log(SWAY_DEBUG, "%sctivated keyboard shortcuts inhibitor", inhibit ? "A" : "Dea"); } return cmd_results_new(CMD_SUCCESS, NULL); } // shortcuts_inhibitor [enable|disable|activate|deactivate|toggle] struct cmd_results *seat_cmd_shortcuts_inhibitor(int argc, char **argv) { struct cmd_results *error = checkarg(argc, "shortcuts_inhibitor", EXPECTED_EQUAL_TO, 1); if (error) { return error; } struct seat_config *sc = config->handler_context.seat_config; if (!sc) { return cmd_results_new(CMD_FAILURE, "No seat defined"); } if (strcmp(argv[0], "enable") == 0) { sc->shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; // at runtime disable is an action that also deactivates all active // inhibitors handled in handle_action() } else if (strcmp(argv[0], "disable") == 0 && !config->active) { sc->shortcuts_inhibit = SHORTCUTS_INHIBIT_DISABLE; } else if (!config->active) { return cmd_results_new(CMD_INVALID, "only enable and disable " "can be used in the config"); } else { if (strcmp(sc->name, "*") != 0) { struct sway_seat *seat = input_manager_get_seat(sc->name, false); if (!seat) { return cmd_results_new(CMD_FAILURE, "Seat %s does not exist", sc->name); } error = handle_action(sc, seat, argv[0]); } else { struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { error = handle_action(sc, seat, argv[0]); if (error && error->status != CMD_SUCCESS) { break; } } } } return error ? error : cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/seat/xcursor_theme.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" struct cmd_results *seat_cmd_xcursor_theme(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "xcursor_theme", EXPECTED_AT_LEAST, 1)) || (error = checkarg(argc, "xcursor_theme", EXPECTED_AT_MOST, 2))) { return error; } if (!config->handler_context.seat_config) { return cmd_results_new(CMD_FAILURE, "No seat defined"); } const char *theme_name = argv[0]; unsigned size = 24; if (argc == 2) { char *end; size = strtoul(argv[1], &end, 10); if (*end) { return cmd_results_new( CMD_INVALID, "Expected a positive integer size"); } } free(config->handler_context.seat_config->xcursor_theme.name); config->handler_context.seat_config->xcursor_theme.name = strdup(theme_name); config->handler_context.seat_config->xcursor_theme.size = size; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/seat.c ================================================ #include #include #include "sway/commands.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" #include "log.h" #include "stringop.h" // must be in order for the bsearch // these handlers perform actions on the seat static const struct cmd_handler seat_action_handlers[] = { { "cursor", seat_cmd_cursor }, }; // must be in order for the bsearch // these handlers alter the seat config static const struct cmd_handler seat_handlers[] = { { "attach", seat_cmd_attach }, { "fallback", seat_cmd_fallback }, { "hide_cursor", seat_cmd_hide_cursor }, { "idle_inhibit", seat_cmd_idle_inhibit }, { "idle_wake", seat_cmd_idle_wake }, { "keyboard_grouping", seat_cmd_keyboard_grouping }, { "pointer_constraint", seat_cmd_pointer_constraint }, { "shortcuts_inhibitor", seat_cmd_shortcuts_inhibitor }, { "xcursor_theme", seat_cmd_xcursor_theme }, }; static struct cmd_results *action_handlers(int argc, char **argv) { struct cmd_results *res = config_subcommand(argv, argc, seat_action_handlers, sizeof(seat_action_handlers)); free_seat_config(config->handler_context.seat_config); config->handler_context.seat_config = NULL; return res; } static struct cmd_results *config_handlers(int argc, char **argv) { struct cmd_results *res = config_subcommand(argv, argc, seat_handlers, sizeof(seat_handlers)); if (res && res->status != CMD_SUCCESS) { free_seat_config(config->handler_context.seat_config); } else { struct seat_config *sc = store_seat_config(config->handler_context.seat_config); if (!config->reading) { input_manager_apply_seat_config(sc); } } config->handler_context.seat_config = NULL; return res; } struct cmd_results *cmd_seat(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "seat", EXPECTED_AT_LEAST, 2))) { return error; } if (!strcmp(argv[0], "-")) { if (config->reading) { return cmd_results_new(CMD_FAILURE, "Current seat alias (-) cannot be used in the config"); } config->handler_context.seat_config = new_seat_config(config->handler_context.seat->wlr_seat->name); } else { config->handler_context.seat_config = new_seat_config(argv[0]); } if (!config->handler_context.seat_config) { return cmd_results_new(CMD_FAILURE, "Couldn't allocate config"); } struct cmd_results *res = NULL; if (find_handler(argv[1], seat_action_handlers, sizeof(seat_action_handlers))) { res = action_handlers(argc - 1, argv + 1); } else { res = config_handlers(argc - 1, argv + 1); } return res ? res : cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/set.c ================================================ #include #include #include #include "sway/commands.h" #include "sway/config.h" #include "list.h" #include "log.h" #include "stringop.h" // sort in order of longest->shortest static int compare_set_qsort(const void *_l, const void *_r) { struct sway_variable const *l = *(void **)_l; struct sway_variable const *r = *(void **)_r; return strlen(r->name) - strlen(l->name); } void free_sway_variable(struct sway_variable *var) { if (!var) { return; } free(var->name); free(var->value); free(var); } struct cmd_results *cmd_set(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "set", EXPECTED_AT_LEAST, 2))) { return error; } if (argv[0][0] != '$') { return cmd_results_new(CMD_INVALID, "variable '%s' must start with $", argv[0]); } struct sway_variable *var = NULL; // Find old variable if it exists int i; for (i = 0; i < config->symbols->length; ++i) { var = config->symbols->items[i]; if (strcmp(var->name, argv[0]) == 0) { break; } var = NULL; } if (var) { free(var->value); } else { var = malloc(sizeof(struct sway_variable)); if (!var) { return cmd_results_new(CMD_FAILURE, "Unable to allocate variable"); } var->name = strdup(argv[0]); list_add(config->symbols, var); list_qsort(config->symbols, compare_set_qsort); } var->value = join_args(argv + 1, argc - 1); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/shortcuts_inhibitor.c ================================================ #include #include "log.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/input/seat.h" #include "sway/server.h" #include "sway/tree/container.h" #include "sway/tree/view.h" struct cmd_results *cmd_shortcuts_inhibitor(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "shortcuts_inhibitor", EXPECTED_EQUAL_TO, 1))) { return error; } struct sway_container *con = config->handler_context.container; if (!con || !con->view) { return cmd_results_new(CMD_INVALID, "Only views can have shortcuts inhibitors"); } struct sway_view *view = con->view; if (strcmp(argv[0], "enable") == 0) { view->shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; } else if (strcmp(argv[0], "disable") == 0) { view->shortcuts_inhibit = SHORTCUTS_INHIBIT_DISABLE; struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor = keyboard_shortcuts_inhibitor_get_for_surface( seat, view->surface); if (!sway_inhibitor) { continue; } wlr_keyboard_shortcuts_inhibitor_v1_deactivate( sway_inhibitor->inhibitor); sway_log(SWAY_DEBUG, "Deactivated keyboard shortcuts " "inhibitor for seat %s on view", seat->wlr_seat->name); } } else { return cmd_results_new(CMD_INVALID, "Expected `shortcuts_inhibitor enable|disable`"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/show_marks.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/root.h" #include "sway/tree/view.h" #include "sway/output.h" #include "list.h" #include "log.h" #include "stringop.h" #include "util.h" static void title_bar_update_iterator(struct sway_container *con, void *data) { container_update_marks(con); } struct cmd_results *cmd_show_marks(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "show_marks", EXPECTED_AT_LEAST, 1))) { return error; } config->show_marks = parse_boolean(argv[0], config->show_marks); if (config->show_marks) { root_for_each_container(title_bar_update_iterator, NULL); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/smart_borders.c ================================================ #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/arrange.h" #include "sway/tree/view.h" #include "util.h" struct cmd_results *cmd_smart_borders(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "smart_borders", EXPECTED_EQUAL_TO, 1))) { return error; } if (strcmp(argv[0], "no_gaps") == 0) { config->hide_edge_borders_smart = ESMART_NO_GAPS; } else { config->hide_edge_borders_smart = parse_boolean(argv[0], true) ? ESMART_ON : ESMART_OFF; } arrange_root(); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/smart_gaps.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/arrange.h" #include "sway/tree/view.h" #include "sway/tree/container.h" #include "log.h" #include "stringop.h" #include "util.h" struct cmd_results *cmd_smart_gaps(int argc, char **argv) { struct cmd_results *error = checkarg(argc, "smart_gaps", EXPECTED_AT_LEAST, 1); if (error) { return error; } if (strcmp(argv[0], "inverse_outer") == 0) { config->smart_gaps = SMART_GAPS_INVERSE_OUTER; } else { config->smart_gaps = parse_boolean(argv[0], config->smart_gaps) ? SMART_GAPS_ON : SMART_GAPS_OFF; } arrange_root(); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/split.c ================================================ #include #include #include "sway/commands.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" #include "log.h" static struct cmd_results *do_split(int layout) { struct sway_container *con = config->handler_context.container; struct sway_workspace *ws = config->handler_context.workspace; if (con) { if (container_is_scratchpad_hidden_or_child(con) && con->pending.fullscreen_mode != FULLSCREEN_GLOBAL) { return cmd_results_new(CMD_FAILURE, "Cannot split a hidden scratchpad container"); } container_split(con, layout); } else { workspace_split(ws, layout); } if (root->fullscreen_global) { arrange_root(); } else { arrange_workspace(ws); } return cmd_results_new(CMD_SUCCESS, NULL); } static struct cmd_results *do_unsplit(void) { struct sway_container *con = config->handler_context.container; struct sway_workspace *ws = config->handler_context.workspace; if (con && con->pending.parent && con->pending.parent->pending.children->length == 1) { container_flatten(con->pending.parent); } else { return cmd_results_new(CMD_FAILURE, "Can only flatten a child container with no siblings"); } if (root->fullscreen_global) { arrange_root(); } else { arrange_workspace(ws); } return cmd_results_new(CMD_SUCCESS, NULL); } struct cmd_results *cmd_split(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "split", EXPECTED_EQUAL_TO, 1))) { return error; } if (!root->outputs->length) { return cmd_results_new(CMD_INVALID, "Can't run this command while there's no outputs connected."); } if (strcasecmp(argv[0], "v") == 0 || strcasecmp(argv[0], "vertical") == 0) { return do_split(L_VERT); } else if (strcasecmp(argv[0], "h") == 0 || strcasecmp(argv[0], "horizontal") == 0) { return do_split(L_HORIZ); } else if (strcasecmp(argv[0], "t") == 0 || strcasecmp(argv[0], "toggle") == 0) { struct sway_container *focused = config->handler_context.container; if (focused && container_parent_layout(focused) == L_VERT) { return do_split(L_HORIZ); } else { return do_split(L_VERT); } } else if (strcasecmp(argv[0], "n") == 0 || strcasecmp(argv[0], "none") == 0) { return do_unsplit(); } else { return cmd_results_new(CMD_FAILURE, "Invalid split command (expected either horizontal or vertical)."); } return cmd_results_new(CMD_SUCCESS, NULL); } struct cmd_results *cmd_splitv(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "splitv", EXPECTED_EQUAL_TO, 0))) { return error; } return do_split(L_VERT); } struct cmd_results *cmd_splith(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "splith", EXPECTED_EQUAL_TO, 0))) { return error; } return do_split(L_HORIZ); } struct cmd_results *cmd_splitt(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "splitt", EXPECTED_EQUAL_TO, 0))) { return error; } struct sway_container *con = config->handler_context.container; if (con && container_parent_layout(con) == L_VERT) { return do_split(L_HORIZ); } else { return do_split(L_VERT); } } ================================================ FILE: sway/commands/sticky.c ================================================ #include #include #include "sway/commands.h" #include "sway/input/seat.h" #include "sway/ipc-server.h" #include "sway/output.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "list.h" #include "log.h" #include "util.h" struct cmd_results *cmd_sticky(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "sticky", EXPECTED_EQUAL_TO, 1))) { return error; } struct sway_container *container = config->handler_context.container; if (container == NULL) { return cmd_results_new(CMD_FAILURE, "No current container"); }; container->is_sticky = parse_boolean(argv[0], container->is_sticky); if (container_is_sticky_or_child(container) && !container_is_scratchpad_hidden(container)) { // move container to active workspace struct sway_workspace *active_workspace = output_get_active_workspace(container->pending.workspace->output); if (!sway_assert(active_workspace, "Expected output to have a workspace")) { return cmd_results_new(CMD_FAILURE, "Expected output to have a workspace"); } if (container->pending.workspace != active_workspace) { struct sway_workspace *old_workspace = container->pending.workspace; container_detach(container); workspace_add_floating(active_workspace, container); container_handle_fullscreen_reparent(container); arrange_workspace(active_workspace); workspace_consider_destroy(old_workspace); } } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/swap.c ================================================ #include #include "config.h" #include "log.h" #include "sway/commands.h" #include "sway/output.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/root.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "stringop.h" static const char expected_syntax[] = "Expected 'swap container with id|con_id|mark '"; static bool test_con_id(struct sway_container *container, void *data) { size_t *con_id = data; return container->node.id == *con_id; } #if WLR_HAS_XWAYLAND static bool test_id(struct sway_container *container, void *data) { xcb_window_t *wid = data; return (container->view && container->view->type == SWAY_VIEW_XWAYLAND && container->view->wlr_xwayland_surface->window_id == *wid); } #endif static bool test_mark(struct sway_container *container, void *mark) { if (container->marks->length) { return list_seq_find(container->marks, (int (*)(const void *, const void *))strcmp, mark) != -1; } return false; } struct cmd_results *cmd_swap(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "swap", EXPECTED_AT_LEAST, 4))) { return error; } if (!root->outputs->length) { return cmd_results_new(CMD_INVALID, "Can't run this command while there's no outputs connected."); } if (strcasecmp(argv[0], "container") || strcasecmp(argv[1], "with")) { return cmd_results_new(CMD_INVALID, "%s", expected_syntax); } struct sway_container *current = config->handler_context.container; struct sway_container *other = NULL; char *value = join_args(argv + 3, argc - 3); if (strcasecmp(argv[2], "id") == 0) { #if WLR_HAS_XWAYLAND xcb_window_t id = strtol(value, NULL, 0); other = root_find_container(test_id, &id); #endif } else if (strcasecmp(argv[2], "con_id") == 0) { size_t con_id = atoi(value); other = root_find_container(test_con_id, &con_id); } else if (strcasecmp(argv[2], "mark") == 0) { other = root_find_container(test_mark, value); } else { free(value); return cmd_results_new(CMD_INVALID, "%s", expected_syntax); } if (!other) { error = cmd_results_new(CMD_FAILURE, "Failed to find %s '%s'", argv[2], value); } else if (!current) { error = cmd_results_new(CMD_FAILURE, "Can only swap with containers and views"); } else if (current == other) { error = cmd_results_new(CMD_FAILURE, "Cannot swap a container with itself"); } else if (container_has_ancestor(current, other) || container_has_ancestor(other, current)) { error = cmd_results_new(CMD_FAILURE, "Cannot swap ancestor and descendant"); } free(value); if (error) { return error; } container_swap(current, other); if (root->fullscreen_global) { arrange_root(); } else { struct sway_node *current_parent = node_get_parent(¤t->node); struct sway_node *other_parent = node_get_parent(&other->node); if (current_parent) { arrange_node(current_parent); } if (other_parent && current_parent != other_parent) { arrange_node(other_parent); } } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/swaybg_command.c ================================================ #include #include "sway/commands.h" #include "log.h" #include "stringop.h" struct cmd_results *cmd_swaybg_command(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "swaybg_command", EXPECTED_AT_LEAST, 1))) { return error; } free(config->swaybg_command); config->swaybg_command = NULL; char *new_command = join_args(argv, argc); if (strcmp(new_command, "-") != 0) { config->swaybg_command = new_command; sway_log(SWAY_DEBUG, "Using custom swaybg command: %s", config->swaybg_command); } else { free(new_command); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/swaynag_command.c ================================================ #include #include "sway/commands.h" #include "log.h" #include "stringop.h" struct cmd_results *cmd_swaynag_command(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "swaynag_command", EXPECTED_AT_LEAST, 1))) { return error; } free(config->swaynag_command); config->swaynag_command = NULL; char *new_command = join_args(argv, argc); if (strcmp(new_command, "-") != 0) { config->swaynag_command = new_command; sway_log(SWAY_DEBUG, "Using custom swaynag command: %s", config->swaynag_command); } else { free(new_command); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/tiling_drag.c ================================================ #include "sway/commands.h" #include "util.h" struct cmd_results *cmd_tiling_drag(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "tiling_drag", EXPECTED_EQUAL_TO, 1))) { return error; } config->tiling_drag = parse_boolean(argv[0], config->tiling_drag); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/tiling_drag_threshold.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "log.h" struct cmd_results *cmd_tiling_drag_threshold(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "tiling_drag_threshold", EXPECTED_EQUAL_TO, 1))) { return error; } char *inv; int value = strtol(argv[0], &inv, 10); if (*inv != '\0' || value < 0) { return cmd_results_new(CMD_INVALID, "Invalid threshold specified"); } config->tiling_drag_threshold = value; return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/title_align.c ================================================ #include "sway/commands.h" #include "sway/config.h" #include "sway/output.h" #include "sway/tree/container.h" #include "sway/tree/root.h" static void arrange_title_bar_iterator(struct sway_container *con, void *data) { container_arrange_title_bar(con); } struct cmd_results *cmd_title_align(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "title_align", EXPECTED_AT_LEAST, 1))) { return error; } if (strcmp(argv[0], "left") == 0) { config->title_align = ALIGN_LEFT; } else if (strcmp(argv[0], "center") == 0) { config->title_align = ALIGN_CENTER; } else if (strcmp(argv[0], "right") == 0) { config->title_align = ALIGN_RIGHT; } else { return cmd_results_new(CMD_INVALID, "Expected 'title_align left|center|right'"); } root_for_each_container(arrange_title_bar_iterator, NULL); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/title_format.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/view.h" #include "log.h" #include "stringop.h" struct cmd_results *cmd_title_format(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "title_format", EXPECTED_AT_LEAST, 1))) { return error; } struct sway_container *container = config->handler_context.container; if (!container) { return cmd_results_new(CMD_INVALID, "Only valid containers can have a title_format"); } char *format = join_args(argv, argc); if (container->title_format) { free(container->title_format); } container->title_format = format; if (container->view) { view_update_title(container->view, true); } else { container_update_representation(container); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/titlebar_border_thickness.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/output.h" #include "sway/tree/arrange.h" #include "log.h" struct cmd_results *cmd_titlebar_border_thickness(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "titlebar_border_thickness", EXPECTED_EQUAL_TO, 1))) { return error; } char *inv; int value = strtol(argv[0], &inv, 10); if (*inv != '\0' || value < 0 || value > config->titlebar_v_padding) { return cmd_results_new(CMD_FAILURE, "Invalid size specified"); } config->titlebar_border_thickness = value; for (int i = 0; i < root->outputs->length; ++i) { struct sway_output *output = root->outputs->items[i]; struct sway_workspace *ws = output_get_active_workspace(output); if (!sway_assert(ws, "Expected output to have a workspace")) { return cmd_results_new(CMD_FAILURE, "Expected output to have a workspace"); } arrange_workspace(ws); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/titlebar_padding.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/output.h" #include "sway/tree/arrange.h" #include "log.h" struct cmd_results *cmd_titlebar_padding(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "titlebar_padding", EXPECTED_AT_LEAST, 1))) { return error; } char *inv; int h_value = strtol(argv[0], &inv, 10); if (*inv != '\0' || h_value < 0 || h_value < config->titlebar_border_thickness) { return cmd_results_new(CMD_FAILURE, "Invalid size specified"); } int v_value; if (argc == 1) { v_value = h_value; } else { v_value = strtol(argv[1], &inv, 10); if (*inv != '\0' || v_value < 0 || v_value < config->titlebar_border_thickness) { return cmd_results_new(CMD_FAILURE, "Invalid size specified"); } } config->titlebar_v_padding = v_value; config->titlebar_h_padding = h_value; for (int i = 0; i < root->outputs->length; ++i) { struct sway_output *output = root->outputs->items[i]; arrange_workspace(output_get_active_workspace(output)); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/unmark.c ================================================ #include #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/root.h" #include "sway/tree/view.h" #include "list.h" #include "log.h" #include "stringop.h" static void remove_mark(struct sway_container *con) { container_clear_marks(con); container_update_marks(con); } static void remove_all_marks_iterator(struct sway_container *con, void *data) { remove_mark(con); } // unmark Remove all marks from all views // unmark foo Remove single mark from whichever view has it // [criteria] unmark Remove all marks from matched view // [criteria] unmark foo Remove single mark from matched view struct cmd_results *cmd_unmark(int argc, char **argv) { // Determine the container struct sway_container *con = NULL; if (config->handler_context.node_overridden) { con = config->handler_context.container; } // Determine the mark char *mark = NULL; if (argc > 0) { mark = join_args(argv, argc); } if (con && mark) { // Remove the mark from the given container if (container_has_mark(con, mark)) { container_find_and_unmark(mark); } } else if (con && !mark) { // Clear all marks from the given container remove_mark(con); } else if (!con && mark) { // Remove mark from whichever container has it container_find_and_unmark(mark); } else { // Remove all marks from all containers root_for_each_container(remove_all_marks_iterator, NULL); } free(mark); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/urgent.c ================================================ #include "log.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/view.h" #include "util.h" struct cmd_results *cmd_urgent(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "urgent", EXPECTED_EQUAL_TO, 1))) { return error; } struct sway_container *container = config->handler_context.container; if (!container) { return cmd_results_new(CMD_FAILURE, "No current container"); } if (!container->view) { return cmd_results_new(CMD_INVALID, "Only views can be urgent"); } struct sway_view *view = container->view; if (strcmp(argv[0], "allow") == 0) { view->allow_request_urgent = true; } else if (strcmp(argv[0], "deny") == 0) { view->allow_request_urgent = false; } else { view_set_urgent(view, parse_boolean(argv[0], view_is_urgent(view))); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/workspace.c ================================================ #include #include #include #include #include "sway/commands.h" #include "sway/config.h" #include "sway/input/seat.h" #include "sway/tree/workspace.h" #include "list.h" #include "log.h" #include "stringop.h" static struct workspace_config *workspace_config_find_or_create(char *ws_name) { struct workspace_config *wsc = workspace_find_config(ws_name); if (wsc) { return wsc; } wsc = calloc(1, sizeof(struct workspace_config)); if (!wsc) { return NULL; } wsc->workspace = strdup(ws_name); wsc->outputs = create_list(); wsc->gaps_inner = INT_MIN; wsc->gaps_outer.top = INT_MIN; wsc->gaps_outer.right = INT_MIN; wsc->gaps_outer.bottom = INT_MIN; wsc->gaps_outer.left = INT_MIN; list_add(config->workspace_configs, wsc); return wsc; } void free_workspace_config(struct workspace_config *wsc) { free(wsc->workspace); list_free_items_and_destroy(wsc->outputs); free(wsc); } static void prevent_invalid_outer_gaps(struct workspace_config *wsc) { if (wsc->gaps_outer.top != INT_MIN && wsc->gaps_outer.top < -wsc->gaps_inner) { wsc->gaps_outer.top = -wsc->gaps_inner; } if (wsc->gaps_outer.right != INT_MIN && wsc->gaps_outer.right < -wsc->gaps_inner) { wsc->gaps_outer.right = -wsc->gaps_inner; } if (wsc->gaps_outer.bottom != INT_MIN && wsc->gaps_outer.bottom < -wsc->gaps_inner) { wsc->gaps_outer.bottom = -wsc->gaps_inner; } if (wsc->gaps_outer.left != INT_MIN && wsc->gaps_outer.left < -wsc->gaps_inner) { wsc->gaps_outer.left = -wsc->gaps_inner; } } static struct cmd_results *cmd_workspace_gaps(int argc, char **argv, int gaps_location) { const char expected[] = "Expected 'workspace gaps " "inner|outer|horizontal|vertical|top|right|bottom|left '"; if (gaps_location == 0) { return cmd_results_new(CMD_INVALID, "%s", expected); } struct cmd_results *error = NULL; if ((error = checkarg(argc, "workspace", EXPECTED_EQUAL_TO, gaps_location + 3))) { return error; } char *ws_name = join_args(argv, argc - 3); struct workspace_config *wsc = workspace_config_find_or_create(ws_name); free(ws_name); if (!wsc) { return cmd_results_new(CMD_FAILURE, "Unable to allocate workspace output"); } char *end; int amount = strtol(argv[gaps_location + 2], &end, 10); if (strlen(end)) { return cmd_results_new(CMD_FAILURE, "%s", expected); } bool valid = false; char *type = argv[gaps_location + 1]; if (!strcasecmp(type, "inner")) { valid = true; wsc->gaps_inner = (amount >= 0) ? amount : 0; } else { if (!strcasecmp(type, "outer") || !strcasecmp(type, "vertical") || !strcasecmp(type, "top")) { valid = true; wsc->gaps_outer.top = amount; } if (!strcasecmp(type, "outer") || !strcasecmp(type, "horizontal") || !strcasecmp(type, "right")) { valid = true; wsc->gaps_outer.right = amount; } if (!strcasecmp(type, "outer") || !strcasecmp(type, "vertical") || !strcasecmp(type, "bottom")) { valid = true; wsc->gaps_outer.bottom = amount; } if (!strcasecmp(type, "outer") || !strcasecmp(type, "horizontal") || !strcasecmp(type, "left")) { valid = true; wsc->gaps_outer.left = amount; } } if (!valid) { return cmd_results_new(CMD_INVALID, "%s", expected); } // Prevent invalid gaps configurations. if (wsc->gaps_inner != INT_MIN && wsc->gaps_inner < 0) { wsc->gaps_inner = 0; } prevent_invalid_outer_gaps(wsc); return error; } struct cmd_results *cmd_workspace(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "workspace", EXPECTED_AT_LEAST, 1))) { return error; } int output_location = -1; int gaps_location = -1; for (int i = 0; i < argc; ++i) { if (strcasecmp(argv[i], "output") == 0) { output_location = i; break; } } for (int i = 0; i < argc; ++i) { if (strcasecmp(argv[i], "gaps") == 0) { gaps_location = i; break; } } if (output_location == 0) { return cmd_results_new(CMD_INVALID, "Expected 'workspace output '"); } else if (output_location > 0) { if ((error = checkarg(argc, "workspace", EXPECTED_AT_LEAST, output_location + 2))) { return error; } char *ws_name = join_args(argv, output_location); struct workspace_config *wsc = workspace_config_find_or_create(ws_name); free(ws_name); if (!wsc) { return cmd_results_new(CMD_FAILURE, "Unable to allocate workspace output"); } for (int i = output_location + 1; i < argc; ++i) { list_add(wsc->outputs, strdup(argv[i])); } } else if (gaps_location >= 0) { if ((error = cmd_workspace_gaps(argc, argv, gaps_location))) { return error; } } else { if (config->reading || !config->active) { return cmd_results_new(CMD_DEFER, NULL); } else if (!root->outputs->length) { return cmd_results_new(CMD_INVALID, "Can't run this command while there's no outputs connected."); } if (root->fullscreen_global) { return cmd_results_new(CMD_FAILURE, "Can't switch workspaces while fullscreen global"); } bool auto_back_and_forth = true; while (strcasecmp(argv[0], "--no-auto-back-and-forth") == 0) { auto_back_and_forth = false; if ((error = checkarg(--argc, "workspace", EXPECTED_AT_LEAST, 1))) { return error; } ++argv; } struct sway_seat *seat = config->handler_context.seat; struct sway_workspace *ws = NULL; if (strcasecmp(argv[0], "number") == 0) { if (argc < 2) { return cmd_results_new(CMD_INVALID, "Expected workspace number"); } if (!isdigit(argv[1][0])) { return cmd_results_new(CMD_INVALID, "Invalid workspace number '%s'", argv[1]); } if (!(ws = workspace_by_number(argv[1]))) { char *name = join_args(argv + 1, argc - 1); ws = workspace_create(NULL, name); free(name); } if (ws && auto_back_and_forth) { ws = workspace_auto_back_and_forth(ws); } } else if (strcasecmp(argv[0], "next") == 0 || strcasecmp(argv[0], "prev") == 0 || strcasecmp(argv[0], "next_on_output") == 0 || strcasecmp(argv[0], "prev_on_output") == 0 || strcasecmp(argv[0], "current") == 0) { ws = workspace_by_name(argv[0]); } else if (strcasecmp(argv[0], "back_and_forth") == 0) { if (!seat->prev_workspace_name) { return cmd_results_new(CMD_INVALID, "There is no previous workspace"); } if (!(ws = workspace_by_name(argv[0]))) { ws = workspace_create(NULL, seat->prev_workspace_name); } } else { char *name = join_args(argv, argc); if (!(ws = workspace_by_name(name))) { ws = workspace_create(NULL, name); } free(name); if (ws && auto_back_and_forth) { ws = workspace_auto_back_and_forth(ws); } } if (!ws) { return cmd_results_new(CMD_FAILURE, "No workspace to switch to"); } workspace_switch(ws); seat_consider_warp_to_focus(seat); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/workspace_layout.c ================================================ #include #include #include "sway/commands.h" struct cmd_results *cmd_workspace_layout(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "workspace_layout", EXPECTED_EQUAL_TO, 1))) { return error; } if (strcasecmp(argv[0], "default") == 0) { config->default_layout = L_NONE; } else if (strcasecmp(argv[0], "stacking") == 0) { config->default_layout = L_STACKED; } else if (strcasecmp(argv[0], "tabbed") == 0) { config->default_layout = L_TABBED; } else { return cmd_results_new(CMD_INVALID, "Expected 'workspace_layout '"); } return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/ws_auto_back_and_forth.c ================================================ #include #include #include "sway/commands.h" #include "util.h" struct cmd_results *cmd_ws_auto_back_and_forth(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "workspace_auto_back_and_forth", EXPECTED_EQUAL_TO, 1))) { return error; } config->auto_back_and_forth = parse_boolean(argv[0], config->auto_back_and_forth); return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands/xwayland.c ================================================ #include "sway/config.h" #include "log.h" #include "sway/commands.h" #include "sway/server.h" #include "util.h" struct cmd_results *cmd_xwayland(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "xwayland", EXPECTED_EQUAL_TO, 1))) { return error; } #ifdef WLR_HAS_XWAYLAND enum xwayland_mode xwayland; if (strcmp(argv[0], "force") == 0) { xwayland = XWAYLAND_MODE_IMMEDIATE; } else if (parse_boolean(argv[0], true)) { xwayland = XWAYLAND_MODE_LAZY; } else { xwayland = XWAYLAND_MODE_DISABLED; } // config->xwayland is reset to the previous value on reload in // load_main_config() if (config->reloading && config->xwayland != xwayland) { return cmd_results_new(CMD_FAILURE, "xwayland can only be enabled/disabled at launch"); } config->xwayland = xwayland; #else sway_log(SWAY_INFO, "Ignoring `xwayland` command, " "sway hasn't been built with Xwayland support"); #endif return cmd_results_new(CMD_SUCCESS, NULL); } ================================================ FILE: sway/commands.c ================================================ #include #include #include #include #include #include #include #include "sway/commands.h" #include "sway/config.h" #include "sway/criteria.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" #include "sway/tree/view.h" #include "stringop.h" #include "log.h" // Returns error object, or NULL if check succeeds. struct cmd_results *checkarg(int argc, const char *name, enum expected_args type, int val) { const char *error_name = NULL; switch (type) { case EXPECTED_AT_LEAST: if (argc < val) { error_name = "at least "; } break; case EXPECTED_AT_MOST: if (argc > val) { error_name = "at most "; } break; case EXPECTED_EQUAL_TO: if (argc != val) { error_name = ""; } } return error_name ? cmd_results_new(CMD_INVALID, "Invalid %s command " "(expected %s%d argument%s, got %d)", name, error_name, val, val != 1 ? "s" : "", argc) : NULL; } /* Keep alphabetized */ static const struct cmd_handler handlers[] = { { "assign", cmd_assign }, { "bar", cmd_bar }, { "bindcode", cmd_bindcode }, { "bindgesture", cmd_bindgesture }, { "bindswitch", cmd_bindswitch }, { "bindsym", cmd_bindsym }, { "client.background", cmd_client_noop }, { "client.focused", cmd_client_focused }, { "client.focused_inactive", cmd_client_focused_inactive }, { "client.focused_tab_title", cmd_client_focused_tab_title }, { "client.placeholder", cmd_client_noop }, { "client.unfocused", cmd_client_unfocused }, { "client.urgent", cmd_client_urgent }, { "default_border", cmd_default_border }, { "default_floating_border", cmd_default_floating_border }, { "exec", cmd_exec }, { "exec_always", cmd_exec_always }, { "floating_maximum_size", cmd_floating_maximum_size }, { "floating_minimum_size", cmd_floating_minimum_size }, { "floating_modifier", cmd_floating_modifier }, { "focus", cmd_focus }, { "focus_follows_mouse", cmd_focus_follows_mouse }, { "focus_on_window_activation", cmd_focus_on_window_activation }, { "focus_wrapping", cmd_focus_wrapping }, { "font", cmd_font }, { "for_window", cmd_for_window }, { "force_display_urgency_hint", cmd_force_display_urgency_hint }, { "force_focus_wrapping", cmd_force_focus_wrapping }, { "fullscreen", cmd_fullscreen }, { "gaps", cmd_gaps }, { "hide_edge_borders", cmd_hide_edge_borders }, { "input", cmd_input }, { "mode", cmd_mode }, { "mouse_warping", cmd_mouse_warping }, { "new_float", cmd_new_float }, { "new_window", cmd_new_window }, { "no_focus", cmd_no_focus }, { "output", cmd_output }, { "popup_during_fullscreen", cmd_popup_during_fullscreen }, { "seat", cmd_seat }, { "set", cmd_set }, { "show_marks", cmd_show_marks }, { "smart_borders", cmd_smart_borders }, { "smart_gaps", cmd_smart_gaps }, { "tiling_drag", cmd_tiling_drag }, { "tiling_drag_threshold", cmd_tiling_drag_threshold }, { "title_align", cmd_title_align }, { "titlebar_border_thickness", cmd_titlebar_border_thickness }, { "titlebar_padding", cmd_titlebar_padding }, { "unbindcode", cmd_unbindcode }, { "unbindgesture", cmd_unbindgesture }, { "unbindswitch", cmd_unbindswitch }, { "unbindsym", cmd_unbindsym }, { "workspace", cmd_workspace }, { "workspace_auto_back_and_forth", cmd_ws_auto_back_and_forth }, }; /* Config-time only commands. Keep alphabetized */ static const struct cmd_handler config_handlers[] = { { "default_orientation", cmd_default_orientation }, { "include", cmd_include }, { "primary_selection", cmd_primary_selection }, { "swaybg_command", cmd_swaybg_command }, { "swaynag_command", cmd_swaynag_command }, { "workspace_layout", cmd_workspace_layout }, { "xwayland", cmd_xwayland }, }; /* Runtime-only commands. Keep alphabetized */ static const struct cmd_handler command_handlers[] = { { "allow_tearing", cmd_allow_tearing }, { "border", cmd_border }, { "create_output", cmd_create_output }, { "exit", cmd_exit }, { "floating", cmd_floating }, { "fullscreen", cmd_fullscreen }, { "inhibit_idle", cmd_inhibit_idle }, { "kill", cmd_kill }, { "layout", cmd_layout }, { "mark", cmd_mark }, { "max_render_time", cmd_max_render_time }, { "move", cmd_move }, { "nop", cmd_nop }, { "opacity", cmd_opacity }, { "reload", cmd_reload }, { "rename", cmd_rename }, { "resize", cmd_resize }, { "scratchpad", cmd_scratchpad }, { "shortcuts_inhibitor", cmd_shortcuts_inhibitor }, { "split", cmd_split }, { "splith", cmd_splith }, { "splitt", cmd_splitt }, { "splitv", cmd_splitv }, { "sticky", cmd_sticky }, { "swap", cmd_swap }, { "title_format", cmd_title_format }, { "unmark", cmd_unmark }, { "urgent", cmd_urgent }, }; static int handler_compare(const void *_a, const void *_b) { const struct cmd_handler *a = _a; const struct cmd_handler *b = _b; return strcasecmp(a->command, b->command); } const struct cmd_handler *find_handler(const char *line, const struct cmd_handler *handlers, size_t handlers_size) { if (!handlers || !handlers_size) { return NULL; } const struct cmd_handler query = { .command = line }; return bsearch(&query, handlers, handlers_size / sizeof(struct cmd_handler), sizeof(struct cmd_handler), handler_compare); } static const struct cmd_handler *find_handler_ex(char *line, const struct cmd_handler *config_handlers, size_t config_handlers_size, const struct cmd_handler *command_handlers, size_t command_handlers_size, const struct cmd_handler *handlers, size_t handlers_size) { const struct cmd_handler *handler = NULL; if (config->reading) { handler = find_handler(line, config_handlers, config_handlers_size); } else if (config->active) { handler = find_handler(line, command_handlers, command_handlers_size); } return handler ? handler : find_handler(line, handlers, handlers_size); } static const struct cmd_handler *find_core_handler(char *line) { return find_handler_ex(line, config_handlers, sizeof(config_handlers), command_handlers, sizeof(command_handlers), handlers, sizeof(handlers)); } static void set_config_node(struct sway_node *node, bool node_overridden) { config->handler_context.node = node; config->handler_context.container = NULL; config->handler_context.workspace = NULL; config->handler_context.node_overridden = node_overridden; if (node == NULL) { return; } switch (node->type) { case N_CONTAINER: config->handler_context.container = node->sway_container; config->handler_context.workspace = node->sway_container->pending.workspace; break; case N_WORKSPACE: config->handler_context.workspace = node->sway_workspace; break; case N_ROOT: case N_OUTPUT: break; } } list_t *execute_command(char *_exec, struct sway_seat *seat, struct sway_container *con) { char *cmd; char matched_delim = ';'; list_t *containers = NULL; bool using_criteria = false; if (seat == NULL) { // passing a NULL seat means we just pick the default seat seat = input_manager_get_default_seat(); if (!sway_assert(seat, "could not find a seat to run the command on")) { return NULL; } } char *exec = strdup(_exec); char *head = exec; list_t *res_list = create_list(); if (!res_list || !exec) { return NULL; } config->handler_context.seat = seat; do { for (; isspace(*head); ++head) {} // Extract criteria (valid for this command list only). if (matched_delim == ';') { using_criteria = false; if (*head == '[') { char *error = NULL; struct criteria *criteria = criteria_parse(head, &error); if (!criteria) { list_add(res_list, cmd_results_new(CMD_INVALID, "%s", error)); free(error); goto cleanup; } list_free(containers); containers = criteria_get_containers(criteria); head += strlen(criteria->raw); criteria_destroy(criteria); using_criteria = true; // Skip leading whitespace for (; isspace(*head); ++head) {} } } // Split command list cmd = argsep(&head, ";,", &matched_delim); for (; isspace(*cmd); ++cmd) {} if (strcmp(cmd, "") == 0) { sway_log(SWAY_INFO, "Ignoring empty command."); continue; } sway_log(SWAY_INFO, "Handling command '%s'", cmd); //TODO better handling of argv int argc; char **argv = split_args(cmd, &argc); if (strcmp(argv[0], "exec") != 0 && strcmp(argv[0], "exec_always") != 0 && strcmp(argv[0], "mode") != 0) { for (int i = 1; i < argc; ++i) { if (*argv[i] == '\"' || *argv[i] == '\'') { strip_quotes(argv[i]); } } } const struct cmd_handler *handler = find_core_handler(argv[0]); if (!handler) { list_add(res_list, cmd_results_new(CMD_INVALID, "Unknown/invalid command '%s'", argv[0])); free_argv(argc, argv); goto cleanup; } // Var replacement, for all but first argument of set for (int i = handler->handle == cmd_set ? 2 : 1; i < argc; ++i) { argv[i] = do_var_replacement(argv[i]); } if (!using_criteria) { if (con) { set_config_node(&con->node, true); } else { set_config_node(seat_get_focus_inactive(seat, &root->node), false); } struct cmd_results *res = handler->handle(argc-1, argv+1); list_add(res_list, res); if (res->status == CMD_INVALID) { free_argv(argc, argv); goto cleanup; } } else if (containers->length == 0) { list_add(res_list, cmd_results_new(CMD_FAILURE, "No matching node.")); } else { struct cmd_results *fail_res = NULL; for (int i = 0; i < containers->length; ++i) { struct sway_container *container = containers->items[i]; set_config_node(&container->node, true); struct cmd_results *res = handler->handle(argc-1, argv+1); if (res->status == CMD_SUCCESS) { free_cmd_results(res); } else { // last failure will take precedence if (fail_res) { free_cmd_results(fail_res); } fail_res = res; if (res->status == CMD_INVALID) { list_add(res_list, fail_res); free_argv(argc, argv); goto cleanup; } } } list_add(res_list, fail_res ? fail_res : cmd_results_new(CMD_SUCCESS, NULL)); } free_argv(argc, argv); } while(head); cleanup: free(exec); list_free(containers); return res_list; } // this is like execute_command above, except: // 1) it ignores empty commands (empty lines) // 2) it does variable substitution // 3) it doesn't split commands (because the multiple commands are supposed to // be chained together) // 4) execute_command handles all state internally while config_command has // some state handled outside (notably the block mode, in read_config) struct cmd_results *config_command(char *exec, char **new_block) { struct cmd_results *results = NULL; int argc; char **argv = split_args(exec, &argc); // Check for empty lines if (!argc) { results = cmd_results_new(CMD_SUCCESS, NULL); goto cleanup; } // Check for the start of a block if (argc > 1 && strcmp(argv[argc - 1], "{") == 0) { *new_block = join_args(argv, argc - 1); results = cmd_results_new(CMD_BLOCK, NULL); goto cleanup; } // Check for the end of a block if (strcmp(argv[argc - 1], "}") == 0) { results = cmd_results_new(CMD_BLOCK_END, NULL); goto cleanup; } // Make sure the command is not stored in a variable if (*argv[0] == '$') { argv[0] = do_var_replacement(argv[0]); char *temp = join_args(argv, argc); free_argv(argc, argv); argv = split_args(temp, &argc); free(temp); if (!argc) { results = cmd_results_new(CMD_SUCCESS, NULL); goto cleanup; } } // Determine the command handler sway_log(SWAY_INFO, "Config command: %s", exec); const struct cmd_handler *handler = find_core_handler(argv[0]); if (!handler || !handler->handle) { if (handler) { results = cmd_results_new(CMD_INVALID, "Command '%s' is shimmed, but unimplemented", argv[0]); } else { results = cmd_results_new(CMD_INVALID, "Unknown/invalid command '%s'", argv[0]); } goto cleanup; } // Do variable replacement if (handler->handle == cmd_set && argc > 1 && *argv[1] == '$') { // Escape the variable name so it does not get replaced by one shorter char *temp = calloc(1, strlen(argv[1]) + 2); temp[0] = '$'; strcpy(&temp[1], argv[1]); free(argv[1]); argv[1] = temp; } char *command = do_var_replacement(join_args(argv, argc)); sway_log(SWAY_INFO, "After replacement: %s", command); free_argv(argc, argv); argv = split_args(command, &argc); free(command); // Strip quotes and unescape the string for (int i = handler->handle == cmd_set ? 2 : 1; i < argc; ++i) { if (handler->handle != cmd_exec && handler->handle != cmd_exec_always && handler->handle != cmd_mode && handler->handle != cmd_bindsym && handler->handle != cmd_bindcode && handler->handle != cmd_bindswitch && handler->handle != cmd_bindgesture && handler->handle != cmd_set && handler->handle != cmd_for_window && (*argv[i] == '\"' || *argv[i] == '\'')) { strip_quotes(argv[i]); } unescape_string(argv[i]); } // Run command results = handler->handle(argc - 1, argv + 1); cleanup: free_argv(argc, argv); return results; } struct cmd_results *config_subcommand(char **argv, int argc, const struct cmd_handler *handlers, size_t handlers_size) { char *command = join_args(argv, argc); sway_log(SWAY_DEBUG, "Subcommand: %s", command); free(command); const struct cmd_handler *handler = find_handler(argv[0], handlers, handlers_size); if (!handler) { return cmd_results_new(CMD_INVALID, "Unknown/invalid command '%s'", argv[0]); } if (handler->handle) { return handler->handle(argc - 1, argv + 1); } return cmd_results_new(CMD_INVALID, "The command '%s' is shimmed, but unimplemented", argv[0]); } struct cmd_results *config_commands_command(char *exec) { struct cmd_results *results = NULL; int argc; char **argv = split_args(exec, &argc); if (!argc) { results = cmd_results_new(CMD_SUCCESS, NULL); goto cleanup; } // Find handler for the command this is setting a policy for char *cmd = argv[0]; if (strcmp(cmd, "}") == 0) { results = cmd_results_new(CMD_BLOCK_END, NULL); goto cleanup; } const struct cmd_handler *handler = find_handler(cmd, NULL, 0); if (!handler && strcmp(cmd, "*") != 0) { results = cmd_results_new(CMD_INVALID, "Unknown/invalid command '%s'", cmd); goto cleanup; } results = cmd_results_new(CMD_SUCCESS, NULL); cleanup: free_argv(argc, argv); return results; } struct cmd_results *cmd_results_new(enum cmd_status status, const char *format, ...) { struct cmd_results *results = malloc(sizeof(struct cmd_results)); if (!results) { sway_log(SWAY_ERROR, "Unable to allocate command results"); return NULL; } results->status = status; if (format) { va_list args; va_start(args, format); results->error = vformat_str(format, args); va_end(args); } else { results->error = NULL; } return results; } void free_cmd_results(struct cmd_results *results) { if (results->error) { free(results->error); } free(results); } char *cmd_results_to_json(list_t *res_list) { json_object *result_array = json_object_new_array(); for (int i = 0; i < res_list->length; ++i) { struct cmd_results *results = res_list->items[i]; json_object *root = json_object_new_object(); json_object_object_add(root, "success", json_object_new_boolean(results->status == CMD_SUCCESS)); if (results->error) { json_object_object_add(root, "parse_error", json_object_new_boolean(results->status == CMD_INVALID)); json_object_object_add( root, "error", json_object_new_string(results->error)); } json_object_array_add(result_array, root); } const char *json = json_object_to_json_string(result_array); char *res = strdup(json); json_object_put(result_array); return res; } ================================================ FILE: sway/config/bar.c ================================================ #include #include #include #include #include #include #include #include #include #include #include #include "sway/config.h" #include "sway/input/keyboard.h" #include "sway/output.h" #include "sway/server.h" #include "config.h" #include "list.h" #include "log.h" #include "stringop.h" #include "util.h" void free_bar_binding(struct bar_binding *binding) { if (!binding) { return; } free(binding->command); free(binding); } void free_bar_config(struct bar_config *bar) { if (!bar) { return; } free(bar->id); free(bar->mode); free(bar->position); free(bar->hidden_state); free(bar->status_command); free(bar->swaybar_command); free(bar->font); free(bar->separator_symbol); if (bar->bindings) { for (int i = 0; i < bar->bindings->length; i++) { free_bar_binding(bar->bindings->items[i]); } } list_free(bar->bindings); list_free_items_and_destroy(bar->outputs); if (bar->client != NULL) { wl_client_destroy(bar->client); } free(bar->colors.background); free(bar->colors.statusline); free(bar->colors.separator); free(bar->colors.focused_background); free(bar->colors.focused_statusline); free(bar->colors.focused_separator); free(bar->colors.focused_workspace_border); free(bar->colors.focused_workspace_bg); free(bar->colors.focused_workspace_text); free(bar->colors.active_workspace_border); free(bar->colors.active_workspace_bg); free(bar->colors.active_workspace_text); free(bar->colors.inactive_workspace_border); free(bar->colors.inactive_workspace_bg); free(bar->colors.inactive_workspace_text); free(bar->colors.urgent_workspace_border); free(bar->colors.urgent_workspace_bg); free(bar->colors.urgent_workspace_text); free(bar->colors.binding_mode_border); free(bar->colors.binding_mode_bg); free(bar->colors.binding_mode_text); #if HAVE_TRAY list_free_items_and_destroy(bar->tray_outputs); free(bar->icon_theme); struct tray_binding *tray_bind = NULL, *tmp_tray_bind = NULL; wl_list_for_each_safe(tray_bind, tmp_tray_bind, &bar->tray_bindings, link) { wl_list_remove(&tray_bind->link); free(tray_bind); } #endif free(bar); } struct bar_config *default_bar_config(void) { struct bar_config *bar = NULL; bar = calloc(1, sizeof(struct bar_config)); if (!bar) { return NULL; } bar->outputs = NULL; bar->position = strdup("bottom"); bar->pango_markup = PANGO_MARKUP_DEFAULT; bar->swaybar_command = NULL; bar->font = NULL; bar->height = 0; bar->workspace_buttons = true; bar->wrap_scroll = false; bar->separator_symbol = NULL; bar->strip_workspace_numbers = false; bar->strip_workspace_name = false; bar->binding_mode_indicator = true; bar->verbose = false; bar->modifier = get_modifier_mask_by_name("Mod4"); bar->status_padding = 1; bar->status_edge_padding = 3; bar->workspace_min_width = 0; if (!(bar->mode = strdup("dock"))) { goto cleanup; } if (!(bar->hidden_state = strdup("hide"))) { goto cleanup; } if (!(bar->bindings = create_list())) { goto cleanup; } // set default colors if (!(bar->colors.background = strndup("#000000ff", 9))) { goto cleanup; } if (!(bar->colors.statusline = strndup("#ffffffff", 9))) { goto cleanup; } if (!(bar->colors.separator = strndup("#666666ff", 9))) { goto cleanup; } if (!(bar->colors.focused_workspace_border = strndup("#4c7899ff", 9))) { goto cleanup; } if (!(bar->colors.focused_workspace_bg = strndup("#285577ff", 9))) { goto cleanup; } if (!(bar->colors.focused_workspace_text = strndup("#ffffffff", 9))) { goto cleanup; } if (!(bar->colors.active_workspace_border = strndup("#333333ff", 9))) { goto cleanup; } if (!(bar->colors.active_workspace_bg = strndup("#5f676aff", 9))) { goto cleanup; } if (!(bar->colors.active_workspace_text = strndup("#ffffffff", 9))) { goto cleanup; } if (!(bar->colors.inactive_workspace_border = strndup("#333333ff", 9))) { goto cleanup; } if (!(bar->colors.inactive_workspace_bg = strndup("#222222ff", 9))) { goto cleanup; } if (!(bar->colors.inactive_workspace_text = strndup("#888888ff", 9))) { goto cleanup; } if (!(bar->colors.urgent_workspace_border = strndup("#2f343aff", 9))) { goto cleanup; } if (!(bar->colors.urgent_workspace_bg = strndup("#900000ff", 9))) { goto cleanup; } if (!(bar->colors.urgent_workspace_text = strndup("#ffffffff", 9))) { goto cleanup; } // if the following colors stay undefined, they fall back to background, // statusline, separator and urgent_workspace_*. bar->colors.focused_background = NULL; bar->colors.focused_statusline = NULL; bar->colors.focused_separator = NULL; bar->colors.binding_mode_border = NULL; bar->colors.binding_mode_bg = NULL; bar->colors.binding_mode_text = NULL; #if HAVE_TRAY bar->tray_padding = 2; wl_list_init(&bar->tray_bindings); #endif return bar; cleanup: free_bar_config(bar); return NULL; } static void handle_swaybar_client_destroy(struct wl_listener *listener, void *data) { struct bar_config *bar = wl_container_of(listener, bar, client_destroy); wl_list_remove(&bar->client_destroy.link); wl_list_init(&bar->client_destroy.link); bar->client = NULL; } static void invoke_swaybar(struct bar_config *bar) { int sockets[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) != 0) { sway_log_errno(SWAY_ERROR, "socketpair failed"); return; } if (!sway_set_cloexec(sockets[0], true) || !sway_set_cloexec(sockets[1], true)) { return; } bar->client = wl_client_create(server.wl_display, sockets[0]); if (bar->client == NULL) { sway_log_errno(SWAY_ERROR, "wl_client_create failed"); return; } bar->client_destroy.notify = handle_swaybar_client_destroy; wl_client_add_destroy_listener(bar->client, &bar->client_destroy); pid_t pid = fork(); if (pid < 0) { sway_log(SWAY_ERROR, "Failed to create fork for swaybar"); return; } else if (pid == 0) { if (!sway_set_cloexec(sockets[1], false)) { _exit(EXIT_FAILURE); } char wayland_socket_str[16]; snprintf(wayland_socket_str, sizeof(wayland_socket_str), "%d", sockets[1]); setenv("WAYLAND_SOCKET", wayland_socket_str, true); // run custom swaybar char *const cmd[] = { bar->swaybar_command ? bar->swaybar_command : "swaybar", "-b", bar->id, NULL}; execvp(cmd[0], cmd); _exit(EXIT_FAILURE); } if (close(sockets[1]) != 0) { sway_log_errno(SWAY_ERROR, "close failed"); return; } sway_log(SWAY_DEBUG, "Spawned swaybar %s", bar->id); } void load_swaybar(struct bar_config *bar) { if (bar->client != NULL) { wl_client_destroy(bar->client); } sway_log(SWAY_DEBUG, "Invoking swaybar for bar id '%s'", bar->id); invoke_swaybar(bar); } void load_swaybars(void) { for (int i = 0; i < config->bars->length; ++i) { struct bar_config *bar = config->bars->items[i]; load_swaybar(bar); } } ================================================ FILE: sway/config/input.c ================================================ #include #include #include #include "sway/config.h" #include "sway/input/keyboard.h" #include "sway/server.h" #include "log.h" struct input_config *new_input_config(const char* identifier) { struct input_config *input = calloc(1, sizeof(struct input_config)); if (!input) { sway_log(SWAY_DEBUG, "Unable to allocate input config"); return NULL; } sway_log(SWAY_DEBUG, "new_input_config(%s)", identifier); if (!(input->identifier = strdup(identifier))) { free(input); sway_log(SWAY_DEBUG, "Unable to allocate input config"); return NULL; } input->input_type = NULL; input->tap = INT_MIN; input->tap_button_map = INT_MIN; input->drag = INT_MIN; input->drag_lock = INT_MIN; input->dwt = INT_MIN; input->dwtp = INT_MIN; input->send_events = INT_MIN; input->click_method = INT_MIN; input->clickfinger_button_map = INT_MIN; input->middle_emulation = INT_MIN; input->natural_scroll = INT_MIN; input->accel_profile = INT_MIN; input->rotation_angle = FLT_MIN; input->pointer_accel = FLT_MIN; input->scroll_factor = FLT_MIN; input->scroll_button = INT_MIN; input->scroll_button_lock = INT_MIN; input->scroll_method = INT_MIN; input->left_handed = INT_MIN; input->repeat_delay = INT_MIN; input->repeat_rate = INT_MIN; input->xkb_numlock = INT_MIN; input->xkb_capslock = INT_MIN; input->xkb_file_is_set = false; input->tools = create_list(); return input; } void merge_input_config(struct input_config *dst, struct input_config *src) { if (src->accel_profile != INT_MIN) { dst->accel_profile = src->accel_profile; } if (src->click_method != INT_MIN) { dst->click_method = src->click_method; } if (src->clickfinger_button_map != INT_MIN) { dst->clickfinger_button_map = src->clickfinger_button_map; } if (src->drag != INT_MIN) { dst->drag = src->drag; } if (src->drag_lock != INT_MIN) { dst->drag_lock = src->drag_lock; } if (src->dwt != INT_MIN) { dst->dwt = src->dwt; } if (src->dwtp != INT_MIN) { dst->dwtp = src->dwtp; } if (src->left_handed != INT_MIN) { dst->left_handed = src->left_handed; } if (src->middle_emulation != INT_MIN) { dst->middle_emulation = src->middle_emulation; } if (src->natural_scroll != INT_MIN) { dst->natural_scroll = src->natural_scroll; } if (src->rotation_angle != FLT_MIN) { dst->rotation_angle = src->rotation_angle; } if (src->pointer_accel != FLT_MIN) { dst->pointer_accel = src->pointer_accel; } if (src->scroll_factor != FLT_MIN) { dst->scroll_factor = src->scroll_factor; } if (src->repeat_delay != INT_MIN) { dst->repeat_delay = src->repeat_delay; } if (src->repeat_rate != INT_MIN) { dst->repeat_rate = src->repeat_rate; } if (src->scroll_method != INT_MIN) { dst->scroll_method = src->scroll_method; } if (src->scroll_button != INT_MIN) { dst->scroll_button = src->scroll_button; } if (src->scroll_button_lock != INT_MIN) { dst->scroll_button_lock = src->scroll_button_lock; } if (src->send_events != INT_MIN) { dst->send_events = src->send_events; } if (src->tap != INT_MIN) { dst->tap = src->tap; } if (src->tap_button_map != INT_MIN) { dst->tap_button_map = src->tap_button_map; } if (src->xkb_file_is_set) { free(dst->xkb_file); dst->xkb_file = src->xkb_file ? strdup(src->xkb_file) : NULL; dst->xkb_file_is_set = dst->xkb_file != NULL; } if (src->xkb_layout) { free(dst->xkb_layout); dst->xkb_layout = strdup(src->xkb_layout); } if (src->xkb_model) { free(dst->xkb_model); dst->xkb_model = strdup(src->xkb_model); } if (src->xkb_options) { free(dst->xkb_options); dst->xkb_options = strdup(src->xkb_options); } if (src->xkb_rules) { free(dst->xkb_rules); dst->xkb_rules = strdup(src->xkb_rules); } if (src->xkb_variant) { free(dst->xkb_variant); dst->xkb_variant = strdup(src->xkb_variant); } if (src->xkb_numlock != INT_MIN) { dst->xkb_numlock = src->xkb_numlock; } if (src->xkb_capslock != INT_MIN) { dst->xkb_capslock = src->xkb_capslock; } if (src->mapped_from_region) { free(dst->mapped_from_region); dst->mapped_from_region = malloc(sizeof(struct input_config_mapped_from_region)); memcpy(dst->mapped_from_region, src->mapped_from_region, sizeof(struct input_config_mapped_from_region)); } if (src->mapped_to) { dst->mapped_to = src->mapped_to; } if (src->mapped_to_output) { free(dst->mapped_to_output); dst->mapped_to_output = strdup(src->mapped_to_output); } if (src->mapped_to_region) { free(dst->mapped_to_region); dst->mapped_to_region = malloc(sizeof(struct wlr_box)); memcpy(dst->mapped_to_region, src->mapped_to_region, sizeof(struct wlr_box)); } if (src->calibration_matrix.configured) { dst->calibration_matrix.configured = src->calibration_matrix.configured; memcpy(dst->calibration_matrix.matrix, src->calibration_matrix.matrix, sizeof(src->calibration_matrix.matrix)); } for (int i = 0; i < src->tools->length; i++) { struct input_config_tool *src_tool = src->tools->items[i]; for (int j = 0; j < dst->tools->length; j++) { struct input_config_tool *dst_tool = dst->tools->items[j]; if (src_tool->type == dst_tool->type) { dst_tool->mode = src_tool->mode; goto tool_merge_outer; } } struct input_config_tool *dst_tool = malloc(sizeof(*dst_tool)); memcpy(dst_tool, src_tool, sizeof(*dst_tool)); list_add(dst->tools, dst_tool); tool_merge_outer:; } } static bool validate_xkb_merge(struct input_config *dest, struct input_config *src, char **xkb_error) { struct input_config *temp = new_input_config("temp"); if (dest) { merge_input_config(temp, dest); } merge_input_config(temp, src); struct xkb_keymap *keymap = sway_keyboard_compile_keymap(temp, xkb_error); free_input_config(temp); if (!keymap) { return false; } xkb_keymap_unref(keymap); return true; } static bool validate_wildcard_on_all(struct input_config *wildcard, char **error) { for (int i = 0; i < config->input_configs->length; i++) { struct input_config *ic = config->input_configs->items[i]; if (strcmp(wildcard->identifier, ic->identifier) != 0) { sway_log(SWAY_DEBUG, "Validating xkb merge of * on %s", ic->identifier); if (!validate_xkb_merge(ic, wildcard, error)) { return false; } } } for (int i = 0; i < config->input_type_configs->length; i++) { struct input_config *ic = config->input_type_configs->items[i]; sway_log(SWAY_DEBUG, "Validating xkb merge of * config on %s", ic->identifier); if (!validate_xkb_merge(ic, wildcard, error)) { return false; } } return true; } static void merge_wildcard_on_all(struct input_config *wildcard) { for (int i = 0; i < config->input_configs->length; i++) { struct input_config *ic = config->input_configs->items[i]; if (strcmp(wildcard->identifier, ic->identifier) != 0) { sway_log(SWAY_DEBUG, "Merging input * config on %s", ic->identifier); merge_input_config(ic, wildcard); } } for (int i = 0; i < config->input_type_configs->length; i++) { struct input_config *ic = config->input_type_configs->items[i]; sway_log(SWAY_DEBUG, "Merging input * config on %s", ic->identifier); merge_input_config(ic, wildcard); } } static bool validate_type_on_existing(struct input_config *type_wildcard, char **error) { for (int i = 0; i < config->input_configs->length; i++) { struct input_config *ic = config->input_configs->items[i]; if (ic->input_type == NULL) { continue; } if (strcmp(ic->input_type, type_wildcard->identifier + 5) == 0) { sway_log(SWAY_DEBUG, "Validating merge of %s on %s", type_wildcard->identifier, ic->identifier); if (!validate_xkb_merge(ic, type_wildcard, error)) { return false; } } } return true; } static void merge_type_on_existing(struct input_config *type_wildcard) { for (int i = 0; i < config->input_configs->length; i++) { struct input_config *ic = config->input_configs->items[i]; if (ic->input_type == NULL) { continue; } if (strcmp(ic->input_type, type_wildcard->identifier + 5) == 0) { sway_log(SWAY_DEBUG, "Merging %s top of %s", type_wildcard->identifier, ic->identifier); merge_input_config(ic, type_wildcard); } } } static const char *set_input_type(struct input_config *ic) { struct sway_input_device *input_device; wl_list_for_each(input_device, &server.input->devices, link) { if (strcmp(input_device->identifier, ic->identifier) == 0) { ic->input_type = input_device_get_type(input_device); break; } } return ic->input_type; } struct input_config *store_input_config(struct input_config *ic, char **error) { bool wildcard = strcmp(ic->identifier, "*") == 0; if (wildcard && error && !validate_wildcard_on_all(ic, error)) { return NULL; } bool type = has_prefix(ic->identifier, "type:"); if (type && error && !validate_type_on_existing(ic, error)) { return NULL; } list_t *config_list = type ? config->input_type_configs : config->input_configs; struct input_config *current = NULL; bool new_current = false; int i = list_seq_find(config_list, input_identifier_cmp, ic->identifier); if (i >= 0) { current = config_list->items[i]; } if (!current && !wildcard && !type && set_input_type(ic)) { for (i = 0; i < config->input_type_configs->length; i++) { struct input_config *tc = config->input_type_configs->items[i]; if (strcmp(ic->input_type, tc->identifier + 5) == 0) { current = new_input_config(ic->identifier); current->input_type = ic->input_type; merge_input_config(current, tc); new_current = true; break; } } } i = list_seq_find(config->input_configs, input_identifier_cmp, "*"); if (!current && i >= 0) { current = new_input_config(ic->identifier); merge_input_config(current, config->input_configs->items[i]); new_current = true; } if (error && !validate_xkb_merge(current, ic, error)) { if (new_current) { free_input_config(current); } return NULL; } if (wildcard) { merge_wildcard_on_all(ic); } if (type) { merge_type_on_existing(ic); } if (current) { merge_input_config(current, ic); free_input_config(ic); ic = current; } ic->xkb_file_is_set = ic->xkb_file != NULL; if (!current || new_current) { list_add(config_list, ic); } sway_log(SWAY_DEBUG, "Config stored for input %s", ic->identifier); return ic; } void input_config_fill_rule_names(struct input_config *ic, struct xkb_rule_names *rules) { rules->layout = ic->xkb_layout; rules->model = ic->xkb_model; rules->options = ic->xkb_options; rules->rules = ic->xkb_rules; rules->variant = ic->xkb_variant; } void free_input_config(struct input_config *ic) { if (!ic) { return; } free(ic->identifier); free(ic->xkb_file); free(ic->xkb_layout); free(ic->xkb_model); free(ic->xkb_options); free(ic->xkb_rules); free(ic->xkb_variant); free(ic->mapped_from_region); free(ic->mapped_to_output); free(ic->mapped_to_region); list_free_items_and_destroy(ic->tools); free(ic); } int input_identifier_cmp(const void *item, const void *data) { const struct input_config *ic = item; const char *identifier = data; return strcmp(ic->identifier, identifier); } ================================================ FILE: sway/config/output.c ================================================ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sway/config.h" #include "sway/desktop/transaction.h" #include "sway/input/cursor.h" #include "sway/layers.h" #include "sway/lock.h" #include "sway/output.h" #include "sway/server.h" #include "sway/tree/arrange.h" #include "sway/tree/root.h" #include "log.h" #include "util.h" #if WLR_HAS_DRM_BACKEND #include #endif void output_get_identifier(char *identifier, size_t len, struct sway_output *output) { struct wlr_output *wlr_output = output->wlr_output; snprintf(identifier, len, "%s %s %s", wlr_output->make ? wlr_output->make : "Unknown", wlr_output->model ? wlr_output->model : "Unknown", wlr_output->serial ? wlr_output->serial : "Unknown"); } const char *sway_output_scale_filter_to_string(enum scale_filter_mode scale_filter) { switch (scale_filter) { case SCALE_FILTER_DEFAULT: return "smart"; case SCALE_FILTER_LINEAR: return "linear"; case SCALE_FILTER_NEAREST: return "nearest"; case SCALE_FILTER_SMART: return "smart"; } sway_assert(false, "Unknown value for scale_filter."); return NULL; } struct output_config *new_output_config(const char *name) { struct output_config *oc = calloc(1, sizeof(struct output_config)); if (oc == NULL) { return NULL; } oc->name = strdup(name); if (oc->name == NULL) { free(oc); return NULL; } oc->enabled = -1; oc->width = oc->height = -1; oc->refresh_rate = -1; oc->custom_mode = -1; oc->drm_mode.type = -1; oc->x = oc->y = INT_MAX; oc->scale = -1; oc->scale_filter = SCALE_FILTER_DEFAULT; oc->transform = -1; oc->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; oc->max_render_time = -1; oc->adaptive_sync = -1; oc->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT; oc->color_profile = COLOR_PROFILE_DEFAULT; oc->color_transform = NULL; oc->power = -1; oc->allow_tearing = -1; oc->hdr = -1; return oc; } // supersede_output_config clears all fields in dst that were set in src static void supersede_output_config(struct output_config *dst, struct output_config *src) { if (src->enabled != -1) { dst->enabled = -1; } if (src->width != -1) { dst->width = -1; } if (src->height != -1) { dst->height = -1; } if (src->x != INT_MAX) { dst->x = INT_MAX; } if (src->y != INT_MAX) { dst->y = INT_MAX; } if (src->scale != -1) { dst->scale = -1; } if (src->scale_filter != SCALE_FILTER_DEFAULT) { dst->scale_filter = SCALE_FILTER_DEFAULT; } if (src->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN) { dst->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; } if (src->refresh_rate != -1) { dst->refresh_rate = -1; } if (src->custom_mode != -1) { dst->custom_mode = -1; } if (src->drm_mode.type != (uint32_t) -1) { dst->drm_mode.type = -1; } if (src->transform != -1) { dst->transform = -1; } if (src->max_render_time != -1) { dst->max_render_time = -1; } if (src->adaptive_sync != -1) { dst->adaptive_sync = -1; } if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { dst->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT; } if (src->color_profile != COLOR_PROFILE_DEFAULT) { if (dst->color_transform) { wlr_color_transform_unref(dst->color_transform); dst->color_transform = NULL; } dst->color_profile = COLOR_PROFILE_DEFAULT; } if (src->background) { free(dst->background); dst->background = NULL; } if (src->background_option) { free(dst->background_option); dst->background_option = NULL; } if (src->background_fallback) { free(dst->background_fallback); dst->background_fallback = NULL; } if (src->power != -1) { dst->power = -1; } if (src->allow_tearing != -1) { dst->allow_tearing = -1; } if (src->hdr != -1) { dst->hdr = -1; } } // merge_output_config sets all fields in dst that were set in src static void merge_output_config(struct output_config *dst, struct output_config *src) { if (src->enabled != -1) { dst->enabled = src->enabled; } if (src->width != -1) { dst->width = src->width; } if (src->height != -1) { dst->height = src->height; } if (src->x != INT_MAX) { dst->x = src->x; } if (src->y != INT_MAX) { dst->y = src->y; } if (src->scale != -1) { dst->scale = src->scale; } if (src->scale_filter != SCALE_FILTER_DEFAULT) { dst->scale_filter = src->scale_filter; } if (src->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN) { dst->subpixel = src->subpixel; } if (src->refresh_rate != -1) { dst->refresh_rate = src->refresh_rate; } if (src->custom_mode != -1) { dst->custom_mode = src->custom_mode; } if (src->drm_mode.type != (uint32_t) -1) { memcpy(&dst->drm_mode, &src->drm_mode, sizeof(src->drm_mode)); } if (src->transform != -1) { dst->transform = src->transform; } if (src->max_render_time != -1) { dst->max_render_time = src->max_render_time; } if (src->adaptive_sync != -1) { dst->adaptive_sync = src->adaptive_sync; } if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { dst->render_bit_depth = src->render_bit_depth; } if (src->color_profile != COLOR_PROFILE_DEFAULT) { if (src->color_transform) { wlr_color_transform_ref(src->color_transform); } wlr_color_transform_unref(dst->color_transform); dst->color_profile = src->color_profile; dst->color_transform = src->color_transform; } if (src->background) { free(dst->background); dst->background = strdup(src->background); } if (src->background_option) { free(dst->background_option); dst->background_option = strdup(src->background_option); } if (src->background_fallback) { free(dst->background_fallback); dst->background_fallback = strdup(src->background_fallback); } if (src->power != -1) { dst->power = src->power; } if (src->allow_tearing != -1) { dst->allow_tearing = src->allow_tearing; } if (src->hdr != -1) { dst->hdr = src->hdr; } } void store_output_config(struct output_config *oc) { bool merged = false; bool wildcard = strcmp(oc->name, "*") == 0; struct sway_output *output = wildcard ? NULL : all_output_by_name_or_id(oc->name); char id[128]; if (output) { output_get_identifier(id, sizeof(id), output); } for (int i = 0; i < config->output_configs->length; i++) { struct output_config *old = config->output_configs->items[i]; // If the old config matches the new config's name, regardless of // whether it was name or identifier, merge on top of the existing // config. If the new config is a wildcard, this also merges on top of // old wildcard configs. if (strcmp(old->name, oc->name) == 0) { merge_output_config(old, oc); merged = true; continue; } // If the new config is a wildcard config we supersede all non-wildcard // configs. Old wildcard configs have already been handled above. if (wildcard) { supersede_output_config(old, oc); continue; } // If the new config matches an output's name, and the old config // matches on that output's identifier, supersede it. if (output && strcmp(old->name, id) == 0 && strcmp(oc->name, output->wlr_output->name) == 0) { supersede_output_config(old, oc); } } sway_log(SWAY_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz " "position %d,%d scale %f subpixel %s transform %d) (bg %s %s) (power %d) " "(max render time: %d) (allow tearing: %d) (hdr: %d)", oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate, oc->x, oc->y, oc->scale, sway_wl_output_subpixel_to_string(oc->subpixel), oc->transform, oc->background, oc->background_option, oc->power, oc->max_render_time, oc->allow_tearing, oc->hdr); // If the configuration was not merged into an existing configuration, add // it to the list. Otherwise we're done with it and can free it. if (!merged) { list_add(config->output_configs, oc); } else { free_output_config(oc); } } static void set_mode(struct wlr_output *output, struct wlr_output_state *pending, int width, int height, float refresh_rate, bool custom) { // Not all floating point integers can be represented exactly // as (int)(1000 * mHz / 1000.f) // round() the result to avoid any error int mhz = (int)roundf(refresh_rate * 1000); // If no target refresh rate is given, match highest available mhz = mhz <= 0 ? INT_MAX : mhz; if (wl_list_empty(&output->modes) || custom) { wlr_output_state_set_custom_mode(pending, width, height, refresh_rate > 0 ? mhz : 0); return; } struct wlr_output_mode *mode, *best = NULL; int best_diff_mhz = INT_MAX; wl_list_for_each(mode, &output->modes, link) { if (mode->width == width && mode->height == height) { int diff_mhz = abs(mode->refresh - mhz); if (diff_mhz < best_diff_mhz) { best_diff_mhz = diff_mhz; best = mode; if (best_diff_mhz == 0) { break; } } } } if (!best) { best = wlr_output_preferred_mode(output); sway_log(SWAY_INFO, "Configured mode (%dx%d@%.3fHz) not available, " "applying preferred mode (%dx%d@%.3fHz)", width, height, refresh_rate, best->width, best->height, best->refresh / 1000.f); } wlr_output_state_set_mode(pending, best); } static void set_modeline(struct wlr_output *output, struct wlr_output_state *pending, drmModeModeInfo *drm_mode) { #if WLR_HAS_DRM_BACKEND if (!wlr_output_is_drm(output)) { sway_log(SWAY_ERROR, "Modeline can only be set to DRM output"); return; } struct wlr_output_mode *mode = wlr_drm_connector_add_mode(output, drm_mode); if (mode) { wlr_output_state_set_mode(pending, mode); } #else sway_log(SWAY_ERROR, "Modeline can only be set to DRM output"); #endif } bool output_supports_hdr(struct wlr_output *output, const char **unsupported_reason_ptr) { const char *unsupported_reason = NULL; if (!(output->supported_primaries & WLR_COLOR_NAMED_PRIMARIES_BT2020)) { unsupported_reason = "BT2020 primaries not supported by output"; } else if (!(output->supported_transfer_functions & WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ)) { unsupported_reason = "PQ transfer function not supported by output"; } else if (!server.renderer->features.output_color_transform) { unsupported_reason = "renderer doesn't support output color transforms"; } if (unsupported_reason_ptr != NULL) { *unsupported_reason_ptr = unsupported_reason; } return unsupported_reason == NULL; } static void set_hdr(struct wlr_output *output, struct wlr_output_state *pending, bool enabled) { const char *unsupported_reason = NULL; if (enabled && !output_supports_hdr(output, &unsupported_reason)) { sway_log(SWAY_ERROR, "Cannot enable HDR on output %s: %s", output->name, unsupported_reason); enabled = false; } if (!enabled) { if (output->supported_primaries != 0 || output->supported_transfer_functions != 0) { sway_log(SWAY_DEBUG, "Disabling HDR on output %s", output->name); wlr_output_state_set_image_description(pending, NULL); } return; } sway_log(SWAY_DEBUG, "Enabling HDR on output %s", output->name); const struct wlr_output_image_description image_desc = { .primaries = WLR_COLOR_NAMED_PRIMARIES_BT2020, .transfer_function = WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ, }; wlr_output_state_set_image_description(pending, &image_desc); } /* Some manufacturers hardcode the aspect-ratio of the output in the physical * size field. */ static bool phys_size_is_aspect_ratio(struct wlr_output *output) { return (output->phys_width == 1600 && output->phys_height == 900) || (output->phys_width == 1600 && output->phys_height == 1000) || (output->phys_width == 160 && output->phys_height == 90) || (output->phys_width == 160 && output->phys_height == 100) || (output->phys_width == 16 && output->phys_height == 9) || (output->phys_width == 16 && output->phys_height == 10); } // The minimum DPI at which we turn on a scale of 2 #define HIDPI_DPI_LIMIT (2 * 96) // The minimum screen height at which we turn on a scale of 2 #define HIDPI_MIN_HEIGHT 1200 // 1 inch = 25.4 mm #define MM_PER_INCH 25.4 static int compute_default_scale(struct wlr_output *output, struct wlr_output_state *pending) { struct wlr_box box = { .width = output->width, .height = output->height }; if (pending->committed & WLR_OUTPUT_STATE_MODE) { switch (pending->mode_type) { case WLR_OUTPUT_STATE_MODE_FIXED: box.width = pending->mode->width; box.height = pending->mode->height; break; case WLR_OUTPUT_STATE_MODE_CUSTOM: box.width = pending->custom_mode.width; box.height = pending->custom_mode.height; break; } } enum wl_output_transform transform = output->transform; if (pending->committed & WLR_OUTPUT_STATE_TRANSFORM) { transform = pending->transform; } wlr_box_transform(&box, &box, transform, box.width, box.height); int width = box.width; int height = box.height; if (height < HIDPI_MIN_HEIGHT) { return 1; } if (output->phys_width == 0 || output->phys_height == 0) { return 1; } if (phys_size_is_aspect_ratio(output)) { return 1; } double dpi_x = (double) width / (output->phys_width / MM_PER_INCH); double dpi_y = (double) height / (output->phys_height / MM_PER_INCH); if (dpi_x <= HIDPI_DPI_LIMIT || dpi_y <= HIDPI_DPI_LIMIT) { return 1; } return 2; } static enum render_bit_depth bit_depth_from_format(uint32_t render_format) { if (render_format == DRM_FORMAT_XRGB2101010 || render_format == DRM_FORMAT_XBGR2101010) { return RENDER_BIT_DEPTH_10; } else if (render_format == DRM_FORMAT_XRGB8888 || render_format == DRM_FORMAT_ARGB8888) { return RENDER_BIT_DEPTH_8; } else if (render_format == DRM_FORMAT_RGB565) { return RENDER_BIT_DEPTH_6; } return RENDER_BIT_DEPTH_DEFAULT; } static enum render_bit_depth get_config_render_bit_depth(const struct output_config *oc) { if (oc && oc->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { return oc->render_bit_depth; } if (oc && oc->hdr == 1) { return RENDER_BIT_DEPTH_10; } return RENDER_BIT_DEPTH_8; } static bool render_format_is_bgr(uint32_t fmt) { return fmt == DRM_FORMAT_XBGR2101010 || fmt == DRM_FORMAT_XBGR8888; } static bool output_config_is_disabling(struct output_config *oc) { return oc && (!oc->enabled || oc->power == 0); } static void queue_output_config(struct output_config *oc, struct sway_output *output, struct wlr_output_state *pending) { if (output == root->fallback_output) { return; } struct wlr_output *wlr_output = output->wlr_output; if (output_config_is_disabling(oc)) { wlr_output_state_set_enabled(pending, false); return; } wlr_output_state_set_enabled(pending, true); if (oc && oc->drm_mode.type != 0 && oc->drm_mode.type != (uint32_t) -1) { set_modeline(wlr_output, pending, &oc->drm_mode); } else if (oc && oc->width > 0 && oc->height > 0) { set_mode(wlr_output, pending, oc->width, oc->height, oc->refresh_rate, oc->custom_mode == 1); } else if (!wl_list_empty(&wlr_output->modes)) { struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output); wlr_output_state_set_mode(pending, preferred_mode); } if (oc && oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN) { wlr_output_state_set_subpixel(pending, oc->subpixel); } else { wlr_output_state_set_subpixel(pending, output->detected_subpixel); } if (oc && oc->transform >= 0) { wlr_output_state_set_transform(pending, oc->transform); #if WLR_HAS_DRM_BACKEND } else if (wlr_output_is_drm(wlr_output)) { wlr_output_state_set_transform(pending, wlr_drm_connector_get_panel_orientation(wlr_output)); #endif } else { wlr_output_state_set_transform(pending, WL_OUTPUT_TRANSFORM_NORMAL); } // Apply the scale after sorting out the mode, because the scale // auto-detection reads the pending output size if (oc && oc->scale > 0) { // The factional-scale-v1 protocol uses increments of 120ths to send // the scale factor to the client. Adjust the scale so that we use the // same value as the clients'. wlr_output_state_set_scale(pending, round(oc->scale * 120) / 120); } else { wlr_output_state_set_scale(pending, compute_default_scale(wlr_output, pending)); } if (wlr_output->adaptive_sync_supported) { if (oc && oc->adaptive_sync != -1) { wlr_output_state_set_adaptive_sync_enabled(pending, oc->adaptive_sync == 1); } else { wlr_output_state_set_adaptive_sync_enabled(pending, false); } } enum render_bit_depth render_bit_depth = get_config_render_bit_depth(oc); if (render_bit_depth == RENDER_BIT_DEPTH_10 && bit_depth_from_format(output->wlr_output->render_format) == render_bit_depth) { // 10-bit was set successfully before, try to save some tests by reusing the format wlr_output_state_set_render_format(pending, output->wlr_output->render_format); } else if (render_bit_depth == RENDER_BIT_DEPTH_10) { wlr_output_state_set_render_format(pending, DRM_FORMAT_XRGB2101010); } else if (render_bit_depth == RENDER_BIT_DEPTH_6) { wlr_output_state_set_render_format(pending, DRM_FORMAT_RGB565); } else { wlr_output_state_set_render_format(pending, DRM_FORMAT_XRGB8888); } bool hdr = oc && oc->hdr == 1; bool color_profile = oc && (oc->color_transform != NULL || oc->color_profile == COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES); if (hdr && color_profile) { sway_log(SWAY_ERROR, "Cannot use HDR on output %s: output has a color profile set", wlr_output->name); hdr = false; } set_hdr(wlr_output, pending, hdr); } struct config_output_state { struct wlr_color_transform *color_transform; }; static void config_output_state_finish(struct config_output_state *state) { wlr_color_transform_unref(state->color_transform); } static struct wlr_color_transform *color_profile_from_device(struct wlr_output *wlr_output, struct wlr_color_transform *transfer_function) { struct wlr_color_primaries srgb_primaries; wlr_color_primaries_from_named(&srgb_primaries, WLR_COLOR_NAMED_PRIMARIES_SRGB); const struct wlr_color_primaries *primaries = wlr_output->default_primaries; if (primaries == NULL) { sway_log(SWAY_INFO, "output has no reported color information"); if (transfer_function) { wlr_color_transform_ref(transfer_function); } return transfer_function; } else if (memcmp(primaries, &srgb_primaries, sizeof(*primaries)) == 0) { sway_log(SWAY_INFO, "output reports sRGB colors, no correction needed"); if (transfer_function) { wlr_color_transform_ref(transfer_function); } return transfer_function; } else { sway_log(SWAY_INFO, "Creating color profile from reported color primaries: " "R(%f, %f) G(%f, %f) B(%f, %f) W(%f, %f)", primaries->red.x, primaries->red.y, primaries->green.x, primaries->green.y, primaries->blue.x, primaries->blue.y, primaries->white.x, primaries->white.y); float matrix[9]; wlr_color_primaries_transform_absolute_colorimetric(&srgb_primaries, primaries, matrix); struct wlr_color_transform *matrix_transform = wlr_color_transform_init_matrix(matrix); if (matrix_transform == NULL) { return NULL; } struct wlr_color_transform *resolved_tf = transfer_function ? wlr_color_transform_ref(transfer_function) : wlr_color_transform_init_linear_to_inverse_eotf(WLR_COLOR_TRANSFER_FUNCTION_GAMMA22); if (resolved_tf == NULL) { wlr_color_transform_unref(matrix_transform); return NULL; } struct wlr_color_transform *transforms[] = { matrix_transform, resolved_tf }; size_t transforms_len = sizeof(transforms) / sizeof(transforms[0]); struct wlr_color_transform *result = wlr_color_transform_init_pipeline(transforms, transforms_len); wlr_color_transform_unref(matrix_transform); wlr_color_transform_unref(resolved_tf); return result; } } static struct wlr_color_transform *get_color_profile(struct wlr_output *output, struct output_config *oc) { if (oc && oc->color_profile == COLOR_PROFILE_TRANSFORM) { if (oc->color_transform) { wlr_color_transform_ref(oc->color_transform); } return oc->color_transform; } else if (oc && oc->color_profile == COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES) { return color_profile_from_device(output, oc->color_transform); } else { return NULL; } } static bool finalize_output_config(struct output_config *oc, struct sway_output *output, const struct wlr_output_state *applied, const struct config_output_state *config_applied) { if (output == root->fallback_output) { return false; } struct wlr_output *wlr_output = output->wlr_output; if (oc && !oc->enabled) { sway_log(SWAY_DEBUG, "Disabling output %s", oc->name); if (output->enabled) { output_disable(output); wlr_output_layout_remove(root->output_layout, wlr_output); } return true; } enum scale_filter_mode scale_filter_old = output->scale_filter; enum scale_filter_mode scale_filter_new = oc ? oc->scale_filter : SCALE_FILTER_DEFAULT; switch (scale_filter_new) { case SCALE_FILTER_DEFAULT: case SCALE_FILTER_SMART: output->scale_filter = ceilf(wlr_output->scale) == wlr_output->scale ? SCALE_FILTER_NEAREST : SCALE_FILTER_LINEAR; break; case SCALE_FILTER_LINEAR: case SCALE_FILTER_NEAREST: output->scale_filter = scale_filter_new; break; } if (scale_filter_old != output->scale_filter) { sway_log(SWAY_DEBUG, "Set %s scale_filter to %s", oc->name, sway_output_scale_filter_to_string(output->scale_filter)); wlr_damage_ring_add_whole(&output->scene_output->damage_ring); } // Find position for it if (oc && oc->x != INT_MAX && oc->y != INT_MAX) { sway_log(SWAY_DEBUG, "Set %s position to %d, %d", oc->name, oc->x, oc->y); wlr_output_layout_add(root->output_layout, wlr_output, oc->x, oc->y); } else { wlr_output_layout_add_auto(root->output_layout, wlr_output); } if (!output->enabled) { output_enable(output); } wlr_color_transform_unref(output->color_transform); if (config_applied->color_transform != NULL) { wlr_color_transform_ref(config_applied->color_transform); } output->color_transform = config_applied->color_transform; output->max_render_time = oc && oc->max_render_time > 0 ? oc->max_render_time : 0; output->allow_tearing = oc && oc->allow_tearing > 0; output->hdr = applied->image_description != NULL; return true; } static void output_update_position(struct sway_output *output) { struct wlr_box output_box; wlr_output_layout_get_box(root->output_layout, output->wlr_output, &output_box); output->lx = output_box.x; output->ly = output_box.y; output->width = output_box.width; output->height = output_box.height; } // find_output_config_from_list returns a merged output_config containing all // stored configuration that applies to the specified output. static struct output_config *find_output_config_from_list( struct output_config **configs, size_t configs_len, struct sway_output *sway_output) { const char *name = sway_output->wlr_output->name; struct output_config *result = new_output_config(name); if (result == NULL) { return NULL; } char id[128]; output_get_identifier(id, sizeof(id), sway_output); // We take a new config and merge on top, in order, the wildcard config, // output config by name, and output config by identifier to form the final // config. If there are multiple matches, they are merged in order. struct output_config *oc = NULL; const char *names[] = {"*", name, id, NULL}; for (const char **name = &names[0]; *name; name++) { for (size_t idx = 0; idx < configs_len; idx++) { oc = configs[idx]; if (strcmp(oc->name, *name) == 0) { merge_output_config(result, oc); } } } return result; } struct output_config *find_output_config(struct sway_output *sway_output) { return find_output_config_from_list( (struct output_config **)config->output_configs->items, config->output_configs->length, sway_output); } static bool config_has_manual_mode(struct output_config *oc) { if (!oc) { return false; } if (oc->drm_mode.type != 0 && oc->drm_mode.type != (uint32_t)-1) { return true; } else if (oc->width > 0 && oc->height > 0) { return true; } return false; } /** * An output config pre-matched to an output */ struct matched_output_config { struct sway_output *output; struct output_config *config; }; struct search_context { struct wlr_output_swapchain_manager *swapchain_mgr; struct wlr_backend_output_state *states; struct matched_output_config *configs; size_t configs_len; bool degrade_to_off; }; static void dump_output_state(struct wlr_output *wlr_output, struct wlr_output_state *state) { sway_log(SWAY_DEBUG, "Output state for %s", wlr_output->name); if (state->committed & WLR_OUTPUT_STATE_ENABLED) { sway_log(SWAY_DEBUG, " enabled: %s", state->enabled ? "yes" : "no"); } if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { char *format_name = drmGetFormatName(state->render_format); sway_log(SWAY_DEBUG, " render_format: %s", format_name); free(format_name); } if (state->committed & WLR_OUTPUT_STATE_MODE) { if (state->mode_type == WLR_OUTPUT_STATE_MODE_CUSTOM) { sway_log(SWAY_DEBUG, " custom mode: %dx%d@%dmHz", state->custom_mode.width, state->custom_mode.height, state->custom_mode.refresh); } else { sway_log(SWAY_DEBUG, " mode: %dx%d@%dmHz%s", state->mode->width, state->mode->height, state->mode->refresh, state->mode->preferred ? " (preferred)" : ""); } } if (state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) { sway_log(SWAY_DEBUG, " adaptive_sync: %s", state->adaptive_sync_enabled ? "enabled": "disabled"); } if (state->committed & WLR_OUTPUT_STATE_SCALE) { sway_log(SWAY_DEBUG, " scale: %f", state->scale); } if (state->committed & WLR_OUTPUT_STATE_SUBPIXEL) { sway_log(SWAY_DEBUG, " subpixel: %s", sway_wl_output_subpixel_to_string(state->subpixel)); } } static bool search_valid_config(struct search_context *ctx, size_t output_idx); static void reset_output_state(struct wlr_output_state *state) { wlr_output_state_finish(state); wlr_output_state_init(state); state->committed = 0; } static void clear_later_output_states(struct wlr_backend_output_state *states, size_t configs_len, size_t output_idx) { // Clear and disable all output states after this one to avoid conflict // with previous tests. for (size_t idx = output_idx+1; idx < configs_len; idx++) { struct wlr_backend_output_state *backend_state = &states[idx]; struct wlr_output_state *state = &backend_state->base; reset_output_state(state); wlr_output_state_set_enabled(state, false); } } static bool search_finish(struct search_context *ctx, size_t output_idx) { struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; struct wlr_output_state *state = &backend_state->base; struct wlr_output *wlr_output = backend_state->output; clear_later_output_states(ctx->states, ctx->configs_len, output_idx); dump_output_state(wlr_output, state); return wlr_output_swapchain_manager_prepare(ctx->swapchain_mgr, ctx->states, ctx->configs_len) && search_valid_config(ctx, output_idx+1); } static bool search_adaptive_sync(struct search_context *ctx, size_t output_idx) { struct matched_output_config *cfg = &ctx->configs[output_idx]; struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; struct wlr_output_state *state = &backend_state->base; if (!backend_state->output->adaptive_sync_supported) { return search_finish(ctx, output_idx); } if (cfg->config && cfg->config->adaptive_sync == 1) { wlr_output_state_set_adaptive_sync_enabled(state, true); if (search_finish(ctx, output_idx)) { return true; } } wlr_output_state_set_adaptive_sync_enabled(state, false); return search_finish(ctx, output_idx); } static bool search_mode(struct search_context *ctx, size_t output_idx) { struct matched_output_config *cfg = &ctx->configs[output_idx]; struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; struct wlr_output_state *state = &backend_state->base; struct wlr_output *wlr_output = backend_state->output; // We only search for mode if one is not explicitly specified in the config if (config_has_manual_mode(cfg->config)) { return search_adaptive_sync(ctx, output_idx); } struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output); if (preferred_mode) { wlr_output_state_set_mode(state, preferred_mode); if (search_adaptive_sync(ctx, output_idx)) { return true; } } if (wl_list_empty(&wlr_output->modes)) { state->committed &= ~WLR_OUTPUT_STATE_MODE; return search_adaptive_sync(ctx, output_idx); } struct wlr_output_mode *mode; wl_list_for_each(mode, &backend_state->output->modes, link) { if (mode == preferred_mode) { continue; } wlr_output_state_set_mode(state, mode); if (search_adaptive_sync(ctx, output_idx)) { return true; } } return false; } static bool search_render_format(struct search_context *ctx, size_t output_idx) { struct matched_output_config *cfg = &ctx->configs[output_idx]; struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; struct wlr_output_state *state = &backend_state->base; struct wlr_output *wlr_output = backend_state->output; uint32_t fmts[] = { DRM_FORMAT_XRGB2101010, DRM_FORMAT_XBGR2101010, DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_RGB565, DRM_FORMAT_INVALID, }; if (render_format_is_bgr(wlr_output->render_format)) { // Start with BGR in the unlikely event that we previously required it. fmts[0] = DRM_FORMAT_XBGR2101010; fmts[1] = DRM_FORMAT_XRGB2101010; } const struct wlr_drm_format_set *primary_formats = wlr_output_get_primary_formats(wlr_output, server.allocator->buffer_caps); enum render_bit_depth needed_bits = get_config_render_bit_depth(cfg->config); for (size_t idx = 0; fmts[idx] != DRM_FORMAT_INVALID; idx++) { enum render_bit_depth format_bits = bit_depth_from_format(fmts[idx]); if (needed_bits < format_bits) { continue; } // If primary_formats is NULL, all formats are supported if (primary_formats && !wlr_drm_format_set_get(primary_formats, fmts[idx])) { // This is not a supported format for this output continue; } wlr_output_state_set_render_format(state, fmts[idx]); if (search_mode(ctx, output_idx)) { return true; } } return false; } static bool search_valid_config(struct search_context *ctx, size_t output_idx) { if (output_idx >= ctx->configs_len) { // We reached the end of the search, all good! return true; } struct matched_output_config *cfg = &ctx->configs[output_idx]; struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; struct wlr_output_state *state = &backend_state->base; struct wlr_output *wlr_output = backend_state->output; if (!output_config_is_disabling(cfg->config)) { // Search through our possible configurations, doing a depth-first // through render_format, modes, adaptive_sync and the next output's // config. queue_output_config(cfg->config, cfg->output, &backend_state->base); if (search_render_format(ctx, output_idx)) { return true; } else if (!ctx->degrade_to_off) { return false; } // We could not get anything to work, try to disable this output to see // if we can at least make the outputs before us work. sway_log(SWAY_DEBUG, "Unable to find valid config with output %s, disabling", wlr_output->name); reset_output_state(state); } wlr_output_state_set_enabled(state, false); return search_finish(ctx, output_idx); } static int compare_matched_output_config_priority(const void *a, const void *b) { const struct matched_output_config *amc = a; const struct matched_output_config *bmc = b; bool a_disabling = output_config_is_disabling(amc->config); bool b_disabling = output_config_is_disabling(bmc->config); bool a_enabled = amc->output->enabled; bool b_enabled = bmc->output->enabled; // We want to give priority to existing enabled outputs. To do so, we want // the configuration order to be: // 1. Existing, enabled outputs // 2. Outputs that need to be enabled // 3. Disabled or disabling outputs if (a_enabled && !a_disabling) { return -1; } else if (b_enabled && !b_disabling) { return 1; } else if (b_disabling && !a_disabling) { return -1; } else if (a_disabling && !b_disabling) { return 1; } return 0; } static void sort_output_configs_by_priority( struct matched_output_config *configs, size_t configs_len) { qsort(configs, configs_len, sizeof(*configs), compare_matched_output_config_priority); } static bool apply_resolved_output_configs(struct matched_output_config *configs, size_t configs_len, bool test_only, bool degrade_to_off) { struct wlr_backend_output_state *states = calloc(configs_len, sizeof(*states)); if (!states) { return false; } struct config_output_state *config_states = calloc(configs_len, sizeof(*config_states)); if (!config_states) { free(states); return false; } sway_log(SWAY_DEBUG, "Committing %zd outputs", configs_len); for (size_t idx = 0; idx < configs_len; idx++) { struct matched_output_config *cfg = &configs[idx]; struct wlr_backend_output_state *backend_state = &states[idx]; struct config_output_state *config_state = &config_states[idx]; backend_state->output = cfg->output->wlr_output; wlr_output_state_init(&backend_state->base); queue_output_config(cfg->config, cfg->output, &backend_state->base); dump_output_state(cfg->output->wlr_output, &backend_state->base); config_state->color_transform = get_color_profile(cfg->output->wlr_output, cfg->config); } struct wlr_output_swapchain_manager swapchain_mgr; wlr_output_swapchain_manager_init(&swapchain_mgr, server.backend); bool ok = wlr_output_swapchain_manager_prepare(&swapchain_mgr, states, configs_len); if (!ok) { sway_log(SWAY_ERROR, "Requested backend configuration failed, searching for valid fallbacks"); struct search_context ctx = { .swapchain_mgr = &swapchain_mgr, .states = states, .configs = configs, .configs_len = configs_len, .degrade_to_off = degrade_to_off, }; if (!search_valid_config(&ctx, 0)) { sway_log(SWAY_ERROR, "Search for valid config failed"); goto out; } } if (test_only) { // The swapchain manager already did a test for us goto out; } for (size_t idx = 0; idx < configs_len; idx++) { struct matched_output_config *cfg = &configs[idx]; struct wlr_backend_output_state *backend_state = &states[idx]; struct config_output_state *config_state = &config_states[idx]; struct wlr_scene_output_state_options opts = { .swapchain = wlr_output_swapchain_manager_get_swapchain( &swapchain_mgr, backend_state->output), .color_transform = config_state->color_transform, }; struct wlr_scene_output *scene_output = cfg->output->scene_output; struct wlr_output_state *state = &backend_state->base; if (!wlr_scene_output_build_state(scene_output, state, &opts)) { sway_log(SWAY_ERROR, "Building output state for '%s' failed", backend_state->output->name); goto out; } } ok = wlr_backend_commit(server.backend, states, configs_len); if (!ok) { sway_log(SWAY_ERROR, "Backend commit failed"); goto out; } sway_log(SWAY_DEBUG, "Commit of %zd outputs succeeded", configs_len); wlr_output_swapchain_manager_apply(&swapchain_mgr); for (size_t idx = 0; idx < configs_len; idx++) { struct matched_output_config *cfg = &configs[idx]; struct wlr_backend_output_state *backend_state = &states[idx]; struct config_output_state *config_state = &config_states[idx]; sway_log(SWAY_DEBUG, "Finalizing config for %s", cfg->output->wlr_output->name); finalize_output_config(cfg->config, cfg->output, &backend_state->base, config_state); } // Output layout being applied in finalize_output_config can shift outputs // around, so we do a second pass to update positions and arrange. for (size_t idx = 0; idx < configs_len; idx++) { struct matched_output_config *cfg = &configs[idx]; output_update_position(cfg->output); arrange_layers(cfg->output); } arrange_root(); arrange_locks(); update_output_manager_config(&server); transaction_commit_dirty(); out: wlr_output_swapchain_manager_finish(&swapchain_mgr); for (size_t idx = 0; idx < configs_len; idx++) { struct wlr_backend_output_state *backend_state = &states[idx]; wlr_output_state_finish(&backend_state->base); config_output_state_finish(&config_states[idx]); } free(states); free(config_states); // Reconfigure all devices, since input config may have been applied before // this output came online, and some config items (like map_to_output) are // dependent on an output being present. input_manager_configure_all_input_mappings(); // Reconfigure the cursor images, since the scale may have changed. input_manager_configure_xcursor(); struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); cursor_rebase(seat->cursor); } return ok; } bool apply_output_configs(struct output_config **ocs, size_t ocs_len, bool test_only, bool degrade_to_off) { size_t configs_len = wl_list_length(&root->all_outputs); struct matched_output_config *configs = calloc(configs_len, sizeof(*configs)); if (!configs) { return false; } int config_idx = 0; struct sway_output *sway_output; wl_list_for_each(sway_output, &root->all_outputs, link) { if (sway_output == root->fallback_output) { configs_len--; continue; } struct matched_output_config *config = &configs[config_idx++]; config->output = sway_output; config->config = find_output_config_from_list(ocs, ocs_len, sway_output); } sort_output_configs_by_priority(configs, configs_len); bool ok = apply_resolved_output_configs(configs, configs_len, test_only, degrade_to_off); for (size_t idx = 0; idx < configs_len; idx++) { struct matched_output_config *cfg = &configs[idx]; free_output_config(cfg->config); } free(configs); return ok; } void apply_stored_output_configs(void) { apply_output_configs((struct output_config **)config->output_configs->items, config->output_configs->length, false, true); } void free_output_config(struct output_config *oc) { if (!oc) { return; } free(oc->name); free(oc->background); free(oc->background_option); free(oc->background_fallback); wlr_color_transform_unref(oc->color_transform); free(oc); } static void handle_swaybg_client_destroy(struct wl_listener *listener, void *data) { struct sway_config *sway_config = wl_container_of(listener, sway_config, swaybg_client_destroy); wl_list_remove(&sway_config->swaybg_client_destroy.link); wl_list_init(&sway_config->swaybg_client_destroy.link); sway_config->swaybg_client = NULL; } static bool _spawn_swaybg(char **command) { if (config->swaybg_client != NULL) { wl_client_destroy(config->swaybg_client); } int sockets[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) != 0) { sway_log_errno(SWAY_ERROR, "socketpair failed"); return false; } if (!sway_set_cloexec(sockets[0], true) || !sway_set_cloexec(sockets[1], true)) { return false; } config->swaybg_client = wl_client_create(server.wl_display, sockets[0]); if (config->swaybg_client == NULL) { sway_log_errno(SWAY_ERROR, "wl_client_create failed"); return false; } config->swaybg_client_destroy.notify = handle_swaybg_client_destroy; wl_client_add_destroy_listener(config->swaybg_client, &config->swaybg_client_destroy); pid_t pid = fork(); if (pid < 0) { sway_log_errno(SWAY_ERROR, "fork failed"); return false; } else if (pid == 0) { if (!sway_set_cloexec(sockets[1], false)) { _exit(EXIT_FAILURE); } char wayland_socket_str[16]; snprintf(wayland_socket_str, sizeof(wayland_socket_str), "%d", sockets[1]); setenv("WAYLAND_SOCKET", wayland_socket_str, true); execvp(command[0], command); sway_log_errno(SWAY_ERROR, "failed to execute '%s' " "(background configuration probably not applied)", command[0]); _exit(EXIT_FAILURE); } if (close(sockets[1]) != 0) { sway_log_errno(SWAY_ERROR, "close failed"); return false; } return true; } bool spawn_swaybg(void) { if (!config->swaybg_command) { return true; } size_t length = 2; for (int i = 0; i < config->output_configs->length; i++) { struct output_config *oc = config->output_configs->items[i]; if (!oc->background) { continue; } if (strcmp(oc->background_option, "solid_color") == 0) { length += 4; } else if (oc->background_fallback) { length += 8; } else { length += 6; } } char **cmd = calloc(length, sizeof(char *)); if (!cmd) { sway_log(SWAY_ERROR, "Failed to allocate spawn_swaybg command"); return false; } size_t i = 0; cmd[i++] = config->swaybg_command; for (int j = 0; j < config->output_configs->length; j++) { struct output_config *oc = config->output_configs->items[j]; if (!oc->background) { continue; } if (strcmp(oc->background_option, "solid_color") == 0) { cmd[i++] = "-o"; cmd[i++] = oc->name; cmd[i++] = "-c"; cmd[i++] = oc->background; } else { cmd[i++] = "-o"; cmd[i++] = oc->name; cmd[i++] = "-i"; cmd[i++] = oc->background; cmd[i++] = "-m"; cmd[i++] = oc->background_option; if (oc->background_fallback) { cmd[i++] = "-c"; cmd[i++] = oc->background_fallback; } } assert(i <= length); } for (size_t k = 0; k < i; k++) { sway_log(SWAY_DEBUG, "spawn_swaybg cmd[%zd] = %s", k, cmd[k]); } bool result = _spawn_swaybg(cmd); free(cmd); return result; } ================================================ FILE: sway/config/seat.c ================================================ #include #include #include #include "sway/config.h" #include "log.h" struct seat_config *new_seat_config(const char* name) { struct seat_config *seat = calloc(1, sizeof(struct seat_config)); if (!seat) { sway_log(SWAY_DEBUG, "Unable to allocate seat config"); return NULL; } seat->name = strdup(name); if (!sway_assert(seat->name, "could not allocate name for seat")) { free(seat); return NULL; } seat->idle_inhibit_sources = seat->idle_wake_sources = UINT32_MAX; seat->fallback = -1; seat->attachments = create_list(); if (!sway_assert(seat->attachments, "could not allocate seat attachments list")) { free(seat->name); free(seat); return NULL; } seat->hide_cursor_timeout = -1; seat->hide_cursor_when_typing = HIDE_WHEN_TYPING_DEFAULT; seat->allow_constrain = CONSTRAIN_DEFAULT; seat->shortcuts_inhibit = SHORTCUTS_INHIBIT_DEFAULT; seat->keyboard_grouping = KEYBOARD_GROUP_DEFAULT; seat->xcursor_theme.name = NULL; seat->xcursor_theme.size = 24; return seat; } static void merge_wildcard_on_all(struct seat_config *wildcard) { for (int i = 0; i < config->seat_configs->length; i++) { struct seat_config *sc = config->seat_configs->items[i]; if (strcmp(wildcard->name, sc->name) != 0) { sway_log(SWAY_DEBUG, "Merging seat * config on %s", sc->name); merge_seat_config(sc, wildcard); } } } struct seat_config *store_seat_config(struct seat_config *sc) { bool wildcard = strcmp(sc->name, "*") == 0; if (wildcard) { merge_wildcard_on_all(sc); } int i = list_seq_find(config->seat_configs, seat_name_cmp, sc->name); if (i >= 0) { sway_log(SWAY_DEBUG, "Merging on top of existing seat config"); struct seat_config *current = config->seat_configs->items[i]; merge_seat_config(current, sc); free_seat_config(sc); sc = current; } else if (!wildcard) { sway_log(SWAY_DEBUG, "Adding non-wildcard seat config"); i = list_seq_find(config->seat_configs, seat_name_cmp, "*"); if (i >= 0) { sway_log(SWAY_DEBUG, "Merging on top of seat * config"); struct seat_config *current = new_seat_config(sc->name); merge_seat_config(current, config->seat_configs->items[i]); merge_seat_config(current, sc); free_seat_config(sc); sc = current; } list_add(config->seat_configs, sc); } else { // New wildcard config. Just add it sway_log(SWAY_DEBUG, "Adding seat * config"); list_add(config->seat_configs, sc); } sway_log(SWAY_DEBUG, "Config stored for seat %s", sc->name); return sc; } struct seat_attachment_config *seat_attachment_config_new(void) { struct seat_attachment_config *attachment = calloc(1, sizeof(struct seat_attachment_config)); if (!attachment) { sway_log(SWAY_DEBUG, "cannot allocate attachment config"); return NULL; } return attachment; } static void seat_attachment_config_free( struct seat_attachment_config *attachment) { free(attachment->identifier); free(attachment); } static struct seat_attachment_config *seat_attachment_config_copy( struct seat_attachment_config *attachment) { struct seat_attachment_config *copy = seat_attachment_config_new(); if (!copy) { return NULL; } copy->identifier = strdup(attachment->identifier); return copy; } static void merge_seat_attachment_config(struct seat_attachment_config *dest, struct seat_attachment_config *source) { // nothing to merge yet, but there will be some day } void merge_seat_config(struct seat_config *dest, struct seat_config *source) { if (source->fallback != -1) { dest->fallback = source->fallback; } for (int i = 0; i < source->attachments->length; ++i) { struct seat_attachment_config *source_attachment = source->attachments->items[i]; bool found = false; for (int j = 0; j < dest->attachments->length; ++j) { struct seat_attachment_config *dest_attachment = dest->attachments->items[j]; if (strcmp(source_attachment->identifier, dest_attachment->identifier) == 0) { merge_seat_attachment_config(dest_attachment, source_attachment); found = true; } } if (!found) { struct seat_attachment_config *copy = seat_attachment_config_copy(source_attachment); if (copy) { list_add(dest->attachments, copy); } } } if (source->hide_cursor_timeout != -1) { dest->hide_cursor_timeout = source->hide_cursor_timeout; } if (source->hide_cursor_when_typing != HIDE_WHEN_TYPING_DEFAULT) { dest->hide_cursor_when_typing = source->hide_cursor_when_typing; } if (source->allow_constrain != CONSTRAIN_DEFAULT) { dest->allow_constrain = source->allow_constrain; } if (source->shortcuts_inhibit != SHORTCUTS_INHIBIT_DEFAULT) { dest->shortcuts_inhibit = source->shortcuts_inhibit; } if (source->keyboard_grouping != KEYBOARD_GROUP_DEFAULT) { dest->keyboard_grouping = source->keyboard_grouping; } if (source->xcursor_theme.name != NULL) { free(dest->xcursor_theme.name); dest->xcursor_theme.name = strdup(source->xcursor_theme.name); dest->xcursor_theme.size = source->xcursor_theme.size; } if (source->idle_inhibit_sources != UINT32_MAX) { dest->idle_inhibit_sources = source->idle_inhibit_sources; } if (source->idle_wake_sources != UINT32_MAX) { dest->idle_wake_sources = source->idle_wake_sources; } } struct seat_config *copy_seat_config(struct seat_config *seat) { struct seat_config *copy = new_seat_config(seat->name); if (copy == NULL) { return NULL; } merge_seat_config(copy, seat); return copy; } void free_seat_config(struct seat_config *seat) { if (!seat) { return; } free(seat->name); for (int i = 0; i < seat->attachments->length; ++i) { seat_attachment_config_free(seat->attachments->items[i]); } list_free(seat->attachments); free(seat->xcursor_theme.name); free(seat); } int seat_name_cmp(const void *item, const void *data) { const struct seat_config *sc = item; const char *name = data; return strcmp(sc->name, name); } struct seat_attachment_config *seat_config_get_attachment( struct seat_config *seat_config, char *identifier) { for (int i = 0; i < seat_config->attachments->length; ++i) { struct seat_attachment_config *attachment = seat_config->attachments->items[i]; if (strcmp(attachment->identifier, identifier) == 0) { return attachment; } } return NULL; } ================================================ FILE: sway/config.c ================================================ #undef _POSIX_C_SOURCE #define _XOPEN_SOURCE 700 // for realpath #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sway/input/input-manager.h" #include "sway/input/seat.h" #include "sway/input/switch.h" #include "sway/commands.h" #include "sway/config.h" #include "sway/criteria.h" #include "sway/desktop/transaction.h" #include "sway/server.h" #include "sway/swaynag.h" #include "sway/tree/arrange.h" #include "sway/tree/root.h" #include "sway/tree/workspace.h" #include "cairo_util.h" #include "pango.h" #include "stringop.h" #include "list.h" #include "log.h" #include "util.h" struct sway_config *config = NULL; static struct xkb_state *keysym_translation_state_create( struct xkb_rule_names rules, uint32_t context_flags) { struct xkb_context *context = xkb_context_new(context_flags | XKB_CONTEXT_NO_SECURE_GETENV); struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_names( context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); xkb_context_unref(context); if (xkb_keymap == NULL) { sway_log(SWAY_ERROR, "Failed to compile keysym translation XKB keymap"); return NULL; } return xkb_state_new(xkb_keymap); } static void keysym_translation_state_destroy( struct xkb_state *state) { if (state == NULL) { return; } xkb_keymap_unref(xkb_state_get_keymap(state)); xkb_state_unref(state); } static void free_mode(struct sway_mode *mode) { if (!mode) { return; } free(mode->name); if (mode->keysym_bindings) { for (int i = 0; i < mode->keysym_bindings->length; i++) { free_sway_binding(mode->keysym_bindings->items[i]); } list_free(mode->keysym_bindings); } if (mode->keycode_bindings) { for (int i = 0; i < mode->keycode_bindings->length; i++) { free_sway_binding(mode->keycode_bindings->items[i]); } list_free(mode->keycode_bindings); } if (mode->mouse_bindings) { for (int i = 0; i < mode->mouse_bindings->length; i++) { free_sway_binding(mode->mouse_bindings->items[i]); } list_free(mode->mouse_bindings); } if (mode->switch_bindings) { for (int i = 0; i < mode->switch_bindings->length; i++) { free_switch_binding(mode->switch_bindings->items[i]); } list_free(mode->switch_bindings); } if (mode->gesture_bindings) { for (int i = 0; i < mode->gesture_bindings->length; i++) { free_gesture_binding(mode->gesture_bindings->items[i]); } list_free(mode->gesture_bindings); } free(mode); } void free_config(struct sway_config *config) { if (!config) { return; } memset(&config->handler_context, 0, sizeof(config->handler_context)); // TODO: handle all currently unhandled lists as we add implementations if (config->symbols) { for (int i = 0; i < config->symbols->length; ++i) { free_sway_variable(config->symbols->items[i]); } list_free(config->symbols); } if (config->modes) { for (int i = 0; i < config->modes->length; ++i) { free_mode(config->modes->items[i]); } list_free(config->modes); } if (config->bars) { for (int i = 0; i < config->bars->length; ++i) { free_bar_config(config->bars->items[i]); } list_free(config->bars); } list_free(config->cmd_queue); if (config->workspace_configs) { for (int i = 0; i < config->workspace_configs->length; i++) { free_workspace_config(config->workspace_configs->items[i]); } list_free(config->workspace_configs); } if (config->output_configs) { for (int i = 0; i < config->output_configs->length; i++) { free_output_config(config->output_configs->items[i]); } list_free(config->output_configs); } if (config->swaybg_client != NULL) { wl_client_destroy(config->swaybg_client); } if (config->input_configs) { for (int i = 0; i < config->input_configs->length; i++) { free_input_config(config->input_configs->items[i]); } list_free(config->input_configs); } if (config->input_type_configs) { for (int i = 0; i < config->input_type_configs->length; i++) { free_input_config(config->input_type_configs->items[i]); } list_free(config->input_type_configs); } if (config->seat_configs) { for (int i = 0; i < config->seat_configs->length; i++) { free_seat_config(config->seat_configs->items[i]); } list_free(config->seat_configs); } if (config->criteria) { for (int i = 0; i < config->criteria->length; ++i) { criteria_destroy(config->criteria->items[i]); } list_free(config->criteria); } list_free(config->no_focus); list_free(config->active_bar_modifiers); list_free_items_and_destroy(config->config_chain); free(config->floating_scroll_up_cmd); free(config->floating_scroll_down_cmd); free(config->floating_scroll_left_cmd); free(config->floating_scroll_right_cmd); free(config->font); free(config->swaybg_command); free(config->swaynag_command); free((char *)config->current_config_path); free((char *)config->current_config); keysym_translation_state_destroy(config->keysym_translation_state); free(config); } static void destroy_removed_seats(struct sway_config *old_config, struct sway_config *new_config) { struct seat_config *seat_config; struct sway_seat *seat; int i; for (i = 0; i < old_config->seat_configs->length; i++) { seat_config = old_config->seat_configs->items[i]; // Skip the wildcard seat config, it won't have a matching real seat. if (strcmp(seat_config->name, "*") == 0) { continue; } /* Also destroy seats that aren't present in new config */ if (new_config && list_seq_find(new_config->seat_configs, seat_name_cmp, seat_config->name) < 0) { seat = input_manager_get_seat(seat_config->name, false); if (seat) { seat_destroy(seat); } } } } static void config_defaults(struct sway_config *config) { if (!(config->swaynag_command = strdup("swaynag"))) goto cleanup; config->swaynag_config_errors = (struct swaynag_instance){0}; config->swaynag_config_errors.args = "--type error " "--message 'There are errors in your config file' " "--detailed-message " "--button-no-terminal 'Exit sway' 'swaymsg exit' " "--button-no-terminal 'Reload sway' 'swaymsg reload'"; config->swaynag_config_errors.detailed = true; if (!(config->symbols = create_list())) goto cleanup; if (!(config->modes = create_list())) goto cleanup; if (!(config->bars = create_list())) goto cleanup; if (!(config->workspace_configs = create_list())) goto cleanup; if (!(config->criteria = create_list())) goto cleanup; if (!(config->no_focus = create_list())) goto cleanup; if (!(config->seat_configs = create_list())) goto cleanup; if (!(config->output_configs = create_list())) goto cleanup; if (!(config->input_type_configs = create_list())) goto cleanup; if (!(config->input_configs = create_list())) goto cleanup; if (!(config->cmd_queue = create_list())) goto cleanup; if (!(config->current_mode = malloc(sizeof(struct sway_mode)))) goto cleanup; if (!(config->current_mode->name = malloc(sizeof("default")))) goto cleanup; strcpy(config->current_mode->name, "default"); if (!(config->current_mode->keysym_bindings = create_list())) goto cleanup; if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup; if (!(config->current_mode->mouse_bindings = create_list())) goto cleanup; if (!(config->current_mode->switch_bindings = create_list())) goto cleanup; if (!(config->current_mode->gesture_bindings = create_list())) goto cleanup; list_add(config->modes, config->current_mode); config->floating_mod = 0; config->floating_mod_inverse = false; config->dragging_key = BTN_LEFT; config->resizing_key = BTN_RIGHT; if (!(config->floating_scroll_up_cmd = strdup(""))) goto cleanup; if (!(config->floating_scroll_down_cmd = strdup(""))) goto cleanup; if (!(config->floating_scroll_left_cmd = strdup(""))) goto cleanup; if (!(config->floating_scroll_right_cmd = strdup(""))) goto cleanup; config->default_layout = L_NONE; config->default_orientation = L_NONE; if (!(config->font = strdup("monospace 10"))) goto cleanup; config->font_description = pango_font_description_from_string(config->font); config->urgent_timeout = 500; config->focus_on_window_activation = FOWA_URGENT; config->popup_during_fullscreen = POPUP_SMART; config->xwayland = XWAYLAND_MODE_LAZY; config->titlebar_border_thickness = 1; config->titlebar_h_padding = 5; config->titlebar_v_padding = 4; // floating view config->floating_maximum_width = 0; config->floating_maximum_height = 0; config->floating_minimum_width = 75; config->floating_minimum_height = 50; // Flags config->focus_follows_mouse = FOLLOWS_YES; config->mouse_warping = WARP_OUTPUT; config->focus_wrapping = WRAP_YES; config->validating = false; config->reloading = false; config->active = false; config->failed = false; config->auto_back_and_forth = false; config->reading = false; config->show_marks = true; config->title_align = ALIGN_LEFT; config->tiling_drag = true; config->tiling_drag_threshold = 9; config->primary_selection = true; config->smart_gaps = SMART_GAPS_OFF; config->gaps_inner = 0; config->gaps_outer.top = 0; config->gaps_outer.right = 0; config->gaps_outer.bottom = 0; config->gaps_outer.left = 0; if (!(config->active_bar_modifiers = create_list())) goto cleanup; if (!(config->swaybg_command = strdup("swaybg"))) goto cleanup; if (!(config->config_chain = create_list())) goto cleanup; config->current_config_path = NULL; config->current_config = NULL; // borders config->border = B_NORMAL; config->floating_border = B_NORMAL; config->border_thickness = 2; config->floating_border_thickness = 2; config->hide_edge_borders = E_NONE; config->hide_edge_borders_smart = ESMART_OFF; config->hide_lone_tab = false; config->has_focused_tab_title = false; // border colors color_to_rgba(config->border_colors.focused.border, 0x4C7899FF); color_to_rgba(config->border_colors.focused.background, 0x285577FF); color_to_rgba(config->border_colors.focused.text, 0xFFFFFFFF); color_to_rgba(config->border_colors.focused.indicator, 0x2E9EF4FF); color_to_rgba(config->border_colors.focused.child_border, 0x285577FF); color_to_rgba(config->border_colors.focused_inactive.border, 0x333333FF); color_to_rgba(config->border_colors.focused_inactive.background, 0x5F676AFF); color_to_rgba(config->border_colors.focused_inactive.text, 0xFFFFFFFF); color_to_rgba(config->border_colors.focused_inactive.indicator, 0x484E50FF); color_to_rgba(config->border_colors.focused_inactive.child_border, 0x5F676AFF); color_to_rgba(config->border_colors.unfocused.border, 0x333333FF); color_to_rgba(config->border_colors.unfocused.background, 0x222222FF); color_to_rgba(config->border_colors.unfocused.text, 0x888888FF); color_to_rgba(config->border_colors.unfocused.indicator, 0x292D2EFF); color_to_rgba(config->border_colors.unfocused.child_border, 0x222222FF); color_to_rgba(config->border_colors.urgent.border, 0x2F343AFF); color_to_rgba(config->border_colors.urgent.background, 0x900000FF); color_to_rgba(config->border_colors.urgent.text, 0xFFFFFFFF); color_to_rgba(config->border_colors.urgent.indicator, 0x900000FF); color_to_rgba(config->border_colors.urgent.child_border, 0x900000FF); color_to_rgba(config->border_colors.placeholder.border, 0x000000FF); color_to_rgba(config->border_colors.placeholder.background, 0x0C0C0CFF); color_to_rgba(config->border_colors.placeholder.text, 0xFFFFFFFF); color_to_rgba(config->border_colors.placeholder.indicator, 0x000000FF); color_to_rgba(config->border_colors.placeholder.child_border, 0x0C0C0CFF); color_to_rgba(config->border_colors.background, 0xFFFFFFFF); // The keysym to keycode translation struct xkb_rule_names rules = {0}; config->keysym_translation_state = keysym_translation_state_create(rules, 0); if (config->keysym_translation_state == NULL) { config->keysym_translation_state = keysym_translation_state_create(rules, XKB_CONTEXT_NO_ENVIRONMENT_NAMES); } if (config->keysym_translation_state == NULL) { goto cleanup; } return; cleanup: sway_abort("Unable to allocate config structures"); } static bool file_exists(const char *path) { return path && access(path, R_OK) != -1; } static char *config_path(const char *prefix, const char *config_folder) { if (!prefix || !prefix[0] || !config_folder || !config_folder[0]) { return NULL; } return format_str("%s/%s/config", prefix, config_folder); } static char *get_config_path(void) { char *path = NULL; const char *home = getenv("HOME"); char *config_home_fallback = NULL; const char *config_home = getenv("XDG_CONFIG_HOME"); if ((config_home == NULL || config_home[0] == '\0') && home != NULL) { config_home_fallback = format_str("%s/.config", home); config_home = config_home_fallback; } struct config_path { const char *prefix; const char *config_folder; }; struct config_path config_paths[] = { { .prefix = home, .config_folder = ".sway"}, { .prefix = config_home, .config_folder = "sway"}, { .prefix = home, .config_folder = ".i3"}, { .prefix = config_home, .config_folder = "i3"}, { .prefix = SYSCONFDIR, .config_folder = "sway"}, { .prefix = SYSCONFDIR, .config_folder = "i3"} }; size_t num_config_paths = sizeof(config_paths)/sizeof(config_paths[0]); for (size_t i = 0; i < num_config_paths; i++) { path = config_path(config_paths[i].prefix, config_paths[i].config_folder); if (!path) { continue; } if (file_exists(path)) { break; } free(path); path = NULL; } free(config_home_fallback); return path; } static bool load_config(const char *path, struct sway_config *config, struct swaynag_instance *swaynag) { if (path == NULL) { sway_log(SWAY_ERROR, "Unable to find a config file!"); return false; } sway_log(SWAY_INFO, "Loading config from %s", path); struct stat sb; if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { sway_log(SWAY_ERROR, "%s is a directory not a config file", path); return false; } FILE *f = fopen(path, "r"); if (!f) { sway_log(SWAY_ERROR, "Unable to open %s for reading", path); return false; } bool config_load_success = read_config(f, config, swaynag); fclose(f); if (!config_load_success) { sway_log(SWAY_ERROR, "Error(s) loading config!"); } return config->active || !config->validating || config_load_success; } bool load_main_config(const char *file, bool is_active, bool validating) { char *path; if (file != NULL) { path = strdup(file); } else { path = get_config_path(); } if (path == NULL) { sway_log(SWAY_ERROR, "Cannot find config file"); return false; } char *real_path = realpath(path, NULL); if (real_path == NULL) { sway_log(SWAY_ERROR, "%s not found", path); free(path); return false; } struct sway_config *old_config = config; config = calloc(1, sizeof(struct sway_config)); if (!config) { sway_abort("Unable to allocate config"); } config_defaults(config); config->validating = validating; if (is_active) { sway_log(SWAY_DEBUG, "Performing configuration file %s", validating ? "validation" : "reload"); config->reloading = true; config->active = true; // xwayland can only be enabled/disabled at launch sway_log(SWAY_DEBUG, "xwayland will remain %s", old_config->xwayland ? "enabled" : "disabled"); config->xwayland = old_config->xwayland; // primary_selection can only be enabled/disabled at launch sway_log(SWAY_DEBUG, "primary_selection will remain %s", old_config->primary_selection ? "enabled" : "disabled"); config->primary_selection = old_config->primary_selection; if (!config->validating) { if (old_config->swaybg_client != NULL) { wl_client_destroy(old_config->swaybg_client); } if (old_config->swaynag_config_errors.client != NULL) { wl_client_destroy(old_config->swaynag_config_errors.client); } input_manager_reset_all_inputs(); } } config->user_config_path = file ? true : false; config->current_config_path = path; list_add(config->config_chain, real_path); config->reading = true; bool success = load_config(path, config, &config->swaynag_config_errors); if (validating) { free_config(config); config = old_config; return success; } // Only really necessary if not explicitly `font` is set in the config. config_update_font_height(); if (!validating) { input_manager_verify_fallback_seat(); for (int i = 0; i < config->input_configs->length; i++) { input_manager_apply_input_config(config->input_configs->items[i]); } for (int i = 0; i < config->input_type_configs->length; i++) { input_manager_apply_input_config( config->input_type_configs->items[i]); } for (int i = 0; i < config->seat_configs->length; i++) { input_manager_apply_seat_config(config->seat_configs->items[i]); } sway_switch_retrigger_bindings_for_all(); spawn_swaybg(); config->reloading = false; if (is_active) { request_modeset(); if (config->swaynag_config_errors.client != NULL) { swaynag_show(&config->swaynag_config_errors); } } } if (old_config) { destroy_removed_seats(old_config, config); free_config(old_config); } config->reading = false; return success; } static bool load_include_config(const char *path, struct sway_config *config, struct swaynag_instance *swaynag) { // save parent config const char *parent_config = config->current_config_path; char *real_path = realpath(path, NULL); if (real_path == NULL) { sway_log(SWAY_DEBUG, "%s not found.", path); return false; } // check if config has already been included int j; for (j = 0; j < config->config_chain->length; ++j) { char *old_path = config->config_chain->items[j]; if (strcmp(real_path, old_path) == 0) { sway_log(SWAY_DEBUG, "%s already included once, won't be included again.", real_path); free(real_path); return false; } } config->current_config_path = real_path; list_add(config->config_chain, real_path); int index = config->config_chain->length - 1; if (!load_config(real_path, config, swaynag)) { free(real_path); config->current_config_path = parent_config; list_del(config->config_chain, index); return false; } // restore current_config_path config->current_config_path = parent_config; return true; } void load_include_configs(const char *path, struct sway_config *config, struct swaynag_instance *swaynag) { char *wd = getcwd(NULL, 0); char *parent_path = strdup(config->current_config_path); const char *parent_dir = dirname(parent_path); if (chdir(parent_dir) < 0) { sway_log(SWAY_ERROR, "failed to change working directory"); goto cleanup; } wordexp_t p; if (wordexp(path, &p, 0) == 0) { char **w = p.we_wordv; size_t i; for (i = 0; i < p.we_wordc; ++i) { load_include_config(w[i], config, swaynag); } wordfree(&p); } // Attempt to restore working directory before returning. if (chdir(wd) < 0) { sway_log(SWAY_ERROR, "failed to change working directory"); } cleanup: free(parent_path); free(wd); } void run_deferred_commands(void) { if (!config->cmd_queue->length) { return; } sway_log(SWAY_DEBUG, "Running deferred commands"); while (config->cmd_queue->length) { char *line = config->cmd_queue->items[0]; list_t *res_list = execute_command(line, NULL, NULL); for (int i = 0; i < res_list->length; ++i) { struct cmd_results *res = res_list->items[i]; if (res->status != CMD_SUCCESS) { sway_log(SWAY_ERROR, "Error on line '%s': %s", line, res->error); } free_cmd_results(res); } list_del(config->cmd_queue, 0); list_free(res_list); free(line); } } void run_deferred_bindings(void) { struct sway_seat *seat; wl_list_for_each(seat, &(server.input->seats), link) { if (!seat->deferred_bindings->length) { continue; } sway_log(SWAY_DEBUG, "Running deferred bindings for seat %s", seat->wlr_seat->name); while (seat->deferred_bindings->length) { struct sway_binding *binding = seat->deferred_bindings->items[0]; seat_execute_command(seat, binding); list_del(seat->deferred_bindings, 0); free_sway_binding(binding); } } } // get line, with backslash continuation static ssize_t getline_with_cont(char **lineptr, size_t *line_size, FILE *file, int *nlines) { char *next_line = NULL; size_t next_line_size = 0; ssize_t nread = getline(lineptr, line_size, file); *nlines = nread == -1 ? 0 : 1; while (nread >= 2 && strcmp(&(*lineptr)[nread - 2], "\\\n") == 0 && (*lineptr)[0] != '#') { ssize_t next_nread = getline(&next_line, &next_line_size, file); if (next_nread == -1) { break; } (*nlines)++; nread += next_nread - 2; if ((ssize_t) *line_size < nread + 1) { *line_size = nread + 1; char *old_ptr = *lineptr; *lineptr = realloc(*lineptr, *line_size); if (!*lineptr) { free(old_ptr); nread = -1; break; } } strcpy(&(*lineptr)[nread - next_nread], next_line); } free(next_line); return nread; } static int detect_brace(FILE *file) { int ret = 0; int lines = 0; long pos = ftell(file); char *line = NULL; size_t line_size = 0; while ((getline(&line, &line_size, file)) != -1) { lines++; strip_whitespace(line); if (*line) { if (strcmp(line, "{") == 0) { ret = lines; } break; } } free(line); if (ret == 0) { fseek(file, pos, SEEK_SET); } return ret; } static char *expand_line(const char *block, const char *line, bool add_brace) { int size = (block ? strlen(block) + 1 : 0) + strlen(line) + (add_brace ? 2 : 0) + 1; char *expanded = calloc(1, size); if (!expanded) { sway_log(SWAY_ERROR, "Cannot allocate expanded line buffer"); return NULL; } snprintf(expanded, size, "%s%s%s%s", block ? block : "", block ? " " : "", line, add_brace ? " {" : ""); return expanded; } bool read_config(FILE *file, struct sway_config *config, struct swaynag_instance *swaynag) { bool reading_main_config = false; char *this_config = NULL; size_t config_size = 0; if (config->current_config == NULL) { reading_main_config = true; int ret_seek = fseek(file, 0, SEEK_END); long ret_tell = ftell(file); if (ret_seek == -1 || ret_tell == -1) { sway_log(SWAY_ERROR, "Unable to get size of config file"); return false; } config_size = ret_tell; rewind(file); config->current_config = this_config = calloc(1, config_size + 1); if (this_config == NULL) { sway_log(SWAY_ERROR, "Unable to allocate buffer for config contents"); return false; } } bool success = true; int line_number = 0; char *line = NULL; size_t line_size = 0; ssize_t nread; list_t *stack = create_list(); size_t read = 0; int nlines = 0; while ((nread = getline_with_cont(&line, &line_size, file, &nlines)) != -1) { if (reading_main_config) { if (read + nread > config_size) { sway_log(SWAY_ERROR, "Config file changed during reading"); success = false; break; } strcpy(&this_config[read], line); read += nread; } if (line[nread - 1] == '\n') { line[nread - 1] = '\0'; } line_number += nlines; sway_log(SWAY_DEBUG, "Read line %d: %s", line_number, line); strip_whitespace(line); if (!*line || line[0] == '#') { continue; } int brace_detected = 0; if (line[strlen(line) - 1] != '{' && line[strlen(line) - 1] != '}') { brace_detected = detect_brace(file); if (brace_detected > 0) { line_number += brace_detected; sway_log(SWAY_DEBUG, "Detected open brace on line %d", line_number); } } char *block = stack->length ? stack->items[0] : NULL; char *expanded = expand_line(block, line, brace_detected > 0); if (!expanded) { success = false; break; } config->current_config_line_number = line_number; config->current_config_line = line; struct cmd_results *res; char *new_block = NULL; if (block && strcmp(block, "") == 0) { // Special case res = config_commands_command(expanded); } else { res = config_command(expanded, &new_block); } switch(res->status) { case CMD_FAILURE: case CMD_INVALID: sway_log(SWAY_ERROR, "Error on line %i '%s': %s (%s)", line_number, line, res->error, config->current_config_path); if (!config->validating) { swaynag_log(config->swaynag_command, swaynag, "Error on line %i (%s) '%s': %s", line_number, config->current_config_path, line, res->error); } success = false; break; case CMD_DEFER: sway_log(SWAY_DEBUG, "Deferring command `%s'", line); list_add(config->cmd_queue, strdup(expanded)); break; case CMD_BLOCK_COMMANDS: sway_log(SWAY_DEBUG, "Entering commands block"); list_insert(stack, 0, ""); break; case CMD_BLOCK: sway_log(SWAY_DEBUG, "Entering block '%s'", new_block); list_insert(stack, 0, strdup(new_block)); if (strcmp(new_block, "bar") == 0) { config->current_bar = NULL; } break; case CMD_BLOCK_END: if (!block) { sway_log(SWAY_DEBUG, "Unmatched '}' on line %i", line_number); success = false; break; } if (strcmp(block, "bar") == 0) { config->current_bar = NULL; } sway_log(SWAY_DEBUG, "Exiting block '%s'", block); list_del(stack, 0); free(block); memset(&config->handler_context, 0, sizeof(config->handler_context)); default:; } free(new_block); free(expanded); free_cmd_results(res); } free(line); list_free_items_and_destroy(stack); config->current_config_line_number = 0; config->current_config_line = NULL; return success; } void config_add_swaynag_warning(char *fmt, ...) { if (config->reading && !config->validating) { va_list args; va_start(args, fmt); char *str = vformat_str(fmt, args); va_end(args); if (str == NULL) { return; } swaynag_log(config->swaynag_command, &config->swaynag_config_errors, "Warning on line %i (%s) '%s': %s", config->current_config_line_number, config->current_config_path, config->current_config_line, str); free(str); } } char *do_var_replacement(char *str) { int i; char *find = str; while ((find = strchr(find, '$'))) { // Skip if escaped. if (find > str && find[-1] == '\\') { if (find == str + 1 || !(find > str + 1 && find[-2] == '\\')) { ++find; continue; } } // Unescape double $ and move on if (find[1] == '$') { size_t length = strlen(find + 1); memmove(find, find + 1, length); find[length] = '\0'; ++find; continue; } // Find matching variable for (i = 0; i < config->symbols->length; ++i) { struct sway_variable *var = config->symbols->items[i]; if (has_prefix(find, var->name)) { int vnlen = strlen(var->name); int vvlen = strlen(var->value); char *newstr = malloc(strlen(str) - vnlen + vvlen + 1); if (!newstr) { sway_log(SWAY_ERROR, "Unable to allocate replacement " "during variable expansion"); break; } char *newptr = newstr; int offset = find - str; strncpy(newptr, str, offset); newptr += offset; memcpy(newptr, var->value, vvlen); newptr += vvlen; strcpy(newptr, find + vnlen); free(str); str = newstr; find = str + offset + vvlen; break; } } if (i == config->symbols->length) { ++find; } } return str; } // the naming is intentional (albeit long): a workspace_output_cmp function // would compare two structs in full, while this method only compares the // workspace. int workspace_output_cmp_workspace(const void *a, const void *b) { const struct workspace_config *wsa = a, *wsb = b; return lenient_strcmp(wsa->workspace, wsb->workspace); } void config_update_font_height(void) { int prev_max_height = config->font_height; get_text_metrics(config->font_description, &config->font_height, &config->font_baseline); if (config->font_height != prev_max_height) { arrange_root(); } } static void translate_binding_list(list_t *bindings, list_t *bindsyms, list_t *bindcodes) { for (int i = 0; i < bindings->length; ++i) { struct sway_binding *binding = bindings->items[i]; translate_binding(binding); switch (binding->type) { case BINDING_KEYSYM: binding_add_translated(binding, bindsyms); break; case BINDING_KEYCODE: binding_add_translated(binding, bindcodes); break; default: sway_assert(false, "unexpected translated binding type: %d", binding->type); break; } } } void translate_keysyms(struct input_config *input_config) { keysym_translation_state_destroy(config->keysym_translation_state); struct xkb_rule_names rules = {0}; input_config_fill_rule_names(input_config, &rules); config->keysym_translation_state = keysym_translation_state_create(rules, 0); if (config->keysym_translation_state == NULL) { sway_log(SWAY_ERROR, "Failed to create keysym translation XKB state " "for device '%s'", input_config->identifier); return; } for (int i = 0; i < config->modes->length; ++i) { struct sway_mode *mode = config->modes->items[i]; list_t *bindsyms = create_list(); list_t *bindcodes = create_list(); translate_binding_list(mode->keysym_bindings, bindsyms, bindcodes); translate_binding_list(mode->keycode_bindings, bindsyms, bindcodes); list_free(mode->keysym_bindings); list_free(mode->keycode_bindings); mode->keysym_bindings = bindsyms; mode->keycode_bindings = bindcodes; } sway_log(SWAY_DEBUG, "Translated keysyms using config for device '%s'", input_config->identifier); } ================================================ FILE: sway/criteria.c ================================================ #include #include #include #include #define PCRE2_CODE_UNIT_WIDTH 8 #include #include "sway/criteria.h" #include "sway/tree/container.h" #include "sway/config.h" #include "sway/server.h" #include "sway/tree/root.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "stringop.h" #include "list.h" #include "log.h" #include "config.h" bool criteria_is_empty(struct criteria *criteria) { return !criteria->title && !criteria->shell && !criteria->all && !criteria->app_id && !criteria->con_mark && !criteria->con_id #if WLR_HAS_XWAYLAND && !criteria->class && !criteria->id && !criteria->instance && !criteria->window_role && criteria->window_type == ATOM_LAST #endif && !criteria->floating && !criteria->tiling && !criteria->urgent && !criteria->workspace && !criteria->pid && !criteria->sandbox_engine && !criteria->sandbox_app_id && !criteria->sandbox_instance_id && !criteria->tag; } // The error pointer is used for parsing functions, and saves having to pass it // as an argument in several places. char *error = NULL; // Returns error string on failure or NULL otherwise. static bool generate_regex(pcre2_code **regex, char *value) { int errorcode; PCRE2_SIZE offset; *regex = pcre2_compile((PCRE2_SPTR)value, PCRE2_ZERO_TERMINATED, PCRE2_UTF | PCRE2_UCP, &errorcode, &offset, NULL); if (!*regex) { PCRE2_UCHAR buffer[256]; pcre2_get_error_message(errorcode, buffer, sizeof(buffer)); const char *fmt = "Regex compilation for '%s' failed: %s"; int len = strlen(fmt) + strlen(value) + strlen((char*) buffer) - 3; error = malloc(len); snprintf(error, len, fmt, value, buffer); return false; } return true; } static bool pattern_create(struct pattern **pattern, char *value) { *pattern = calloc(1, sizeof(struct pattern)); if (!*pattern) { sway_log(SWAY_ERROR, "Failed to allocate pattern"); } if (strcmp(value, "__focused__") == 0) { (*pattern)->match_type = PATTERN_FOCUSED; } else { (*pattern)->match_type = PATTERN_PCRE2; if (!generate_regex(&(*pattern)->regex, value)) { return false; }; } return true; } static void pattern_destroy(struct pattern *pattern) { if (pattern) { if (pattern->regex) { pcre2_code_free(pattern->regex); } free(pattern); } } void criteria_destroy(struct criteria *criteria) { pattern_destroy(criteria->title); pattern_destroy(criteria->shell); pattern_destroy(criteria->app_id); #if WLR_HAS_XWAYLAND pattern_destroy(criteria->class); pattern_destroy(criteria->instance); pattern_destroy(criteria->window_role); #endif pattern_destroy(criteria->con_mark); pattern_destroy(criteria->workspace); pattern_destroy(criteria->sandbox_engine); pattern_destroy(criteria->sandbox_app_id); pattern_destroy(criteria->sandbox_instance_id); pattern_destroy(criteria->tag); free(criteria->target); free(criteria->cmdlist); free(criteria->raw); free(criteria); } static int regex_cmp(const char *item, const pcre2_code *regex) { pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL); int result = pcre2_match(regex, (PCRE2_SPTR)item, strlen(item), 0, 0, match_data, NULL); pcre2_match_data_free(match_data); return result; } #if WLR_HAS_XWAYLAND static bool view_has_window_type(struct sway_view *view, enum atom_name name) { if (view->type != SWAY_VIEW_XWAYLAND) { return false; } struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface; struct sway_xwayland *xwayland = &server.xwayland; xcb_atom_t desired_atom = xwayland->atoms[name]; for (size_t i = 0; i < surface->window_type_len; ++i) { if (surface->window_type[i] == desired_atom) { return true; } } return false; } #endif static int cmp_urgent(const void *_a, const void *_b) { struct sway_view *a = *(void **)_a; struct sway_view *b = *(void **)_b; if (a->urgent.tv_sec < b->urgent.tv_sec) { return -1; } else if (a->urgent.tv_sec > b->urgent.tv_sec) { return 1; } if (a->urgent.tv_nsec < b->urgent.tv_nsec) { return -1; } else if (a->urgent.tv_nsec > b->urgent.tv_nsec) { return 1; } return 0; } static void find_urgent_iterator(struct sway_container *con, void *data) { if (!con->view || !view_is_urgent(con->view)) { return; } list_t *urgent_views = data; list_add(urgent_views, con->view); } static bool has_container_criteria(struct criteria *criteria) { return criteria->con_mark || criteria->con_id; } static bool criteria_matches_container(struct criteria *criteria, struct sway_container *container) { if (criteria->con_mark) { bool exists = false; struct sway_container *con = container; for (int i = 0; i < con->marks->length; ++i) { if (regex_cmp(con->marks->items[i], criteria->con_mark->regex) >= 0) { exists = true; break; } } if (!exists) { return false; } } if (criteria->con_id) { // Internal ID if (container->node.id != criteria->con_id) { return false; } } return true; } static bool criteria_matches_view(struct criteria *criteria, struct sway_view *view) { struct sway_seat *seat = input_manager_current_seat(); struct sway_container *focus = seat_get_focused_container(seat); struct sway_view *focused = focus ? focus->view : NULL; if (!view->container) { return false; } if (criteria->title) { const char *title = view_get_title(view); if (!title) { title = ""; } switch (criteria->title->match_type) { case PATTERN_FOCUSED: if (!focused || lenient_strcmp(title, view_get_title(focused))) { return false; } break; case PATTERN_PCRE2: if (regex_cmp(title, criteria->title->regex) < 0) { return false; } break; } } if (criteria->shell) { const char *shell = view_get_shell(view); if (!shell) { shell = ""; } switch (criteria->shell->match_type) { case PATTERN_FOCUSED: if (!focused || strcmp(shell, view_get_shell(focused))) { return false; } break; case PATTERN_PCRE2: if (regex_cmp(shell, criteria->shell->regex) < 0) { return false; } break; } } if (criteria->app_id) { const char *app_id = view_get_app_id(view); if (!app_id) { app_id = ""; } switch (criteria->app_id->match_type) { case PATTERN_FOCUSED: if (!focused || lenient_strcmp(app_id, view_get_app_id(focused))) { return false; } break; case PATTERN_PCRE2: if (regex_cmp(app_id, criteria->app_id->regex) < 0) { return false; } break; } } if (criteria->sandbox_engine) { const char *sandbox_engine = view_get_sandbox_engine(view); if (!sandbox_engine) { return false; } switch (criteria->sandbox_engine->match_type) { case PATTERN_FOCUSED: if (!focused || lenient_strcmp(sandbox_engine, view_get_sandbox_engine(focused))) { return false; } break; case PATTERN_PCRE2: if (regex_cmp(sandbox_engine, criteria->sandbox_engine->regex) < 0) { return false; } break; } } if (criteria->sandbox_app_id) { const char *sandbox_app_id = view_get_sandbox_app_id(view); if (!sandbox_app_id) { return false; } switch (criteria->sandbox_app_id->match_type) { case PATTERN_FOCUSED: if (!focused || lenient_strcmp(sandbox_app_id, view_get_sandbox_app_id(focused))) { return false; } break; case PATTERN_PCRE2: if (regex_cmp(sandbox_app_id, criteria->sandbox_app_id->regex) < 0) { return false; } break; } } if (criteria->sandbox_instance_id) { const char *sandbox_instance_id = view_get_sandbox_instance_id(view); if (!sandbox_instance_id) { return false; } switch (criteria->sandbox_instance_id->match_type) { case PATTERN_FOCUSED: if (!focused || lenient_strcmp(sandbox_instance_id, view_get_sandbox_instance_id(focused))) { return false; } break; case PATTERN_PCRE2: if (regex_cmp(sandbox_instance_id, criteria->sandbox_instance_id->regex) < 0) { return false; } break; } } if (criteria->tag) { const char *tag = view_get_tag(view); if (!tag) { return false; } switch (criteria->tag->match_type) { case PATTERN_FOCUSED: if (!focused || lenient_strcmp(tag, view_get_tag(focused))) { return false; } break; case PATTERN_PCRE2: if (regex_cmp(tag, criteria->tag->regex) < 0) { return false; } break; } } if (!criteria_matches_container(criteria, view->container)) { return false; } #if WLR_HAS_XWAYLAND if (criteria->id) { // X11 window ID uint32_t x11_window_id = view_get_x11_window_id(view); if (!x11_window_id || x11_window_id != criteria->id) { return false; } } if (criteria->class) { const char *class = view_get_class(view); if (!class) { class = ""; } switch (criteria->class->match_type) { case PATTERN_FOCUSED: if (!focused || lenient_strcmp(class, view_get_class(focused))) { return false; } break; case PATTERN_PCRE2: if (regex_cmp(class, criteria->class->regex) < 0) { return false; } break; } } if (criteria->instance) { const char *instance = view_get_instance(view); if (!instance) { instance = ""; } switch (criteria->instance->match_type) { case PATTERN_FOCUSED: if (!focused || lenient_strcmp(instance, view_get_instance(focused))) { return false; } break; case PATTERN_PCRE2: if (regex_cmp(instance, criteria->instance->regex) < 0) { return false; } break; } } if (criteria->window_role) { const char *window_role = view_get_window_role(view); if (!window_role) { window_role = ""; } switch (criteria->window_role->match_type) { case PATTERN_FOCUSED: if (!focused || lenient_strcmp(window_role, view_get_window_role(focused))) { return false; } break; case PATTERN_PCRE2: if (regex_cmp(window_role, criteria->window_role->regex) < 0) { return false; } break; } } if (criteria->window_type != ATOM_LAST) { if (!view_has_window_type(view, criteria->window_type)) { return false; } } #endif if (criteria->floating) { if (!container_is_floating(view->container)) { return false; } } if (criteria->tiling) { if (container_is_floating(view->container)) { return false; } } if (criteria->urgent) { if (!view_is_urgent(view)) { return false; } list_t *urgent_views = create_list(); root_for_each_container(find_urgent_iterator, urgent_views); list_stable_sort(urgent_views, cmp_urgent); struct sway_view *target; if (criteria->urgent == 'o') { // oldest target = urgent_views->items[0]; } else { // latest target = urgent_views->items[urgent_views->length - 1]; } list_free(urgent_views); if (view != target) { return false; } } if (criteria->workspace) { struct sway_workspace *ws = view->container->pending.workspace; if (!ws) { return false; } switch (criteria->workspace->match_type) { case PATTERN_FOCUSED: if (!focused || strcmp(ws->name, focused->container->pending.workspace->name)) { return false; } break; case PATTERN_PCRE2: if (regex_cmp(ws->name, criteria->workspace->regex) < 0) { return false; } break; } } if (criteria->pid) { if (criteria->pid != view->pid) { return false; } } return true; } list_t *criteria_for_view(struct sway_view *view, enum criteria_type types) { list_t *criterias = config->criteria; list_t *matches = create_list(); for (int i = 0; i < criterias->length; ++i) { struct criteria *criteria = criterias->items[i]; if ((criteria->type & types) && criteria_matches_view(criteria, view)) { list_add(matches, criteria); } } return matches; } struct match_data { struct criteria *criteria; list_t *matches; }; static void criteria_get_containers_iterator(struct sway_container *container, void *data) { struct match_data *match_data = data; if (container->view) { if (criteria_matches_view(match_data->criteria, container->view)) { list_add(match_data->matches, container); } } else if (has_container_criteria(match_data->criteria)) { if (criteria_matches_container(match_data->criteria, container)) { list_add(match_data->matches, container); } } } list_t *criteria_get_containers(struct criteria *criteria) { list_t *matches = create_list(); struct match_data data = { .criteria = criteria, .matches = matches, }; root_for_each_container(criteria_get_containers_iterator, &data); return matches; } #if WLR_HAS_XWAYLAND static enum atom_name parse_window_type(const char *type) { if (strcasecmp(type, "normal") == 0) { return NET_WM_WINDOW_TYPE_NORMAL; } else if (strcasecmp(type, "dialog") == 0) { return NET_WM_WINDOW_TYPE_DIALOG; } else if (strcasecmp(type, "utility") == 0) { return NET_WM_WINDOW_TYPE_UTILITY; } else if (strcasecmp(type, "toolbar") == 0) { return NET_WM_WINDOW_TYPE_TOOLBAR; } else if (strcasecmp(type, "splash") == 0) { return NET_WM_WINDOW_TYPE_SPLASH; } else if (strcasecmp(type, "menu") == 0) { return NET_WM_WINDOW_TYPE_MENU; } else if (strcasecmp(type, "dropdown_menu") == 0) { return NET_WM_WINDOW_TYPE_DROPDOWN_MENU; } else if (strcasecmp(type, "popup_menu") == 0) { return NET_WM_WINDOW_TYPE_POPUP_MENU; } else if (strcasecmp(type, "tooltip") == 0) { return NET_WM_WINDOW_TYPE_TOOLTIP; } else if (strcasecmp(type, "notification") == 0) { return NET_WM_WINDOW_TYPE_NOTIFICATION; } return ATOM_LAST; // ie. invalid } #endif enum criteria_token { T_ALL, T_APP_ID, T_CON_ID, T_CON_MARK, T_FLOATING, #if WLR_HAS_XWAYLAND T_CLASS, T_ID, T_INSTANCE, T_WINDOW_ROLE, T_WINDOW_TYPE, #endif T_SHELL, T_TILING, T_TITLE, T_URGENT, T_WORKSPACE, T_PID, T_SANDBOX_ENGINE, T_SANDBOX_APP_ID, T_SANDBOX_INSTANCE_ID, T_TAG, T_INVALID, }; static enum criteria_token token_from_name(char *name) { if (strcmp(name, "all") == 0) { return T_ALL; } else if (strcmp(name, "app_id") == 0) { return T_APP_ID; } else if (strcmp(name, "con_id") == 0) { return T_CON_ID; } else if (strcmp(name, "con_mark") == 0) { return T_CON_MARK; #if WLR_HAS_XWAYLAND } else if (strcmp(name, "class") == 0) { return T_CLASS; } else if (strcmp(name, "id") == 0) { return T_ID; } else if (strcmp(name, "instance") == 0) { return T_INSTANCE; } else if (strcmp(name, "window_role") == 0) { return T_WINDOW_ROLE; } else if (strcmp(name, "window_type") == 0) { return T_WINDOW_TYPE; #endif } else if (strcmp(name, "shell") == 0) { return T_SHELL; } else if (strcmp(name, "title") == 0) { return T_TITLE; } else if (strcmp(name, "urgent") == 0) { return T_URGENT; } else if (strcmp(name, "workspace") == 0) { return T_WORKSPACE; } else if (strcmp(name, "tiling") == 0) { return T_TILING; } else if (strcmp(name, "floating") == 0) { return T_FLOATING; } else if (strcmp(name, "pid") == 0) { return T_PID; } else if (strcmp(name, "sandbox_engine") == 0) { return T_SANDBOX_ENGINE; } else if (strcmp(name, "sandbox_app_id") == 0) { return T_SANDBOX_APP_ID; } else if (strcmp(name, "sandbox_instance_id") == 0) { return T_SANDBOX_INSTANCE_ID; } else if (strcmp(name, "tag") == 0) { return T_TAG; } return T_INVALID; } static bool parse_token(struct criteria *criteria, char *name, char *value) { enum criteria_token token = token_from_name(name); if (token == T_INVALID) { const char *fmt = "Token '%s' is not recognized"; int len = strlen(fmt) + strlen(name) - 1; error = malloc(len); snprintf(error, len, fmt, name); return false; } // Require value, unless token is all, floating or tiled if (!value && token != T_ALL && token != T_FLOATING && token != T_TILING) { const char *fmt = "Token '%s' requires a value"; int len = strlen(fmt) + strlen(name) - 1; error = malloc(len); snprintf(error, len, fmt, name); return false; } char *endptr = NULL; switch (token) { case T_ALL: criteria->all = true; break; case T_TITLE: pattern_create(&criteria->title, value); break; case T_SHELL: pattern_create(&criteria->shell, value); break; case T_APP_ID: pattern_create(&criteria->app_id, value); break; case T_CON_ID: if (strcmp(value, "__focused__") == 0) { struct sway_seat *seat = input_manager_current_seat(); struct sway_container *focus = seat_get_focused_container(seat); criteria->con_id = focus ? focus->node.id : 0; } else { criteria->con_id = strtoul(value, &endptr, 10); if (*endptr != 0) { error = strdup("The value for 'con_id' should be '__focused__' or numeric"); } } break; case T_CON_MARK: pattern_create(&criteria->con_mark, value); break; #if WLR_HAS_XWAYLAND case T_CLASS: pattern_create(&criteria->class, value); break; case T_ID: criteria->id = strtoul(value, &endptr, 10); if (*endptr != 0) { error = strdup("The value for 'id' should be numeric"); } break; case T_INSTANCE: pattern_create(&criteria->instance, value); break; case T_WINDOW_ROLE: pattern_create(&criteria->window_role, value); break; case T_WINDOW_TYPE: criteria->window_type = parse_window_type(value); break; #endif case T_FLOATING: criteria->floating = true; break; case T_TILING: criteria->tiling = true; break; case T_URGENT: if (strcmp(value, "latest") == 0 || strcmp(value, "newest") == 0 || strcmp(value, "last") == 0 || strcmp(value, "recent") == 0) { criteria->urgent = 'l'; } else if (strcmp(value, "oldest") == 0 || strcmp(value, "first") == 0) { criteria->urgent = 'o'; } else { error = strdup("The value for 'urgent' must be 'first', 'last', " "'latest', 'newest', 'oldest' or 'recent'"); } break; case T_WORKSPACE: pattern_create(&criteria->workspace, value); break; case T_PID: criteria->pid = strtoul(value, &endptr, 10); if (*endptr != 0) { error = strdup("The value for 'pid' should be numeric"); } break; case T_SANDBOX_ENGINE: pattern_create(&criteria->sandbox_engine, value); break; case T_SANDBOX_APP_ID: pattern_create(&criteria->sandbox_app_id, value); break; case T_SANDBOX_INSTANCE_ID: pattern_create(&criteria->sandbox_instance_id, value); break; case T_TAG: pattern_create(&criteria->tag, value); break; case T_INVALID: break; } if (error) { return false; } return true; } static void skip_spaces(char **head) { while (**head == ' ') { ++*head; } } // Remove escaping slashes from value static void unescape(char *value) { if (!strchr(value, '\\')) { return; } char *copy = calloc(strlen(value) + 1, 1); char *readhead = value; char *writehead = copy; while (*readhead) { if (*readhead == '\\' && *(readhead + 1) == '"') { // skip the slash ++readhead; } *writehead = *readhead; ++writehead; ++readhead; } strcpy(value, copy); free(copy); } /** * Parse a raw criteria string such as [class="foo" instance="bar"] into a * criteria struct. * * If errors are found, NULL will be returned and the error argument will be * populated with an error string. It is up to the caller to free the error. */ struct criteria *criteria_parse(char *raw, char **error_arg) { *error_arg = NULL; error = NULL; char *head = raw; skip_spaces(&head); if (*head != '[') { *error_arg = strdup("No criteria"); return NULL; } ++head; struct criteria *criteria = calloc(1, sizeof(struct criteria)); #if WLR_HAS_XWAYLAND criteria->window_type = ATOM_LAST; // default value #endif char *name = NULL, *value = NULL; bool in_quotes = false; while (*head && *head != ']') { skip_spaces(&head); // Parse token name char *namestart = head; while ((*head >= 'a' && *head <= 'z') || *head == '_') { ++head; } name = calloc(head - namestart + 1, 1); if (head != namestart) { memcpy(name, namestart, head - namestart); } // Parse token value skip_spaces(&head); value = NULL; if (*head == '=') { ++head; skip_spaces(&head); if (*head == '"') { in_quotes = true; ++head; } char *valuestart = head; if (in_quotes) { while (*head && (*head != '"' || *(head - 1) == '\\')) { ++head; } if (!*head) { *error_arg = strdup("Quote mismatch in criteria"); goto cleanup; } } else { while (*head && *head != ' ' && *head != ']') { ++head; } } value = calloc(head - valuestart + 1, 1); memcpy(value, valuestart, head - valuestart); if (in_quotes) { ++head; in_quotes = false; } unescape(value); sway_log(SWAY_DEBUG, "Found pair: %s=%s", name, value); } if (!parse_token(criteria, name, value)) { *error_arg = error; goto cleanup; } skip_spaces(&head); free(name); free(value); name = NULL; value = NULL; } if (*head != ']') { *error_arg = strdup("No closing brace found in criteria"); goto cleanup; } if (criteria_is_empty(criteria)) { *error_arg = strdup("Criteria is empty"); goto cleanup; } ++head; int len = head - raw; criteria->raw = calloc(len + 1, 1); memcpy(criteria->raw, raw, len); return criteria; cleanup: free(name); free(value); criteria_destroy(criteria); return NULL; } bool criteria_is_equal(struct criteria *left, struct criteria *right) { if (left->type != right->type) { return false; } // XXX Only implemented for CT_NO_FOCUS for now. if (left->type == CT_NO_FOCUS) { return strcmp(left->raw, right->raw) == 0; } if (left->type == CT_COMMAND) { return strcmp(left->raw, right->raw) == 0 && strcmp(left->cmdlist, right->cmdlist) == 0; } return false; } bool criteria_already_exists(struct criteria *criteria) { // XXX Only implemented for CT_NO_FOCUS and CT_COMMAND for now. // While criteria_is_equal also obeys this limitation, this is a shortcut // to avoid processing the list. if (criteria->type != CT_NO_FOCUS && criteria->type != CT_COMMAND) { return false; } list_t *criterias = config->criteria; for (int i = 0; i < criterias->length; ++i) { struct criteria *existing = criterias->items[i]; if (criteria_is_equal(criteria, existing)) { return true; } } return false; } ================================================ FILE: sway/decoration.c ================================================ #include #include "sway/decoration.h" #include "sway/desktop/transaction.h" #include "sway/server.h" #include "sway/tree/arrange.h" #include "sway/tree/view.h" #include "log.h" static void server_decoration_handle_destroy(struct wl_listener *listener, void *data) { struct sway_server_decoration *deco = wl_container_of(listener, deco, destroy); wl_list_remove(&deco->destroy.link); wl_list_remove(&deco->mode.link); wl_list_remove(&deco->link); free(deco); } static void server_decoration_handle_mode(struct wl_listener *listener, void *data) { struct sway_server_decoration *deco = wl_container_of(listener, deco, mode); struct sway_view *view = view_from_wlr_surface(deco->wlr_server_decoration->surface); if (view == NULL || view->surface != deco->wlr_server_decoration->surface) { return; } bool csd = deco->wlr_server_decoration->mode == WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT; view_update_csd_from_client(view, csd); arrange_container(view->container); transaction_commit_dirty(); } void handle_server_decoration(struct wl_listener *listener, void *data) { struct wlr_server_decoration *wlr_deco = data; struct sway_server_decoration *deco = calloc(1, sizeof(*deco)); if (deco == NULL) { return; } deco->wlr_server_decoration = wlr_deco; wl_signal_add(&wlr_deco->events.destroy, &deco->destroy); deco->destroy.notify = server_decoration_handle_destroy; wl_signal_add(&wlr_deco->events.mode, &deco->mode); deco->mode.notify = server_decoration_handle_mode; wl_list_insert(&server.decorations, &deco->link); } struct sway_server_decoration *decoration_from_surface( struct wlr_surface *surface) { struct sway_server_decoration *deco; wl_list_for_each(deco, &server.decorations, link) { if (deco->wlr_server_decoration->surface == surface) { return deco; } } return NULL; } ================================================ FILE: sway/desktop/idle_inhibit_v1.c ================================================ #include #include #include #include "log.h" #include "sway/desktop/idle_inhibit_v1.h" #include "sway/input/seat.h" #include "sway/tree/container.h" #include "sway/tree/view.h" #include "sway/server.h" static void destroy_inhibitor(struct sway_idle_inhibitor_v1 *inhibitor) { wl_list_remove(&inhibitor->link); wl_list_remove(&inhibitor->destroy.link); sway_idle_inhibit_v1_check_active(); free(inhibitor); } static void handle_destroy(struct wl_listener *listener, void *data) { struct sway_idle_inhibitor_v1 *inhibitor = wl_container_of(listener, inhibitor, destroy); sway_log(SWAY_DEBUG, "Sway idle inhibitor destroyed"); destroy_inhibitor(inhibitor); } void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data) { struct wlr_idle_inhibitor_v1 *wlr_inhibitor = data; struct sway_idle_inhibit_manager_v1 *manager = wl_container_of(listener, manager, new_idle_inhibitor_v1); sway_log(SWAY_DEBUG, "New sway idle inhibitor"); struct sway_idle_inhibitor_v1 *inhibitor = calloc(1, sizeof(struct sway_idle_inhibitor_v1)); if (!inhibitor) { return; } inhibitor->mode = INHIBIT_IDLE_APPLICATION; inhibitor->wlr_inhibitor = wlr_inhibitor; wl_list_insert(&manager->inhibitors, &inhibitor->link); inhibitor->destroy.notify = handle_destroy; wl_signal_add(&wlr_inhibitor->events.destroy, &inhibitor->destroy); sway_idle_inhibit_v1_check_active(); } void handle_manager_destroy(struct wl_listener *listener, void *data) { struct sway_idle_inhibit_manager_v1 *manager = wl_container_of(listener, manager, manager_destroy); wl_list_remove(&manager->manager_destroy.link); wl_list_remove(&manager->new_idle_inhibitor_v1.link); } void sway_idle_inhibit_v1_user_inhibitor_register(struct sway_view *view, enum sway_idle_inhibit_mode mode) { struct sway_idle_inhibit_manager_v1 *manager = &server.idle_inhibit_manager_v1; struct sway_idle_inhibitor_v1 *inhibitor = calloc(1, sizeof(struct sway_idle_inhibitor_v1)); if (!inhibitor) { return; } inhibitor->mode = mode; inhibitor->view = view; wl_list_insert(&manager->inhibitors, &inhibitor->link); inhibitor->destroy.notify = handle_destroy; wl_signal_add(&view->events.unmap, &inhibitor->destroy); sway_idle_inhibit_v1_check_active(); } struct sway_idle_inhibitor_v1 *sway_idle_inhibit_v1_user_inhibitor_for_view( struct sway_view *view) { struct sway_idle_inhibit_manager_v1 *manager = &server.idle_inhibit_manager_v1; struct sway_idle_inhibitor_v1 *inhibitor; wl_list_for_each(inhibitor, &manager->inhibitors, link) { if (inhibitor->mode != INHIBIT_IDLE_APPLICATION && inhibitor->view == view) { return inhibitor; } } return NULL; } struct sway_idle_inhibitor_v1 *sway_idle_inhibit_v1_application_inhibitor_for_view( struct sway_view *view) { struct sway_idle_inhibit_manager_v1 *manager = &server.idle_inhibit_manager_v1; struct sway_idle_inhibitor_v1 *inhibitor; wl_list_for_each(inhibitor, &manager->inhibitors, link) { if (inhibitor->mode == INHIBIT_IDLE_APPLICATION && view_from_wlr_surface(inhibitor->wlr_inhibitor->surface) == view) { return inhibitor; } } return NULL; } void sway_idle_inhibit_v1_user_inhibitor_destroy( struct sway_idle_inhibitor_v1 *inhibitor) { if (!inhibitor) { return; } if (!sway_assert(inhibitor->mode != INHIBIT_IDLE_APPLICATION, "User should not be able to destroy application inhibitor")) { return; } destroy_inhibitor(inhibitor); } bool sway_idle_inhibit_v1_is_active(struct sway_idle_inhibitor_v1 *inhibitor) { if (server.session_lock.lock) { // A session lock is active. In this case, only application inhibitors // on the session lock surface can have any effect. if (inhibitor->mode != INHIBIT_IDLE_APPLICATION) { return false; } struct wlr_surface *wlr_surface = inhibitor->wlr_inhibitor->surface; if (!wlr_session_lock_surface_v1_try_from_wlr_surface(wlr_surface)) { return false; } return wlr_surface->mapped; } switch (inhibitor->mode) { case INHIBIT_IDLE_APPLICATION:; struct wlr_surface *wlr_surface = inhibitor->wlr_inhibitor->surface; struct wlr_layer_surface_v1 *layer_surface = wlr_layer_surface_v1_try_from_wlr_surface(wlr_surface); if (layer_surface) { // Layer surfaces can be occluded but are always on screen after // they have been mapped. return layer_surface->output && layer_surface->output->enabled && wlr_surface->mapped; } // If there is no view associated with the inhibitor, assume invisible struct sway_view *view = view_from_wlr_surface(wlr_surface); return view && view->container && view_is_visible(view); case INHIBIT_IDLE_FOCUS:; struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { struct sway_container *con = seat_get_focused_container(seat); if (con && con->view && con->view == inhibitor->view) { return true; } } return false; case INHIBIT_IDLE_FULLSCREEN: return inhibitor->view->container && container_is_fullscreen_or_child(inhibitor->view->container) && view_is_visible(inhibitor->view); case INHIBIT_IDLE_OPEN: // Inhibitor is destroyed on unmap so it must be open/mapped return true; case INHIBIT_IDLE_VISIBLE: return view_is_visible(inhibitor->view); } return false; } void sway_idle_inhibit_v1_check_active(void) { struct sway_idle_inhibit_manager_v1 *manager = &server.idle_inhibit_manager_v1; struct sway_idle_inhibitor_v1 *inhibitor; bool inhibited = false; wl_list_for_each(inhibitor, &manager->inhibitors, link) { if ((inhibited = sway_idle_inhibit_v1_is_active(inhibitor))) { break; } } wlr_idle_notifier_v1_set_inhibited(server.idle_notifier_v1, inhibited); } bool sway_idle_inhibit_manager_v1_init(void) { struct sway_idle_inhibit_manager_v1 *manager = &server.idle_inhibit_manager_v1; manager->wlr_manager = wlr_idle_inhibit_v1_create(server.wl_display); if (!manager->wlr_manager) { return false; } wl_signal_add(&manager->wlr_manager->events.new_inhibitor, &manager->new_idle_inhibitor_v1); manager->new_idle_inhibitor_v1.notify = handle_idle_inhibitor_v1; wl_signal_add(&manager->wlr_manager->events.destroy, &manager->manager_destroy); manager->manager_destroy.notify = handle_manager_destroy; wl_list_init(&manager->inhibitors); return true; } ================================================ FILE: sway/desktop/launcher.c ================================================ #include #include #include #include "sway/input/seat.h" #include "sway/output.h" #include "sway/desktop/launcher.h" #include "sway/server.h" #include "sway/tree/node.h" #include "sway/tree/container.h" #include "sway/tree/workspace.h" #include "sway/tree/root.h" #include "log.h" /** * Get the pid of a parent process given the pid of a child process. * * Returns the parent pid or NULL if the parent pid cannot be determined. */ static pid_t get_parent_pid(pid_t child) { pid_t parent = -1; char file_name[100]; char *buffer = NULL; const char *sep = " "; FILE *stat = NULL; size_t buf_size = 0; snprintf(file_name, sizeof(file_name), "/proc/%d/stat", child); if ((stat = fopen(file_name, "r"))) { if (getline(&buffer, &buf_size, stat) != -1) { strtok(buffer, sep); // pid strtok(NULL, sep); // executable name strtok(NULL, sep); // state char *token = strtok(NULL, sep); // parent pid parent = strtol(token, NULL, 10); } free(buffer); fclose(stat); } if (parent) { return (parent == child) ? -1 : parent; } return -1; } void launcher_ctx_consume(struct launcher_ctx *ctx) { // The view is now responsible for destroying this ctx wl_list_remove(&ctx->token_destroy.link); wl_list_init(&ctx->token_destroy.link); if (!ctx->activated) { // An unactivated token hasn't been destroyed yet wlr_xdg_activation_token_v1_destroy(ctx->token); } ctx->token = NULL; // Prevent additional matches wl_list_remove(&ctx->link); wl_list_init(&ctx->link); } void launcher_ctx_destroy(struct launcher_ctx *ctx) { if (ctx == NULL) { return; } wl_list_remove(&ctx->node_destroy.link); wl_list_remove(&ctx->token_destroy.link); if (ctx->seat) { wl_list_remove(&ctx->seat_destroy.link); } wl_list_remove(&ctx->link); wlr_xdg_activation_token_v1_destroy(ctx->token); free(ctx->fallback_name); free(ctx); } struct launcher_ctx *launcher_ctx_find_pid(pid_t pid) { if (wl_list_empty(&server.pending_launcher_ctxs)) { return NULL; } struct launcher_ctx *ctx = NULL; sway_log(SWAY_DEBUG, "Looking up workspace for pid %d", pid); do { struct launcher_ctx *_ctx = NULL; wl_list_for_each(_ctx, &server.pending_launcher_ctxs, link) { if (pid == _ctx->pid) { ctx = _ctx; sway_log(SWAY_DEBUG, "found %s match for pid %d: %s", node_type_to_str(ctx->node->type), pid, node_get_name(ctx->node)); break; } } pid = get_parent_pid(pid); } while (pid > 1); return ctx; } struct sway_workspace *launcher_ctx_get_workspace( struct launcher_ctx *ctx) { struct sway_workspace *ws = NULL; struct sway_output *output = NULL; switch (ctx->node->type) { case N_CONTAINER: // Unimplemented // TODO: add container matching? ws = ctx->node->sway_container->pending.workspace; break; case N_WORKSPACE: ws = ctx->node->sway_workspace; break; case N_OUTPUT: output = ctx->node->sway_output; ws = workspace_by_name(ctx->fallback_name); if (!ws) { sway_log(SWAY_DEBUG, "Creating workspace %s for pid %d because it disappeared", ctx->fallback_name, ctx->pid); if (!output->enabled) { sway_log(SWAY_DEBUG, "Workspace output %s is disabled, trying another one", output->wlr_output->name); output = NULL; } ws = workspace_create(output, ctx->fallback_name); } break; case N_ROOT: ws = workspace_create(NULL, ctx->fallback_name); break; } return ws; } static void ctx_handle_node_destroy(struct wl_listener *listener, void *data) { struct launcher_ctx *ctx = wl_container_of(listener, ctx, node_destroy); switch (ctx->node->type) { case N_CONTAINER: // Unimplemented break; case N_WORKSPACE:; struct sway_workspace *ws = ctx->node->sway_workspace; wl_list_remove(&ctx->node_destroy.link); wl_list_init(&ctx->node_destroy.link); // We want to save this ws name to recreate later, hopefully on the // same output free(ctx->fallback_name); ctx->fallback_name = strdup(ws->name); if (!ws->output || ws->output->node.destroying) { // If the output is being destroyed it would be pointless to track // If the output is being disabled, we'll find out if it's still // disabled when we try to match it. ctx->node = &root->node; break; } ctx->node = &ws->output->node; wl_signal_add(&ctx->node->events.destroy, &ctx->node_destroy); break; case N_OUTPUT: wl_list_remove(&ctx->node_destroy.link); wl_list_init(&ctx->node_destroy.link); // We'll make the ws ctx->name somewhere else ctx->node = &root->node; break; case N_ROOT: // Unreachable break; } } static void token_handle_destroy(struct wl_listener *listener, void *data) { struct launcher_ctx *ctx = wl_container_of(listener, ctx, token_destroy); ctx->token = NULL; launcher_ctx_destroy(ctx); } struct launcher_ctx *launcher_ctx_create(struct wlr_xdg_activation_token_v1 *token, struct sway_node *node) { struct launcher_ctx *ctx = calloc(1, sizeof(struct launcher_ctx)); const char *fallback_name = NULL; struct sway_workspace *ws = NULL; switch (node->type) { case N_CONTAINER: // Unimplemented free(ctx); return NULL; case N_WORKSPACE: ws = node->sway_workspace; fallback_name = ws->name; break; case N_OUTPUT:; struct sway_output *output = node->sway_output; ws = output_get_active_workspace(output); fallback_name = ws ? ws->name : NULL; break; case N_ROOT: // Unimplemented free(ctx); return NULL; } if (!fallback_name) { // TODO: implement a better fallback. free(ctx); return NULL; } ctx->fallback_name = strdup(fallback_name); ctx->token = token; ctx->node = node; // Having surface set means that the focus check in wlroots has passed ctx->had_focused_surface = token->surface != NULL; ctx->node_destroy.notify = ctx_handle_node_destroy; wl_signal_add(&ctx->node->events.destroy, &ctx->node_destroy); ctx->token_destroy.notify = token_handle_destroy; wl_signal_add(&token->events.destroy, &ctx->token_destroy); wl_list_init(&ctx->link); wl_list_insert(&server.pending_launcher_ctxs, &ctx->link); token->data = ctx; return ctx; } static void launch_ctx_handle_seat_destroy(struct wl_listener *listener, void *data) { struct launcher_ctx *ctx = wl_container_of(listener, ctx, seat_destroy); ctx->seat = NULL; wl_list_remove(&ctx->seat_destroy.link); } // Creates a context with a new token for the internal launcher struct launcher_ctx *launcher_ctx_create_internal(void) { struct sway_seat *seat = input_manager_current_seat(); struct sway_workspace *ws = seat_get_focused_workspace(seat); if (!ws) { sway_log(SWAY_DEBUG, "Failed to create launch context. No workspace."); return NULL; } struct wlr_xdg_activation_token_v1 *token = wlr_xdg_activation_token_v1_create(server.xdg_activation_v1); struct launcher_ctx *ctx = launcher_ctx_create(token, &ws->node); if (!ctx) { wlr_xdg_activation_token_v1_destroy(token); return NULL; } ctx->seat = seat; ctx->seat_destroy.notify = launch_ctx_handle_seat_destroy; wl_signal_add(&seat->wlr_seat->events.destroy, &ctx->seat_destroy); return ctx; } const char *launcher_ctx_get_token_name(struct launcher_ctx *ctx) { const char *token = wlr_xdg_activation_token_v1_get_name(ctx->token); return token; } ================================================ FILE: sway/desktop/layer_shell.c ================================================ #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "sway/scene_descriptor.h" #include "sway/desktop/transaction.h" #include "sway/input/cursor.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" #include "sway/layers.h" #include "sway/output.h" #include "sway/server.h" #include "sway/tree/arrange.h" #include "sway/tree/workspace.h" struct wlr_layer_surface_v1 *toplevel_layer_surface_from_surface( struct wlr_surface *surface) { struct wlr_layer_surface_v1 *layer; do { if (!surface) { return NULL; } // Topmost layer surface if ((layer = wlr_layer_surface_v1_try_from_wlr_surface(surface))) { return layer; } // Layer subsurface if (wlr_subsurface_try_from_wlr_surface(surface)) { surface = wlr_surface_get_root_surface(surface); continue; } // Layer surface popup struct wlr_xdg_surface *xdg_surface = NULL; if ((xdg_surface = wlr_xdg_surface_try_from_wlr_surface(surface)) && xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP && xdg_surface->popup != NULL) { if (!xdg_surface->popup->parent) { return NULL; } surface = wlr_surface_get_root_surface(xdg_surface->popup->parent); continue; } // Return early if the surface is not a layer/xdg_popup/sub surface return NULL; } while (true); } static void arrange_surface(struct sway_output *output, const struct wlr_box *full_area, struct wlr_box *usable_area, struct wlr_scene_tree *tree, bool exclusive) { struct wlr_scene_node *node; wl_list_for_each(node, &tree->children, link) { struct sway_layer_surface *surface = scene_descriptor_try_get(node, SWAY_SCENE_DESC_LAYER_SHELL); // surface could be null during destruction if (!surface) { continue; } if (!surface->scene->layer_surface->initialized) { continue; } if ((surface->scene->layer_surface->current.exclusive_zone > 0) != exclusive) { continue; } wlr_scene_layer_surface_v1_configure(surface->scene, full_area, usable_area); } } void arrange_layers(struct sway_output *output) { struct wlr_box usable_area = { 0 }; wlr_output_effective_resolution(output->wlr_output, &usable_area.width, &usable_area.height); const struct wlr_box full_area = usable_area; arrange_surface(output, &full_area, &usable_area, output->layers.shell_overlay, true); arrange_surface(output, &full_area, &usable_area, output->layers.shell_top, true); arrange_surface(output, &full_area, &usable_area, output->layers.shell_bottom, true); arrange_surface(output, &full_area, &usable_area, output->layers.shell_background, true); arrange_surface(output, &full_area, &usable_area, output->layers.shell_overlay, false); arrange_surface(output, &full_area, &usable_area, output->layers.shell_top, false); arrange_surface(output, &full_area, &usable_area, output->layers.shell_bottom, false); arrange_surface(output, &full_area, &usable_area, output->layers.shell_background, false); if (!wlr_box_equal(&usable_area, &output->usable_area)) { sway_log(SWAY_DEBUG, "Usable area changed, rearranging output"); output->usable_area = usable_area; arrange_output(output); } else { arrange_popups(root->layers.popup); } // Find topmost keyboard interactive layer, if such a layer exists struct wlr_scene_tree *layers_above_shell[] = { output->layers.shell_overlay, output->layers.shell_top, }; size_t nlayers = sizeof(layers_above_shell) / sizeof(layers_above_shell[0]); struct wlr_scene_node *node; struct sway_layer_surface *topmost = NULL; for (size_t i = 0; i < nlayers; ++i) { wl_list_for_each_reverse(node, &layers_above_shell[i]->children, link) { struct sway_layer_surface *surface = scene_descriptor_try_get(node, SWAY_SCENE_DESC_LAYER_SHELL); if (surface && surface->layer_surface->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE && surface->layer_surface->surface->mapped) { topmost = surface; break; } } if (topmost != NULL) { break; } } struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { seat->has_exclusive_layer = false; if (topmost != NULL) { seat_set_focus_layer(seat, topmost->layer_surface); } else if (seat->focused_layer && seat->focused_layer->current.keyboard_interactive != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) { seat_set_focus_layer(seat, NULL); } } } static struct wlr_scene_tree *sway_layer_get_scene(struct sway_output *output, enum zwlr_layer_shell_v1_layer type) { switch (type) { case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND: return output->layers.shell_background; case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: return output->layers.shell_bottom; case ZWLR_LAYER_SHELL_V1_LAYER_TOP: return output->layers.shell_top; case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: return output->layers.shell_overlay; } sway_assert(false, "unreachable"); return NULL; } static struct sway_layer_surface *sway_layer_surface_create( struct wlr_scene_layer_surface_v1 *scene) { struct sway_layer_surface *surface = calloc(1, sizeof(*surface)); if (!surface) { sway_log(SWAY_ERROR, "Could not allocate a scene_layer surface"); return NULL; } struct wlr_scene_tree *popups = wlr_scene_tree_create(root->layers.popup); if (!popups) { sway_log(SWAY_ERROR, "Could not allocate a scene_layer popup node"); free(surface); return NULL; } surface->desc.relative = &scene->tree->node; if (!scene_descriptor_assign(&popups->node, SWAY_SCENE_DESC_POPUP, &surface->desc)) { sway_log(SWAY_ERROR, "Failed to allocate a popup scene descriptor"); wlr_scene_node_destroy(&popups->node); free(surface); return NULL; } surface->tree = scene->tree; surface->scene = scene; surface->layer_surface = scene->layer_surface; surface->popups = popups; surface->layer_surface->data = surface; return surface; } static struct sway_layer_surface *find_mapped_layer_by_client( struct wl_client *client, struct sway_output *ignore_output) { for (int i = 0; i < root->outputs->length; ++i) { struct sway_output *output = root->outputs->items[i]; if (output == ignore_output) { continue; } // For now we'll only check the overlay layer struct wlr_scene_node *node; wl_list_for_each (node, &output->layers.shell_overlay->children, link) { struct sway_layer_surface *surface = scene_descriptor_try_get(node, SWAY_SCENE_DESC_LAYER_SHELL); if (!surface) { continue; } struct wlr_layer_surface_v1 *layer_surface = surface->layer_surface; struct wl_resource *resource = layer_surface->resource; if (wl_resource_get_client(resource) == client && layer_surface->surface->mapped) { return surface; } } } return NULL; } static void handle_node_destroy(struct wl_listener *listener, void *data) { struct sway_layer_surface *layer = wl_container_of(listener, layer, node_destroy); // destroy the scene descriptor straight away if it exists, otherwise // we will try to reflow still considering the destroyed node. scene_descriptor_destroy(&layer->tree->node, SWAY_SCENE_DESC_LAYER_SHELL); // Determine if this layer is being used by an exclusive client. If it is, // try and find another layer owned by this client to pass focus to. struct sway_seat *seat = input_manager_get_default_seat(); struct wl_client *client = wl_resource_get_client(layer->layer_surface->resource); if (!server.session_lock.lock) { struct sway_layer_surface *consider_layer = find_mapped_layer_by_client(client, layer->output); if (consider_layer) { seat_set_focus_layer(seat, consider_layer->layer_surface); } } if (layer->output) { arrange_layers(layer->output); transaction_commit_dirty(); } wlr_scene_node_destroy(&layer->popups->node); wl_list_remove(&layer->map.link); wl_list_remove(&layer->unmap.link); wl_list_remove(&layer->surface_commit.link); wl_list_remove(&layer->node_destroy.link); wl_list_remove(&layer->new_popup.link); layer->layer_surface->data = NULL; wl_list_remove(&layer->link); free(layer); } static void handle_surface_commit(struct wl_listener *listener, void *data) { struct sway_layer_surface *surface = wl_container_of(listener, surface, surface_commit); struct wlr_layer_surface_v1 *layer_surface = surface->layer_surface; uint32_t committed = layer_surface->current.committed; if (layer_surface->initialized && committed & WLR_LAYER_SURFACE_V1_STATE_LAYER) { enum zwlr_layer_shell_v1_layer layer_type = layer_surface->current.layer; struct wlr_scene_tree *output_layer = sway_layer_get_scene( surface->output, layer_type); wlr_scene_node_reparent(&surface->scene->tree->node, output_layer); } if (layer_surface->initial_commit || committed || layer_surface->surface->mapped != surface->mapped) { surface->mapped = layer_surface->surface->mapped; arrange_layers(surface->output); transaction_commit_dirty(); } } static void handle_map(struct wl_listener *listener, void *data) { struct sway_layer_surface *surface = wl_container_of(listener, surface, map); struct wlr_layer_surface_v1 *layer_surface = surface->scene->layer_surface; // focus on new surface if (layer_surface->current.keyboard_interactive && (layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY || layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP)) { struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { // but only if the currently focused layer has a lower precedence if (!seat->focused_layer || seat->focused_layer->current.layer >= layer_surface->current.layer) { seat_set_focus_layer(seat, layer_surface); } } arrange_layers(surface->output); } cursor_rebase_all(); } static void handle_unmap(struct wl_listener *listener, void *data) { struct sway_layer_surface *surface = wl_container_of( listener, surface, unmap); struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { if (seat->focused_layer == surface->layer_surface) { seat_set_focus_layer(seat, NULL); } } cursor_rebase_all(); } static void popup_handle_destroy(struct wl_listener *listener, void *data) { struct sway_layer_popup *popup = wl_container_of(listener, popup, destroy); wl_list_remove(&popup->destroy.link); wl_list_remove(&popup->new_popup.link); wl_list_remove(&popup->commit.link); wl_list_remove(&popup->reposition.link); free(popup); } static void popup_unconstrain(struct sway_layer_popup *popup) { struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; struct sway_output *output = popup->toplevel->output; // if a client tries to create a popup while we are in the process of destroying // its output, don't crash. if (!output) { return; } int lx, ly; wlr_scene_node_coords(&popup->toplevel->scene->tree->node, &lx, &ly); // the output box expressed in the coordinate system of the toplevel parent // of the popup struct wlr_box output_toplevel_sx_box = { .x = output->lx - lx, .y = output->ly - ly, .width = output->width, .height = output->height, }; wlr_xdg_popup_unconstrain_from_box(wlr_popup, &output_toplevel_sx_box); } static void popup_handle_commit(struct wl_listener *listener, void *data) { struct sway_layer_popup *popup = wl_container_of(listener, popup, commit); if (popup->wlr_popup->base->initial_commit) { popup_unconstrain(popup); } } static void popup_handle_reposition(struct wl_listener *listener, void *data) { struct sway_layer_popup *popup = wl_container_of(listener, popup, reposition); popup_unconstrain(popup); } static void popup_handle_new_popup(struct wl_listener *listener, void *data); static struct sway_layer_popup *create_popup(struct wlr_xdg_popup *wlr_popup, struct sway_layer_surface *toplevel, struct wlr_scene_tree *parent) { struct sway_layer_popup *popup = calloc(1, sizeof(*popup)); if (popup == NULL) { return NULL; } popup->toplevel = toplevel; popup->wlr_popup = wlr_popup; popup->scene = wlr_scene_xdg_surface_create(parent, wlr_popup->base); if (!popup->scene) { free(popup); return NULL; } popup->destroy.notify = popup_handle_destroy; wl_signal_add(&wlr_popup->events.destroy, &popup->destroy); popup->new_popup.notify = popup_handle_new_popup; wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); popup->commit.notify = popup_handle_commit; wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); popup->reposition.notify = popup_handle_reposition; wl_signal_add(&wlr_popup->events.reposition, &popup->reposition); return popup; } static void popup_handle_new_popup(struct wl_listener *listener, void *data) { struct sway_layer_popup *sway_layer_popup = wl_container_of(listener, sway_layer_popup, new_popup); struct wlr_xdg_popup *wlr_popup = data; create_popup(wlr_popup, sway_layer_popup->toplevel, sway_layer_popup->scene); } static void handle_new_popup(struct wl_listener *listener, void *data) { struct sway_layer_surface *sway_layer_surface = wl_container_of(listener, sway_layer_surface, new_popup); struct wlr_xdg_popup *wlr_popup = data; create_popup(wlr_popup, sway_layer_surface, sway_layer_surface->popups); } void handle_layer_shell_surface(struct wl_listener *listener, void *data) { struct wlr_layer_surface_v1 *layer_surface = data; sway_log(SWAY_DEBUG, "new layer surface: namespace %s layer %d anchor %" PRIu32 " size %" PRIu32 "x%" PRIu32 " margin %" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",", layer_surface->namespace, layer_surface->pending.layer, layer_surface->pending.anchor, layer_surface->pending.desired_width, layer_surface->pending.desired_height, layer_surface->pending.margin.top, layer_surface->pending.margin.right, layer_surface->pending.margin.bottom, layer_surface->pending.margin.left); if (!layer_surface->output) { // Assign last active output struct sway_output *output = NULL; struct sway_seat *seat = input_manager_get_default_seat(); if (seat) { struct sway_workspace *ws = seat_get_focused_workspace(seat); if (ws != NULL) { output = ws->output; } } if (!output || output == root->fallback_output) { if (!root->outputs->length) { sway_log(SWAY_ERROR, "no output to auto-assign layer surface '%s' to", layer_surface->namespace); wlr_layer_surface_v1_destroy(layer_surface); return; } output = root->outputs->items[0]; } layer_surface->output = output->wlr_output; } struct sway_output *output = layer_surface->output->data; enum zwlr_layer_shell_v1_layer layer_type = layer_surface->pending.layer; struct wlr_scene_tree *output_layer = sway_layer_get_scene( output, layer_type); struct wlr_scene_layer_surface_v1 *scene_surface = wlr_scene_layer_surface_v1_create(output_layer, layer_surface); if (!scene_surface) { sway_log(SWAY_ERROR, "Could not allocate a layer_surface_v1"); return; } struct sway_layer_surface *surface = sway_layer_surface_create(scene_surface); if (!surface) { wlr_layer_surface_v1_destroy(layer_surface); sway_log(SWAY_ERROR, "Could not allocate a sway_layer_surface"); return; } if (!scene_descriptor_assign(&scene_surface->tree->node, SWAY_SCENE_DESC_LAYER_SHELL, surface)) { sway_log(SWAY_ERROR, "Failed to allocate a layer surface descriptor"); // destroying the layer_surface will also destroy its corresponding // scene node wlr_layer_surface_v1_destroy(layer_surface); return; } surface->output = output; wl_list_insert(&output->layer_surfaces, &surface->link); // now that the surface's output is known, we can advertise its scale wlr_fractional_scale_v1_notify_scale(surface->layer_surface->surface, layer_surface->output->scale); wlr_surface_set_preferred_buffer_scale(surface->layer_surface->surface, ceil(layer_surface->output->scale)); surface->surface_commit.notify = handle_surface_commit; wl_signal_add(&layer_surface->surface->events.commit, &surface->surface_commit); surface->map.notify = handle_map; wl_signal_add(&layer_surface->surface->events.map, &surface->map); surface->unmap.notify = handle_unmap; wl_signal_add(&layer_surface->surface->events.unmap, &surface->unmap); surface->new_popup.notify = handle_new_popup; wl_signal_add(&layer_surface->events.new_popup, &surface->new_popup); surface->node_destroy.notify = handle_node_destroy; wl_signal_add(&scene_surface->tree->node.events.destroy, &surface->node_destroy); } void destroy_layers(struct sway_output *output) { struct sway_layer_surface *layer, *layer_tmp; wl_list_for_each_safe(layer, layer_tmp, &output->layer_surfaces, link) { layer->output = NULL; wlr_layer_surface_v1_destroy(layer->layer_surface); } } ================================================ FILE: sway/desktop/output.c ================================================ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "log.h" #include "sway/config.h" #include "sway/desktop/transaction.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" #include "sway/ipc-server.h" #include "sway/layers.h" #include "sway/output.h" #include "sway/scene_descriptor.h" #include "sway/server.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/root.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #if WLR_HAS_DRM_BACKEND #include #include #endif bool output_match_name_or_id(struct sway_output *output, const char *name_or_id) { if (strcmp(name_or_id, "*") == 0) { return true; } char identifier[128]; output_get_identifier(identifier, sizeof(identifier), output); return strcasecmp(identifier, name_or_id) == 0 || strcasecmp(output->wlr_output->name, name_or_id) == 0; } struct sway_output *output_by_name_or_id(const char *name_or_id) { for (int i = 0; i < root->outputs->length; ++i) { struct sway_output *output = root->outputs->items[i]; if (output_match_name_or_id(output, name_or_id)) { return output; } } return NULL; } struct sway_output *all_output_by_name_or_id(const char *name_or_id) { struct sway_output *output; wl_list_for_each(output, &root->all_outputs, link) { if (output_match_name_or_id(output, name_or_id)) { return output; } } return NULL; } struct sway_workspace *output_get_active_workspace(struct sway_output *output) { struct sway_seat *seat = input_manager_current_seat(); struct sway_node *focus = seat_get_active_tiling_child(seat, &output->node); if (!focus) { if (!output->workspaces->length) { return NULL; } return output->workspaces->items[0]; } return focus->sway_workspace; } struct send_frame_done_data { struct timespec when; int msec_until_refresh; struct sway_output *output; }; struct buffer_timer { struct wl_listener destroy; struct wl_event_source *frame_done_timer; }; static int handle_buffer_timer(void *data) { struct wlr_scene_surface *scene_surface = data; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); wlr_scene_surface_send_frame_done(scene_surface, &now); return 0; } static void handle_buffer_timer_destroy(struct wl_listener *listener, void *data) { struct buffer_timer *timer = wl_container_of(listener, timer, destroy); wl_list_remove(&timer->destroy.link); wl_event_source_remove(timer->frame_done_timer); free(timer); } static struct buffer_timer *buffer_timer_get_or_create(struct wlr_scene_surface *scene_surface) { struct wlr_scene_buffer *buffer = scene_surface->buffer; struct buffer_timer *timer = scene_descriptor_try_get(&buffer->node, SWAY_SCENE_DESC_BUFFER_TIMER); if (timer) { return timer; } timer = calloc(1, sizeof(struct buffer_timer)); if (!timer) { return NULL; } timer->frame_done_timer = wl_event_loop_add_timer(server.wl_event_loop, handle_buffer_timer, scene_surface); if (!timer->frame_done_timer) { free(timer); return NULL; } scene_descriptor_assign(&buffer->node, SWAY_SCENE_DESC_BUFFER_TIMER, timer); timer->destroy.notify = handle_buffer_timer_destroy; wl_signal_add(&buffer->node.events.destroy, &timer->destroy); return timer; } static void send_frame_done_iterator(struct wlr_scene_buffer *buffer, int x, int y, void *user_data) { struct send_frame_done_data *data = user_data; struct sway_output *output = data->output; int view_max_render_time = 0; if (buffer->primary_output != data->output->scene_output) { return; } struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(buffer); if (scene_surface == NULL) { return; } struct wlr_scene_node *current = &buffer->node; while (true) { struct sway_view *view = scene_descriptor_try_get(current, SWAY_SCENE_DESC_VIEW); if (view) { view_max_render_time = view->max_render_time; break; } if (!current->parent) { break; } current = ¤t->parent->node; } int delay = data->msec_until_refresh - output->max_render_time - view_max_render_time; struct buffer_timer *timer = NULL; if (output->max_render_time != 0 && view_max_render_time != 0 && delay > 0) { timer = buffer_timer_get_or_create(scene_surface); } if (timer) { wl_event_source_timer_update(timer->frame_done_timer, delay); } else { wlr_scene_surface_send_frame_done(scene_surface, &data->when); } } static enum wlr_scale_filter_mode get_scale_filter(struct sway_output *output, struct wlr_scene_buffer *buffer) { // if we are scaling down, we should always choose linear if (buffer->dst_width > 0 && buffer->dst_height > 0 && ( buffer->dst_width < buffer->WLR_PRIVATE.buffer_width || buffer->dst_height < buffer->WLR_PRIVATE.buffer_height)) { return WLR_SCALE_FILTER_BILINEAR; } switch (output->scale_filter) { case SCALE_FILTER_LINEAR: return WLR_SCALE_FILTER_BILINEAR; case SCALE_FILTER_NEAREST: return WLR_SCALE_FILTER_NEAREST; default: abort(); // unreachable } } void output_configure_scene(struct sway_output *output, struct wlr_scene_node *node, float opacity) { if (!node->enabled) { return; } struct sway_container *con = scene_descriptor_try_get(node, SWAY_SCENE_DESC_CONTAINER); if (con) { opacity = con->alpha; } if (node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); struct wlr_scene_surface *surface = wlr_scene_surface_try_from_buffer(buffer); if (surface) { const struct wlr_alpha_modifier_surface_v1_state *alpha_modifier_state = wlr_alpha_modifier_v1_get_surface_state(surface->surface); if (alpha_modifier_state != NULL) { opacity *= (float)alpha_modifier_state->multiplier; } } // hack: don't call the scene setter because that will damage all outputs // We don't want to damage outputs that aren't our current output that // we're configuring if (output) { buffer->filter_mode = get_scale_filter(output, buffer); } wlr_scene_buffer_set_opacity(buffer, opacity); } else if (node->type == WLR_SCENE_NODE_TREE) { struct wlr_scene_tree *tree = wlr_scene_tree_from_node(node); struct wlr_scene_node *node; wl_list_for_each(node, &tree->children, link) { output_configure_scene(output, node, opacity); } } } static bool output_can_tear(struct sway_output *output) { struct sway_workspace *workspace = output->current.active_workspace; if (!workspace) { return false; } struct sway_container *fullscreen_con = root->fullscreen_global; if (!fullscreen_con) { fullscreen_con = workspace->current.fullscreen; } if (fullscreen_con && fullscreen_con->view) { return (output->allow_tearing && view_can_tear(fullscreen_con->view)); } return false; } static int output_repaint_timer_handler(void *data) { struct sway_output *output = data; output->wlr_output->frame_pending = false; if (!output->wlr_output->enabled) { return 0; } output_configure_scene(output, &root->root_scene->tree.node, 1.0f); struct wlr_scene_output_state_options opts = { .color_transform = output->color_transform, }; struct wlr_scene_output *scene_output = output->scene_output; if (!wlr_scene_output_needs_frame(scene_output)) { return 0; } struct wlr_output_state pending; wlr_output_state_init(&pending); if (!wlr_scene_output_build_state(output->scene_output, &pending, &opts)) { wlr_output_state_finish(&pending); return 0; } if (output_can_tear(output)) { pending.tearing_page_flip = true; if (!wlr_output_test_state(output->wlr_output, &pending)) { sway_log(SWAY_DEBUG, "Output test failed on '%s', retrying without tearing page-flip", output->wlr_output->name); pending.tearing_page_flip = false; } } if (!wlr_output_commit_state(output->wlr_output, &pending)) { sway_log(SWAY_ERROR, "Page-flip failed on output %s", output->wlr_output->name); } wlr_output_state_finish(&pending); return 0; } static void handle_frame(struct wl_listener *listener, void *user_data) { struct sway_output *output = wl_container_of(listener, output, frame); if (!output->enabled || !output->wlr_output->enabled) { return; } // Compute predicted milliseconds until the next refresh. It's used for // delaying both output rendering and surface frame callbacks. int msec_until_refresh = 0; if (output->max_render_time != 0) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); const long NSEC_IN_SECONDS = 1000000000; struct timespec predicted_refresh = output->last_presentation; predicted_refresh.tv_nsec += output->refresh_nsec % NSEC_IN_SECONDS; predicted_refresh.tv_sec += output->refresh_nsec / NSEC_IN_SECONDS; if (predicted_refresh.tv_nsec >= NSEC_IN_SECONDS) { predicted_refresh.tv_sec += 1; predicted_refresh.tv_nsec -= NSEC_IN_SECONDS; } // If the predicted refresh time is before the current time then // there's no point in delaying. // // We only check tv_sec because if the predicted refresh time is less // than a second before the current time, then msec_until_refresh will // end up slightly below zero, which will effectively disable the delay // without potential disastrous negative overflows that could occur if // tv_sec was not checked. if (predicted_refresh.tv_sec >= now.tv_sec) { long nsec_until_refresh = (predicted_refresh.tv_sec - now.tv_sec) * NSEC_IN_SECONDS + (predicted_refresh.tv_nsec - now.tv_nsec); // We want msec_until_refresh to be conservative, that is, floored. // If we have 7.9 msec until refresh, we better compute the delay // as if we had only 7 msec, so that we don't accidentally delay // more than necessary and miss a frame. msec_until_refresh = nsec_until_refresh / 1000000; } } int delay = msec_until_refresh - output->max_render_time; // If the delay is less than 1 millisecond (which is the least we can wait) // then just render right away. if (delay < 1) { output_repaint_timer_handler(output); } else { output->wlr_output->frame_pending = true; wl_event_source_timer_update(output->repaint_timer, delay); } // Send frame done to all visible surfaces struct send_frame_done_data data = {0}; clock_gettime(CLOCK_MONOTONIC, &data.when); data.msec_until_refresh = msec_until_refresh; data.output = output; wlr_scene_output_for_each_buffer(output->scene_output, send_frame_done_iterator, &data); } void update_output_manager_config(struct sway_server *server) { struct wlr_output_configuration_v1 *config = wlr_output_configuration_v1_create(); struct sway_output *output; wl_list_for_each(output, &root->all_outputs, link) { if (output == root->fallback_output) { continue; } struct wlr_output_configuration_head_v1 *config_head = wlr_output_configuration_head_v1_create(config, output->wlr_output); struct wlr_box output_box; wlr_output_layout_get_box(root->output_layout, output->wlr_output, &output_box); // We mark the output enabled when it's switched off but not disabled config_head->state.enabled = !wlr_box_empty(&output_box); config_head->state.x = output_box.x; config_head->state.y = output_box.y; } wlr_output_manager_v1_set_configuration(server->output_manager_v1, config); ipc_event_output(); } static int timer_modeset_handle(void *data) { struct sway_server *server = data; wl_event_source_remove(server->delayed_modeset); server->delayed_modeset = NULL; apply_stored_output_configs(); return 0; } void request_modeset(void) { if (server.delayed_modeset == NULL) { server.delayed_modeset = wl_event_loop_add_timer(server.wl_event_loop, timer_modeset_handle, &server); wl_event_source_timer_update(server.delayed_modeset, 10); } } bool modeset_is_pending(void) { return server.delayed_modeset != NULL; } void force_modeset(void) { if (server.delayed_modeset != NULL) { wl_event_source_remove(server.delayed_modeset); server.delayed_modeset = NULL; } apply_stored_output_configs(); } static void begin_destroy(struct sway_output *output) { wl_list_remove(&output->layout_destroy.link); wl_list_remove(&output->destroy.link); wl_list_remove(&output->present.link); wl_list_remove(&output->frame.link); wl_list_remove(&output->request_state.link); // Remove the scene_output first to ensure that the scene does not emit // events for this output. wlr_scene_output_destroy(output->scene_output); output->scene_output = NULL; if (output->enabled) { output_disable(output); } output_begin_destroy(output); wl_list_remove(&output->link); output->wlr_output->data = NULL; output->wlr_output = NULL; wl_event_source_remove(output->repaint_timer); output->repaint_timer = NULL; request_modeset(); } static void handle_destroy(struct wl_listener *listener, void *data) { struct sway_output *output = wl_container_of(listener, output, destroy); begin_destroy(output); } static void handle_layout_destroy(struct wl_listener *listener, void *data) { struct sway_output *output = wl_container_of(listener, output, layout_destroy); begin_destroy(output); } static void handle_present(struct wl_listener *listener, void *data) { struct sway_output *output = wl_container_of(listener, output, present); struct wlr_output_event_present *output_event = data; if (!output->enabled || !output_event->presented) { return; } output->last_presentation = output_event->when; output->refresh_nsec = output_event->refresh; } static void handle_request_state(struct wl_listener *listener, void *data) { struct sway_output *output = wl_container_of(listener, output, request_state); const struct wlr_output_event_request_state *event = data; const struct wlr_output_state *state = event->state; // Store the requested changes so that the active configuration is // consistent with the current state, and to avoid duplicate logic to apply // the changes. struct output_config *oc = new_output_config(output->wlr_output->name); if (!oc) { sway_log(SWAY_ERROR, "Allocation failed"); return; } int committed = state->committed; if (committed & WLR_OUTPUT_STATE_MODE) { if (state->mode != NULL) { oc->width = state->mode->width; oc->height = state->mode->height; oc->refresh_rate = state->mode->refresh / 1000.f; } else { oc->width = state->custom_mode.width; oc->height = state->custom_mode.height; oc->refresh_rate = state->custom_mode.refresh / 1000.f; } committed &= ~WLR_OUTPUT_STATE_MODE; } if (committed & WLR_OUTPUT_STATE_SCALE) { oc->scale = state->scale; committed &= ~WLR_OUTPUT_STATE_SCALE; } if (committed & WLR_OUTPUT_STATE_TRANSFORM) { oc->transform = state->transform; committed &= ~WLR_OUTPUT_STATE_TRANSFORM; } // We do not expect or support any other changes here assert(committed == 0); store_output_config(oc); force_modeset(); } static unsigned int last_headless_num = 0; void handle_new_output(struct wl_listener *listener, void *data) { struct sway_server *server = wl_container_of(listener, server, new_output); struct wlr_output *wlr_output = data; if (wlr_output == root->fallback_output->wlr_output) { return; } if (wlr_output_is_headless(wlr_output)) { char name[64]; snprintf(name, sizeof(name), "HEADLESS-%u", ++last_headless_num); wlr_output_set_name(wlr_output, name); } sway_log(SWAY_DEBUG, "New output %p: %s (non-desktop: %d)", wlr_output, wlr_output->name, wlr_output->non_desktop); if (wlr_output->non_desktop) { sway_log(SWAY_DEBUG, "Not configuring non-desktop output"); struct sway_output_non_desktop *non_desktop = output_non_desktop_create(wlr_output); #if WLR_HAS_DRM_BACKEND if (server->drm_lease_manager) { wlr_drm_lease_v1_manager_offer_output(server->drm_lease_manager, wlr_output); } #endif list_add(root->non_desktop_outputs, non_desktop); return; } if (!wlr_output_init_render(wlr_output, server->allocator, server->renderer)) { sway_log(SWAY_ERROR, "Failed to init output render"); return; } // Create the scene output here so we're not accidentally creating one for // the fallback output struct wlr_scene_output *scene_output = wlr_scene_output_create(root->root_scene, wlr_output); if (!scene_output) { sway_log(SWAY_ERROR, "Failed to create a scene output"); return; } struct sway_output *output = output_create(wlr_output); if (!output) { sway_log(SWAY_ERROR, "Failed to create a sway output"); wlr_scene_output_destroy(scene_output); return; } output->server = server; output->scene_output = scene_output; wl_signal_add(&root->output_layout->events.destroy, &output->layout_destroy); output->layout_destroy.notify = handle_layout_destroy; wl_signal_add(&wlr_output->events.destroy, &output->destroy); output->destroy.notify = handle_destroy; wl_signal_add(&wlr_output->events.present, &output->present); output->present.notify = handle_present; wl_signal_add(&wlr_output->events.frame, &output->frame); output->frame.notify = handle_frame; wl_signal_add(&wlr_output->events.request_state, &output->request_state); output->request_state.notify = handle_request_state; output->repaint_timer = wl_event_loop_add_timer(server->wl_event_loop, output_repaint_timer_handler, output); if (server->session_lock.lock) { sway_session_lock_add_output(server->session_lock.lock, output); } request_modeset(); } static struct output_config *output_config_for_config_head( struct wlr_output_configuration_head_v1 *config_head) { struct output_config *oc = new_output_config(config_head->state.output->name); if (!oc) { return NULL; } oc->enabled = config_head->state.enabled; if (!oc->enabled) { return oc; } if (config_head->state.mode != NULL) { struct wlr_output_mode *mode = config_head->state.mode; oc->width = mode->width; oc->height = mode->height; oc->refresh_rate = mode->refresh / 1000.f; } else { oc->width = config_head->state.custom_mode.width; oc->height = config_head->state.custom_mode.height; oc->refresh_rate = config_head->state.custom_mode.refresh / 1000.f; } oc->x = config_head->state.x; oc->y = config_head->state.y; oc->transform = config_head->state.transform; oc->scale = config_head->state.scale; oc->adaptive_sync = config_head->state.adaptive_sync_enabled; return oc; } static void output_manager_apply(struct sway_server *server, struct wlr_output_configuration_v1 *cfg, bool test_only) { bool ok = false; size_t configs_len = config->output_configs->length + wl_list_length(&cfg->heads); struct output_config **configs = calloc(configs_len, sizeof(*configs)); if (!configs) { sway_log(SWAY_ERROR, "Allocation failed"); goto error; } size_t start_new_configs = config->output_configs->length; for (size_t idx = 0; idx < start_new_configs; idx++) { configs[idx] = config->output_configs->items[idx]; } size_t config_idx = start_new_configs; struct wlr_output_configuration_head_v1 *config_head; wl_list_for_each(config_head, &cfg->heads, link) { // Generate the configuration and store it as a temporary // config. We keep a record of it so we can remove it later. struct output_config *oc = output_config_for_config_head(config_head); if (!oc) { sway_log(SWAY_ERROR, "Allocation failed"); goto error_config; } configs[config_idx++] = oc; } // Try to commit without degrade to off enabled. Note that this will fail // if any output configured for enablement fails to be enabled, even if it // was not part of the config heads we were asked to configure. ok = apply_output_configs(configs, configs_len, test_only, false); error_config: for (size_t idx = start_new_configs; idx < configs_len; idx++) { struct output_config *cfg = configs[idx]; if (!test_only && ok) { store_output_config(cfg); } else { free_output_config(cfg); } } free(configs); error: if (ok) { wlr_output_configuration_v1_send_succeeded(cfg); if (server->delayed_modeset != NULL) { wl_event_source_remove(server->delayed_modeset); server->delayed_modeset = NULL; } } else { wlr_output_configuration_v1_send_failed(cfg); } wlr_output_configuration_v1_destroy(cfg); } void handle_output_manager_apply(struct wl_listener *listener, void *data) { struct sway_server *server = wl_container_of(listener, server, output_manager_apply); struct wlr_output_configuration_v1 *config = data; output_manager_apply(server, config, false); } void handle_output_manager_test(struct wl_listener *listener, void *data) { struct sway_server *server = wl_container_of(listener, server, output_manager_test); struct wlr_output_configuration_v1 *config = data; output_manager_apply(server, config, true); } void handle_output_power_manager_set_mode(struct wl_listener *listener, void *data) { struct wlr_output_power_v1_set_mode_event *event = data; struct sway_output *output = event->output->data; struct output_config *oc = new_output_config(output->wlr_output->name); if (!oc) { sway_log(SWAY_ERROR, "Allocation failed"); return; } switch (event->mode) { case ZWLR_OUTPUT_POWER_V1_MODE_OFF: oc->power = 0; break; case ZWLR_OUTPUT_POWER_V1_MODE_ON: oc->power = 1; break; } store_output_config(oc); request_modeset(); } ================================================ FILE: sway/desktop/tearing.c ================================================ #include #include #include "sway/server.h" #include "sway/tree/view.h" #include "sway/output.h" #include "log.h" struct sway_tearing_controller { struct wlr_tearing_control_v1 *tearing_control; struct wl_listener set_hint; struct wl_listener destroy; struct wl_list link; // sway_server::tearing_controllers }; static void handle_tearing_controller_set_hint(struct wl_listener *listener, void *data) { struct sway_tearing_controller *controller = wl_container_of(listener, controller, set_hint); struct sway_view *view = view_from_wlr_surface( controller->tearing_control->surface); if (view) { view->tearing_hint = controller->tearing_control->current; } } static void handle_tearing_controller_destroy(struct wl_listener *listener, void *data) { struct sway_tearing_controller *controller = wl_container_of(listener, controller, destroy); wl_list_remove(&controller->set_hint.link); wl_list_remove(&controller->destroy.link); wl_list_remove(&controller->link); free(controller); } void handle_new_tearing_hint(struct wl_listener *listener, void *data) { struct sway_server *server = wl_container_of(listener, server, tearing_control_new_object); struct wlr_tearing_control_v1 *tearing_control = data; enum wp_tearing_control_v1_presentation_hint hint = wlr_tearing_control_manager_v1_surface_hint_from_surface( server->tearing_control_v1, tearing_control->surface); sway_log(SWAY_DEBUG, "New presentation hint %d received for surface %p", hint, tearing_control->surface); struct sway_tearing_controller *controller = calloc(1, sizeof(struct sway_tearing_controller)); if (!controller) { return; } controller->tearing_control = tearing_control; controller->set_hint.notify = handle_tearing_controller_set_hint; wl_signal_add(&tearing_control->events.set_hint, &controller->set_hint); controller->destroy.notify = handle_tearing_controller_destroy; wl_signal_add(&tearing_control->events.destroy, &controller->destroy); wl_list_init(&controller->link); wl_list_insert(&server->tearing_controllers, &controller->link); } ================================================ FILE: sway/desktop/transaction.c ================================================ #include #include #include #include #include #include "sway/config.h" #include "sway/scene_descriptor.h" #include "sway/desktop/idle_inhibit_v1.h" #include "sway/desktop/transaction.h" #include "sway/input/cursor.h" #include "sway/input/input-manager.h" #include "sway/output.h" #include "sway/server.h" #include "sway/tree/container.h" #include "sway/tree/node.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "list.h" #include "log.h" struct sway_transaction { struct wl_event_source *timer; list_t *instructions; // struct sway_transaction_instruction * size_t num_waiting; size_t num_configures; struct timespec commit_time; }; struct sway_transaction_instruction { struct sway_transaction *transaction; struct sway_node *node; union { struct sway_output_state output_state; struct sway_workspace_state workspace_state; struct sway_container_state container_state; }; uint32_t serial; bool server_request; bool waiting; }; static struct sway_transaction *transaction_create(void) { struct sway_transaction *transaction = calloc(1, sizeof(struct sway_transaction)); if (!sway_assert(transaction, "Unable to allocate transaction")) { return NULL; } transaction->instructions = create_list(); return transaction; } static void transaction_destroy(struct sway_transaction *transaction) { // Free instructions for (int i = 0; i < transaction->instructions->length; ++i) { struct sway_transaction_instruction *instruction = transaction->instructions->items[i]; struct sway_node *node = instruction->node; node->ntxnrefs--; if (node->instruction == instruction) { node->instruction = NULL; } if (node->destroying && node->ntxnrefs == 0) { switch (node->type) { case N_ROOT: sway_assert(false, "Never reached"); break; case N_OUTPUT: output_destroy(node->sway_output); break; case N_WORKSPACE: workspace_destroy(node->sway_workspace); break; case N_CONTAINER: container_destroy(node->sway_container); break; } } free(instruction); } list_free(transaction->instructions); if (transaction->timer) { wl_event_source_remove(transaction->timer); } free(transaction); } static void copy_output_state(struct sway_output *output, struct sway_transaction_instruction *instruction) { struct sway_output_state *state = &instruction->output_state; if (state->workspaces) { state->workspaces->length = 0; } else { state->workspaces = create_list(); } list_cat(state->workspaces, output->workspaces); state->active_workspace = output_get_active_workspace(output); } static void copy_workspace_state(struct sway_workspace *ws, struct sway_transaction_instruction *instruction) { struct sway_workspace_state *state = &instruction->workspace_state; state->fullscreen = ws->fullscreen; state->x = ws->x; state->y = ws->y; state->width = ws->width; state->height = ws->height; state->layout = ws->layout; state->output = ws->output; if (state->floating) { state->floating->length = 0; } else { state->floating = create_list(); } if (state->tiling) { state->tiling->length = 0; } else { state->tiling = create_list(); } list_cat(state->floating, ws->floating); list_cat(state->tiling, ws->tiling); struct sway_seat *seat = input_manager_current_seat(); state->focused = seat_get_focus(seat) == &ws->node; // Set focused_inactive_child to the direct tiling child struct sway_container *focus = seat_get_focus_inactive_tiling(seat, ws); if (focus) { while (focus->pending.parent) { focus = focus->pending.parent; } } state->focused_inactive_child = focus; } static void copy_container_state(struct sway_container *container, struct sway_transaction_instruction *instruction) { struct sway_container_state *state = &instruction->container_state; if (state->children) { list_free(state->children); } memcpy(state, &container->pending, sizeof(struct sway_container_state)); if (!container->view) { // We store a copy of the child list to avoid having it mutated after // we copy the state. state->children = create_list(); list_cat(state->children, container->pending.children); } else { state->children = NULL; } struct sway_seat *seat = input_manager_current_seat(); state->focused = seat_get_focus(seat) == &container->node; if (!container->view) { struct sway_node *focus = seat_get_active_tiling_child(seat, &container->node); state->focused_inactive_child = focus ? focus->sway_container : NULL; } } static void transaction_add_node(struct sway_transaction *transaction, struct sway_node *node, bool server_request) { struct sway_transaction_instruction *instruction = NULL; // Check if we have an instruction for this node already, in which case we // update that instead of creating a new one. if (node->ntxnrefs > 0) { for (int idx = 0; idx < transaction->instructions->length; idx++) { struct sway_transaction_instruction *other = transaction->instructions->items[idx]; if (other->node == node) { instruction = other; break; } } } if (!instruction) { instruction = calloc(1, sizeof(struct sway_transaction_instruction)); if (!sway_assert(instruction, "Unable to allocate instruction")) { return; } instruction->transaction = transaction; instruction->node = node; instruction->server_request = server_request; list_add(transaction->instructions, instruction); node->ntxnrefs++; } else if (server_request) { instruction->server_request = true; } switch (node->type) { case N_ROOT: break; case N_OUTPUT: copy_output_state(node->sway_output, instruction); break; case N_WORKSPACE: copy_workspace_state(node->sway_workspace, instruction); break; case N_CONTAINER: copy_container_state(node->sway_container, instruction); break; } } static void apply_output_state(struct sway_output *output, struct sway_output_state *state) { list_free(output->current.workspaces); memcpy(&output->current, state, sizeof(struct sway_output_state)); } static void apply_workspace_state(struct sway_workspace *ws, struct sway_workspace_state *state) { list_free(ws->current.floating); list_free(ws->current.tiling); memcpy(&ws->current, state, sizeof(struct sway_workspace_state)); } static void apply_container_state(struct sway_container *container, struct sway_container_state *state) { struct sway_view *view = container->view; // There are separate children lists for each instruction state, the // container's current state and the container's pending state // (ie. con->children). The list itself needs to be freed here. // Any child containers which are being deleted will be cleaned up in // transaction_destroy(). list_free(container->current.children); memcpy(&container->current, state, sizeof(struct sway_container_state)); if (view) { if (view->saved_surface_tree) { if (!container->node.destroying || container->node.ntxnrefs == 1) { view_remove_saved_buffer(view); } } // If the view hasn't responded to the configure, center it within // the container. This is important for fullscreen views which // refuse to resize to the size of the output. if (view->surface) { view_center_and_clip_surface(view); } } } static void arrange_title_bar(struct sway_container *con, int x, int y, int width, int height) { container_update(con); bool has_title_bar = height > 0; wlr_scene_node_set_enabled(&con->title_bar.tree->node, has_title_bar); if (!has_title_bar) { return; } wlr_scene_node_set_position(&con->title_bar.tree->node, x, y); con->title_width = width; container_arrange_title_bar(con); } static void disable_container(struct sway_container *con) { if (con->view) { wlr_scene_node_reparent(&con->view->scene_tree->node, con->content_tree); } else { for (int i = 0; i < con->current.children->length; i++) { struct sway_container *child = con->current.children->items[i]; wlr_scene_node_reparent(&child->scene_tree->node, con->content_tree); disable_container(child); } } } static void arrange_container(struct sway_container *con, int width, int height, bool title_bar, int gaps); static void arrange_children(enum sway_container_layout layout, list_t *children, struct sway_container *active, struct wlr_scene_tree *content, int width, int height, int gaps) { int title_bar_height = container_titlebar_height(); if (layout == L_TABBED) { struct sway_container *first = children->length == 1 ? ((struct sway_container *)children->items[0]) : NULL; if (config->hide_lone_tab && first && first->view && first->current.border != B_NORMAL) { title_bar_height = 0; } double w = (double) width / children->length; int title_offset = 0; for (int i = 0; i < children->length; i++) { struct sway_container *child = children->items[i]; bool activated = child == active; int next_title_offset = round(w * i + w); arrange_title_bar(child, title_offset, -title_bar_height, next_title_offset - title_offset, title_bar_height); wlr_scene_node_set_enabled(&child->border.tree->node, activated); wlr_scene_node_set_enabled(&child->scene_tree->node, true); wlr_scene_node_set_position(&child->scene_tree->node, 0, title_bar_height); wlr_scene_node_reparent(&child->scene_tree->node, content); int net_height = height - title_bar_height; if (activated && width > 0 && net_height > 0) { arrange_container(child, width, net_height, title_bar_height == 0, 0); } else { disable_container(child); } title_offset = next_title_offset; } } else if (layout == L_STACKED) { struct sway_container *first = children->length == 1 ? ((struct sway_container *)children->items[0]) : NULL; if (config->hide_lone_tab && first && first->view && first->current.border != B_NORMAL) { title_bar_height = 0; } int title_height = title_bar_height * children->length; int y = 0; for (int i = 0; i < children->length; i++) { struct sway_container *child = children->items[i]; bool activated = child == active; arrange_title_bar(child, 0, y - title_height, width, title_bar_height); wlr_scene_node_set_enabled(&child->border.tree->node, activated); wlr_scene_node_set_enabled(&child->scene_tree->node, true); wlr_scene_node_set_position(&child->scene_tree->node, 0, title_height); wlr_scene_node_reparent(&child->scene_tree->node, content); int net_height = height - title_height; if (activated && width > 0 && net_height > 0) { arrange_container(child, width, net_height, title_bar_height == 0, 0); } else { disable_container(child); } y += title_bar_height; } } else if (layout == L_VERT) { int off = 0; for (int i = 0; i < children->length; i++) { struct sway_container *child = children->items[i]; int cheight = child->current.height; wlr_scene_node_set_enabled(&child->border.tree->node, true); wlr_scene_node_set_position(&child->scene_tree->node, 0, off); wlr_scene_node_reparent(&child->scene_tree->node, content); if (width > 0 && cheight > 0) { arrange_container(child, width, cheight, true, gaps); off += cheight + gaps; } else { disable_container(child); } } } else if (layout == L_HORIZ) { int off = 0; for (int i = 0; i < children->length; i++) { struct sway_container *child = children->items[i]; int cwidth = child->current.width; wlr_scene_node_set_enabled(&child->border.tree->node, true); wlr_scene_node_set_position(&child->scene_tree->node, off, 0); wlr_scene_node_reparent(&child->scene_tree->node, content); if (cwidth > 0 && height > 0) { arrange_container(child, cwidth, height, true, gaps); off += cwidth + gaps; } else { disable_container(child); } } } else { sway_assert(false, "unreachable"); } } static void arrange_container(struct sway_container *con, int width, int height, bool title_bar, int gaps) { // this container might have previously been in the scratchpad, // make sure it's enabled for viewing wlr_scene_node_set_enabled(&con->scene_tree->node, true); if (con->output_handler) { wlr_scene_buffer_set_dest_size(con->output_handler, width, height); } if (con->view) { int border_top = container_titlebar_height(); int border_width = con->current.border_thickness; if (title_bar && con->current.border != B_NORMAL) { wlr_scene_node_set_enabled(&con->title_bar.tree->node, false); wlr_scene_node_set_enabled(&con->border.top->node, true); } else { wlr_scene_node_set_enabled(&con->border.top->node, false); } if (con->current.border == B_NORMAL) { if (title_bar) { arrange_title_bar(con, 0, 0, width, border_top); } else { border_top = 0; // should be handled by the parent container } } else if (con->current.border == B_PIXEL) { container_update(con); border_top = title_bar && con->current.border_top ? border_width : 0; } else if (con->current.border == B_NONE) { container_update(con); border_top = 0; border_width = 0; } else if (con->current.border == B_CSD) { border_top = 0; border_width = 0; } else { sway_assert(false, "unreachable"); } int border_bottom = con->current.border_bottom ? border_width : 0; int border_left = con->current.border_left ? border_width : 0; int border_right = con->current.border_right ? border_width : 0; int vert_border_height = MAX(0, height - border_top - border_bottom); wlr_scene_rect_set_size(con->border.top, width, border_top); wlr_scene_rect_set_size(con->border.bottom, width, border_bottom); wlr_scene_rect_set_size(con->border.left, border_left, vert_border_height); wlr_scene_rect_set_size(con->border.right, border_right, vert_border_height); wlr_scene_node_set_position(&con->border.top->node, 0, 0); wlr_scene_node_set_position(&con->border.bottom->node, 0, height - border_bottom); wlr_scene_node_set_position(&con->border.left->node, 0, border_top); wlr_scene_node_set_position(&con->border.right->node, width - border_right, border_top); // make sure to reparent, it's possible that the client just came out of // fullscreen mode where the parent of the surface is not the container wlr_scene_node_reparent(&con->view->scene_tree->node, con->content_tree); wlr_scene_node_set_position(&con->view->scene_tree->node, border_left, border_top); } else { // make sure to disable the title bar if the parent is not managing it if (title_bar) { wlr_scene_node_set_enabled(&con->title_bar.tree->node, false); } arrange_children(con->current.layout, con->current.children, con->current.focused_inactive_child, con->content_tree, width, height, gaps); } } static int container_get_gaps(struct sway_container *con) { struct sway_workspace *ws = con->current.workspace; struct sway_container *temp = con; while (temp) { enum sway_container_layout layout; if (temp->current.parent) { layout = temp->current.parent->current.layout; } else { layout = ws->current.layout; } if (layout == L_TABBED || layout == L_STACKED) { return 0; } temp = temp->pending.parent; } return ws->gaps_inner; } static void arrange_fullscreen(struct wlr_scene_tree *tree, struct sway_container *fs, struct sway_workspace *ws, int width, int height) { struct wlr_scene_node *fs_node; if (fs->view) { fs_node = &fs->view->scene_tree->node; // if we only care about the view, disable any decorations wlr_scene_node_set_enabled(&fs->scene_tree->node, false); } else { fs_node = &fs->scene_tree->node; arrange_container(fs, width, height, true, container_get_gaps(fs)); } wlr_scene_node_reparent(fs_node, tree); wlr_scene_node_lower_to_bottom(fs_node); wlr_scene_node_set_position(fs_node, 0, 0); } static void arrange_workspace_floating(struct sway_workspace *ws) { for (int i = 0; i < ws->current.floating->length; i++) { struct sway_container *floater = ws->current.floating->items[i]; struct wlr_scene_tree *layer = root->layers.floating; if (floater->current.fullscreen_mode != FULLSCREEN_NONE) { continue; } if (root->fullscreen_global) { if (container_is_transient_for(floater, root->fullscreen_global)) { layer = root->layers.fullscreen_global; } } else { for (int i = 0; i < root->outputs->length; i++) { struct sway_output *output = root->outputs->items[i]; struct sway_workspace *active = output->current.active_workspace; if (active && active->fullscreen && container_is_transient_for(floater, active->fullscreen)) { layer = root->layers.fullscreen; } } } wlr_scene_node_reparent(&floater->scene_tree->node, layer); wlr_scene_node_set_position(&floater->scene_tree->node, floater->current.x, floater->current.y); wlr_scene_node_set_enabled(&floater->scene_tree->node, true); wlr_scene_node_set_enabled(&floater->border.tree->node, true); arrange_container(floater, floater->current.width, floater->current.height, true, ws->gaps_inner); } } static void arrange_workspace_tiling(struct sway_workspace *ws, int width, int height) { arrange_children(ws->current.layout, ws->current.tiling, ws->current.focused_inactive_child, ws->layers.tiling, width, height, ws->gaps_inner); } static void disable_workspace(struct sway_workspace *ws) { // if any containers were just moved to a disabled workspace it will // have the parent of the old workspace. Move the workspace so that it won't // be shown. for (int i = 0; i < ws->current.tiling->length; i++) { struct sway_container *child = ws->current.tiling->items[i]; wlr_scene_node_reparent(&child->scene_tree->node, ws->layers.tiling); disable_container(child); } for (int i = 0; i < ws->current.floating->length; i++) { struct sway_container *floater = ws->current.floating->items[i]; wlr_scene_node_reparent(&floater->scene_tree->node, root->layers.floating); disable_container(floater); wlr_scene_node_set_enabled(&floater->scene_tree->node, false); } } static void arrange_output(struct sway_output *output, int width, int height) { for (int i = 0; i < output->current.workspaces->length; i++) { struct sway_workspace *child = output->current.workspaces->items[i]; bool activated = output->current.active_workspace == child && output->wlr_output->enabled; wlr_scene_node_reparent(&child->layers.tiling->node, output->layers.tiling); wlr_scene_node_reparent(&child->layers.fullscreen->node, output->layers.fullscreen); for (int i = 0; i < child->current.floating->length; i++) { struct sway_container *floater = child->current.floating->items[i]; wlr_scene_node_reparent(&floater->scene_tree->node, root->layers.floating); wlr_scene_node_set_enabled(&floater->scene_tree->node, activated); } if (activated) { struct sway_container *fs = child->current.fullscreen; wlr_scene_node_set_enabled(&child->layers.tiling->node, !fs); wlr_scene_node_set_enabled(&child->layers.fullscreen->node, fs); wlr_scene_node_set_enabled(&output->layers.shell_background->node, !fs); wlr_scene_node_set_enabled(&output->layers.shell_bottom->node, !fs); wlr_scene_node_set_enabled(&output->layers.fullscreen->node, fs); if (fs) { disable_workspace(child); wlr_scene_rect_set_size(output->fullscreen_background, width, height); arrange_workspace_floating(child); arrange_fullscreen(child->layers.fullscreen, fs, child, width, height); } else { struct wlr_box *area = &output->usable_area; struct side_gaps *gaps = &child->current_gaps; wlr_scene_node_set_position(&child->layers.tiling->node, gaps->left + area->x, gaps->top + area->y); arrange_workspace_tiling(child, area->width - gaps->left - gaps->right, area->height - gaps->top - gaps->bottom); arrange_workspace_floating(child); } } else { wlr_scene_node_set_enabled(&child->layers.tiling->node, false); wlr_scene_node_set_enabled(&child->layers.fullscreen->node, false); disable_workspace(child); } } } void arrange_popups(struct wlr_scene_tree *popups) { struct wlr_scene_node *node; wl_list_for_each(node, &popups->children, link) { struct sway_popup_desc *popup = scene_descriptor_try_get(node, SWAY_SCENE_DESC_POPUP); if (popup) { int lx, ly; wlr_scene_node_coords(popup->relative, &lx, &ly); wlr_scene_node_set_position(node, lx, ly); } } } static void arrange_root(struct sway_root *root) { struct sway_container *fs = root->fullscreen_global; wlr_scene_node_set_enabled(&root->layers.shell_background->node, !fs); wlr_scene_node_set_enabled(&root->layers.shell_bottom->node, !fs); wlr_scene_node_set_enabled(&root->layers.tiling->node, !fs); wlr_scene_node_set_enabled(&root->layers.floating->node, !fs); wlr_scene_node_set_enabled(&root->layers.shell_top->node, !fs); wlr_scene_node_set_enabled(&root->layers.fullscreen->node, !fs); // hide all contents in the scratchpad for (int i = 0; i < root->scratchpad->length; i++) { struct sway_container *con = root->scratchpad->items[i]; // When a container is moved to a scratchpad, it's possible that it // was moved into a floating container as part of the same transaction. // In this case, we need to make sure we reparent all the container's // children so that disabling the container will disable all descendants. if (!con->view) for (int ii = 0; ii < con->current.children->length; ii++) { struct sway_container *child = con->current.children->items[ii]; wlr_scene_node_reparent(&child->scene_tree->node, con->content_tree); } wlr_scene_node_set_enabled(&con->scene_tree->node, false); } if (fs) { for (int i = 0; i < root->outputs->length; i++) { struct sway_output *output = root->outputs->items[i]; struct sway_workspace *ws = output->current.active_workspace; wlr_scene_output_set_position(output->scene_output, output->lx, output->ly); // disable all workspaces to get to a known state for (int j = 0; j < output->current.workspaces->length; j++) { struct sway_workspace *workspace = output->current.workspaces->items[j]; disable_workspace(workspace); } // arrange the active workspace if (ws) { arrange_workspace_floating(ws); } } arrange_fullscreen(root->layers.fullscreen_global, fs, NULL, root->width, root->height); } else { for (int i = 0; i < root->outputs->length; i++) { struct sway_output *output = root->outputs->items[i]; wlr_scene_output_set_position(output->scene_output, output->lx, output->ly); wlr_scene_node_reparent(&output->layers.shell_background->node, root->layers.shell_background); wlr_scene_node_reparent(&output->layers.shell_bottom->node, root->layers.shell_bottom); wlr_scene_node_reparent(&output->layers.tiling->node, root->layers.tiling); wlr_scene_node_reparent(&output->layers.shell_top->node, root->layers.shell_top); wlr_scene_node_reparent(&output->layers.shell_overlay->node, root->layers.shell_overlay); wlr_scene_node_reparent(&output->layers.fullscreen->node, root->layers.fullscreen); wlr_scene_node_reparent(&output->layers.session_lock->node, root->layers.session_lock); wlr_scene_node_set_position(&output->layers.shell_background->node, output->lx, output->ly); wlr_scene_node_set_position(&output->layers.shell_bottom->node, output->lx, output->ly); wlr_scene_node_set_position(&output->layers.tiling->node, output->lx, output->ly); wlr_scene_node_set_position(&output->layers.fullscreen->node, output->lx, output->ly); wlr_scene_node_set_position(&output->layers.shell_top->node, output->lx, output->ly); wlr_scene_node_set_position(&output->layers.shell_overlay->node, output->lx, output->ly); wlr_scene_node_set_position(&output->layers.session_lock->node, output->lx, output->ly); arrange_output(output, output->width, output->height); } } arrange_popups(root->layers.popup); } /** * Apply a transaction to the "current" state of the tree. */ static void transaction_apply(struct sway_transaction *transaction) { sway_log(SWAY_DEBUG, "Applying transaction %p", transaction); if (debug.txn_timings) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); struct timespec *commit = &transaction->commit_time; float ms = (now.tv_sec - commit->tv_sec) * 1000 + (now.tv_nsec - commit->tv_nsec) / 1000000.0; sway_log(SWAY_DEBUG, "Transaction %p: %.1fms waiting " "(%.1f frames if 60Hz)", transaction, ms, ms / (1000.0f / 60)); } // Apply the instruction state to the node's current state for (int i = 0; i < transaction->instructions->length; ++i) { struct sway_transaction_instruction *instruction = transaction->instructions->items[i]; struct sway_node *node = instruction->node; switch (node->type) { case N_ROOT: break; case N_OUTPUT: apply_output_state(node->sway_output, &instruction->output_state); break; case N_WORKSPACE: apply_workspace_state(node->sway_workspace, &instruction->workspace_state); break; case N_CONTAINER: apply_container_state(node->sway_container, &instruction->container_state); break; } node->instruction = NULL; } } static void transaction_commit_pending(void); static void transaction_progress(void) { if (!server.queued_transaction) { return; } if (server.queued_transaction->num_waiting > 0) { return; } transaction_apply(server.queued_transaction); arrange_root(root); cursor_rebase_all(); transaction_destroy(server.queued_transaction); server.queued_transaction = NULL; if (!server.pending_transaction) { sway_idle_inhibit_v1_check_active(); return; } transaction_commit_pending(); } static int handle_timeout(void *data) { struct sway_transaction *transaction = data; sway_log(SWAY_DEBUG, "Transaction %p timed out (%zi waiting)", transaction, transaction->num_waiting); transaction->num_waiting = 0; transaction_progress(); return 0; } static bool should_configure(struct sway_node *node, struct sway_transaction_instruction *instruction) { if (!node_is_view(node)) { return false; } if (node->destroying) { return false; } if (!instruction->server_request) { return false; } struct sway_container_state *cstate = &node->sway_container->current; struct sway_container_state *istate = &instruction->container_state; #if WLR_HAS_XWAYLAND // Xwayland views are position-aware and need to be reconfigured // when their position changes. if (node->sway_container->view->type == SWAY_VIEW_XWAYLAND) { // Sway logical coordinates are doubles, but they get truncated to // integers when sent to Xwayland through `xcb_configure_window`. // X11 apps will not respond to duplicate configure requests (from their // truncated point of view) and cause transactions to time out. if ((int)cstate->content_x != (int)istate->content_x || (int)cstate->content_y != (int)istate->content_y) { return true; } } #endif if (cstate->content_width == istate->content_width && cstate->content_height == istate->content_height) { return false; } return true; } static void transaction_commit(struct sway_transaction *transaction) { sway_log(SWAY_DEBUG, "Transaction %p committing with %i instructions", transaction, transaction->instructions->length); transaction->num_waiting = 0; for (int i = 0; i < transaction->instructions->length; ++i) { struct sway_transaction_instruction *instruction = transaction->instructions->items[i]; struct sway_node *node = instruction->node; bool hidden = node_is_view(node) && !node->destroying && !view_is_visible(node->sway_container->view); if (should_configure(node, instruction)) { instruction->serial = view_configure(node->sway_container->view, instruction->container_state.content_x, instruction->container_state.content_y, instruction->container_state.content_width, instruction->container_state.content_height); if (!hidden) { instruction->waiting = true; ++transaction->num_waiting; } view_send_frame_done(node->sway_container->view); } if (!hidden && node_is_view(node) && !node->sway_container->view->saved_surface_tree) { view_save_buffer(node->sway_container->view); } node->instruction = instruction; } transaction->num_configures = transaction->num_waiting; if (debug.txn_timings) { clock_gettime(CLOCK_MONOTONIC, &transaction->commit_time); } if (debug.noatomic) { transaction->num_waiting = 0; } else if (debug.txn_wait) { // Force the transaction to time out even if all views are ready. // We do this by inflating the waiting counter. transaction->num_waiting += 1000000; } if (transaction->num_waiting) { // Set up a timer which the views must respond within transaction->timer = wl_event_loop_add_timer(server.wl_event_loop, handle_timeout, transaction); if (transaction->timer) { wl_event_source_timer_update(transaction->timer, server.txn_timeout_ms); } else { sway_log_errno(SWAY_ERROR, "Unable to create transaction timer " "(some imperfect frames might be rendered)"); transaction->num_waiting = 0; } } } static void transaction_commit_pending(void) { if (server.queued_transaction) { return; } struct sway_transaction *transaction = server.pending_transaction; server.pending_transaction = NULL; server.queued_transaction = transaction; transaction_commit(transaction); transaction_progress(); } static void set_instruction_ready( struct sway_transaction_instruction *instruction) { struct sway_transaction *transaction = instruction->transaction; if (debug.txn_timings) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); struct timespec *start = &transaction->commit_time; float ms = (now.tv_sec - start->tv_sec) * 1000 + (now.tv_nsec - start->tv_nsec) / 1000000.0; sway_log(SWAY_DEBUG, "Transaction %p: %zi/%zi ready in %.1fms (%s)", transaction, transaction->num_configures - transaction->num_waiting + 1, transaction->num_configures, ms, instruction->node->sway_container->title); } // If the transaction has timed out then its num_waiting will be 0 already. if (instruction->waiting && transaction->num_waiting > 0 && --transaction->num_waiting == 0) { sway_log(SWAY_DEBUG, "Transaction %p is ready", transaction); wl_event_source_timer_update(transaction->timer, 0); } instruction->node->instruction = NULL; transaction_progress(); } bool transaction_notify_view_ready_by_serial(struct sway_view *view, uint32_t serial) { struct sway_transaction_instruction *instruction = view->container->node.instruction; if (instruction != NULL && instruction->serial == serial) { set_instruction_ready(instruction); return true; } return false; } bool transaction_notify_view_ready_by_geometry(struct sway_view *view, double x, double y, int width, int height) { struct sway_transaction_instruction *instruction = view->container->node.instruction; if (instruction != NULL && (int)instruction->container_state.content_x == (int)x && (int)instruction->container_state.content_y == (int)y && instruction->container_state.content_width == width && instruction->container_state.content_height == height) { set_instruction_ready(instruction); return true; } return false; } static void _transaction_commit_dirty(bool server_request) { if (!server.dirty_nodes->length) { return; } if (!server.pending_transaction) { server.pending_transaction = transaction_create(); if (!server.pending_transaction) { return; } } for (int i = 0; i < server.dirty_nodes->length; ++i) { struct sway_node *node = server.dirty_nodes->items[i]; transaction_add_node(server.pending_transaction, node, server_request); node->dirty = false; } server.dirty_nodes->length = 0; transaction_commit_pending(); } void transaction_commit_dirty(void) { _transaction_commit_dirty(true); } void transaction_commit_dirty_client(void) { _transaction_commit_dirty(false); } ================================================ FILE: sway/desktop/xdg_shell.c ================================================ #include #include #include #include #include #include #include #include "log.h" #include "sway/decoration.h" #include "sway/scene_descriptor.h" #include "sway/desktop/transaction.h" #include "sway/input/cursor.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" #include "sway/output.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "sway/xdg_decoration.h" static struct sway_xdg_popup *popup_create( struct wlr_xdg_popup *wlr_popup, struct sway_view *view, struct wlr_scene_tree *parent, struct wlr_scene_tree *image_capture_parent); static void popup_handle_new_popup(struct wl_listener *listener, void *data) { struct sway_xdg_popup *popup = wl_container_of(listener, popup, new_popup); struct wlr_xdg_popup *wlr_popup = data; popup_create(wlr_popup, popup->view, popup->xdg_surface_tree, popup->image_capture_tree); } static void popup_handle_destroy(struct wl_listener *listener, void *data) { struct sway_xdg_popup *popup = wl_container_of(listener, popup, destroy); wl_list_remove(&popup->new_popup.link); wl_list_remove(&popup->destroy.link); wl_list_remove(&popup->surface_commit.link); wl_list_remove(&popup->reposition.link); wlr_scene_node_destroy(&popup->scene_tree->node); free(popup); } static void popup_unconstrain(struct sway_xdg_popup *popup) { struct sway_view *view = popup->view; struct wlr_xdg_popup *wlr_popup = popup->wlr_xdg_popup; struct sway_workspace *workspace = view->container->pending.workspace; if (!workspace) { // is null if in the scratchpad return; } struct sway_output *output = workspace->output; // the output box expressed in the coordinate system of the toplevel parent // of the popup struct wlr_box output_toplevel_sx_box = { .x = output->lx - view->container->pending.content_x + view->geometry.x, .y = output->ly - view->container->pending.content_y + view->geometry.y, .width = output->width, .height = output->height, }; wlr_xdg_popup_unconstrain_from_box(wlr_popup, &output_toplevel_sx_box); } static void popup_handle_surface_commit(struct wl_listener *listener, void *data) { struct sway_xdg_popup *popup = wl_container_of(listener, popup, surface_commit); if (popup->wlr_xdg_popup->base->initial_commit) { popup_unconstrain(popup); } } static void popup_handle_reposition(struct wl_listener *listener, void *data) { struct sway_xdg_popup *popup = wl_container_of(listener, popup, reposition); popup_unconstrain(popup); } static struct sway_xdg_popup *popup_create(struct wlr_xdg_popup *wlr_popup, struct sway_view *view, struct wlr_scene_tree *parent, struct wlr_scene_tree *image_capture_parent) { struct wlr_xdg_surface *xdg_surface = wlr_popup->base; struct sway_xdg_popup *popup = calloc(1, sizeof(struct sway_xdg_popup)); if (!popup) { return NULL; } popup->wlr_xdg_popup = wlr_popup; popup->view = view; popup->scene_tree = wlr_scene_tree_create(parent); if (!popup->scene_tree) { free(popup); return NULL; } popup->xdg_surface_tree = wlr_scene_xdg_surface_create( popup->scene_tree, xdg_surface); if (!popup->xdg_surface_tree) { wlr_scene_node_destroy(&popup->scene_tree->node); free(popup); return NULL; } popup->desc.relative = &view->content_tree->node; popup->desc.view = view; if (!scene_descriptor_assign(&popup->scene_tree->node, SWAY_SCENE_DESC_POPUP, &popup->desc)) { sway_log(SWAY_ERROR, "Failed to allocate a popup scene descriptor"); wlr_scene_node_destroy(&popup->scene_tree->node); free(popup); return NULL; } popup->image_capture_tree = wlr_scene_xdg_surface_create(image_capture_parent, xdg_surface); if (popup->image_capture_tree == NULL) { return NULL; } popup->wlr_xdg_popup = xdg_surface->popup; struct sway_xdg_shell_view *shell_view = wl_container_of(view, shell_view, view); xdg_surface->data = shell_view; wl_signal_add(&xdg_surface->surface->events.commit, &popup->surface_commit); popup->surface_commit.notify = popup_handle_surface_commit; wl_signal_add(&xdg_surface->events.new_popup, &popup->new_popup); popup->new_popup.notify = popup_handle_new_popup; wl_signal_add(&wlr_popup->events.reposition, &popup->reposition); popup->reposition.notify = popup_handle_reposition; wl_signal_add(&wlr_popup->events.destroy, &popup->destroy); popup->destroy.notify = popup_handle_destroy; return popup; } static struct sway_xdg_shell_view *xdg_shell_view_from_view( struct sway_view *view) { if (!sway_assert(view->type == SWAY_VIEW_XDG_SHELL, "Expected xdg_shell view")) { return NULL; } return (struct sway_xdg_shell_view *)view; } static void get_constraints(struct sway_view *view, double *min_width, double *max_width, double *min_height, double *max_height) { struct wlr_xdg_toplevel_state *state = &view->wlr_xdg_toplevel->current; *min_width = state->min_width > 0 ? state->min_width : DBL_MIN; *max_width = state->max_width > 0 ? state->max_width : DBL_MAX; *min_height = state->min_height > 0 ? state->min_height : DBL_MIN; *max_height = state->max_height > 0 ? state->max_height : DBL_MAX; } static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { struct sway_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); if (xdg_shell_view == NULL) { return NULL; } switch (prop) { case VIEW_PROP_TITLE: return view->wlr_xdg_toplevel->title; case VIEW_PROP_APP_ID: return view->wlr_xdg_toplevel->app_id; case VIEW_PROP_TAG: return xdg_shell_view->tag; default: return NULL; } } static uint32_t configure(struct sway_view *view, double lx, double ly, int width, int height) { struct sway_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); if (xdg_shell_view == NULL) { return 0; } return wlr_xdg_toplevel_set_size(view->wlr_xdg_toplevel, width, height); } static void set_activated(struct sway_view *view, bool activated) { if (xdg_shell_view_from_view(view) == NULL) { return; } wlr_xdg_toplevel_set_activated(view->wlr_xdg_toplevel, activated); } static void set_tiled(struct sway_view *view, bool tiled) { if (xdg_shell_view_from_view(view) == NULL) { return; } if (wl_resource_get_version(view->wlr_xdg_toplevel->resource) >= XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION) { enum wlr_edges edges = WLR_EDGE_NONE; if (tiled) { edges = WLR_EDGE_LEFT | WLR_EDGE_RIGHT | WLR_EDGE_TOP | WLR_EDGE_BOTTOM; } wlr_xdg_toplevel_set_tiled(view->wlr_xdg_toplevel, edges); } else { // The version is too low for the tiled state; configure as maximized instead // to stop the client from drawing decorations outside of the toplevel geometry. wlr_xdg_toplevel_set_maximized(view->wlr_xdg_toplevel, tiled); } } static void set_fullscreen(struct sway_view *view, bool fullscreen) { if (xdg_shell_view_from_view(view) == NULL) { return; } wlr_xdg_toplevel_set_fullscreen(view->wlr_xdg_toplevel, fullscreen); } static void set_resizing(struct sway_view *view, bool resizing) { if (xdg_shell_view_from_view(view) == NULL) { return; } wlr_xdg_toplevel_set_resizing(view->wlr_xdg_toplevel, resizing); } static bool wants_floating(struct sway_view *view) { struct wlr_xdg_toplevel *toplevel = view->wlr_xdg_toplevel; struct wlr_xdg_toplevel_state *state = &toplevel->current; return (state->min_width != 0 && state->min_height != 0 && (state->min_width == state->max_width || state->min_height == state->max_height)) || toplevel->parent; } static bool is_transient_for(struct sway_view *child, struct sway_view *ancestor) { if (xdg_shell_view_from_view(child) == NULL) { return false; } struct wlr_xdg_toplevel *toplevel = child->wlr_xdg_toplevel; while (toplevel) { if (toplevel->parent == ancestor->wlr_xdg_toplevel) { return true; } toplevel = toplevel->parent; } return false; } static void _close(struct sway_view *view) { if (xdg_shell_view_from_view(view) == NULL) { return; } wlr_xdg_toplevel_send_close(view->wlr_xdg_toplevel); } static void close_popups(struct sway_view *view) { struct wlr_xdg_popup *popup, *tmp; wl_list_for_each_safe(popup, tmp, &view->wlr_xdg_toplevel->base->popups, link) { wlr_xdg_popup_destroy(popup); } } static void destroy(struct sway_view *view) { struct sway_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); if (xdg_shell_view == NULL) { return; } free(xdg_shell_view->tag); free(xdg_shell_view); } static const struct sway_view_impl view_impl = { .get_constraints = get_constraints, .get_string_prop = get_string_prop, .configure = configure, .set_activated = set_activated, .set_tiled = set_tiled, .set_fullscreen = set_fullscreen, .set_resizing = set_resizing, .wants_floating = wants_floating, .is_transient_for = is_transient_for, .close = _close, .close_popups = close_popups, .destroy = destroy, }; static void handle_commit(struct wl_listener *listener, void *data) { struct sway_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, commit); struct sway_view *view = &xdg_shell_view->view; struct wlr_xdg_surface *xdg_surface = view->wlr_xdg_toplevel->base; if (xdg_surface->initial_commit) { if (view->xdg_decoration != NULL) { set_xdg_decoration_mode(view->xdg_decoration); } // XXX: https://github.com/swaywm/sway/issues/2176 wlr_xdg_surface_schedule_configure(xdg_surface); wlr_xdg_toplevel_set_wm_capabilities(view->wlr_xdg_toplevel, WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); // TODO: wlr_xdg_toplevel_set_bounds() return; } if (!xdg_surface->surface->mapped) { return; } struct wlr_box *new_geo = &xdg_surface->geometry; bool new_size = new_geo->width != view->geometry.width || new_geo->height != view->geometry.height || new_geo->x != view->geometry.x || new_geo->y != view->geometry.y; if (new_size) { // The client changed its surface size in this commit. For floating // containers, we resize the container to match. For tiling containers, // we only recenter the surface. memcpy(&view->geometry, new_geo, sizeof(struct wlr_box)); if (container_is_floating(view->container)) { view_update_size(view); // Only set the toplevel size the current container actually has a size. if (view->container->current.width) { wlr_xdg_toplevel_set_size(view->wlr_xdg_toplevel, view->geometry.width, view->geometry.height); } transaction_commit_dirty_client(); } view_center_and_clip_surface(view); } if (view->container->node.instruction) { bool successful = transaction_notify_view_ready_by_serial(view, xdg_surface->current.configure_serial); // If we saved the view and this commit isn't what we're looking for // that means the user will never actually see the buffers submitted to // us here. Just send frame done events to these surfaces so they can // commit another time for us. if (view->saved_surface_tree && !successful) { view_send_frame_done(view); } } } static void handle_set_title(struct wl_listener *listener, void *data) { struct sway_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, set_title); struct sway_view *view = &xdg_shell_view->view; view_update_title(view, false); view_execute_criteria(view); transaction_commit_dirty(); } static void handle_set_app_id(struct wl_listener *listener, void *data) { struct sway_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, set_app_id); struct sway_view *view = &xdg_shell_view->view; view_update_app_id(view); view_execute_criteria(view); transaction_commit_dirty(); } static void handle_new_popup(struct wl_listener *listener, void *data) { struct sway_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, new_popup); struct wlr_xdg_popup *wlr_popup = data; struct sway_xdg_popup *popup = popup_create(wlr_popup, &xdg_shell_view->view, root->layers.popup, xdg_shell_view->image_capture_tree); if (!popup) { return; } int lx, ly; wlr_scene_node_coords(&popup->view->content_tree->node, &lx, &ly); wlr_scene_node_set_position(&popup->scene_tree->node, lx, ly); } static void handle_request_maximize(struct wl_listener *listener, void *data) { struct sway_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, request_maximize); struct wlr_xdg_toplevel *toplevel = xdg_shell_view->view.wlr_xdg_toplevel; if (!toplevel->base->surface->mapped) { return; } wlr_xdg_surface_schedule_configure(toplevel->base); } static void handle_request_fullscreen(struct wl_listener *listener, void *data) { struct sway_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, request_fullscreen); struct wlr_xdg_toplevel *toplevel = xdg_shell_view->view.wlr_xdg_toplevel; struct sway_view *view = &xdg_shell_view->view; if (!toplevel->base->surface->mapped) { return; } struct sway_container *container = view->container; struct wlr_xdg_toplevel_requested *req = &toplevel->requested; if (req->fullscreen && req->fullscreen_output && req->fullscreen_output->data) { struct sway_output *output = req->fullscreen_output->data; struct sway_workspace *ws = output_get_active_workspace(output); if (ws && !container_is_scratchpad_hidden(container) && container->pending.workspace != ws) { if (container_is_floating(container)) { workspace_add_floating(ws, container); } else { container = workspace_add_tiling(ws, container); } } } container_set_fullscreen(container, req->fullscreen); arrange_root(); transaction_commit_dirty(); } static void handle_request_move(struct wl_listener *listener, void *data) { struct sway_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, request_move); struct sway_view *view = &xdg_shell_view->view; if (!container_is_floating(view->container) || view->container->pending.fullscreen_mode) { return; } struct wlr_xdg_toplevel_move_event *e = data; struct sway_seat *seat = e->seat->seat->data; if (e->serial == seat->last_button_serial) { seatop_begin_move_floating(seat, view->container); } } static void handle_request_resize(struct wl_listener *listener, void *data) { struct sway_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, request_resize); struct sway_view *view = &xdg_shell_view->view; if (!container_is_floating(view->container)) { return; } struct wlr_xdg_toplevel_resize_event *e = data; struct sway_seat *seat = e->seat->seat->data; if (e->serial == seat->last_button_serial) { seatop_begin_resize_floating(seat, view->container, e->edges); } } static void handle_unmap(struct wl_listener *listener, void *data) { struct sway_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, unmap); struct sway_view *view = &xdg_shell_view->view; if (!sway_assert(view->surface, "Cannot unmap unmapped view")) { return; } view_unmap(view); wl_list_remove(&xdg_shell_view->new_popup.link); wl_list_remove(&xdg_shell_view->request_maximize.link); wl_list_remove(&xdg_shell_view->request_fullscreen.link); wl_list_remove(&xdg_shell_view->request_move.link); wl_list_remove(&xdg_shell_view->request_resize.link); wl_list_remove(&xdg_shell_view->set_title.link); wl_list_remove(&xdg_shell_view->set_app_id.link); } static void handle_map(struct wl_listener *listener, void *data) { struct sway_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, map); struct sway_view *view = &xdg_shell_view->view; struct wlr_xdg_toplevel *toplevel = view->wlr_xdg_toplevel; view->natural_width = toplevel->base->geometry.width; view->natural_height = toplevel->base->geometry.height; bool csd = false; if (view->xdg_decoration) { enum wlr_xdg_toplevel_decoration_v1_mode mode = view->xdg_decoration->wlr_xdg_decoration->requested_mode; csd = mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; } else { struct sway_server_decoration *deco = decoration_from_surface(toplevel->base->surface); csd = !deco || deco->wlr_server_decoration->mode == WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT; } view_map(view, toplevel->base->surface, toplevel->requested.fullscreen, toplevel->requested.fullscreen_output, csd); transaction_commit_dirty(); xdg_shell_view->new_popup.notify = handle_new_popup; wl_signal_add(&toplevel->base->events.new_popup, &xdg_shell_view->new_popup); xdg_shell_view->request_maximize.notify = handle_request_maximize; wl_signal_add(&toplevel->events.request_maximize, &xdg_shell_view->request_maximize); xdg_shell_view->request_fullscreen.notify = handle_request_fullscreen; wl_signal_add(&toplevel->events.request_fullscreen, &xdg_shell_view->request_fullscreen); xdg_shell_view->request_move.notify = handle_request_move; wl_signal_add(&toplevel->events.request_move, &xdg_shell_view->request_move); xdg_shell_view->request_resize.notify = handle_request_resize; wl_signal_add(&toplevel->events.request_resize, &xdg_shell_view->request_resize); xdg_shell_view->set_title.notify = handle_set_title; wl_signal_add(&toplevel->events.set_title, &xdg_shell_view->set_title); xdg_shell_view->set_app_id.notify = handle_set_app_id; wl_signal_add(&toplevel->events.set_app_id, &xdg_shell_view->set_app_id); } static void handle_destroy(struct wl_listener *listener, void *data) { struct sway_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, destroy); struct sway_view *view = &xdg_shell_view->view; if (!sway_assert(view->surface == NULL, "Tried to destroy a mapped view")) { return; } wl_list_remove(&xdg_shell_view->destroy.link); wl_list_remove(&xdg_shell_view->map.link); wl_list_remove(&xdg_shell_view->unmap.link); wl_list_remove(&xdg_shell_view->commit.link); view->wlr_xdg_toplevel = NULL; if (view->xdg_decoration) { view->xdg_decoration->view = NULL; } view_begin_destroy(view); } struct sway_view *view_from_wlr_xdg_surface( struct wlr_xdg_surface *xdg_surface) { return xdg_surface->data; } void handle_xdg_shell_toplevel(struct wl_listener *listener, void *data) { struct wlr_xdg_toplevel *xdg_toplevel = data; sway_log(SWAY_DEBUG, "New xdg_shell toplevel title='%s' app_id='%s'", xdg_toplevel->title, xdg_toplevel->app_id); wlr_xdg_surface_ping(xdg_toplevel->base); struct sway_xdg_shell_view *xdg_shell_view = calloc(1, sizeof(struct sway_xdg_shell_view)); if (!sway_assert(xdg_shell_view, "Failed to allocate view")) { return; } if (!view_init(&xdg_shell_view->view, SWAY_VIEW_XDG_SHELL, &view_impl)) { free(xdg_shell_view); return; } xdg_shell_view->view.wlr_xdg_toplevel = xdg_toplevel; xdg_shell_view->map.notify = handle_map; wl_signal_add(&xdg_toplevel->base->surface->events.map, &xdg_shell_view->map); xdg_shell_view->unmap.notify = handle_unmap; wl_signal_add(&xdg_toplevel->base->surface->events.unmap, &xdg_shell_view->unmap); xdg_shell_view->commit.notify = handle_commit; wl_signal_add(&xdg_toplevel->base->surface->events.commit, &xdg_shell_view->commit); xdg_shell_view->destroy.notify = handle_destroy; wl_signal_add(&xdg_toplevel->events.destroy, &xdg_shell_view->destroy); wlr_scene_xdg_surface_create(xdg_shell_view->view.content_tree, xdg_toplevel->base); xdg_shell_view->image_capture_tree = wlr_scene_xdg_surface_create(&xdg_shell_view->view.image_capture_scene->tree, xdg_toplevel->base); xdg_toplevel->base->data = xdg_shell_view; } void xdg_toplevel_tag_manager_v1_handle_set_tag(struct wl_listener *listener, void *data) { const struct wlr_xdg_toplevel_tag_manager_v1_set_tag_event *event = data; struct sway_view *view = view_from_wlr_xdg_surface(event->toplevel->base); struct sway_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); free(xdg_shell_view->tag); xdg_shell_view->tag = strdup(event->tag); view_execute_criteria(view); transaction_commit_dirty(); } ================================================ FILE: sway/desktop/xwayland.c ================================================ #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "sway/desktop/transaction.h" #include "sway/input/cursor.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" #include "sway/output.h" #include "sway/scene_descriptor.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/server.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" static const char *atom_map[ATOM_LAST] = { [NET_WM_WINDOW_TYPE_NORMAL] = "_NET_WM_WINDOW_TYPE_NORMAL", [NET_WM_WINDOW_TYPE_DIALOG] = "_NET_WM_WINDOW_TYPE_DIALOG", [NET_WM_WINDOW_TYPE_UTILITY] = "_NET_WM_WINDOW_TYPE_UTILITY", [NET_WM_WINDOW_TYPE_TOOLBAR] = "_NET_WM_WINDOW_TYPE_TOOLBAR", [NET_WM_WINDOW_TYPE_SPLASH] = "_NET_WM_WINDOW_TYPE_SPLASH", [NET_WM_WINDOW_TYPE_MENU] = "_NET_WM_WINDOW_TYPE_MENU", [NET_WM_WINDOW_TYPE_DROPDOWN_MENU] = "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", [NET_WM_WINDOW_TYPE_POPUP_MENU] = "_NET_WM_WINDOW_TYPE_POPUP_MENU", [NET_WM_WINDOW_TYPE_TOOLTIP] = "_NET_WM_WINDOW_TYPE_TOOLTIP", [NET_WM_WINDOW_TYPE_NOTIFICATION] = "_NET_WM_WINDOW_TYPE_NOTIFICATION", [NET_WM_STATE_MODAL] = "_NET_WM_STATE_MODAL", }; static void unmanaged_handle_request_configure(struct wl_listener *listener, void *data) { struct sway_xwayland_unmanaged *surface = wl_container_of(listener, surface, request_configure); struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; struct wlr_xwayland_surface_configure_event *ev = data; wlr_xwayland_surface_configure(xsurface, ev->x, ev->y, ev->width, ev->height); } static void unmanaged_handle_set_geometry(struct wl_listener *listener, void *data) { struct sway_xwayland_unmanaged *surface = wl_container_of(listener, surface, set_geometry); struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; wlr_scene_node_set_position(&surface->surface_scene->buffer->node, xsurface->x, xsurface->y); } static void unmanaged_handle_map(struct wl_listener *listener, void *data) { struct sway_xwayland_unmanaged *surface = wl_container_of(listener, surface, map); struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; surface->surface_scene = wlr_scene_surface_create(root->layers.unmanaged, xsurface->surface); if (surface->surface_scene) { scene_descriptor_assign(&surface->surface_scene->buffer->node, SWAY_SCENE_DESC_XWAYLAND_UNMANAGED, surface); wlr_scene_node_set_position(&surface->surface_scene->buffer->node, xsurface->x, xsurface->y); wl_signal_add(&xsurface->events.set_geometry, &surface->set_geometry); surface->set_geometry.notify = unmanaged_handle_set_geometry; } if (wlr_xwayland_surface_override_redirect_wants_focus(xsurface)) { struct sway_seat *seat = input_manager_current_seat(); struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; wlr_xwayland_set_seat(xwayland, seat->wlr_seat); seat_set_focus_surface(seat, xsurface->surface, false); } } static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) { struct sway_xwayland_unmanaged *surface = wl_container_of(listener, surface, unmap); struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; if (surface->surface_scene) { wl_list_remove(&surface->set_geometry.link); wlr_scene_node_destroy(&surface->surface_scene->buffer->node); surface->surface_scene = NULL; } struct sway_seat *seat = input_manager_current_seat(); if (seat->wlr_seat->keyboard_state.focused_surface == xsurface->surface) { // This simply returns focus to the parent surface if there's one available. // This seems to handle JetBrains issues. if (xsurface->parent && xsurface->parent->surface && wlr_xwayland_surface_override_redirect_wants_focus(xsurface->parent)) { seat_set_focus_surface(seat, xsurface->parent->surface, false); return; } // Restore focus struct sway_node *previous = seat_get_focus_inactive(seat, &root->node); if (previous) { // Hack to get seat to re-focus the return value of get_focus seat_set_focus(seat, NULL); seat_set_focus(seat, previous); } } } static void unmanaged_handle_request_activate(struct wl_listener *listener, void *data) { struct sway_xwayland_unmanaged *surface = wl_container_of(listener, surface, request_activate); struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; if (xsurface->surface == NULL || !xsurface->surface->mapped) { return; } struct sway_seat *seat = input_manager_current_seat(); struct sway_container *focus = seat_get_focused_container(seat); if (focus && focus->view && focus->view->pid != xsurface->pid) { return; } seat_set_focus_surface(seat, xsurface->surface, false); } static void unmanaged_handle_associate(struct wl_listener *listener, void *data) { struct sway_xwayland_unmanaged *surface = wl_container_of(listener, surface, associate); struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; wl_signal_add(&xsurface->surface->events.map, &surface->map); surface->map.notify = unmanaged_handle_map; wl_signal_add(&xsurface->surface->events.unmap, &surface->unmap); surface->unmap.notify = unmanaged_handle_unmap; } static void unmanaged_handle_dissociate(struct wl_listener *listener, void *data) { struct sway_xwayland_unmanaged *surface = wl_container_of(listener, surface, dissociate); wl_list_remove(&surface->map.link); wl_list_remove(&surface->unmap.link); } static void unmanaged_handle_destroy(struct wl_listener *listener, void *data) { struct sway_xwayland_unmanaged *surface = wl_container_of(listener, surface, destroy); wl_list_remove(&surface->request_configure.link); wl_list_remove(&surface->associate.link); wl_list_remove(&surface->dissociate.link); wl_list_remove(&surface->destroy.link); wl_list_remove(&surface->override_redirect.link); wl_list_remove(&surface->request_activate.link); free(surface); } static void handle_map(struct wl_listener *listener, void *data); static void handle_associate(struct wl_listener *listener, void *data); struct sway_xwayland_view *create_xwayland_view(struct wlr_xwayland_surface *xsurface); static void unmanaged_handle_override_redirect(struct wl_listener *listener, void *data) { struct sway_xwayland_unmanaged *surface = wl_container_of(listener, surface, override_redirect); struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; bool associated = xsurface->surface != NULL; bool mapped = associated && xsurface->surface->mapped; if (mapped) { unmanaged_handle_unmap(&surface->unmap, NULL); } if (associated) { unmanaged_handle_dissociate(&surface->dissociate, NULL); } unmanaged_handle_destroy(&surface->destroy, NULL); xsurface->data = NULL; struct sway_xwayland_view *xwayland_view = create_xwayland_view(xsurface); if (associated) { handle_associate(&xwayland_view->associate, NULL); } if (mapped) { handle_map(&xwayland_view->map, xsurface); } } static struct sway_xwayland_unmanaged *create_unmanaged( struct wlr_xwayland_surface *xsurface) { struct sway_xwayland_unmanaged *surface = calloc(1, sizeof(struct sway_xwayland_unmanaged)); if (surface == NULL) { sway_log(SWAY_ERROR, "Allocation failed"); return NULL; } surface->wlr_xwayland_surface = xsurface; wl_signal_add(&xsurface->events.request_configure, &surface->request_configure); surface->request_configure.notify = unmanaged_handle_request_configure; wl_signal_add(&xsurface->events.associate, &surface->associate); surface->associate.notify = unmanaged_handle_associate; wl_signal_add(&xsurface->events.dissociate, &surface->dissociate); surface->dissociate.notify = unmanaged_handle_dissociate; wl_signal_add(&xsurface->events.destroy, &surface->destroy); surface->destroy.notify = unmanaged_handle_destroy; wl_signal_add(&xsurface->events.set_override_redirect, &surface->override_redirect); surface->override_redirect.notify = unmanaged_handle_override_redirect; wl_signal_add(&xsurface->events.request_activate, &surface->request_activate); surface->request_activate.notify = unmanaged_handle_request_activate; return surface; } static struct sway_xwayland_view *xwayland_view_from_view( struct sway_view *view) { if (!sway_assert(view->type == SWAY_VIEW_XWAYLAND, "Expected xwayland view")) { return NULL; } return (struct sway_xwayland_view *)view; } static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { if (xwayland_view_from_view(view) == NULL) { return NULL; } switch (prop) { case VIEW_PROP_TITLE: return view->wlr_xwayland_surface->title; case VIEW_PROP_CLASS: return view->wlr_xwayland_surface->class; case VIEW_PROP_INSTANCE: return view->wlr_xwayland_surface->instance; case VIEW_PROP_WINDOW_ROLE: return view->wlr_xwayland_surface->role; default: return NULL; } } static uint32_t get_int_prop(struct sway_view *view, enum sway_view_prop prop) { if (xwayland_view_from_view(view) == NULL) { return 0; } switch (prop) { case VIEW_PROP_X11_WINDOW_ID: return view->wlr_xwayland_surface->window_id; case VIEW_PROP_X11_PARENT_ID: if (view->wlr_xwayland_surface->parent) { return view->wlr_xwayland_surface->parent->window_id; } return 0; case VIEW_PROP_WINDOW_TYPE: if (view->wlr_xwayland_surface->window_type_len == 0) { return 0; } return view->wlr_xwayland_surface->window_type[0]; default: return 0; } } static uint32_t configure(struct sway_view *view, double lx, double ly, int width, int height) { struct sway_xwayland_view *xwayland_view = xwayland_view_from_view(view); if (xwayland_view == NULL) { return 0; } struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; wlr_xwayland_surface_configure(xsurface, lx, ly, width, height); // xwayland doesn't give us a serial for the configure return 0; } static void set_activated(struct sway_view *view, bool activated) { if (xwayland_view_from_view(view) == NULL) { return; } struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface; if (activated && surface->minimized) { wlr_xwayland_surface_set_minimized(surface, false); } wlr_xwayland_surface_activate(surface, activated); } static void set_tiled(struct sway_view *view, bool tiled) { if (xwayland_view_from_view(view) == NULL) { return; } struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface; wlr_xwayland_surface_set_maximized(surface, tiled, tiled); } static void set_fullscreen(struct sway_view *view, bool fullscreen) { if (xwayland_view_from_view(view) == NULL) { return; } struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface; wlr_xwayland_surface_set_fullscreen(surface, fullscreen); } static bool wants_floating(struct sway_view *view) { if (xwayland_view_from_view(view) == NULL) { return false; } struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface; struct sway_xwayland *xwayland = &server.xwayland; if (surface->modal) { return true; } for (size_t i = 0; i < surface->window_type_len; ++i) { xcb_atom_t type = surface->window_type[i]; if (type == xwayland->atoms[NET_WM_WINDOW_TYPE_DIALOG] || type == xwayland->atoms[NET_WM_WINDOW_TYPE_UTILITY] || type == xwayland->atoms[NET_WM_WINDOW_TYPE_TOOLBAR] || type == xwayland->atoms[NET_WM_WINDOW_TYPE_SPLASH]) { return true; } } xcb_size_hints_t *size_hints = surface->size_hints; if (size_hints != NULL && size_hints->min_width > 0 && size_hints->min_height > 0 && (size_hints->max_width == size_hints->min_width || size_hints->max_height == size_hints->min_height)) { return true; } return false; } static void handle_set_decorations(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_decorations); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; bool csd = xsurface->decorations != WLR_XWAYLAND_SURFACE_DECORATIONS_ALL; view_update_csd_from_client(view, csd); } static bool is_transient_for(struct sway_view *child, struct sway_view *ancestor) { if (xwayland_view_from_view(child) == NULL) { return false; } struct wlr_xwayland_surface *surface = child->wlr_xwayland_surface; while (surface) { if (surface->parent == ancestor->wlr_xwayland_surface) { return true; } surface = surface->parent; } return false; } static void _close(struct sway_view *view) { if (xwayland_view_from_view(view) == NULL) { return; } wlr_xwayland_surface_close(view->wlr_xwayland_surface); } static void destroy(struct sway_view *view) { struct sway_xwayland_view *xwayland_view = xwayland_view_from_view(view); if (xwayland_view == NULL) { return; } free(xwayland_view); } static void get_constraints(struct sway_view *view, double *min_width, double *max_width, double *min_height, double *max_height) { struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface; xcb_size_hints_t *size_hints = surface->size_hints; if (size_hints == NULL) { *min_width = DBL_MIN; *max_width = DBL_MAX; *min_height = DBL_MIN; *max_height = DBL_MAX; return; } *min_width = size_hints->min_width > 0 ? size_hints->min_width : DBL_MIN; *max_width = size_hints->max_width > 0 ? size_hints->max_width : DBL_MAX; *min_height = size_hints->min_height > 0 ? size_hints->min_height : DBL_MIN; *max_height = size_hints->max_height > 0 ? size_hints->max_height : DBL_MAX; } static const struct sway_view_impl view_impl = { .get_constraints = get_constraints, .get_string_prop = get_string_prop, .get_int_prop = get_int_prop, .configure = configure, .set_activated = set_activated, .set_tiled = set_tiled, .set_fullscreen = set_fullscreen, .wants_floating = wants_floating, .is_transient_for = is_transient_for, .close = _close, .destroy = destroy, }; static void handle_commit(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, commit); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; struct wlr_surface_state *state = &xsurface->surface->current; struct wlr_box new_geo = {0}; new_geo.width = state->width; new_geo.height = state->height; bool new_size = new_geo.width != view->geometry.width || new_geo.height != view->geometry.height; if (new_size) { // The client changed its surface size in this commit. For floating // containers, we resize the container to match. For tiling containers, // we only recenter the surface. memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box)); if (container_is_floating(view->container)) { view_update_size(view); transaction_commit_dirty_client(); } view_center_and_clip_surface(view); } if (view->container->node.instruction) { bool successful = transaction_notify_view_ready_by_geometry(view, xsurface->x, xsurface->y, state->width, state->height); // If we saved the view and this commit isn't what we're looking for // that means the user will never actually see the buffers submitted to // us here. Just send frame done events to these surfaces so they can // commit another time for us. if (view->saved_surface_tree && !successful) { view_send_frame_done(view); } } } static void handle_destroy(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, destroy); struct sway_view *view = &xwayland_view->view; if (view->surface) { view_unmap(view); wl_list_remove(&xwayland_view->commit.link); } xwayland_view->view.wlr_xwayland_surface = NULL; wl_list_remove(&xwayland_view->destroy.link); wl_list_remove(&xwayland_view->request_configure.link); wl_list_remove(&xwayland_view->request_fullscreen.link); wl_list_remove(&xwayland_view->request_minimize.link); wl_list_remove(&xwayland_view->request_move.link); wl_list_remove(&xwayland_view->request_resize.link); wl_list_remove(&xwayland_view->request_activate.link); wl_list_remove(&xwayland_view->set_title.link); wl_list_remove(&xwayland_view->set_class.link); wl_list_remove(&xwayland_view->set_role.link); wl_list_remove(&xwayland_view->set_startup_id.link); wl_list_remove(&xwayland_view->set_window_type.link); wl_list_remove(&xwayland_view->set_hints.link); wl_list_remove(&xwayland_view->set_decorations.link); wl_list_remove(&xwayland_view->associate.link); wl_list_remove(&xwayland_view->dissociate.link); wl_list_remove(&xwayland_view->override_redirect.link); view_begin_destroy(&xwayland_view->view); } static void handle_unmap(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, unmap); struct sway_view *view = &xwayland_view->view; if (!sway_assert(view->surface, "Cannot unmap unmapped view")) { return; } wl_list_remove(&xwayland_view->commit.link); wl_list_remove(&xwayland_view->surface_tree_destroy.link); wlr_scene_node_destroy(&xwayland_view->image_capture_scene_surface->buffer->node); xwayland_view->image_capture_scene_surface = NULL; if (xwayland_view->surface_tree) { wlr_scene_node_destroy(&xwayland_view->surface_tree->node); xwayland_view->surface_tree = NULL; } view_unmap(view); } static void handle_surface_tree_destroy(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, surface_tree_destroy); xwayland_view->surface_tree = NULL; } static void handle_map(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, map); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; view->natural_width = xsurface->width; view->natural_height = xsurface->height; // Wire up the commit listener here, because xwayland map/unmap can change // the underlying wlr_surface wl_signal_add(&xsurface->surface->events.commit, &xwayland_view->commit); xwayland_view->commit.notify = handle_commit; // Put it back into the tree view_map(view, xsurface->surface, xsurface->fullscreen, NULL, false); xwayland_view->surface_tree = wlr_scene_subsurface_tree_create( xwayland_view->view.content_tree, xsurface->surface); if (xwayland_view->surface_tree) { xwayland_view->surface_tree_destroy.notify = handle_surface_tree_destroy; wl_signal_add(&xwayland_view->surface_tree->node.events.destroy, &xwayland_view->surface_tree_destroy); } xwayland_view->image_capture_scene_surface = wlr_scene_surface_create(&xwayland_view->view.image_capture_scene->tree, xsurface->surface); transaction_commit_dirty(); } static void handle_dissociate(struct wl_listener *listener, void *data); static void handle_override_redirect(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, override_redirect); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; bool associated = xsurface->surface != NULL; bool mapped = associated && xsurface->surface->mapped; if (mapped) { handle_unmap(&xwayland_view->unmap, NULL); } if (associated) { handle_dissociate(&xwayland_view->dissociate, NULL); } handle_destroy(&xwayland_view->destroy, view); xsurface->data = NULL; struct sway_xwayland_unmanaged *unmanaged = create_unmanaged(xsurface); if (associated) { unmanaged_handle_associate(&unmanaged->associate, NULL); } if (mapped) { unmanaged_handle_map(&unmanaged->map, xsurface); } } static void handle_request_configure(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_configure); struct wlr_xwayland_surface_configure_event *ev = data; struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; if (xsurface->surface == NULL || !xsurface->surface->mapped) { wlr_xwayland_surface_configure(xsurface, ev->x, ev->y, ev->width, ev->height); return; } if (container_is_floating(view->container)) { // Respect minimum and maximum sizes view->natural_width = ev->width; view->natural_height = ev->height; container_floating_resize_and_center(view->container); configure(view, view->container->pending.content_x, view->container->pending.content_y, view->container->pending.content_width, view->container->pending.content_height); node_set_dirty(&view->container->node); } else { configure(view, view->container->current.content_x, view->container->current.content_y, view->container->current.content_width, view->container->current.content_height); } } static void handle_request_fullscreen(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_fullscreen); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; if (xsurface->surface == NULL || !xsurface->surface->mapped) { return; } container_set_fullscreen(view->container, xsurface->fullscreen); arrange_root(); transaction_commit_dirty(); } static void handle_request_minimize(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_minimize); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; if (xsurface->surface == NULL || !xsurface->surface->mapped) { return; } struct wlr_xwayland_minimize_event *e = data; struct sway_seat *seat = input_manager_current_seat(); bool focused = seat_get_focus(seat) == &view->container->node; wlr_xwayland_surface_set_minimized(xsurface, !focused && e->minimize); } static void handle_request_move(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_move); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; if (xsurface->surface == NULL || !xsurface->surface->mapped) { return; } if (!container_is_floating(view->container) || view->container->pending.fullscreen_mode) { return; } struct sway_seat *seat = input_manager_current_seat(); seatop_begin_move_floating(seat, view->container); } static void handle_request_resize(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_resize); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; if (xsurface->surface == NULL || !xsurface->surface->mapped) { return; } if (!container_is_floating(view->container)) { return; } struct wlr_xwayland_resize_event *e = data; struct sway_seat *seat = input_manager_current_seat(); seatop_begin_resize_floating(seat, view->container, e->edges); } static void handle_request_activate(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_activate); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; if (xsurface->surface == NULL || !xsurface->surface->mapped) { return; } view_request_activate(view, NULL); transaction_commit_dirty(); } static void handle_set_title(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_title); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; if (xsurface->surface == NULL || !xsurface->surface->mapped) { return; } view_update_title(view, false); view_execute_criteria(view); transaction_commit_dirty(); } static void handle_set_class(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_class); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; if (xsurface->surface == NULL || !xsurface->surface->mapped) { return; } view_execute_criteria(view); transaction_commit_dirty(); } static void handle_set_role(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_role); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; if (xsurface->surface == NULL || !xsurface->surface->mapped) { return; } view_execute_criteria(view); transaction_commit_dirty(); } static void handle_set_startup_id(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_startup_id); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; if (xsurface->startup_id == NULL) { return; } struct wlr_xdg_activation_token_v1 *token = wlr_xdg_activation_v1_find_token( server.xdg_activation_v1, xsurface->startup_id); if (token == NULL) { // Tried to activate with an unknown or expired token return; } struct launcher_ctx *ctx = token->data; if (token->data == NULL) { // TODO: support external launchers in X return; } view_assign_ctx(view, ctx); } static void handle_set_window_type(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_window_type); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; if (xsurface->surface == NULL || !xsurface->surface->mapped) { return; } view_execute_criteria(view); transaction_commit_dirty(); } static void handle_set_hints(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_hints); struct sway_view *view = &xwayland_view->view; struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; if (xsurface->surface == NULL || !xsurface->surface->mapped) { return; } const bool hints_urgency = xcb_icccm_wm_hints_get_urgency(xsurface->hints); if (!hints_urgency && view->urgent_timer) { // The view is in the timeout period. We'll ignore the request to // unset urgency so that the view remains urgent until the timer clears // it. return; } if (view->allow_request_urgent) { view_set_urgent(view, hints_urgency); } } static void handle_associate(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, associate); struct wlr_xwayland_surface *xsurface = xwayland_view->view.wlr_xwayland_surface; wl_signal_add(&xsurface->surface->events.unmap, &xwayland_view->unmap); xwayland_view->unmap.notify = handle_unmap; wl_signal_add(&xsurface->surface->events.map, &xwayland_view->map); xwayland_view->map.notify = handle_map; } static void handle_dissociate(struct wl_listener *listener, void *data) { struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, dissociate); wl_list_remove(&xwayland_view->map.link); wl_list_remove(&xwayland_view->unmap.link); } struct sway_view *view_from_wlr_xwayland_surface( struct wlr_xwayland_surface *xsurface) { return xsurface->data; } struct sway_xwayland_view *create_xwayland_view(struct wlr_xwayland_surface *xsurface) { sway_log(SWAY_DEBUG, "New xwayland surface title='%s' class='%s'", xsurface->title, xsurface->class); struct sway_xwayland_view *xwayland_view = calloc(1, sizeof(struct sway_xwayland_view)); if (!sway_assert(xwayland_view, "Failed to allocate view")) { return NULL; } if (!view_init(&xwayland_view->view, SWAY_VIEW_XWAYLAND, &view_impl)) { free(xwayland_view); return NULL; } xwayland_view->view.wlr_xwayland_surface = xsurface; wl_signal_add(&xsurface->events.destroy, &xwayland_view->destroy); xwayland_view->destroy.notify = handle_destroy; wl_signal_add(&xsurface->events.request_configure, &xwayland_view->request_configure); xwayland_view->request_configure.notify = handle_request_configure; wl_signal_add(&xsurface->events.request_fullscreen, &xwayland_view->request_fullscreen); xwayland_view->request_fullscreen.notify = handle_request_fullscreen; wl_signal_add(&xsurface->events.request_minimize, &xwayland_view->request_minimize); xwayland_view->request_minimize.notify = handle_request_minimize; wl_signal_add(&xsurface->events.request_activate, &xwayland_view->request_activate); xwayland_view->request_activate.notify = handle_request_activate; wl_signal_add(&xsurface->events.request_move, &xwayland_view->request_move); xwayland_view->request_move.notify = handle_request_move; wl_signal_add(&xsurface->events.request_resize, &xwayland_view->request_resize); xwayland_view->request_resize.notify = handle_request_resize; wl_signal_add(&xsurface->events.set_title, &xwayland_view->set_title); xwayland_view->set_title.notify = handle_set_title; wl_signal_add(&xsurface->events.set_class, &xwayland_view->set_class); xwayland_view->set_class.notify = handle_set_class; wl_signal_add(&xsurface->events.set_role, &xwayland_view->set_role); xwayland_view->set_role.notify = handle_set_role; wl_signal_add(&xsurface->events.set_startup_id, &xwayland_view->set_startup_id); xwayland_view->set_startup_id.notify = handle_set_startup_id; wl_signal_add(&xsurface->events.set_window_type, &xwayland_view->set_window_type); xwayland_view->set_window_type.notify = handle_set_window_type; wl_signal_add(&xsurface->events.set_hints, &xwayland_view->set_hints); xwayland_view->set_hints.notify = handle_set_hints; wl_signal_add(&xsurface->events.set_decorations, &xwayland_view->set_decorations); xwayland_view->set_decorations.notify = handle_set_decorations; wl_signal_add(&xsurface->events.associate, &xwayland_view->associate); xwayland_view->associate.notify = handle_associate; wl_signal_add(&xsurface->events.dissociate, &xwayland_view->dissociate); xwayland_view->dissociate.notify = handle_dissociate; wl_signal_add(&xsurface->events.set_override_redirect, &xwayland_view->override_redirect); xwayland_view->override_redirect.notify = handle_override_redirect; xsurface->data = xwayland_view; return xwayland_view; } void handle_xwayland_surface(struct wl_listener *listener, void *data) { struct wlr_xwayland_surface *xsurface = data; if (xsurface->override_redirect) { sway_log(SWAY_DEBUG, "New xwayland unmanaged surface"); create_unmanaged(xsurface); return; } create_xwayland_view(xsurface); } void handle_xwayland_ready(struct wl_listener *listener, void *data) { struct sway_server *server = wl_container_of(listener, server, xwayland_ready); struct sway_xwayland *xwayland = &server->xwayland; xcb_connection_t *xcb_conn = xcb_connect(NULL, NULL); int err = xcb_connection_has_error(xcb_conn); if (err) { sway_log(SWAY_ERROR, "XCB connect failed: %d", err); return; } xcb_intern_atom_cookie_t cookies[ATOM_LAST]; for (size_t i = 0; i < ATOM_LAST; i++) { cookies[i] = xcb_intern_atom(xcb_conn, 0, strlen(atom_map[i]), atom_map[i]); } for (size_t i = 0; i < ATOM_LAST; i++) { xcb_generic_error_t *error = NULL; xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(xcb_conn, cookies[i], &error); if (reply != NULL && error == NULL) { xwayland->atoms[i] = reply->atom; } free(reply); if (error != NULL) { sway_log(SWAY_ERROR, "could not resolve atom %s, X11 error code %d", atom_map[i], error->error_code); free(error); break; } } xcb_disconnect(xcb_conn); } ================================================ FILE: sway/input/cursor.c ================================================ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "log.h" #include "util.h" #include "sway/commands.h" #include "sway/input/cursor.h" #include "sway/input/keyboard.h" #include "sway/input/tablet.h" #include "sway/layers.h" #include "sway/output.h" #include "sway/scene_descriptor.h" #include "sway/server.h" #include "sway/tree/container.h" #include "sway/tree/root.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "wlr-layer-shell-unstable-v1-protocol.h" /** * Returns the node at the cursor's position. If there is a surface at that * location, it is stored in **surface (it may not be a view). */ struct sway_node *node_at_coords( struct sway_seat *seat, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { struct wlr_scene_node *scene_node = NULL; struct wlr_scene_node *node; wl_list_for_each_reverse(node, &root->layer_tree->children, link) { struct wlr_scene_tree *layer = wlr_scene_tree_from_node(node); bool non_interactive = scene_descriptor_try_get(&layer->node, SWAY_SCENE_DESC_NON_INTERACTIVE); if (non_interactive) { continue; } scene_node = wlr_scene_node_at(&layer->node, lx, ly, sx, sy); if (scene_node) { break; } } if (scene_node) { // determine what wlr_surface we clicked on if (scene_node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(scene_node); struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(scene_buffer); if (scene_surface) { *surface = scene_surface->surface; } } // determine what container we clicked on struct wlr_scene_node *current = scene_node; while (true) { struct sway_container *con = scene_descriptor_try_get(current, SWAY_SCENE_DESC_CONTAINER); if (!con) { struct sway_view *view = scene_descriptor_try_get(current, SWAY_SCENE_DESC_VIEW); if (view) { con = view->container; } } if (!con) { struct sway_popup_desc *popup = scene_descriptor_try_get(current, SWAY_SCENE_DESC_POPUP); if (popup && popup->view) { con = popup->view->container; } } if (con && (!con->view || con->view->surface)) { return &con->node; } if (scene_descriptor_try_get(current, SWAY_SCENE_DESC_LAYER_SHELL)) { // We don't want to feed through the current workspace on // layer shells return NULL; } #if WLR_HAS_XWAYLAND if (scene_descriptor_try_get(current, SWAY_SCENE_DESC_XWAYLAND_UNMANAGED)) { return NULL; } #endif if (!current->parent) { break; } current = ¤t->parent->node; } } // if we aren't on a container, determine what workspace we are on struct wlr_output *wlr_output = wlr_output_layout_output_at( root->output_layout, lx, ly); if (wlr_output == NULL) { return NULL; } struct sway_output *output = wlr_output->data; if (!output || !output->enabled) { // output is being destroyed or is being enabled return NULL; } struct sway_workspace *ws = output_get_active_workspace(output); if (!ws) { return NULL; } return &ws->node; } void cursor_rebase(struct sway_cursor *cursor) { uint32_t time_msec = get_current_time_in_msec(); seatop_rebase(cursor->seat, time_msec); } void cursor_rebase_all(void) { if (!root->outputs->length) { return; } struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { cursor_rebase(seat->cursor); } } void cursor_update_image(struct sway_cursor *cursor, struct sway_node *node) { if (node && node->type == N_CONTAINER) { // Try a node's resize edge enum wlr_edges edge = find_resize_edge(node->sway_container, NULL, cursor); if (edge == WLR_EDGE_NONE) { cursor_set_image(cursor, "default", NULL); } else if (container_is_floating(node->sway_container)) { cursor_set_image(cursor, wlr_xcursor_get_resize_name(edge), NULL); } else { if (edge & (WLR_EDGE_LEFT | WLR_EDGE_RIGHT)) { cursor_set_image(cursor, "col-resize", NULL); } else { cursor_set_image(cursor, "row-resize", NULL); } } } else { cursor_set_image(cursor, "default", NULL); } } static void cursor_hide(struct sway_cursor *cursor) { wlr_cursor_unset_image(cursor->cursor); cursor->hidden = true; wlr_seat_pointer_notify_clear_focus(cursor->seat->wlr_seat); } static int hide_notify(void *data) { struct sway_cursor *cursor = data; cursor_hide(cursor); return 1; } int cursor_get_timeout(struct sway_cursor *cursor) { if (cursor->pressed_button_count > 0) { // Do not hide cursor unless all buttons are released return 0; } struct seat_config *sc = seat_get_config(cursor->seat); if (!sc) { sc = seat_get_config_by_name("*"); } int timeout = sc ? sc->hide_cursor_timeout : 0; if (timeout < 0) { timeout = 0; } return timeout; } void cursor_notify_key_press(struct sway_cursor *cursor) { if (cursor->hidden) { return; } if (cursor->hide_when_typing == HIDE_WHEN_TYPING_DEFAULT) { // No cached value, need to lookup in the seat_config const struct seat_config *seat_config = seat_get_config(cursor->seat); if (!seat_config) { seat_config = seat_get_config_by_name("*"); if (!seat_config) { return; } } cursor->hide_when_typing = seat_config->hide_cursor_when_typing; // The default is currently disabled if (cursor->hide_when_typing == HIDE_WHEN_TYPING_DEFAULT) { cursor->hide_when_typing = HIDE_WHEN_TYPING_DISABLE; } } if (cursor->hide_when_typing == HIDE_WHEN_TYPING_ENABLE) { cursor_hide(cursor); } } static enum sway_input_idle_source idle_source_from_device( struct wlr_input_device *device) { switch (device->type) { case WLR_INPUT_DEVICE_KEYBOARD: return IDLE_SOURCE_KEYBOARD; case WLR_INPUT_DEVICE_POINTER: return IDLE_SOURCE_POINTER; case WLR_INPUT_DEVICE_TOUCH: return IDLE_SOURCE_TOUCH; case WLR_INPUT_DEVICE_TABLET: return IDLE_SOURCE_TABLET_TOOL; case WLR_INPUT_DEVICE_TABLET_PAD: return IDLE_SOURCE_TABLET_PAD; case WLR_INPUT_DEVICE_SWITCH: return IDLE_SOURCE_SWITCH; } abort(); } void cursor_handle_activity_from_idle_source(struct sway_cursor *cursor, enum sway_input_idle_source idle_source) { wl_event_source_timer_update( cursor->hide_source, cursor_get_timeout(cursor)); seat_idle_notify_activity(cursor->seat, idle_source); if (idle_source != IDLE_SOURCE_TOUCH) { cursor_unhide(cursor); } } void cursor_handle_activity_from_device(struct sway_cursor *cursor, struct wlr_input_device *device) { enum sway_input_idle_source idle_source = idle_source_from_device(device); cursor_handle_activity_from_idle_source(cursor, idle_source); } void cursor_unhide(struct sway_cursor *cursor) { if (!cursor->hidden) { return; } cursor->hidden = false; if (cursor->image_surface) { cursor_set_image_surface(cursor, cursor->image_surface, cursor->hotspot_x, cursor->hotspot_y, cursor->image_client); } else { const char *image = cursor->image; cursor->image = NULL; cursor_set_image(cursor, image, cursor->image_client); } cursor_rebase(cursor); wl_event_source_timer_update(cursor->hide_source, cursor_get_timeout(cursor)); } void pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, struct wlr_input_device *device, double dx, double dy, double dx_unaccel, double dy_unaccel) { wlr_relative_pointer_manager_v1_send_relative_motion( server.relative_pointer_manager, cursor->seat->wlr_seat, (uint64_t)time_msec * 1000, dx, dy, dx_unaccel, dy_unaccel); // Only apply pointer constraints to real pointer input. if (cursor->active_constraint && device->type == WLR_INPUT_DEVICE_POINTER) { struct wlr_surface *surface = NULL; double sx, sy; node_at_coords(cursor->seat, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); if (cursor->active_constraint->surface != surface) { return; } double sx_confined, sy_confined; if (!wlr_region_confine(&cursor->confine, sx, sy, sx + dx, sy + dy, &sx_confined, &sy_confined)) { return; } dx = sx_confined - sx; dy = sy_confined - sy; } wlr_cursor_move(cursor->cursor, device, dx, dy); seatop_pointer_motion(cursor->seat, time_msec); } static void handle_pointer_motion_relative( struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, motion); struct wlr_pointer_motion_event *e = data; cursor_handle_activity_from_device(cursor, &e->pointer->base); pointer_motion(cursor, e->time_msec, &e->pointer->base, e->delta_x, e->delta_y, e->unaccel_dx, e->unaccel_dy); } static void handle_pointer_motion_absolute( struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, motion_absolute); struct wlr_pointer_motion_absolute_event *event = data; cursor_handle_activity_from_device(cursor, &event->pointer->base); double lx, ly; wlr_cursor_absolute_to_layout_coords(cursor->cursor, &event->pointer->base, event->x, event->y, &lx, &ly); double dx = lx - cursor->cursor->x; double dy = ly - cursor->cursor->y; pointer_motion(cursor, event->time_msec, &event->pointer->base, dx, dy, dx, dy); } void dispatch_cursor_button(struct sway_cursor *cursor, struct wlr_input_device *device, uint32_t time_msec, uint32_t button, enum wl_pointer_button_state state) { if (time_msec == 0) { time_msec = get_current_time_in_msec(); } seatop_button(cursor->seat, time_msec, device, button, state); } static void handle_pointer_button(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, button); struct wlr_pointer_button_event *event = data; if (event->state == WL_POINTER_BUTTON_STATE_PRESSED) { cursor->pressed_button_count++; } else { if (cursor->pressed_button_count > 0) { cursor->pressed_button_count--; } else { sway_log(SWAY_ERROR, "Pressed button count was wrong"); } } cursor_handle_activity_from_device(cursor, &event->pointer->base); dispatch_cursor_button(cursor, &event->pointer->base, event->time_msec, event->button, event->state); } void dispatch_cursor_axis(struct sway_cursor *cursor, struct wlr_pointer_axis_event *event) { seatop_pointer_axis(cursor->seat, event); } static void handle_pointer_axis(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, axis); struct wlr_pointer_axis_event *event = data; cursor_handle_activity_from_device(cursor, &event->pointer->base); dispatch_cursor_axis(cursor, event); } static void handle_pointer_frame(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, frame); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); } static void handle_touch_down(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, touch_down); struct wlr_touch_down_event *event = data; cursor_handle_activity_from_device(cursor, &event->touch->base); cursor_hide(cursor); struct sway_seat *seat = cursor->seat; double lx, ly; wlr_cursor_absolute_to_layout_coords(cursor->cursor, &event->touch->base, event->x, event->y, &lx, &ly); seat->touch_id = event->touch_id; seat->touch_x = lx; seat->touch_y = ly; seatop_touch_down(seat, event, lx, ly); } static void handle_touch_up(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, touch_up); struct wlr_touch_up_event *event = data; cursor_handle_activity_from_device(cursor, &event->touch->base); struct sway_seat *seat = cursor->seat; if (cursor->simulating_pointer_from_touch) { if (cursor->pointer_touch_id == cursor->seat->touch_id) { cursor->pointer_touch_up = true; dispatch_cursor_button(cursor, &event->touch->base, event->time_msec, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); } } else { seatop_touch_up(seat, event); } } static void handle_touch_cancel(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, touch_cancel); struct wlr_touch_cancel_event *event = data; cursor_handle_activity_from_device(cursor, &event->touch->base); struct sway_seat *seat = cursor->seat; if (cursor->simulating_pointer_from_touch) { if (cursor->pointer_touch_id == cursor->seat->touch_id) { cursor->pointer_touch_up = true; dispatch_cursor_button(cursor, &event->touch->base, event->time_msec, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); } } else { seatop_touch_cancel(seat, event); } } static void handle_touch_motion(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, touch_motion); struct wlr_touch_motion_event *event = data; cursor_handle_activity_from_device(cursor, &event->touch->base); struct sway_seat *seat = cursor->seat; double lx, ly; wlr_cursor_absolute_to_layout_coords(cursor->cursor, &event->touch->base, event->x, event->y, &lx, &ly); if (seat->touch_id == event->touch_id) { seat->touch_x = lx; seat->touch_y = ly; drag_icons_update_position(seat); } if (cursor->simulating_pointer_from_touch) { if (seat->touch_id == cursor->pointer_touch_id) { double dx, dy; dx = lx - cursor->cursor->x; dy = ly - cursor->cursor->y; pointer_motion(cursor, event->time_msec, &event->touch->base, dx, dy, dx, dy); } } else { seatop_touch_motion(seat, event, lx, ly); } } static void handle_touch_frame(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, touch_frame); struct wlr_seat *wlr_seat = cursor->seat->wlr_seat; if (cursor->simulating_pointer_from_touch) { wlr_seat_pointer_notify_frame(wlr_seat); if (cursor->pointer_touch_up) { cursor->pointer_touch_up = false; cursor->simulating_pointer_from_touch = false; } } else { wlr_seat_touch_notify_frame(wlr_seat); } } static double apply_mapping_from_coord(double low, double high, double value) { if (isnan(value)) { return value; } return (value - low) / (high - low); } static void apply_mapping_from_region(struct wlr_input_device *device, struct input_config_mapped_from_region *region, double *x, double *y) { double x1 = region->x1, x2 = region->x2; double y1 = region->y1, y2 = region->y2; if (region->mm && device->type == WLR_INPUT_DEVICE_TABLET) { struct wlr_tablet *tablet = wlr_tablet_from_input_device(device); if (tablet->width_mm == 0 || tablet->height_mm == 0) { return; } x1 /= tablet->width_mm; x2 /= tablet->width_mm; y1 /= tablet->height_mm; y2 /= tablet->height_mm; } *x = apply_mapping_from_coord(x1, x2, *x); *y = apply_mapping_from_coord(y1, y2, *y); } static void handle_tablet_tool_position(struct sway_cursor *cursor, struct sway_tablet_tool *tool, bool change_x, bool change_y, double x, double y, double dx, double dy, int32_t time_msec) { if (!change_x && !change_y) { return; } struct sway_tablet *tablet = tool->tablet; struct sway_input_device *input_device = tablet->seat_device->input_device; struct input_config *ic = input_device_get_config(input_device); if (ic != NULL && ic->mapped_from_region != NULL) { apply_mapping_from_region(input_device->wlr_device, ic->mapped_from_region, &x, &y); } switch (tool->mode) { case SWAY_TABLET_TOOL_MODE_ABSOLUTE: wlr_cursor_warp_absolute(cursor->cursor, input_device->wlr_device, change_x ? x : NAN, change_y ? y : NAN); break; case SWAY_TABLET_TOOL_MODE_RELATIVE: wlr_cursor_move(cursor->cursor, input_device->wlr_device, dx, dy); break; } double sx, sy; struct wlr_surface *surface = NULL; struct sway_seat *seat = cursor->seat; node_at_coords(seat, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); // The logic for whether we should send a tablet event or an emulated pointer // event is tricky. It comes down to: // * If we began a drag on a non-tablet surface (simulating_pointer_from_tool_tip), // then we should continue sending emulated pointer events regardless of // whether the surface currently under us accepts tablet or not. // * Otherwise, if we are over a surface that accepts tablet, then we should // send tablet events. // * If we began a drag over a tablet surface, we should continue sending // tablet events until the drag is released, even if we are now over a // non-tablet surface. if (!cursor->simulating_pointer_from_tool_tip && ((surface && wlr_surface_accepts_tablet_v2(surface, tablet->tablet_v2)) || wlr_tablet_tool_v2_has_implicit_grab(tool->tablet_v2_tool))) { seatop_tablet_tool_motion(seat, tool, time_msec); } else { wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tablet_v2_tool); pointer_motion(cursor, time_msec, input_device->wlr_device, dx, dy, dx, dy); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); } } static void handle_tool_axis(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_axis); struct wlr_tablet_tool_axis_event *event = data; cursor_handle_activity_from_device(cursor, &event->tablet->base); struct sway_tablet_tool *sway_tool = event->tool->data; if (!sway_tool) { sway_log(SWAY_DEBUG, "tool axis before proximity"); return; } handle_tablet_tool_position(cursor, sway_tool, event->updated_axes & WLR_TABLET_TOOL_AXIS_X, event->updated_axes & WLR_TABLET_TOOL_AXIS_Y, event->x, event->y, event->dx, event->dy, event->time_msec); if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) { wlr_tablet_v2_tablet_tool_notify_pressure( sway_tool->tablet_v2_tool, event->pressure); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) { wlr_tablet_v2_tablet_tool_notify_distance( sway_tool->tablet_v2_tool, event->distance); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X) { sway_tool->tilt_x = event->tilt_x; } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y) { sway_tool->tilt_y = event->tilt_y; } if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) { wlr_tablet_v2_tablet_tool_notify_tilt( sway_tool->tablet_v2_tool, sway_tool->tilt_x, sway_tool->tilt_y); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) { wlr_tablet_v2_tablet_tool_notify_rotation( sway_tool->tablet_v2_tool, event->rotation); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) { wlr_tablet_v2_tablet_tool_notify_slider( sway_tool->tablet_v2_tool, event->slider); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) { wlr_tablet_v2_tablet_tool_notify_wheel( sway_tool->tablet_v2_tool, event->wheel_delta, 0); } } static void handle_tool_tip(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_tip); struct wlr_tablet_tool_tip_event *event = data; cursor_handle_activity_from_device(cursor, &event->tablet->base); struct sway_tablet_tool *sway_tool = event->tool->data; if (!sway_tool) { sway_log(SWAY_DEBUG, "tool tip before proximity"); return; } struct wlr_tablet_v2_tablet *tablet_v2 = sway_tool->tablet->tablet_v2; struct sway_seat *seat = cursor->seat; double sx, sy; struct wlr_surface *surface = NULL; node_at_coords(seat, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); if (cursor->simulating_pointer_from_tool_tip && event->state == WLR_TABLET_TOOL_TIP_UP) { cursor->simulating_pointer_from_tool_tip = false; dispatch_cursor_button(cursor, &event->tablet->base, event->time_msec, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); } else if (!surface || !wlr_surface_accepts_tablet_v2(surface, tablet_v2)) { // If we started holding the tool tip down on a surface that accepts // tablet v2, we should notify that surface if it gets released over a // surface that doesn't support v2. if (event->state == WLR_TABLET_TOOL_TIP_UP) { seatop_tablet_tool_tip(seat, sway_tool, event->time_msec, WLR_TABLET_TOOL_TIP_UP); } else { cursor->simulating_pointer_from_tool_tip = true; dispatch_cursor_button(cursor, &event->tablet->base, event->time_msec, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); } } else { seatop_tablet_tool_tip(seat, sway_tool, event->time_msec, event->state); } } static struct sway_tablet *get_tablet_for_device(struct sway_cursor *cursor, struct wlr_input_device *device) { struct sway_tablet *tablet; wl_list_for_each(tablet, &cursor->tablets, link) { if (tablet->seat_device->input_device->wlr_device == device) { return tablet; } } return NULL; } static void handle_tool_proximity(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_proximity); struct wlr_tablet_tool_proximity_event *event = data; cursor_handle_activity_from_device(cursor, &event->tablet->base); struct wlr_tablet_tool *tool = event->tool; if (!tool->data) { struct sway_tablet *tablet = get_tablet_for_device(cursor, &event->tablet->base); if (!tablet) { sway_log(SWAY_ERROR, "no tablet for tablet tool"); return; } sway_tablet_tool_configure(tablet, tool); } struct sway_tablet_tool *sway_tool = tool->data; if (!sway_tool) { sway_log(SWAY_ERROR, "tablet tool not initialized"); return; } if (event->state == WLR_TABLET_TOOL_PROXIMITY_OUT) { wlr_tablet_v2_tablet_tool_notify_proximity_out(sway_tool->tablet_v2_tool); return; } handle_tablet_tool_position(cursor, sway_tool, true, true, event->x, event->y, 0, 0, event->time_msec); } static void handle_tool_button(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_button); struct wlr_tablet_tool_button_event *event = data; cursor_handle_activity_from_device(cursor, &event->tablet->base); struct sway_tablet_tool *sway_tool = event->tool->data; if (!sway_tool) { sway_log(SWAY_DEBUG, "tool button before proximity"); return; } struct wlr_tablet_v2_tablet *tablet_v2 = sway_tool->tablet->tablet_v2; double sx, sy; struct wlr_surface *surface = NULL; node_at_coords(cursor->seat, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); // TODO: floating resize should support graphics tablet events struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(cursor->seat->wlr_seat); uint32_t modifiers = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; bool mod_pressed = modifiers & config->floating_mod; bool surface_supports_tablet_events = surface && wlr_surface_accepts_tablet_v2(surface, tablet_v2); // Simulate pointer when: // 1. The modifier key is pressed, OR // 2. The surface under the cursor does not support tablet events. bool should_simulate_pointer = mod_pressed || !surface_supports_tablet_events; // Similar to tool tip, we need to selectively simulate mouse events, but we // want to make sure that it is always consistent. Because all tool buttons // currently map to BTN_RIGHT, we need to keep count of how many tool // buttons are currently pressed down so we can send consistent events. // // The logic follows: // - If we are already simulating the pointer, we should continue to do so // until at least no tool button is held down. // - If we should simulate the pointer and no tool button is currently held // down, begin simulating the pointer. // - If neither of the above are true, send the tablet events. if ((cursor->tool_buttons > 0 && cursor->simulating_pointer_from_tool_button) || (cursor->tool_buttons == 0 && should_simulate_pointer)) { cursor->simulating_pointer_from_tool_button = true; // TODO: the user may want to configure which tool buttons are mapped to // which simulated pointer buttons switch (event->state) { case WLR_BUTTON_PRESSED: if (cursor->tool_buttons == 0) { dispatch_cursor_button(cursor, &event->tablet->base, event->time_msec, BTN_RIGHT, WL_POINTER_BUTTON_STATE_PRESSED); } break; case WLR_BUTTON_RELEASED: if (cursor->tool_buttons <= 1) { dispatch_cursor_button(cursor, &event->tablet->base, event->time_msec, BTN_RIGHT, WL_POINTER_BUTTON_STATE_RELEASED); } break; } wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); } else { cursor->simulating_pointer_from_tool_button = false; wlr_tablet_v2_tablet_tool_notify_button(sway_tool->tablet_v2_tool, event->button, (enum zwp_tablet_pad_v2_button_state)event->state); } // Update tool button count. switch (event->state) { case WLR_BUTTON_PRESSED: cursor->tool_buttons++; break; case WLR_BUTTON_RELEASED: if (cursor->tool_buttons == 0) { sway_log(SWAY_ERROR, "inconsistent tablet tool button events"); } else { cursor->tool_buttons--; } break; } } static void check_constraint_region(struct sway_cursor *cursor) { struct wlr_pointer_constraint_v1 *constraint = cursor->active_constraint; pixman_region32_t *region = &constraint->region; struct sway_view *view = view_from_wlr_surface(constraint->surface); if (cursor->active_confine_requires_warp && view) { cursor->active_confine_requires_warp = false; struct sway_container *con = view->container; double sx = cursor->cursor->x - con->pending.content_x + view->geometry.x; double sy = cursor->cursor->y - con->pending.content_y + view->geometry.y; if (!pixman_region32_contains_point(region, floor(sx), floor(sy), NULL)) { int nboxes; pixman_box32_t *boxes = pixman_region32_rectangles(region, &nboxes); if (nboxes > 0) { double sx = (boxes[0].x1 + boxes[0].x2) / 2.; double sy = (boxes[0].y1 + boxes[0].y2) / 2.; wlr_cursor_warp_closest(cursor->cursor, NULL, sx + con->pending.content_x - view->geometry.x, sy + con->pending.content_y - view->geometry.y); cursor_rebase(cursor); } } } // A locked pointer will result in an empty region, thus disallowing all movement if (constraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED) { pixman_region32_copy(&cursor->confine, region); } else { pixman_region32_clear(&cursor->confine); } } static void handle_constraint_commit(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, constraint_commit); struct wlr_pointer_constraint_v1 *constraint = cursor->active_constraint; assert(constraint->surface == data); check_constraint_region(cursor); } static void handle_pointer_constraint_set_region(struct wl_listener *listener, void *data) { struct sway_pointer_constraint *sway_constraint = wl_container_of(listener, sway_constraint, set_region); struct sway_cursor *cursor = sway_constraint->cursor; cursor->active_confine_requires_warp = true; } static void handle_request_pointer_set_cursor(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, request_set_cursor); if (!seatop_allows_set_cursor(cursor->seat)) { return; } struct wlr_seat_pointer_request_set_cursor_event *event = data; struct wl_client *focused_client = NULL; struct wlr_surface *focused_surface = cursor->seat->wlr_seat->pointer_state.focused_surface; if (focused_surface != NULL) { focused_client = wl_resource_get_client(focused_surface->resource); } // TODO: check cursor mode if (focused_client == NULL || event->seat_client->client != focused_client) { sway_log(SWAY_DEBUG, "denying request to set cursor from unfocused client"); return; } cursor_set_image_surface(cursor, event->surface, event->hotspot_x, event->hotspot_y, focused_client); } static void handle_pointer_hold_begin(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of( listener, cursor, hold_begin); struct wlr_pointer_hold_begin_event *event = data; cursor_handle_activity_from_device(cursor, &event->pointer->base); seatop_hold_begin(cursor->seat, event); } static void handle_pointer_hold_end(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of( listener, cursor, hold_end); struct wlr_pointer_hold_end_event *event = data; cursor_handle_activity_from_device(cursor, &event->pointer->base); seatop_hold_end(cursor->seat, event); } static void handle_pointer_pinch_begin(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of( listener, cursor, pinch_begin); struct wlr_pointer_pinch_begin_event *event = data; cursor_handle_activity_from_device(cursor, &event->pointer->base); seatop_pinch_begin(cursor->seat, event); } static void handle_pointer_pinch_update(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of( listener, cursor, pinch_update); struct wlr_pointer_pinch_update_event *event = data; cursor_handle_activity_from_device(cursor, &event->pointer->base); seatop_pinch_update(cursor->seat, event); } static void handle_pointer_pinch_end(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of( listener, cursor, pinch_end); struct wlr_pointer_pinch_end_event *event = data; cursor_handle_activity_from_device(cursor, &event->pointer->base); seatop_pinch_end(cursor->seat, event); } static void handle_pointer_swipe_begin(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of( listener, cursor, swipe_begin); struct wlr_pointer_swipe_begin_event *event = data; cursor_handle_activity_from_device(cursor, &event->pointer->base); seatop_swipe_begin(cursor->seat, event); } static void handle_pointer_swipe_update(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of( listener, cursor, swipe_update); struct wlr_pointer_swipe_update_event *event = data; cursor_handle_activity_from_device(cursor, &event->pointer->base); seatop_swipe_update(cursor->seat, event); } static void handle_pointer_swipe_end(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of( listener, cursor, swipe_end); struct wlr_pointer_swipe_end_event *event = data; cursor_handle_activity_from_device(cursor, &event->pointer->base); seatop_swipe_end(cursor->seat, event); } static void handle_image_surface_destroy(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, image_surface_destroy); cursor_set_image(cursor, NULL, cursor->image_client); cursor_rebase(cursor); } static void set_image_surface(struct sway_cursor *cursor, struct wlr_surface *surface) { wl_list_remove(&cursor->image_surface_destroy.link); cursor->image_surface = surface; if (surface) { wl_signal_add(&surface->events.destroy, &cursor->image_surface_destroy); } else { wl_list_init(&cursor->image_surface_destroy.link); } } void cursor_set_image(struct sway_cursor *cursor, const char *image, struct wl_client *client) { if (!(cursor->seat->wlr_seat->capabilities & WL_SEAT_CAPABILITY_POINTER)) { return; } const char *current_image = cursor->image; set_image_surface(cursor, NULL); cursor->image = image; cursor->hotspot_x = cursor->hotspot_y = 0; cursor->image_client = client; if (cursor->hidden) { return; } if (!image) { wlr_cursor_unset_image(cursor->cursor); } else if (!current_image || strcmp(current_image, image) != 0) { wlr_cursor_set_xcursor(cursor->cursor, cursor->xcursor_manager, image); } } void cursor_set_image_surface(struct sway_cursor *cursor, struct wlr_surface *surface, int32_t hotspot_x, int32_t hotspot_y, struct wl_client *client) { if (!(cursor->seat->wlr_seat->capabilities & WL_SEAT_CAPABILITY_POINTER)) { return; } set_image_surface(cursor, surface); cursor->image = NULL; cursor->hotspot_x = hotspot_x; cursor->hotspot_y = hotspot_y; cursor->image_client = client; if (cursor->hidden) { return; } wlr_cursor_set_surface(cursor->cursor, surface, hotspot_x, hotspot_y); } void sway_cursor_destroy(struct sway_cursor *cursor) { if (!cursor) { return; } wl_event_source_remove(cursor->hide_source); wl_list_remove(&cursor->image_surface_destroy.link); wl_list_remove(&cursor->hold_begin.link); wl_list_remove(&cursor->hold_end.link); wl_list_remove(&cursor->pinch_begin.link); wl_list_remove(&cursor->pinch_update.link); wl_list_remove(&cursor->pinch_end.link); wl_list_remove(&cursor->swipe_begin.link); wl_list_remove(&cursor->swipe_update.link); wl_list_remove(&cursor->swipe_end.link); wl_list_remove(&cursor->motion.link); wl_list_remove(&cursor->motion_absolute.link); wl_list_remove(&cursor->button.link); wl_list_remove(&cursor->axis.link); wl_list_remove(&cursor->frame.link); wl_list_remove(&cursor->touch_down.link); wl_list_remove(&cursor->touch_up.link); wl_list_remove(&cursor->touch_cancel.link); wl_list_remove(&cursor->touch_motion.link); wl_list_remove(&cursor->touch_frame.link); wl_list_remove(&cursor->tool_axis.link); wl_list_remove(&cursor->tool_tip.link); wl_list_remove(&cursor->tool_proximity.link); wl_list_remove(&cursor->tool_button.link); wl_list_remove(&cursor->request_set_cursor.link); wlr_xcursor_manager_destroy(cursor->xcursor_manager); wlr_cursor_destroy(cursor->cursor); free(cursor); } struct sway_cursor *sway_cursor_create(struct sway_seat *seat) { struct sway_cursor *cursor = calloc(1, sizeof(struct sway_cursor)); if (!sway_assert(cursor, "could not allocate sway cursor")) { return NULL; } struct wlr_cursor *wlr_cursor = wlr_cursor_create(); if (!sway_assert(wlr_cursor, "could not allocate wlr cursor")) { free(cursor); return NULL; } cursor->previous.x = wlr_cursor->x; cursor->previous.y = wlr_cursor->y; cursor->seat = seat; wlr_cursor_attach_output_layout(wlr_cursor, root->output_layout); cursor->hide_source = wl_event_loop_add_timer(server.wl_event_loop, hide_notify, cursor); wl_list_init(&cursor->image_surface_destroy.link); cursor->image_surface_destroy.notify = handle_image_surface_destroy; wl_signal_add(&wlr_cursor->events.hold_begin, &cursor->hold_begin); cursor->hold_begin.notify = handle_pointer_hold_begin; wl_signal_add(&wlr_cursor->events.hold_end, &cursor->hold_end); cursor->hold_end.notify = handle_pointer_hold_end; wl_signal_add(&wlr_cursor->events.pinch_begin, &cursor->pinch_begin); cursor->pinch_begin.notify = handle_pointer_pinch_begin; wl_signal_add(&wlr_cursor->events.pinch_update, &cursor->pinch_update); cursor->pinch_update.notify = handle_pointer_pinch_update; wl_signal_add(&wlr_cursor->events.pinch_end, &cursor->pinch_end); cursor->pinch_end.notify = handle_pointer_pinch_end; wl_signal_add(&wlr_cursor->events.swipe_begin, &cursor->swipe_begin); cursor->swipe_begin.notify = handle_pointer_swipe_begin; wl_signal_add(&wlr_cursor->events.swipe_update, &cursor->swipe_update); cursor->swipe_update.notify = handle_pointer_swipe_update; wl_signal_add(&wlr_cursor->events.swipe_end, &cursor->swipe_end); cursor->swipe_end.notify = handle_pointer_swipe_end; // input events wl_signal_add(&wlr_cursor->events.motion, &cursor->motion); cursor->motion.notify = handle_pointer_motion_relative; wl_signal_add(&wlr_cursor->events.motion_absolute, &cursor->motion_absolute); cursor->motion_absolute.notify = handle_pointer_motion_absolute; wl_signal_add(&wlr_cursor->events.button, &cursor->button); cursor->button.notify = handle_pointer_button; wl_signal_add(&wlr_cursor->events.axis, &cursor->axis); cursor->axis.notify = handle_pointer_axis; wl_signal_add(&wlr_cursor->events.frame, &cursor->frame); cursor->frame.notify = handle_pointer_frame; wl_signal_add(&wlr_cursor->events.touch_down, &cursor->touch_down); cursor->touch_down.notify = handle_touch_down; wl_signal_add(&wlr_cursor->events.touch_up, &cursor->touch_up); cursor->touch_up.notify = handle_touch_up; wl_signal_add(&wlr_cursor->events.touch_cancel, &cursor->touch_cancel); cursor->touch_cancel.notify = handle_touch_cancel; wl_signal_add(&wlr_cursor->events.touch_motion, &cursor->touch_motion); cursor->touch_motion.notify = handle_touch_motion; wl_signal_add(&wlr_cursor->events.touch_frame, &cursor->touch_frame); cursor->touch_frame.notify = handle_touch_frame; wl_signal_add(&wlr_cursor->events.tablet_tool_axis, &cursor->tool_axis); cursor->tool_axis.notify = handle_tool_axis; wl_signal_add(&wlr_cursor->events.tablet_tool_tip, &cursor->tool_tip); cursor->tool_tip.notify = handle_tool_tip; wl_signal_add(&wlr_cursor->events.tablet_tool_proximity, &cursor->tool_proximity); cursor->tool_proximity.notify = handle_tool_proximity; wl_signal_add(&wlr_cursor->events.tablet_tool_button, &cursor->tool_button); cursor->tool_button.notify = handle_tool_button; wl_signal_add(&seat->wlr_seat->events.request_set_cursor, &cursor->request_set_cursor); cursor->request_set_cursor.notify = handle_request_pointer_set_cursor; wl_list_init(&cursor->constraint_commit.link); wl_list_init(&cursor->tablets); wl_list_init(&cursor->tablet_pads); cursor->cursor = wlr_cursor; return cursor; } /** * Warps the cursor to the middle of the container argument. * Does nothing if the cursor is already inside the container and `force` is * false. If container is NULL, returns without doing anything. */ void cursor_warp_to_container(struct sway_cursor *cursor, struct sway_container *container, bool force) { if (!container) { return; } struct wlr_box box; container_get_box(container, &box); if (!force && wlr_box_contains_point(&box, cursor->cursor->x, cursor->cursor->y)) { return; } double x = container->pending.x + container->pending.width / 2.0; double y = container->pending.y + container->pending.height / 2.0; wlr_cursor_warp(cursor->cursor, NULL, x, y); cursor_unhide(cursor); } /** * Warps the cursor to the middle of the workspace argument. * If workspace is NULL, returns without doing anything. */ void cursor_warp_to_workspace(struct sway_cursor *cursor, struct sway_workspace *workspace) { if (!workspace) { return; } double x = workspace->x + workspace->width / 2.0; double y = workspace->y + workspace->height / 2.0; wlr_cursor_warp(cursor->cursor, NULL, x, y); cursor_unhide(cursor); } uint32_t get_mouse_bindsym(const char *name, char **error) { if (strncasecmp(name, "button", strlen("button")) == 0) { // Map to x11 mouse buttons int number = name[strlen("button")] - '0'; if (number < 1 || number > 9 || strlen(name) > strlen("button0")) { *error = strdup("Only buttons 1-9 are supported. For other mouse " "buttons, use the name of the event code."); return 0; } static const uint32_t buttons[] = {BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, SWAY_SCROLL_UP, SWAY_SCROLL_DOWN, SWAY_SCROLL_LEFT, SWAY_SCROLL_RIGHT, BTN_SIDE, BTN_EXTRA}; return buttons[number - 1]; } else if (has_prefix(name, "BTN_")) { // Get event code from name int code = libevdev_event_code_from_name(EV_KEY, name); if (code == -1) { *error = format_str("Unknown event %s", name); return 0; } return code; } return 0; } uint32_t get_mouse_bindcode(const char *name, char **error) { // Validate event code errno = 0; char *endptr; int code = strtol(name, &endptr, 10); if (endptr == name && code <= 0) { *error = strdup("Button event code must be a positive integer."); return 0; } else if (errno == ERANGE) { *error = strdup("Button event code out of range."); return 0; } const char *event = libevdev_event_code_get_name(EV_KEY, code); if (!event || !has_prefix(event, "BTN_")) { *error = format_str("Event code %d (%s) is not a button", code, event ? event : "(null)"); return 0; } return code; } uint32_t get_mouse_button(const char *name, char **error) { uint32_t button = get_mouse_bindsym(name, error); if (!button && !*error) { button = get_mouse_bindcode(name, error); } return button; } const char *get_mouse_button_name(uint32_t button) { const char *name = libevdev_event_code_get_name(EV_KEY, button); if (!name) { if (button == SWAY_SCROLL_UP) { name = "SWAY_SCROLL_UP"; } else if (button == SWAY_SCROLL_DOWN) { name = "SWAY_SCROLL_DOWN"; } else if (button == SWAY_SCROLL_LEFT) { name = "SWAY_SCROLL_LEFT"; } else if (button == SWAY_SCROLL_RIGHT) { name = "SWAY_SCROLL_RIGHT"; } } return name; } static void warp_to_constraint_cursor_hint(struct sway_cursor *cursor) { struct wlr_pointer_constraint_v1 *constraint = cursor->active_constraint; if (constraint->current.cursor_hint.enabled) { double sx = constraint->current.cursor_hint.x; double sy = constraint->current.cursor_hint.y; struct sway_view *view = view_from_wlr_surface(constraint->surface); if (!view) { return; } struct sway_container *con = view->container; double lx = sx + con->pending.content_x - view->geometry.x; double ly = sy + con->pending.content_y - view->geometry.y; wlr_cursor_warp(cursor->cursor, NULL, lx, ly); // Warp the pointer as well, so that on the next pointer rebase we don't // send an unexpected synthetic motion event to clients. wlr_seat_pointer_warp(constraint->seat, sx, sy); } } void handle_constraint_destroy(struct wl_listener *listener, void *data) { struct sway_pointer_constraint *sway_constraint = wl_container_of(listener, sway_constraint, destroy); struct wlr_pointer_constraint_v1 *constraint = data; struct sway_cursor *cursor = sway_constraint->cursor; wl_list_remove(&sway_constraint->set_region.link); wl_list_remove(&sway_constraint->destroy.link); if (cursor->active_constraint == constraint) { warp_to_constraint_cursor_hint(cursor); if (cursor->constraint_commit.link.next != NULL) { wl_list_remove(&cursor->constraint_commit.link); } wl_list_init(&cursor->constraint_commit.link); cursor->active_constraint = NULL; } free(sway_constraint); } void handle_pointer_constraint(struct wl_listener *listener, void *data) { struct wlr_pointer_constraint_v1 *constraint = data; struct sway_seat *seat = constraint->seat->data; struct sway_pointer_constraint *sway_constraint = calloc(1, sizeof(struct sway_pointer_constraint)); sway_constraint->cursor = seat->cursor; sway_constraint->constraint = constraint; sway_constraint->set_region.notify = handle_pointer_constraint_set_region; wl_signal_add(&constraint->events.set_region, &sway_constraint->set_region); sway_constraint->destroy.notify = handle_constraint_destroy; wl_signal_add(&constraint->events.destroy, &sway_constraint->destroy); struct wlr_surface *surface = seat->wlr_seat->keyboard_state.focused_surface; if (surface && surface == constraint->surface) { sway_cursor_constrain(seat->cursor, constraint); } } void sway_cursor_constrain(struct sway_cursor *cursor, struct wlr_pointer_constraint_v1 *constraint) { struct seat_config *config = seat_get_config(cursor->seat); if (!config) { config = seat_get_config_by_name("*"); } if (!config || config->allow_constrain == CONSTRAIN_DISABLE) { return; } if (cursor->active_constraint == constraint) { return; } wl_list_remove(&cursor->constraint_commit.link); if (cursor->active_constraint) { if (constraint == NULL) { warp_to_constraint_cursor_hint(cursor); } wlr_pointer_constraint_v1_send_deactivated( cursor->active_constraint); } cursor->active_constraint = constraint; if (constraint == NULL) { wl_list_init(&cursor->constraint_commit.link); return; } cursor->active_confine_requires_warp = true; // FIXME: Big hack, stolen from wlr_pointer_constraints_v1.c:121. // This is necessary because the focus may be set before the surface // has finished committing, which means that warping won't work properly, // since this code will be run *after* the focus has been set. // That is why we duplicate the code here. if (pixman_region32_not_empty(&constraint->current.region)) { pixman_region32_intersect(&constraint->region, &constraint->surface->input_region, &constraint->current.region); } else { pixman_region32_copy(&constraint->region, &constraint->surface->input_region); } check_constraint_region(cursor); wlr_pointer_constraint_v1_send_activated(constraint); cursor->constraint_commit.notify = handle_constraint_commit; wl_signal_add(&constraint->surface->events.commit, &cursor->constraint_commit); } void handle_request_set_cursor_shape(struct wl_listener *listener, void *data) { const struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; struct sway_seat *seat = event->seat_client->seat->data; if (!seatop_allows_set_cursor(seat)) { return; } struct wl_client *focused_client = NULL; struct wlr_surface *focused_surface = seat->wlr_seat->pointer_state.focused_surface; if (focused_surface != NULL) { focused_client = wl_resource_get_client(focused_surface->resource); } // TODO: check cursor mode if (focused_client == NULL || event->seat_client->client != focused_client) { sway_log(SWAY_DEBUG, "denying request to set cursor from unfocused client"); return; } cursor_set_image(seat->cursor, wlr_cursor_shape_v1_name(event->shape), focused_client); } ================================================ FILE: sway/input/input-manager.c ================================================ #include #include #include #include #include #include #include #include #include #include #include "sway/config.h" #include "sway/input/cursor.h" #include "sway/input/input-manager.h" #include "sway/input/keyboard.h" #include "sway/input/libinput.h" #include "sway/input/seat.h" #include "sway/ipc-server.h" #include "sway/server.h" #include "sway/tree/view.h" #include "stringop.h" #include "list.h" #include "log.h" #if WLR_HAS_LIBINPUT_BACKEND #include #endif #define DEFAULT_SEAT "seat0" struct input_config *current_input_config = NULL; struct seat_config *current_seat_config = NULL; struct sway_seat *input_manager_current_seat(void) { struct sway_seat *seat = config->handler_context.seat; if (!seat) { seat = input_manager_get_default_seat(); } return seat; } struct sway_seat *input_manager_get_default_seat(void) { return input_manager_get_seat(DEFAULT_SEAT, true); } struct sway_seat *input_manager_get_seat(const char *seat_name, bool create) { struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { if (strcmp(seat->wlr_seat->name, seat_name) == 0) { return seat; } } return create ? seat_create(seat_name) : NULL; } struct sway_seat *input_manager_sway_seat_from_wlr_seat(struct wlr_seat *wlr_seat) { struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { if (seat->wlr_seat == wlr_seat) { return seat; } } return NULL; } char *input_device_get_identifier(struct wlr_input_device *device) { int vendor = 0, product = 0; #if WLR_HAS_LIBINPUT_BACKEND if (wlr_input_device_is_libinput(device)) { struct libinput_device *libinput_dev = wlr_libinput_get_device_handle(device); vendor = libinput_device_get_id_vendor(libinput_dev); product = libinput_device_get_id_product(libinput_dev); } #endif char *name = strdup(device->name ? device->name : ""); strip_whitespace(name); char *p = name; for (; *p; ++p) { // There are in fact input devices with unprintable characters in its name if (*p == ' ' || !isprint(*p)) { *p = '_'; } } char *identifier = format_str("%d:%d:%s", vendor, product, name); free(name); return identifier; } static bool device_is_touchpad(struct sway_input_device *device) { #if WLR_HAS_LIBINPUT_BACKEND if (device->wlr_device->type != WLR_INPUT_DEVICE_POINTER || !wlr_input_device_is_libinput(device->wlr_device)) { return false; } struct libinput_device *libinput_device = wlr_libinput_get_device_handle(device->wlr_device); return libinput_device_config_tap_get_finger_count(libinput_device) > 0; #else return false; #endif } const char *input_device_get_type(struct sway_input_device *device) { switch (device->wlr_device->type) { case WLR_INPUT_DEVICE_POINTER: if (device_is_touchpad(device)) { return "touchpad"; } else { return "pointer"; } case WLR_INPUT_DEVICE_KEYBOARD: return "keyboard"; case WLR_INPUT_DEVICE_TOUCH: return "touch"; case WLR_INPUT_DEVICE_TABLET: return "tablet_tool"; case WLR_INPUT_DEVICE_TABLET_PAD: return "tablet_pad"; case WLR_INPUT_DEVICE_SWITCH: return "switch"; } return "unknown"; } static void apply_input_type_config(struct sway_input_device *input_device) { const char *device_type = input_device_get_type(input_device); struct input_config *type_config = NULL; for (int i = 0; i < config->input_type_configs->length; i++) { struct input_config *ic = config->input_type_configs->items[i]; if (strcmp(ic->identifier + 5, device_type) == 0) { type_config = ic; break; } } if (type_config == NULL) { return; } for (int i = 0; i < config->input_configs->length; i++) { struct input_config *ic = config->input_configs->items[i]; if (strcmp(input_device->identifier, ic->identifier) == 0) { struct input_config *current = new_input_config(ic->identifier); merge_input_config(current, type_config); merge_input_config(current, ic); current->input_type = device_type; config->input_configs->items[i] = current; free_input_config(ic); ic = NULL; break; } } } static struct sway_input_device *input_sway_device_from_wlr( struct wlr_input_device *device) { struct sway_input_device *input_device = NULL; wl_list_for_each(input_device, &server.input->devices, link) { if (input_device->wlr_device == device) { return input_device; } } return NULL; } static bool input_has_seat_fallback_configuration(void) { struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { struct seat_config *seat_config = seat_get_config(seat); if (seat_config && strcmp(seat_config->name, "*") != 0 && seat_config->fallback != -1) { return true; } } return false; } void input_manager_verify_fallback_seat(void) { struct sway_seat *seat = NULL; if (!input_has_seat_fallback_configuration()) { sway_log(SWAY_DEBUG, "no fallback seat config - creating default"); seat = input_manager_get_default_seat(); struct seat_config *sc = new_seat_config(seat->wlr_seat->name); sc->fallback = true; sc = store_seat_config(sc); input_manager_apply_seat_config(sc); } } static void handle_device_destroy(struct wl_listener *listener, void *data) { struct wlr_input_device *device = data; struct sway_input_device *input_device = input_sway_device_from_wlr(device); if (!sway_assert(input_device, "could not find sway device")) { return; } sway_log(SWAY_DEBUG, "removing device: '%s'", input_device->identifier); struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { seat_remove_device(seat, input_device); } ipc_event_input("removed", input_device); wl_list_remove(&input_device->link); wl_list_remove(&input_device->device_destroy.link); free(input_device->identifier); free(input_device); } static void handle_new_input(struct wl_listener *listener, void *data) { struct sway_input_manager *input = wl_container_of(listener, input, new_input); struct wlr_input_device *device = data; struct sway_input_device *input_device = calloc(1, sizeof(struct sway_input_device)); if (!sway_assert(input_device, "could not allocate input device")) { return; } device->data = input_device; input_device->wlr_device = device; input_device->identifier = input_device_get_identifier(device); wl_list_insert(&input->devices, &input_device->link); sway_log(SWAY_DEBUG, "adding device: '%s'", input_device->identifier); apply_input_type_config(input_device); #if WLR_HAS_LIBINPUT_BACKEND bool config_changed = sway_input_configure_libinput_device(input_device); #else bool config_changed = false; #endif wl_signal_add(&device->events.destroy, &input_device->device_destroy); input_device->device_destroy.notify = handle_device_destroy; input_manager_verify_fallback_seat(); bool added = false; struct sway_seat *seat = NULL; wl_list_for_each(seat, &input->seats, link) { struct seat_config *seat_config = seat_get_config(seat); bool has_attachment = seat_config && (seat_config_get_attachment(seat_config, input_device->identifier) || seat_config_get_attachment(seat_config, "*")); if (has_attachment) { seat_add_device(seat, input_device); added = true; } } if (!added) { wl_list_for_each(seat, &input->seats, link) { struct seat_config *seat_config = seat_get_config(seat); if (seat_config && seat_config->fallback == 1) { seat_add_device(seat, input_device); added = true; } } } if (!added) { sway_log(SWAY_DEBUG, "device '%s' is not configured on any seats", input_device->identifier); } ipc_event_input("added", input_device); if (config_changed) { ipc_event_input("libinput_config", input_device); } } static void handle_keyboard_shortcuts_inhibitor_destroy( struct wl_listener *listener, void *data) { struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor = wl_container_of(listener, sway_inhibitor, destroy); sway_log(SWAY_DEBUG, "Removing keyboard shortcuts inhibitor"); // sway_seat::keyboard_shortcuts_inhibitors wl_list_remove(&sway_inhibitor->link); wl_list_remove(&sway_inhibitor->destroy.link); free(sway_inhibitor); } static void handle_keyboard_shortcuts_inhibit_new_inhibitor( struct wl_listener *listener, void *data) { struct sway_input_manager *input_manager = wl_container_of(listener, input_manager, keyboard_shortcuts_inhibit_new_inhibitor); struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = data; sway_log(SWAY_DEBUG, "Adding keyboard shortcuts inhibitor"); struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor = calloc(1, sizeof(struct sway_keyboard_shortcuts_inhibitor)); if (!sway_assert(sway_inhibitor, "could not allocate keyboard " "shortcuts inhibitor")) { return; } sway_inhibitor->inhibitor = inhibitor; sway_inhibitor->destroy.notify = handle_keyboard_shortcuts_inhibitor_destroy; wl_signal_add(&inhibitor->events.destroy, &sway_inhibitor->destroy); // attach inhibitor to the seat it applies to struct sway_seat *seat = inhibitor->seat->data; wl_list_insert(&seat->keyboard_shortcuts_inhibitors, &sway_inhibitor->link); // per-view, seat-agnostic config via criteria struct sway_view *view = view_from_wlr_surface(inhibitor->surface); enum seat_config_shortcuts_inhibit inhibit = SHORTCUTS_INHIBIT_DEFAULT; if (view) { inhibit = view->shortcuts_inhibit; } if (inhibit == SHORTCUTS_INHIBIT_DEFAULT) { struct seat_config *config = seat_get_config(seat); if (!config) { config = seat_get_config_by_name("*"); } if (config) { inhibit = config->shortcuts_inhibit; } } if (inhibit == SHORTCUTS_INHIBIT_DISABLE) { /** * Here we deny to honour the inhibitor by never sending the * activate signal. We can not, however, destroy the inhibitor * because the protocol doesn't allow for it. So it will linger * until the client removes it im- or explicitly. But at least * it can only be one inhibitor per surface and seat at a time. * * We also want to allow the user to activate the inhibitor * manually later which is why we do this check here where the * inhibitor is already attached to its seat and ready for use. */ return; } wlr_keyboard_shortcuts_inhibitor_v1_activate(inhibitor); } void handle_virtual_keyboard(struct wl_listener *listener, void *data) { struct sway_input_manager *input_manager = wl_container_of(listener, input_manager, virtual_keyboard_new); struct wlr_virtual_keyboard_v1 *keyboard = data; struct wlr_input_device *device = &keyboard->keyboard.base; // TODO: Amend protocol to allow NULL seat struct sway_seat *seat = keyboard->seat ? input_manager_sway_seat_from_wlr_seat(keyboard->seat) : input_manager_get_default_seat(); struct sway_input_device *input_device = calloc(1, sizeof(struct sway_input_device)); if (!sway_assert(input_device, "could not allocate input device")) { return; } device->data = input_device; input_device->is_virtual = true; input_device->wlr_device = device; input_device->identifier = input_device_get_identifier(device); wl_list_insert(&input_manager->devices, &input_device->link); sway_log(SWAY_DEBUG, "adding virtual keyboard: '%s'", input_device->identifier); wl_signal_add(&device->events.destroy, &input_device->device_destroy); input_device->device_destroy.notify = handle_device_destroy; seat_add_device(seat, input_device); } void handle_virtual_pointer(struct wl_listener *listener, void *data) { struct sway_input_manager *input_manager = wl_container_of(listener, input_manager, 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 sway_seat *seat = event->suggested_seat ? input_manager_sway_seat_from_wlr_seat(event->suggested_seat) : input_manager_get_default_seat(); struct sway_input_device *input_device = calloc(1, sizeof(struct sway_input_device)); if (!sway_assert(input_device, "could not allocate input device")) { return; } device->data = input_device; input_device->is_virtual = true; input_device->wlr_device = device; input_device->identifier = input_device_get_identifier(device); wl_list_insert(&input_manager->devices, &input_device->link); sway_log(SWAY_DEBUG, "adding virtual pointer: '%s'", input_device->identifier); wl_signal_add(&device->events.destroy, &input_device->device_destroy); input_device->device_destroy.notify = handle_device_destroy; seat_add_device(seat, input_device); if (event->suggested_output) { wlr_cursor_map_input_to_output(seat->cursor->cursor, device, event->suggested_output); } } static void handle_transient_seat_manager_create_seat( struct wl_listener *listener, void *data) { struct wlr_transient_seat_v1 *transient_seat = data; static uint64_t i; char name[256]; snprintf(name, sizeof(name), "transient-%"PRIx64, i++); struct sway_seat *seat = seat_create(name); if (seat && seat->wlr_seat) { wlr_transient_seat_v1_ready(transient_seat, seat->wlr_seat); } else { wlr_transient_seat_v1_deny(transient_seat); } } struct sway_input_manager *input_manager_create(struct sway_server *server) { struct sway_input_manager *input = calloc(1, sizeof(struct sway_input_manager)); if (!input) { return NULL; } wl_list_init(&input->devices); wl_list_init(&input->seats); input->new_input.notify = handle_new_input; wl_signal_add(&server->backend->events.new_input, &input->new_input); input->virtual_keyboard = wlr_virtual_keyboard_manager_v1_create( server->wl_display); wl_signal_add(&input->virtual_keyboard->events.new_virtual_keyboard, &input->virtual_keyboard_new); input->virtual_keyboard_new.notify = handle_virtual_keyboard; input->virtual_pointer = wlr_virtual_pointer_manager_v1_create( server->wl_display ); wl_signal_add(&input->virtual_pointer->events.new_virtual_pointer, &input->virtual_pointer_new); input->virtual_pointer_new.notify = handle_virtual_pointer; input->keyboard_shortcuts_inhibit = wlr_keyboard_shortcuts_inhibit_v1_create(server->wl_display); input->keyboard_shortcuts_inhibit_new_inhibitor.notify = handle_keyboard_shortcuts_inhibit_new_inhibitor; wl_signal_add(&input->keyboard_shortcuts_inhibit->events.new_inhibitor, &input->keyboard_shortcuts_inhibit_new_inhibitor); input->pointer_gestures = wlr_pointer_gestures_v1_create(server->wl_display); input->transient_seat_manager = wlr_transient_seat_manager_v1_create(server->wl_display); assert(input->transient_seat_manager); input->transient_seat_create.notify = handle_transient_seat_manager_create_seat; wl_signal_add(&input->transient_seat_manager->events.create_seat, &input->transient_seat_create); return input; } void input_manager_finish(struct sway_input_manager *input) { wl_list_remove(&input->new_input.link); wl_list_remove(&input->virtual_keyboard_new.link); wl_list_remove(&input->virtual_pointer_new.link); wl_list_remove(&input->keyboard_shortcuts_inhibit_new_inhibitor.link); wl_list_remove(&input->transient_seat_create.link); } bool input_manager_has_focus(struct sway_node *node) { struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { if (seat_get_focus(seat) == node) { return true; } } return false; } void input_manager_set_focus(struct sway_node *node) { struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { seat_set_focus(seat, node); seat_consider_warp_to_focus(seat); } } /** * Re-translate keysyms if a change in the input config could affect them. */ static void retranslate_keysyms(struct input_config *input_config) { for (int i = 0; i < config->input_configs->length; ++i) { struct input_config *ic = config->input_configs->items[i]; if (ic->xkb_layout || ic->xkb_file) { // this is the first config with xkb_layout or xkb_file if (ic->identifier == input_config->identifier) { translate_keysyms(ic); } return; } } for (int i = 0; i < config->input_type_configs->length; ++i) { struct input_config *ic = config->input_type_configs->items[i]; if (ic->xkb_layout || ic->xkb_file) { // this is the first config with xkb_layout or xkb_file if (ic->identifier == input_config->identifier) { translate_keysyms(ic); } return; } } } static void input_manager_configure_input( struct sway_input_device *input_device) { #if WLR_HAS_LIBINPUT_BACKEND bool config_changed = sway_input_configure_libinput_device(input_device); #else bool config_changed = false; #endif struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { seat_configure_device(seat, input_device); } if (config_changed) { ipc_event_input("libinput_config", input_device); } } void input_manager_configure_all_input_mappings(void) { struct sway_input_device *input_device; wl_list_for_each(input_device, &server.input->devices, link) { struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { seat_configure_device_mapping(seat, input_device); } #if WLR_HAS_LIBINPUT_BACKEND // Input devices mapped to unavailable outputs get their libinput // send_events setting switched to false. We need to re-enable this // when the output appears. sway_input_configure_libinput_device_send_events(input_device); #endif } } void input_manager_apply_input_config(struct input_config *input_config) { struct sway_input_device *input_device = NULL; bool wildcard = strcmp(input_config->identifier, "*") == 0; bool type_wildcard = has_prefix(input_config->identifier, "type:"); wl_list_for_each(input_device, &server.input->devices, link) { bool type_matches = type_wildcard && strcmp(input_device_get_type(input_device), input_config->identifier + 5) == 0; if (strcmp(input_device->identifier, input_config->identifier) == 0 || wildcard || type_matches) { input_manager_configure_input(input_device); } } retranslate_keysyms(input_config); } void input_manager_reset_input(struct sway_input_device *input_device) { #if WLR_HAS_LIBINPUT_BACKEND sway_input_reset_libinput_device(input_device); #endif struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { seat_reset_device(seat, input_device); } } void input_manager_reset_all_inputs(void) { // Set the active keyboard to NULL to avoid spamming configuration updates // for all keyboard devices. struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { wlr_seat_set_keyboard(seat->wlr_seat, NULL); } struct sway_input_device *input_device = NULL; wl_list_for_each(input_device, &server.input->devices, link) { input_manager_reset_input(input_device); } // If there is at least one keyboard using the default keymap, repeat delay, // and repeat rate, then it is possible that there is a keyboard group that // need their keyboard disarmed. wl_list_for_each(seat, &server.input->seats, link) { struct sway_keyboard_group *group; wl_list_for_each(group, &seat->keyboard_groups, link) { sway_keyboard_disarm_key_repeat(group->seat_device->keyboard); } } } void input_manager_apply_seat_config(struct seat_config *seat_config) { sway_log(SWAY_DEBUG, "applying seat config for seat %s", seat_config->name); if (strcmp(seat_config->name, "*") == 0) { struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { // Only apply the wildcard config directly if there is no seat // specific config struct seat_config *sc = seat_get_config(seat); if (!sc) { sc = seat_config; } seat_apply_config(seat, sc); } } else { struct sway_seat *seat = input_manager_get_seat(seat_config->name, true); if (!seat) { return; } seat_apply_config(seat, seat_config); } // for every device, try to add it to a seat and if no seat has it // attached, add it to the fallback seats. struct sway_input_device *input_device = NULL; wl_list_for_each(input_device, &server.input->devices, link) { list_t *seat_list = create_list(); struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { struct seat_config *seat_config = seat_get_config(seat); if (!seat_config) { continue; } if (seat_config_get_attachment(seat_config, "*") || seat_config_get_attachment(seat_config, input_device->identifier)) { list_add(seat_list, seat); } } if (seat_list->length) { wl_list_for_each(seat, &server.input->seats, link) { bool attached = false; for (int i = 0; i < seat_list->length; ++i) { if (seat == seat_list->items[i]) { attached = true; break; } } if (attached) { seat_add_device(seat, input_device); } else { seat_remove_device(seat, input_device); } } } else { wl_list_for_each(seat, &server.input->seats, link) { struct seat_config *seat_config = seat_get_config(seat); if (seat_config && seat_config->fallback == 1) { seat_add_device(seat, input_device); } else { seat_remove_device(seat, input_device); } } } list_free(seat_list); } } void input_manager_configure_xcursor(void) { struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { seat_configure_xcursor(seat); } } struct input_config *input_device_get_config(struct sway_input_device *device) { struct input_config *wildcard_config = NULL; struct input_config *input_config = NULL; for (int i = 0; i < config->input_configs->length; ++i) { input_config = config->input_configs->items[i]; if (strcmp(input_config->identifier, device->identifier) == 0) { return input_config; } else if (strcmp(input_config->identifier, "*") == 0) { wildcard_config = input_config; } } const char *device_type = input_device_get_type(device); for (int i = 0; i < config->input_type_configs->length; ++i) { input_config = config->input_type_configs->items[i]; if (strcmp(input_config->identifier + 5, device_type) == 0) { return input_config; } } return wildcard_config; } ================================================ FILE: sway/input/keyboard.c ================================================ #include #include #include #include #include #include #include #include #include #include #include "sway/commands.h" #include "sway/input/input-manager.h" #include "sway/input/keyboard.h" #include "sway/input/seat.h" #include "sway/input/cursor.h" #include "sway/ipc-server.h" #include "sway/server.h" #include "log.h" #if WLR_HAS_SESSION #include #endif static struct modifier_key { char *name; uint32_t mod; } modifiers[] = { { XKB_MOD_NAME_SHIFT, WLR_MODIFIER_SHIFT }, { XKB_MOD_NAME_CAPS, WLR_MODIFIER_CAPS }, { XKB_MOD_NAME_CTRL, WLR_MODIFIER_CTRL }, { "Ctrl", WLR_MODIFIER_CTRL }, { XKB_MOD_NAME_ALT, WLR_MODIFIER_ALT }, { "Alt", WLR_MODIFIER_ALT }, { XKB_MOD_NAME_NUM, WLR_MODIFIER_MOD2 }, { "Mod3", WLR_MODIFIER_MOD3 }, { XKB_MOD_NAME_LOGO, WLR_MODIFIER_LOGO }, { "Super", WLR_MODIFIER_LOGO }, { "Mod5", WLR_MODIFIER_MOD5 }, }; uint32_t get_modifier_mask_by_name(const char *name) { int i; for (i = 0; i < (int)(sizeof(modifiers) / sizeof(struct modifier_key)); ++i) { if (strcasecmp(modifiers[i].name, name) == 0) { return modifiers[i].mod; } } return 0; } const char *get_modifier_name_by_mask(uint32_t modifier) { int i; for (i = 0; i < (int)(sizeof(modifiers) / sizeof(struct modifier_key)); ++i) { if (modifiers[i].mod == modifier) { return modifiers[i].name; } } return NULL; } int get_modifier_names(const char **names, uint32_t modifier_masks) { int length = 0; int i; for (i = 0; i < (int)(sizeof(modifiers) / sizeof(struct modifier_key)); ++i) { if ((modifier_masks & modifiers[i].mod) != 0) { names[length] = modifiers[i].name; ++length; modifier_masks ^= modifiers[i].mod; } } return length; } /** * Remove all key ids associated to a keycode from the list of pressed keys */ static bool state_erase_key(struct sway_shortcut_state *state, uint32_t keycode) { bool found = false; size_t j = 0; for (size_t i = 0; i < state->npressed; ++i) { if (i > j) { state->pressed_keys[j] = state->pressed_keys[i]; state->pressed_keycodes[j] = state->pressed_keycodes[i]; } if (state->pressed_keycodes[i] != keycode) { ++j; } else { found = true; } } while(state->npressed > j) { --state->npressed; state->pressed_keys[state->npressed] = 0; state->pressed_keycodes[state->npressed] = 0; } state->current_key = 0; return found; } /** * Add a key id (with associated keycode) to the list of pressed keys, * if the list is not full. */ static void state_add_key(struct sway_shortcut_state *state, uint32_t keycode, uint32_t key_id) { if (state->npressed >= SWAY_KEYBOARD_PRESSED_KEYS_CAP) { return; } size_t i = 0; while (i < state->npressed && state->pressed_keys[i] < key_id) { ++i; } size_t j = state->npressed; while (j > i) { state->pressed_keys[j] = state->pressed_keys[j - 1]; state->pressed_keycodes[j] = state->pressed_keycodes[j - 1]; --j; } state->pressed_keys[i] = key_id; state->pressed_keycodes[i] = keycode; state->npressed++; state->current_key = key_id; } /** * Update the shortcut model state in response to new input */ static bool update_shortcut_state(struct sway_shortcut_state *state, uint32_t keycode, enum wl_keyboard_key_state keystate, uint32_t new_key, uint32_t raw_modifiers) { bool last_key_was_a_modifier = raw_modifiers != state->last_raw_modifiers; state->last_raw_modifiers = raw_modifiers; if (last_key_was_a_modifier && state->last_keycode) { // Last pressed key before this one was a modifier state_erase_key(state, state->last_keycode); } if (keystate == WL_KEYBOARD_KEY_STATE_PRESSED) { // Add current key to set; there may be duplicates state_add_key(state, keycode, new_key); state->last_keycode = keycode; } else { return state_erase_key(state, keycode); } return false; } /** * If one exists, finds a binding which matches the shortcut model state, * current modifiers, release state, and locked state. */ static void get_active_binding(const struct sway_shortcut_state *state, list_t *bindings, struct sway_binding **current_binding, uint32_t modifiers, bool release, bool locked, bool inhibited, const char *input, bool exact_input, xkb_layout_index_t group) { for (int i = 0; i < bindings->length; ++i) { struct sway_binding *binding = bindings->items[i]; bool binding_locked = (binding->flags & BINDING_LOCKED) != 0; bool binding_inhibited = (binding->flags & BINDING_INHIBITED) != 0; bool binding_release = binding->flags & BINDING_RELEASE; if (modifiers ^ binding->modifiers || release != binding_release || locked > binding_locked || inhibited > binding_inhibited || (binding->group != XKB_LAYOUT_INVALID && binding->group != group) || (strcmp(binding->input, input) != 0 && (strcmp(binding->input, "*") != 0 || exact_input))) { continue; } bool match = false; if (state->npressed == (size_t)binding->keys->length) { match = true; for (size_t j = 0; j < state->npressed; j++) { uint32_t key = *(uint32_t *)binding->keys->items[j]; if (key != state->pressed_keys[j]) { match = false; break; } } } else if (binding->keys->length == 1) { /* * If no multiple-key binding has matched, try looking for * single-key bindings that match the newly-pressed key. */ match = state->current_key == *(uint32_t *)binding->keys->items[0]; } if (!match) { continue; } if (*current_binding) { if (*current_binding == binding) { continue; } bool current_locked = ((*current_binding)->flags & BINDING_LOCKED) != 0; bool current_inhibited = ((*current_binding)->flags & BINDING_INHIBITED) != 0; bool current_input = strcmp((*current_binding)->input, input) == 0; bool current_group_set = (*current_binding)->group != XKB_LAYOUT_INVALID; bool binding_input = strcmp(binding->input, input) == 0; bool binding_group_set = binding->group != XKB_LAYOUT_INVALID; if (current_input == binding_input && current_locked == binding_locked && current_inhibited == binding_inhibited && current_group_set == binding_group_set) { sway_log(SWAY_DEBUG, "Encountered conflicting bindings %d and %d", (*current_binding)->order, binding->order); continue; } if (current_input && !binding_input) { continue; // Prefer the correct input } if (current_input == binding_input && (*current_binding)->group == group) { continue; // Prefer correct group for matching inputs } if (current_input == binding_input && current_group_set == binding_group_set && current_locked == locked) { continue; // Prefer correct lock state for matching input+group } if (current_input == binding_input && current_group_set == binding_group_set && current_locked == binding_locked && current_inhibited == inhibited) { // Prefer correct inhibition state for matching // input+group+locked continue; } } *current_binding = binding; if (strcmp((*current_binding)->input, input) == 0 && (((*current_binding)->flags & BINDING_LOCKED) == locked) && (((*current_binding)->flags & BINDING_INHIBITED) == inhibited) && (*current_binding)->group == group) { return; // If a perfect match is found, quit searching } } } /** * Execute a built-in, hardcoded compositor binding. These are triggered from a * single keysym. * * Returns true if the keysym was handled by a binding and false if the event * should be propagated to clients. */ static bool keyboard_execute_compositor_binding(struct sway_keyboard *keyboard, const xkb_keysym_t *pressed_keysyms, uint32_t modifiers, size_t keysyms_len) { for (size_t i = 0; i < keysyms_len; ++i) { xkb_keysym_t keysym = pressed_keysyms[i]; if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { #if WLR_HAS_SESSION if (server.session) { unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1; wlr_session_change_vt(server.session, vt); } #endif return true; } } return false; } static bool keyboard_execute_pointer_keysyms(struct sway_keyboard *keyboard, uint32_t time, const xkb_keysym_t *pressed_keysyms, size_t keysyms_len, enum wl_keyboard_key_state state) { struct sway_cursor *cursor = keyboard->seat_device->sway_seat->cursor; for (size_t i = 0; i < keysyms_len; ++i) { xkb_keysym_t keysym = pressed_keysyms[i]; uint32_t button = wlr_keyboard_keysym_to_pointer_button(keysym); if (button != 0) { dispatch_cursor_button(cursor, &keyboard->wlr->base, time, button, (enum wl_pointer_button_state)state); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); return true; } int dx, dy; wlr_keyboard_keysym_to_pointer_motion(keysym, &dx, &dy); if (state == WL_KEYBOARD_KEY_STATE_PRESSED && (dx != 0 || dy != 0)) { dx *= 10; dy *= 10; pointer_motion(cursor, time, &keyboard->wlr->base, dx, dy, dx, dy); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); return true; } } return false; } /** * Get keysyms and modifiers from the keyboard as xkb sees them. * * This uses the xkb keysyms translation based on pressed modifiers and clears * the consumed modifiers from the list of modifiers passed to keybind * detection. * * On US layout, pressing Alt+Shift+2 will trigger Alt+@. */ static size_t keyboard_keysyms_translated(struct sway_keyboard *keyboard, xkb_keycode_t keycode, const xkb_keysym_t **keysyms, uint32_t *modifiers) { *modifiers = wlr_keyboard_get_modifiers(keyboard->wlr); xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2( keyboard->wlr->xkb_state, keycode, XKB_CONSUMED_MODE_XKB); *modifiers = *modifiers & ~consumed; return xkb_state_key_get_syms(keyboard->wlr->xkb_state, keycode, keysyms); } /** * Get keysyms and modifiers from the keyboard as if modifiers didn't change * keysyms. * * This avoids the xkb keysym translation based on modifiers considered pressed * in the state. * * This will trigger keybinds such as Alt+Shift+2. */ static size_t keyboard_keysyms_raw(struct sway_keyboard *keyboard, xkb_keycode_t keycode, const xkb_keysym_t **keysyms, uint32_t *modifiers) { *modifiers = wlr_keyboard_get_modifiers(keyboard->wlr); xkb_layout_index_t layout_index = xkb_state_key_get_layout( keyboard->wlr->xkb_state, keycode); return xkb_keymap_key_get_syms_by_level(keyboard->wlr->keymap, keycode, layout_index, 0, keysyms); } void sway_keyboard_disarm_key_repeat(struct sway_keyboard *keyboard) { if (!keyboard) { return; } keyboard->repeat_binding = NULL; if (wl_event_source_timer_update(keyboard->key_repeat_source, 0) < 0) { sway_log(SWAY_DEBUG, "failed to disarm key repeat timer"); } } struct key_info { uint32_t keycode; uint32_t code_modifiers; const xkb_keysym_t *raw_keysyms; uint32_t raw_modifiers; size_t raw_keysyms_len; const xkb_keysym_t *translated_keysyms; uint32_t translated_modifiers; size_t translated_keysyms_len; }; static void update_keyboard_state(struct sway_keyboard *keyboard, uint32_t raw_keycode, enum wl_keyboard_key_state keystate, struct key_info *keyinfo) { // Identify new keycode, raw keysym(s), and translated keysym(s) keyinfo->keycode = raw_keycode + 8; keyinfo->raw_keysyms_len = keyboard_keysyms_raw(keyboard, keyinfo->keycode, &keyinfo->raw_keysyms, &keyinfo->raw_modifiers); keyinfo->translated_keysyms_len = keyboard_keysyms_translated(keyboard, keyinfo->keycode, &keyinfo->translated_keysyms, &keyinfo->translated_modifiers); keyinfo->code_modifiers = wlr_keyboard_get_modifiers(keyboard->wlr); // Update shortcut model keyinfo update_shortcut_state(&keyboard->state_keycodes, raw_keycode, keystate, keyinfo->keycode, keyinfo->code_modifiers); for (size_t i = 0; i < keyinfo->raw_keysyms_len; ++i) { update_shortcut_state(&keyboard->state_keysyms_raw, raw_keycode, keystate, keyinfo->raw_keysyms[i], keyinfo->code_modifiers); } for (size_t i = 0; i < keyinfo->translated_keysyms_len; ++i) { update_shortcut_state(&keyboard->state_keysyms_translated, raw_keycode, keystate, keyinfo->translated_keysyms[i], keyinfo->code_modifiers); } } /** * Get keyboard grab of the seat from sway_keyboard if we should forward events * to it. * * Returns NULL if the keyboard is not grabbed by an input method, * or if event is from virtual keyboard of the same client as grab. * TODO: see swaywm/wlroots#2322 */ static struct wlr_input_method_keyboard_grab_v2 *keyboard_get_im_grab( struct sway_keyboard *keyboard) { struct wlr_input_method_v2 *input_method = keyboard->seat_device-> sway_seat->im_relay.input_method; struct wlr_virtual_keyboard_v1 *virtual_keyboard = wlr_input_device_get_virtual_keyboard(keyboard->seat_device->input_device->wlr_device); if (!input_method || !input_method->keyboard_grab || (virtual_keyboard && wl_resource_get_client(virtual_keyboard->resource) == wl_resource_get_client(input_method->keyboard_grab->resource))) { return NULL; } return input_method->keyboard_grab; } static void handle_key_event(struct sway_keyboard *keyboard, struct wlr_keyboard_key_event *event) { struct sway_seat *seat = keyboard->seat_device->sway_seat; struct wlr_seat *wlr_seat = seat->wlr_seat; struct wlr_input_device *wlr_device = keyboard->seat_device->input_device->wlr_device; char *device_identifier = input_device_get_identifier(wlr_device); bool exact_identifier = keyboard->wlr->group != NULL; seat_idle_notify_activity(seat, IDLE_SOURCE_KEYBOARD); bool locked = server.session_lock.lock; struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor = keyboard_shortcuts_inhibitor_get_for_focused_surface(seat); bool shortcuts_inhibited = sway_inhibitor && sway_inhibitor->inhibitor->active; if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { cursor_notify_key_press(seat->cursor); } // Identify new keycode, raw keysym(s), and translated keysym(s) struct key_info keyinfo; update_keyboard_state(keyboard, event->keycode, event->state, &keyinfo); bool handled = false; // Identify active release binding struct sway_binding *binding_released = NULL; get_active_binding(&keyboard->state_keycodes, config->current_mode->keycode_bindings, &binding_released, keyinfo.code_modifiers, true, locked, shortcuts_inhibited, device_identifier, exact_identifier, keyboard->effective_layout); get_active_binding(&keyboard->state_keysyms_raw, config->current_mode->keysym_bindings, &binding_released, keyinfo.raw_modifiers, true, locked, shortcuts_inhibited, device_identifier, exact_identifier, keyboard->effective_layout); get_active_binding(&keyboard->state_keysyms_translated, config->current_mode->keysym_bindings, &binding_released, keyinfo.translated_modifiers, true, locked, shortcuts_inhibited, device_identifier, exact_identifier, keyboard->effective_layout); // Execute stored release binding once no longer active if (keyboard->held_binding && binding_released != keyboard->held_binding && event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { seat_execute_command(seat, keyboard->held_binding); handled = true; } if (binding_released != keyboard->held_binding) { keyboard->held_binding = NULL; } if (binding_released && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { keyboard->held_binding = binding_released; } // Identify and execute active pressed binding struct sway_binding *binding = NULL; if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { get_active_binding(&keyboard->state_keycodes, config->current_mode->keycode_bindings, &binding, keyinfo.code_modifiers, false, locked, shortcuts_inhibited, device_identifier, exact_identifier, keyboard->effective_layout); get_active_binding(&keyboard->state_keysyms_raw, config->current_mode->keysym_bindings, &binding, keyinfo.raw_modifiers, false, locked, shortcuts_inhibited, device_identifier, exact_identifier, keyboard->effective_layout); get_active_binding(&keyboard->state_keysyms_translated, config->current_mode->keysym_bindings, &binding, keyinfo.translated_modifiers, false, locked, shortcuts_inhibited, device_identifier, exact_identifier, keyboard->effective_layout); } // Set up (or clear) keyboard repeat for a pressed binding. Since the // binding may remove the keyboard, the timer needs to be updated first if (binding && !(binding->flags & BINDING_NOREPEAT) && keyboard->wlr->repeat_info.delay > 0) { keyboard->repeat_binding = binding; if (wl_event_source_timer_update(keyboard->key_repeat_source, keyboard->wlr->repeat_info.delay) < 0) { sway_log(SWAY_DEBUG, "failed to set key repeat timer"); } } else if (keyboard->repeat_binding) { sway_keyboard_disarm_key_repeat(keyboard); } if (binding) { seat_execute_command(seat, binding); handled = true; } if (!handled && keyboard->wlr->group) { // Only handle device specific bindings for keyboards in a group free(device_identifier); return; } // Compositor bindings if (!handled && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { handled = keyboard_execute_compositor_binding( keyboard, keyinfo.translated_keysyms, keyinfo.translated_modifiers, keyinfo.translated_keysyms_len); } if (!handled && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { handled = keyboard_execute_compositor_binding( keyboard, keyinfo.raw_keysyms, keyinfo.raw_modifiers, keyinfo.raw_keysyms_len); } if (!handled) { handled = keyboard_execute_pointer_keysyms(keyboard, event->time_msec, keyinfo.translated_keysyms, keyinfo.translated_keysyms_len, event->state); } if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { // If the pressed event was sent to a client and we have a focused // surface immediately before this event, also send the released // event. In particular, don't send the released event to the IM grab. bool pressed_sent = update_shortcut_state( &keyboard->state_pressed_sent, event->keycode, event->state, keyinfo.keycode, 0); if (pressed_sent && seat->wlr_seat->keyboard_state.focused_surface) { wlr_seat_set_keyboard(wlr_seat, keyboard->wlr); wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec, event->keycode, event->state); handled = true; } } if (!handled) { struct wlr_input_method_keyboard_grab_v2 *kb_grab = keyboard_get_im_grab(keyboard); if (kb_grab) { wlr_input_method_keyboard_grab_v2_set_keyboard(kb_grab, keyboard->wlr); wlr_input_method_keyboard_grab_v2_send_key(kb_grab, event->time_msec, event->keycode, event->state); handled = true; } } if (!handled && event->state != WL_KEYBOARD_KEY_STATE_RELEASED) { // If a released event failed pressed sent test, and not in sent to // keyboard grab, it is still not handled. Don't handle released here. update_shortcut_state( &keyboard->state_pressed_sent, event->keycode, event->state, keyinfo.keycode, 0); wlr_seat_set_keyboard(wlr_seat, keyboard->wlr); wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec, event->keycode, event->state); } free(device_identifier); } static void handle_keyboard_key(struct wl_listener *listener, void *data) { struct sway_keyboard *keyboard = wl_container_of(listener, keyboard, keyboard_key); handle_key_event(keyboard, data); } static void handle_keyboard_group_key(struct wl_listener *listener, void *data) { struct sway_keyboard_group *sway_group = wl_container_of(listener, sway_group, keyboard_key); handle_key_event(sway_group->seat_device->keyboard, data); } static void handle_keyboard_group_enter(struct wl_listener *listener, void *data) { struct sway_keyboard_group *sway_group = wl_container_of(listener, sway_group, enter); struct sway_keyboard *keyboard = sway_group->seat_device->keyboard; struct wl_array *keycodes = data; uint32_t *keycode; wl_array_for_each(keycode, keycodes) { struct key_info keyinfo; update_keyboard_state(keyboard, *keycode, WL_KEYBOARD_KEY_STATE_PRESSED, &keyinfo); } } static void handle_keyboard_group_leave(struct wl_listener *listener, void *data) { struct sway_keyboard_group *sway_group = wl_container_of(listener, sway_group, leave); struct sway_keyboard *keyboard = sway_group->seat_device->keyboard; struct wl_array *keycodes = data; bool pressed_sent = false; uint32_t *keycode; wl_array_for_each(keycode, keycodes) { struct key_info keyinfo; update_keyboard_state(keyboard, *keycode, WL_KEYBOARD_KEY_STATE_RELEASED, &keyinfo); pressed_sent |= update_shortcut_state(&keyboard->state_pressed_sent, *keycode, WL_KEYBOARD_KEY_STATE_RELEASED, keyinfo.keycode, 0); } if (!pressed_sent) { return; } // Refocus the focused node, layer surface, or unmanaged surface so that // it picks up the current keyboard state. struct sway_seat *seat = keyboard->seat_device->sway_seat; struct sway_node *focus = seat_get_focus(seat); if (focus) { seat_set_focus(seat, NULL); seat_set_focus(seat, focus); } else if (seat->focused_layer) { struct wlr_layer_surface_v1 *layer = seat->focused_layer; seat_set_focus_layer(seat, NULL); seat_set_focus_layer(seat, layer); } else { struct wlr_surface *unmanaged = seat->wlr_seat->keyboard_state.focused_surface; seat_set_focus_surface(seat, NULL, false); seat_set_focus_surface(seat, unmanaged, false); } } static int handle_keyboard_repeat(void *data) { struct sway_keyboard *keyboard = data; if (keyboard->repeat_binding) { if (keyboard->wlr->repeat_info.rate > 0) { // We queue the next event first, as the command might cancel it if (wl_event_source_timer_update(keyboard->key_repeat_source, 1000 / keyboard->wlr->repeat_info.rate) < 0) { sway_log(SWAY_DEBUG, "failed to update key repeat timer"); } } seat_execute_command(keyboard->seat_device->sway_seat, keyboard->repeat_binding); } return 0; } static void determine_bar_visibility(uint32_t modifiers) { for (int i = 0; i < config->bars->length; ++i) { struct bar_config *bar = config->bars->items[i]; if (bar->modifier == 0) { continue; } bool vis_by_mod = (~modifiers & bar->modifier) == 0; if (bar->visible_by_modifier != vis_by_mod) { // If visible by modifier is set, send that it is no longer visible // by modifier (regardless of bar mode and state). Otherwise, only // send the visible by modifier status if mode and state are hide if (bar->visible_by_modifier || strcmp(bar->mode, bar->hidden_state) == 0) { bar->visible_by_modifier = vis_by_mod; ipc_event_bar_state_update(bar); } } } } static void handle_modifier_event(struct sway_keyboard *keyboard) { if (!keyboard->wlr->group) { struct wlr_input_method_keyboard_grab_v2 *kb_grab = keyboard_get_im_grab(keyboard); if (kb_grab) { wlr_input_method_keyboard_grab_v2_set_keyboard(kb_grab, keyboard->wlr); wlr_input_method_keyboard_grab_v2_send_modifiers(kb_grab, &keyboard->wlr->modifiers); } else { struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat; wlr_seat_set_keyboard(wlr_seat, keyboard->wlr); wlr_seat_keyboard_notify_modifiers(wlr_seat, &keyboard->wlr->modifiers); } uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard->wlr); determine_bar_visibility(modifiers); } if (keyboard->wlr->modifiers.group != keyboard->effective_layout) { keyboard->effective_layout = keyboard->wlr->modifiers.group; if (!wlr_keyboard_group_from_wlr_keyboard(keyboard->wlr)) { ipc_event_input("xkb_layout", keyboard->seat_device->input_device); } } } static void handle_keyboard_modifiers(struct wl_listener *listener, void *data) { struct sway_keyboard *keyboard = wl_container_of(listener, keyboard, keyboard_modifiers); handle_modifier_event(keyboard); } static void handle_keyboard_group_modifiers(struct wl_listener *listener, void *data) { struct sway_keyboard_group *group = wl_container_of(listener, group, keyboard_modifiers); handle_modifier_event(group->seat_device->keyboard); } struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat, struct sway_seat_device *device) { struct sway_keyboard *keyboard = calloc(1, sizeof(struct sway_keyboard)); if (!sway_assert(keyboard, "could not allocate sway keyboard")) { return NULL; } keyboard->seat_device = device; keyboard->wlr = wlr_keyboard_from_input_device(device->input_device->wlr_device); device->keyboard = keyboard; wl_list_init(&keyboard->keyboard_key.link); wl_list_init(&keyboard->keyboard_modifiers.link); keyboard->key_repeat_source = wl_event_loop_add_timer(server.wl_event_loop, handle_keyboard_repeat, keyboard); return keyboard; } static void handle_xkb_context_log(struct xkb_context *context, enum xkb_log_level level, const char *format, va_list args) { char *error = vformat_str(format, args); size_t len = strlen(error); if (error[len - 1] == '\n') { error[len - 1] = '\0'; } sway_log_importance_t importance = SWAY_DEBUG; if (level <= XKB_LOG_LEVEL_ERROR) { // Critical and Error importance = SWAY_ERROR; } else if (level <= XKB_LOG_LEVEL_INFO) { // Warning and Info importance = SWAY_INFO; } sway_log(importance, "[xkbcommon] %s", error); char **data = xkb_context_get_user_data(context); if (importance == SWAY_ERROR && data && !*data) { *data = error; } else { free(error); } } struct xkb_keymap *sway_keyboard_compile_keymap(struct input_config *ic, char **error) { struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_SECURE_GETENV); if (!sway_assert(context, "cannot create XKB context")) { return NULL; } xkb_context_set_user_data(context, error); xkb_context_set_log_fn(context, handle_xkb_context_log); struct xkb_keymap *keymap = NULL; if (ic && ic->xkb_file) { FILE *keymap_file = fopen(ic->xkb_file, "r"); if (!keymap_file) { sway_log_errno(SWAY_ERROR, "cannot read xkb file %s", ic->xkb_file); if (error) { *error = format_str("cannot read xkb file %s: %s", ic->xkb_file, strerror(errno)); } goto cleanup; } keymap = xkb_keymap_new_from_file(context, keymap_file, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); if (fclose(keymap_file) != 0) { sway_log_errno(SWAY_ERROR, "Failed to close xkb file %s", ic->xkb_file); } } else { struct xkb_rule_names rules = {0}; if (ic) { input_config_fill_rule_names(ic, &rules); } keymap = xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); } cleanup: xkb_context_set_user_data(context, NULL); xkb_context_unref(context); return keymap; } static bool repeat_info_match(struct sway_keyboard *a, struct wlr_keyboard *b) { return a->repeat_rate == b->repeat_info.rate && a->repeat_delay == b->repeat_info.delay; } static void destroy_empty_wlr_keyboard_group(void *data) { wlr_keyboard_group_destroy(data); } static void sway_keyboard_group_remove(struct sway_keyboard *keyboard) { struct sway_input_device *device = keyboard->seat_device->input_device; struct wlr_keyboard_group *wlr_group = keyboard->wlr->group; sway_log(SWAY_DEBUG, "Removing keyboard %s from group %p", device->identifier, wlr_group); wlr_keyboard_group_remove_keyboard(keyboard->wlr->group, keyboard->wlr); if (wl_list_empty(&wlr_group->devices)) { sway_log(SWAY_DEBUG, "Destroying empty keyboard group %p", wlr_group); struct sway_keyboard_group *sway_group = wlr_group->data; wlr_group->data = NULL; wl_list_remove(&sway_group->link); wl_list_remove(&sway_group->keyboard_key.link); wl_list_remove(&sway_group->keyboard_modifiers.link); wl_list_remove(&sway_group->enter.link); wl_list_remove(&sway_group->leave.link); sway_keyboard_destroy(sway_group->seat_device->keyboard); free(sway_group->seat_device->input_device); free(sway_group->seat_device); free(sway_group); // To prevent use-after-free conditions when handling key events, defer // freeing the wlr_keyboard_group until idle wl_event_loop_add_idle(server.wl_event_loop, destroy_empty_wlr_keyboard_group, wlr_group); } } static void sway_keyboard_group_remove_invalid(struct sway_keyboard *keyboard) { if (!keyboard->wlr->group) { return; } struct sway_seat *seat = keyboard->seat_device->sway_seat; struct seat_config *sc = seat_get_config(seat); if (!sc) { sc = seat_get_config_by_name("*"); } switch (sc ? sc->keyboard_grouping : KEYBOARD_GROUP_DEFAULT) { case KEYBOARD_GROUP_NONE: sway_keyboard_group_remove(keyboard); break; case KEYBOARD_GROUP_DEFAULT: /* fallthrough */ case KEYBOARD_GROUP_SMART:; struct wlr_keyboard_group *group = keyboard->wlr->group; if (!wlr_keyboard_keymaps_match(keyboard->keymap, group->keyboard.keymap) || !repeat_info_match(keyboard, &group->keyboard)) { sway_keyboard_group_remove(keyboard); } break; } } static void sway_keyboard_group_add(struct sway_keyboard *keyboard) { struct sway_input_device *device = keyboard->seat_device->input_device; struct sway_seat *seat = keyboard->seat_device->sway_seat; struct seat_config *sc = seat_get_config(seat); if (device->is_virtual) { // Virtual devices should not be grouped return; } if (!sc) { sc = seat_get_config_by_name("*"); } if (sc && sc->keyboard_grouping == KEYBOARD_GROUP_NONE) { // Keyboard grouping is disabled for the seat return; } struct sway_keyboard_group *group; wl_list_for_each(group, &seat->keyboard_groups, link) { switch (sc ? sc->keyboard_grouping : KEYBOARD_GROUP_DEFAULT) { case KEYBOARD_GROUP_NONE: // Nothing to do. This shouldn't even be reached return; case KEYBOARD_GROUP_DEFAULT: /* fallthrough */ case KEYBOARD_GROUP_SMART:; struct wlr_keyboard_group *wlr_group = group->wlr_group; if (wlr_keyboard_keymaps_match(keyboard->keymap, wlr_group->keyboard.keymap) && repeat_info_match(keyboard, &wlr_group->keyboard)) { sway_log(SWAY_DEBUG, "Adding keyboard %s to group %p", device->identifier, wlr_group); wlr_keyboard_group_add_keyboard(wlr_group, keyboard->wlr); return; } break; } } struct sway_keyboard_group *sway_group = calloc(1, sizeof(struct sway_keyboard_group)); if (!sway_group) { sway_log(SWAY_ERROR, "Failed to allocate sway_keyboard_group"); return; } sway_group->wlr_group = wlr_keyboard_group_create(); if (!sway_group->wlr_group) { sway_log(SWAY_ERROR, "Failed to create keyboard group"); goto cleanup; } sway_group->wlr_group->data = sway_group; wlr_keyboard_set_keymap(&sway_group->wlr_group->keyboard, keyboard->keymap); wlr_keyboard_set_repeat_info(&sway_group->wlr_group->keyboard, keyboard->repeat_rate, keyboard->repeat_delay); sway_log(SWAY_DEBUG, "Created keyboard group %p", sway_group->wlr_group); sway_group->seat_device = calloc(1, sizeof(struct sway_seat_device)); if (!sway_group->seat_device) { sway_log(SWAY_ERROR, "Failed to allocate sway_seat_device for group"); goto cleanup; } sway_group->seat_device->sway_seat = seat; sway_group->seat_device->input_device = calloc(1, sizeof(struct sway_input_device)); if (!sway_group->seat_device->input_device) { sway_log(SWAY_ERROR, "Failed to allocate sway_input_device for group"); goto cleanup; } sway_group->seat_device->input_device->wlr_device = &sway_group->wlr_group->keyboard.base; if (!sway_keyboard_create(seat, sway_group->seat_device)) { sway_log(SWAY_ERROR, "Failed to allocate sway_keyboard for group"); goto cleanup; } sway_log(SWAY_DEBUG, "Adding keyboard %s to group %p", device->identifier, sway_group->wlr_group); wlr_keyboard_group_add_keyboard(sway_group->wlr_group, keyboard->wlr); wl_list_insert(&seat->keyboard_groups, &sway_group->link); wl_signal_add(&sway_group->wlr_group->keyboard.events.key, &sway_group->keyboard_key); sway_group->keyboard_key.notify = handle_keyboard_group_key; wl_signal_add(&sway_group->wlr_group->keyboard.events.modifiers, &sway_group->keyboard_modifiers); sway_group->keyboard_modifiers.notify = handle_keyboard_group_modifiers; wl_signal_add(&sway_group->wlr_group->events.enter, &sway_group->enter); sway_group->enter.notify = handle_keyboard_group_enter; wl_signal_add(&sway_group->wlr_group->events.leave, &sway_group->leave); sway_group->leave.notify = handle_keyboard_group_leave; return; cleanup: if (sway_group && sway_group->wlr_group) { wlr_keyboard_group_destroy(sway_group->wlr_group); } free(sway_group->seat_device->keyboard); free(sway_group->seat_device->input_device); free(sway_group->seat_device); free(sway_group); } static void sway_keyboard_set_layout(struct sway_keyboard *keyboard, struct input_config *input_config) { struct xkb_keymap *keymap = sway_keyboard_compile_keymap(input_config, NULL); if (!keymap) { sway_log(SWAY_ERROR, "Failed to compile keymap. Attempting defaults"); keymap = sway_keyboard_compile_keymap(NULL, NULL); if (!keymap) { sway_log(SWAY_ERROR, "Failed to compile default keymap. Aborting configure"); return; } } bool keymap_changed = keyboard->keymap ? !wlr_keyboard_keymaps_match(keyboard->keymap, keymap) : true; bool effective_layout_changed = keyboard->effective_layout != 0; if (keymap_changed || config->reloading) { xkb_keymap_unref(keyboard->keymap); keyboard->keymap = keymap; keyboard->effective_layout = 0; sway_keyboard_group_remove_invalid(keyboard); wlr_keyboard_set_keymap(keyboard->wlr, keyboard->keymap); if (!keyboard->wlr->group) { sway_keyboard_group_add(keyboard); } xkb_mod_mask_t locked_mods = 0; if (input_config && input_config->xkb_numlock > 0) { xkb_mod_index_t mod_index = xkb_map_mod_get_index(keymap, XKB_MOD_NAME_NUM); if (mod_index != XKB_MOD_INVALID) { locked_mods |= (uint32_t)1 << mod_index; } } if (input_config && input_config->xkb_capslock > 0) { xkb_mod_index_t mod_index = xkb_map_mod_get_index(keymap, XKB_MOD_NAME_CAPS); if (mod_index != XKB_MOD_INVALID) { locked_mods |= (uint32_t)1 << mod_index; } } if (locked_mods) { wlr_keyboard_notify_modifiers(keyboard->wlr, 0, 0, locked_mods, 0); uint32_t leds = 0; for (uint32_t i = 0; i < WLR_LED_COUNT; ++i) { if (xkb_state_led_index_is_active(keyboard->wlr->xkb_state, keyboard->wlr->led_indexes[i])) { leds |= (1 << i); } } if (keyboard->wlr->group) { wlr_keyboard_led_update(&keyboard->wlr->group->keyboard, leds); } else { wlr_keyboard_led_update(keyboard->wlr, leds); } } } else { xkb_keymap_unref(keymap); sway_keyboard_group_remove_invalid(keyboard); if (!keyboard->wlr->group) { sway_keyboard_group_add(keyboard); } } if (keymap_changed) { ipc_event_input("xkb_keymap", keyboard->seat_device->input_device); } else if (effective_layout_changed) { ipc_event_input("xkb_layout", keyboard->seat_device->input_device); } } void sway_keyboard_configure(struct sway_keyboard *keyboard) { struct input_config *input_config = input_device_get_config(keyboard->seat_device->input_device); if (!sway_assert(!wlr_keyboard_group_from_wlr_keyboard(keyboard->wlr), "sway_keyboard_configure should not be called with a " "keyboard group's keyboard")) { return; } int repeat_rate = 25; if (input_config && input_config->repeat_rate != INT_MIN) { repeat_rate = input_config->repeat_rate; } int repeat_delay = 600; if (input_config && input_config->repeat_delay != INT_MIN) { repeat_delay = input_config->repeat_delay; } bool repeat_info_changed = keyboard->repeat_rate != repeat_rate || keyboard->repeat_delay != repeat_delay; if (repeat_info_changed || config->reloading) { keyboard->repeat_rate = repeat_rate; keyboard->repeat_delay = repeat_delay; wlr_keyboard_set_repeat_info(keyboard->wlr, keyboard->repeat_rate, keyboard->repeat_delay); } if (!keyboard->seat_device->input_device->is_virtual) { sway_keyboard_set_layout(keyboard, input_config); } // If the seat has no active keyboard, set this one struct wlr_seat *seat = keyboard->seat_device->sway_seat->wlr_seat; struct wlr_keyboard *current_keyboard = seat->keyboard_state.keyboard; if (current_keyboard == NULL) { wlr_seat_set_keyboard(seat, keyboard->wlr); } wl_list_remove(&keyboard->keyboard_key.link); wl_signal_add(&keyboard->wlr->events.key, &keyboard->keyboard_key); keyboard->keyboard_key.notify = handle_keyboard_key; wl_list_remove(&keyboard->keyboard_modifiers.link); wl_signal_add(&keyboard->wlr->events.modifiers, &keyboard->keyboard_modifiers); keyboard->keyboard_modifiers.notify = handle_keyboard_modifiers; } void sway_keyboard_destroy(struct sway_keyboard *keyboard) { if (!keyboard) { return; } if (keyboard->wlr->group) { sway_keyboard_group_remove(keyboard); } struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat; if (wlr_seat_get_keyboard(wlr_seat) == keyboard->wlr) { wlr_seat_set_keyboard(wlr_seat, NULL); } if (keyboard->keymap) { xkb_keymap_unref(keyboard->keymap); } wl_list_remove(&keyboard->keyboard_key.link); wl_list_remove(&keyboard->keyboard_modifiers.link); sway_keyboard_disarm_key_repeat(keyboard); wl_event_source_remove(keyboard->key_repeat_source); free(keyboard); } ================================================ FILE: sway/input/libinput.c ================================================ #include #include #include #include #include #include "log.h" #include "sway/config.h" #include "sway/output.h" #include "sway/input/input-manager.h" #include "sway/ipc-server.h" static void log_status(enum libinput_config_status status) { if (status != LIBINPUT_CONFIG_STATUS_SUCCESS) { sway_log(SWAY_ERROR, "Failed to apply libinput config: %s", libinput_config_status_to_str(status)); } } static bool set_send_events(struct libinput_device *device, uint32_t mode) { if (libinput_device_config_send_events_get_mode(device) == mode) { return false; } sway_log(SWAY_DEBUG, "send_events_set_mode(%" PRIu32 ")", mode); log_status(libinput_device_config_send_events_set_mode(device, mode)); return true; } static bool set_tap(struct libinput_device *device, enum libinput_config_tap_state tap) { if (libinput_device_config_tap_get_finger_count(device) <= 0 || libinput_device_config_tap_get_enabled(device) == tap) { return false; } sway_log(SWAY_DEBUG, "tap_set_enabled(%d)", tap); log_status(libinput_device_config_tap_set_enabled(device, tap)); return true; } static bool set_tap_button_map(struct libinput_device *device, enum libinput_config_tap_button_map map) { if (libinput_device_config_tap_get_finger_count(device) <= 0 || libinput_device_config_tap_get_button_map(device) == map) { return false; } sway_log(SWAY_DEBUG, "tap_set_button_map(%d)", map); log_status(libinput_device_config_tap_set_button_map(device, map)); return true; } static bool set_tap_drag(struct libinput_device *device, enum libinput_config_drag_state drag) { if (libinput_device_config_tap_get_finger_count(device) <= 0 || libinput_device_config_tap_get_drag_enabled(device) == drag) { return false; } sway_log(SWAY_DEBUG, "tap_set_drag_enabled(%d)", drag); log_status(libinput_device_config_tap_set_drag_enabled(device, drag)); return true; } static bool set_tap_drag_lock(struct libinput_device *device, enum libinput_config_drag_lock_state lock) { if (libinput_device_config_tap_get_finger_count(device) <= 0 || libinput_device_config_tap_get_drag_lock_enabled(device) == lock) { return false; } sway_log(SWAY_DEBUG, "tap_set_drag_lock_enabled(%d)", lock); log_status(libinput_device_config_tap_set_drag_lock_enabled(device, lock)); return true; } static bool set_accel_speed(struct libinput_device *device, double speed) { if (!libinput_device_config_accel_is_available(device) || libinput_device_config_accel_get_speed(device) == speed) { return false; } sway_log(SWAY_DEBUG, "accel_set_speed(%f)", speed); log_status(libinput_device_config_accel_set_speed(device, speed)); return true; } static bool set_rotation_angle(struct libinput_device *device, double angle) { if (!libinput_device_config_rotation_is_available(device) || libinput_device_config_rotation_get_angle(device) == angle) { return false; } sway_log(SWAY_DEBUG, "rotation_set_angle(%f)", angle); log_status(libinput_device_config_rotation_set_angle(device, angle)); return true; } static bool set_accel_profile(struct libinput_device *device, enum libinput_config_accel_profile profile) { if (!libinput_device_config_accel_is_available(device) || libinput_device_config_accel_get_profile(device) == profile) { return false; } sway_log(SWAY_DEBUG, "accel_set_profile(%d)", profile); log_status(libinput_device_config_accel_set_profile(device, profile)); return true; } static bool set_natural_scroll(struct libinput_device *d, bool n) { if (!libinput_device_config_scroll_has_natural_scroll(d) || libinput_device_config_scroll_get_natural_scroll_enabled(d) == n) { return false; } sway_log(SWAY_DEBUG, "scroll_set_natural_scroll(%d)", n); log_status(libinput_device_config_scroll_set_natural_scroll_enabled(d, n)); return true; } static bool set_left_handed(struct libinput_device *device, bool left) { if (!libinput_device_config_left_handed_is_available(device) || libinput_device_config_left_handed_get(device) == left) { return false; } sway_log(SWAY_DEBUG, "left_handed_set(%d)", left); log_status(libinput_device_config_left_handed_set(device, left)); return true; } static bool set_click_method(struct libinput_device *device, enum libinput_config_click_method method) { uint32_t click = libinput_device_config_click_get_methods(device); if ((click & ~LIBINPUT_CONFIG_CLICK_METHOD_NONE) == 0 || libinput_device_config_click_get_method(device) == method) { return false; } sway_log(SWAY_DEBUG, "click_set_method(%d)", method); log_status(libinput_device_config_click_set_method(device, method)); return true; } static bool set_clickfinger_button_map(struct libinput_device *device, enum libinput_config_clickfinger_button_map map) { if (libinput_device_config_click_get_clickfinger_button_map(device) == map) { return false; } sway_log(SWAY_DEBUG, "clickfinger_set_button_map(%d)", map); log_status(libinput_device_config_click_set_clickfinger_button_map(device, map)); return true; } static bool set_middle_emulation(struct libinput_device *dev, enum libinput_config_middle_emulation_state mid) { if (!libinput_device_config_middle_emulation_is_available(dev) || libinput_device_config_middle_emulation_get_enabled(dev) == mid) { return false; } sway_log(SWAY_DEBUG, "middle_emulation_set_enabled(%d)", mid); log_status(libinput_device_config_middle_emulation_set_enabled(dev, mid)); return true; } static bool set_scroll_method(struct libinput_device *device, enum libinput_config_scroll_method method) { uint32_t scroll = libinput_device_config_scroll_get_methods(device); if ((scroll & ~LIBINPUT_CONFIG_SCROLL_NO_SCROLL) == 0 || libinput_device_config_scroll_get_method(device) == method) { return false; } sway_log(SWAY_DEBUG, "scroll_set_method(%d)", method); log_status(libinput_device_config_scroll_set_method(device, method)); return true; } static bool set_scroll_button(struct libinput_device *dev, uint32_t button) { uint32_t scroll = libinput_device_config_scroll_get_methods(dev); if ((scroll & ~LIBINPUT_CONFIG_SCROLL_NO_SCROLL) == 0 || libinput_device_config_scroll_get_button(dev) == button) { return false; } sway_log(SWAY_DEBUG, "scroll_set_button(%" PRIu32 ")", button); log_status(libinput_device_config_scroll_set_button(dev, button)); return true; } static bool set_scroll_button_lock(struct libinput_device *dev, enum libinput_config_scroll_button_lock_state lock) { uint32_t scroll = libinput_device_config_scroll_get_methods(dev); if ((scroll & ~LIBINPUT_CONFIG_SCROLL_NO_SCROLL) == 0 || libinput_device_config_scroll_get_button_lock(dev) == lock) { return false; } sway_log(SWAY_DEBUG, "scroll_set_button_lock(%" PRIu32 ")", lock); log_status(libinput_device_config_scroll_set_button_lock(dev, lock)); return true; } static bool set_dwt(struct libinput_device *device, bool dwt) { if (!libinput_device_config_dwt_is_available(device) || libinput_device_config_dwt_get_enabled(device) == dwt) { return false; } sway_log(SWAY_DEBUG, "dwt_set_enabled(%d)", dwt); log_status(libinput_device_config_dwt_set_enabled(device, dwt)); return true; } static bool set_dwtp(struct libinput_device *device, bool dwtp) { if (!libinput_device_config_dwtp_is_available(device) || libinput_device_config_dwtp_get_enabled(device) == dwtp) { return false; } sway_log(SWAY_DEBUG, "dwtp_set_enabled(%d)", dwtp); log_status(libinput_device_config_dwtp_set_enabled(device, dwtp)); return true; } static bool set_calibration_matrix(struct libinput_device *dev, float mat[6]) { if (!libinput_device_config_calibration_has_matrix(dev)) { return false; } bool changed = false; float current[6]; libinput_device_config_calibration_get_matrix(dev, current); for (int i = 0; i < 6; i++) { if (current[i] != mat[i]) { changed = true; break; } } if (changed) { sway_log(SWAY_DEBUG, "calibration_set_matrix(%f, %f, %f, %f, %f, %f)", mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); log_status(libinput_device_config_calibration_set_matrix(dev, mat)); } return changed; } static bool configure_send_events(struct libinput_device *device, struct input_config *ic) { if (ic->mapped_to_output && strcmp("*", ic->mapped_to_output) != 0 && !output_by_name_or_id(ic->mapped_to_output)) { sway_log(SWAY_DEBUG, "%s '%s' is mapped to offline output '%s'; disabling input", ic->input_type, ic->identifier, ic->mapped_to_output); return set_send_events(device, LIBINPUT_CONFIG_SEND_EVENTS_DISABLED); } else if (ic->send_events != INT_MIN) { return set_send_events(device, ic->send_events); } else { // Have to reset to the default mode here, otherwise if ic->send_events // is unset and a mapped output just came online after being disabled, // we'd remain stuck sending no events. return set_send_events(device, libinput_device_config_send_events_get_default_mode(device)); } } bool sway_input_configure_libinput_device(struct sway_input_device *input_device) { struct input_config *ic = input_device_get_config(input_device); if (!ic || !wlr_input_device_is_libinput(input_device->wlr_device)) { return false; } struct libinput_device *device = wlr_libinput_get_device_handle(input_device->wlr_device); sway_log(SWAY_DEBUG, "sway_input_configure_libinput_device('%s' on '%s')", ic->identifier, input_device->identifier); bool changed = configure_send_events(device, ic); if (ic->tap != INT_MIN) { changed |= set_tap(device, ic->tap); } if (ic->tap_button_map != INT_MIN) { changed |= set_tap_button_map(device, ic->tap_button_map); } if (ic->drag != INT_MIN) { changed |= set_tap_drag(device, ic->drag); } if (ic->drag_lock != INT_MIN) { changed |= set_tap_drag_lock(device, ic->drag_lock); } if (ic->pointer_accel != FLT_MIN) { changed |= set_accel_speed(device, ic->pointer_accel); } if (ic->rotation_angle != FLT_MIN) { changed |= set_rotation_angle(device, ic->rotation_angle); } if (ic->accel_profile != INT_MIN) { changed |= set_accel_profile(device, ic->accel_profile); } if (ic->natural_scroll != INT_MIN) { changed |= set_natural_scroll(device, ic->natural_scroll); } if (ic->left_handed != INT_MIN) { changed |= set_left_handed(device, ic->left_handed); } if (ic->click_method != INT_MIN) { changed |= set_click_method(device, ic->click_method); } if (ic->clickfinger_button_map != INT_MIN) { changed |= set_clickfinger_button_map(device, ic->clickfinger_button_map); } if (ic->middle_emulation != INT_MIN) { changed |= set_middle_emulation(device, ic->middle_emulation); } if (ic->scroll_method != INT_MIN) { changed |= set_scroll_method(device, ic->scroll_method); } if (ic->scroll_button != INT_MIN) { changed |= set_scroll_button(device, ic->scroll_button); } if (ic->scroll_button_lock != INT_MIN) { changed |= set_scroll_button_lock(device, ic->scroll_button_lock); } if (ic->dwt != INT_MIN) { changed |= set_dwt(device, ic->dwt); } if (ic->dwtp != INT_MIN) { changed |= set_dwtp(device, ic->dwtp); } if (ic->calibration_matrix.configured) { changed |= set_calibration_matrix(device, ic->calibration_matrix.matrix); } return changed; } void sway_input_configure_libinput_device_send_events( struct sway_input_device *input_device) { struct input_config *ic = input_device_get_config(input_device); if (!ic || !wlr_input_device_is_libinput(input_device->wlr_device)) { return; } struct libinput_device *device = wlr_libinput_get_device_handle(input_device->wlr_device); bool changed = configure_send_events(device, ic); if (changed) { ipc_event_input("libinput_config", input_device); } } void sway_input_reset_libinput_device(struct sway_input_device *input_device) { if (!wlr_input_device_is_libinput(input_device->wlr_device)) { return; } struct libinput_device *device = wlr_libinput_get_device_handle(input_device->wlr_device); sway_log(SWAY_DEBUG, "sway_input_reset_libinput_device(%s)", input_device->identifier); bool changed = false; changed |= set_send_events(device, libinput_device_config_send_events_get_default_mode(device)); changed |= set_tap(device, libinput_device_config_tap_get_default_enabled(device)); changed |= set_tap_button_map(device, libinput_device_config_tap_get_default_button_map(device)); changed |= set_tap_drag(device, libinput_device_config_tap_get_default_drag_enabled(device)); changed |= set_tap_drag_lock(device, libinput_device_config_tap_get_default_drag_lock_enabled(device)); changed |= set_accel_speed(device, libinput_device_config_accel_get_default_speed(device)); changed |= set_rotation_angle(device, libinput_device_config_rotation_get_default_angle(device)); changed |= set_accel_profile(device, libinput_device_config_accel_get_default_profile(device)); changed |= set_natural_scroll(device, libinput_device_config_scroll_get_default_natural_scroll_enabled( device)); changed |= set_left_handed(device, libinput_device_config_left_handed_get_default(device)); changed |= set_click_method(device, libinput_device_config_click_get_default_method(device)); changed |= set_clickfinger_button_map(device, libinput_device_config_click_get_default_clickfinger_button_map(device)); changed |= set_middle_emulation(device, libinput_device_config_middle_emulation_get_default_enabled(device)); changed |= set_scroll_method(device, libinput_device_config_scroll_get_default_method(device)); changed |= set_scroll_button(device, libinput_device_config_scroll_get_default_button(device)); changed |= set_dwt(device, libinput_device_config_dwt_get_default_enabled(device)); changed |= set_dwtp(device, libinput_device_config_dwtp_get_default_enabled(device)); float matrix[6]; libinput_device_config_calibration_get_default_matrix(device, matrix); changed |= set_calibration_matrix(device, matrix); if (changed) { ipc_event_input("libinput_config", input_device); } } static bool sway_udev_device_is_builtin(struct udev_device *udev_device) { const char *id_path = udev_device_get_property_value(udev_device, "ID_PATH"); if (!id_path) { return false; } if (has_prefix(id_path, "platform-")) { return true; } return has_prefix(id_path, "pci-") && strstr(id_path, "-platform-"); } bool sway_libinput_device_is_builtin(struct sway_input_device *sway_device) { if (!wlr_input_device_is_libinput(sway_device->wlr_device)) { return false; } struct libinput_device *device = wlr_libinput_get_device_handle(sway_device->wlr_device); struct udev_device *udev_device = libinput_device_get_udev_device(device); if (!udev_device) { return false; } bool is_builtin = sway_udev_device_is_builtin(udev_device); udev_device_unref(udev_device); return is_builtin; } ================================================ FILE: sway/input/seat.c ================================================ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "list.h" #include "log.h" #include "sway/config.h" #include "sway/scene_descriptor.h" #include "sway/input/cursor.h" #include "sway/input/input-manager.h" #include "sway/input/keyboard.h" #include "sway/input/libinput.h" #include "sway/input/seat.h" #include "sway/input/switch.h" #include "sway/input/tablet.h" #include "sway/ipc-server.h" #include "sway/layers.h" #include "sway/output.h" #include "sway/server.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/root.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" static void seat_device_destroy(struct sway_seat_device *seat_device) { if (!seat_device) { return; } sway_keyboard_destroy(seat_device->keyboard); sway_tablet_destroy(seat_device->tablet); sway_tablet_pad_destroy(seat_device->tablet_pad); sway_switch_destroy(seat_device->switch_device); wlr_cursor_detach_input_device(seat_device->sway_seat->cursor->cursor, seat_device->input_device->wlr_device); wl_list_remove(&seat_device->link); free(seat_device); } static void seat_node_destroy(struct sway_seat_node *seat_node) { wl_list_remove(&seat_node->destroy.link); wl_list_remove(&seat_node->link); /* * This is the only time we remove items from the focus stack without * immediately re-adding them. If we just removed the last thing, * mark that nothing has focus anymore. */ if (wl_list_empty(&seat_node->seat->focus_stack)) { seat_node->seat->has_focus = false; } free(seat_node); } void seat_destroy(struct sway_seat *seat) { wlr_seat_destroy(seat->wlr_seat); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { struct sway_seat *seat = wl_container_of(listener, seat, destroy); if (seat == config->handler_context.seat) { config->handler_context.seat = input_manager_get_default_seat(); } struct sway_seat_device *seat_device, *next; wl_list_for_each_safe(seat_device, next, &seat->devices, link) { seat_device_destroy(seat_device); } struct sway_seat_node *seat_node, *next_seat_node; wl_list_for_each_safe(seat_node, next_seat_node, &seat->focus_stack, link) { seat_node_destroy(seat_node); } sway_input_method_relay_finish(&seat->im_relay); sway_cursor_destroy(seat->cursor); wl_list_remove(&seat->new_node.link); wl_list_remove(&seat->request_start_drag.link); wl_list_remove(&seat->start_drag.link); wl_list_remove(&seat->request_set_selection.link); wl_list_remove(&seat->request_set_primary_selection.link); wl_list_remove(&seat->link); wl_list_remove(&seat->destroy.link); for (int i = 0; i < seat->deferred_bindings->length; i++) { free_sway_binding(seat->deferred_bindings->items[i]); } wlr_scene_node_destroy(&seat->scene_tree->node); list_free(seat->deferred_bindings); free(seat->prev_workspace_name); free(seat); } void seat_idle_notify_activity(struct sway_seat *seat, enum sway_input_idle_source source) { if ((source & seat->idle_inhibit_sources) == 0) { return; } wlr_idle_notifier_v1_notify_activity(server.idle_notifier_v1, seat->wlr_seat); } /** * Activate all views within this container recursively. */ static void seat_send_activate(struct sway_node *node, struct sway_seat *seat) { if (node_is_view(node)) { if (!seat_is_input_allowed(seat, node->sway_container->view->surface)) { sway_log(SWAY_DEBUG, "Refusing to set focus, input is inhibited"); return; } view_set_activated(node->sway_container->view, true); } else { list_t *children = node_get_children(node); for (int i = 0; i < children->length; ++i) { struct sway_container *child = children->items[i]; seat_send_activate(&child->node, seat); } } } static struct sway_keyboard *sway_keyboard_for_wlr_keyboard( struct sway_seat *seat, struct wlr_keyboard *wlr_keyboard) { struct sway_seat_device *seat_device; wl_list_for_each(seat_device, &seat->devices, link) { struct sway_input_device *input_device = seat_device->input_device; if (input_device->wlr_device->type != WLR_INPUT_DEVICE_KEYBOARD) { continue; } if (input_device->wlr_device == &wlr_keyboard->base) { return seat_device->keyboard; } } struct sway_keyboard_group *group; wl_list_for_each(group, &seat->keyboard_groups, link) { struct sway_input_device *input_device = group->seat_device->input_device; if (input_device->wlr_device == &wlr_keyboard->base) { return group->seat_device->keyboard; } } return NULL; } static void seat_keyboard_notify_enter(struct sway_seat *seat, struct wlr_surface *surface) { struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat); if (!keyboard) { wlr_seat_keyboard_notify_enter(seat->wlr_seat, surface, NULL, 0, NULL); return; } struct sway_keyboard *sway_keyboard = sway_keyboard_for_wlr_keyboard(seat, keyboard); assert(sway_keyboard && "Cannot find sway_keyboard for seat keyboard"); struct sway_shortcut_state *state = &sway_keyboard->state_pressed_sent; wlr_seat_keyboard_notify_enter(seat->wlr_seat, surface, state->pressed_keycodes, state->npressed, &keyboard->modifiers); } static void seat_tablet_pads_set_focus(struct sway_seat *seat, struct wlr_surface *surface) { struct sway_seat_device *seat_device; wl_list_for_each(seat_device, &seat->devices, link) { sway_tablet_pad_set_focus(seat_device->tablet_pad, surface); } } /** * If con is a view, set it as active and enable keyboard input. * If con is a container, set all child views as active and don't enable * keyboard input on any. */ static void seat_send_focus(struct sway_node *node, struct sway_seat *seat) { seat_send_activate(node, seat); struct sway_view *view = node->type == N_CONTAINER ? node->sway_container->view : NULL; if (view && seat_is_input_allowed(seat, view->surface)) { #if WLR_HAS_XWAYLAND if (view->type == SWAY_VIEW_XWAYLAND) { struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; wlr_xwayland_set_seat(xwayland, seat->wlr_seat); } #endif seat_keyboard_notify_enter(seat, view->surface); seat_tablet_pads_set_focus(seat, view->surface); sway_input_method_relay_set_focus(&seat->im_relay, view->surface); struct wlr_pointer_constraint_v1 *constraint = wlr_pointer_constraints_v1_constraint_for_surface( server.pointer_constraints, view->surface, seat->wlr_seat); sway_cursor_constrain(seat->cursor, constraint); } } void seat_for_each_node(struct sway_seat *seat, void (*f)(struct sway_node *node, void *data), void *data) { struct sway_seat_node *current = NULL; wl_list_for_each(current, &seat->focus_stack, link) { f(current->node, data); } } struct sway_container *seat_get_focus_inactive_view(struct sway_seat *seat, struct sway_node *ancestor) { if (node_is_view(ancestor)) { return ancestor->sway_container; } struct sway_seat_node *current; wl_list_for_each(current, &seat->focus_stack, link) { struct sway_node *node = current->node; if (node_is_view(node) && node_has_ancestor(node, ancestor)) { return node->sway_container; } } return NULL; } static void handle_seat_node_destroy(struct wl_listener *listener, void *data) { struct sway_seat_node *seat_node = wl_container_of(listener, seat_node, destroy); struct sway_seat *seat = seat_node->seat; struct sway_node *node = seat_node->node; struct sway_node *parent = node_get_parent(node); struct sway_node *focus = seat_get_focus(seat); if (node->type == N_WORKSPACE) { seat_node_destroy(seat_node); // If an unmanaged or layer surface is focused when an output gets // disabled and an empty workspace on the output was focused by the // seat, the seat needs to refocus its focus inactive to update the // value of seat->workspace. if (seat->workspace == node->sway_workspace) { struct sway_node *node = seat_get_focus_inactive(seat, &root->node); seat_set_focus(seat, NULL); if (node) { seat_set_focus(seat, node); } else { seat->workspace = NULL; } } return; } // Even though the container being destroyed might be nowhere near the // focused container, we still need to set focus_inactive on a sibling of // the container being destroyed. bool needs_new_focus = focus && (focus == node || node_has_ancestor(focus, node)); seat_node_destroy(seat_node); if (!parent && !needs_new_focus) { // Destroying a container that is no longer in the tree return; } // Find new focus_inactive (ie. sibling, or workspace if no siblings left) struct sway_node *next_focus = NULL; while (next_focus == NULL && parent != NULL) { struct sway_container *con = seat_get_focus_inactive_view(seat, parent); next_focus = con ? &con->node : NULL; if (next_focus == NULL && parent->type == N_WORKSPACE) { next_focus = parent; break; } parent = node_get_parent(parent); } if (!next_focus) { struct sway_workspace *ws = seat_get_last_known_workspace(seat); if (!ws) { return; } struct sway_container *con = seat_get_focus_inactive_view(seat, &ws->node); next_focus = con ? &(con->node) : &(ws->node); } if (next_focus->type == N_WORKSPACE && !workspace_is_visible(next_focus->sway_workspace)) { // Do not change focus to a non-visible workspace return; } if (needs_new_focus) { // Make sure the workspace IPC event gets sent if (node->type == N_CONTAINER && node->sway_container->scratchpad) { seat_set_focus(seat, NULL); } // The structure change might have caused it to move up to the top of // the focus stack without sending focus notifications to the view if (seat_get_focus(seat) == next_focus) { seat_send_focus(next_focus, seat); } else { seat_set_focus(seat, next_focus); } } else { // Setting focus_inactive focus = seat_get_focus_inactive(seat, &root->node); seat_set_raw_focus(seat, next_focus); if (focus->type == N_CONTAINER && focus->sway_container->pending.workspace) { seat_set_raw_focus(seat, &focus->sway_container->pending.workspace->node); } seat_set_raw_focus(seat, focus); } } static struct sway_seat_node *seat_node_from_node( struct sway_seat *seat, struct sway_node *node) { if (node->type == N_ROOT || node->type == N_OUTPUT) { // these don't get seat nodes ever return NULL; } struct sway_seat_node *seat_node = NULL; wl_list_for_each(seat_node, &seat->focus_stack, link) { if (seat_node->node == node) { return seat_node; } } seat_node = calloc(1, sizeof(struct sway_seat_node)); if (seat_node == NULL) { sway_log(SWAY_ERROR, "could not allocate seat node"); return NULL; } seat_node->node = node; seat_node->seat = seat; wl_list_insert(seat->focus_stack.prev, &seat_node->link); wl_signal_add(&node->events.destroy, &seat_node->destroy); seat_node->destroy.notify = handle_seat_node_destroy; return seat_node; } static void handle_new_node(struct wl_listener *listener, void *data) { struct sway_seat *seat = wl_container_of(listener, seat, new_node); struct sway_node *node = data; seat_node_from_node(seat, node); } static void drag_icon_update_position(struct sway_seat *seat, struct wlr_scene_node *node) { struct wlr_drag_icon *wlr_icon = scene_descriptor_try_get(node, SWAY_SCENE_DESC_DRAG_ICON); struct wlr_cursor *cursor = seat->cursor->cursor; switch (wlr_icon->drag->grab_type) { case WLR_DRAG_GRAB_KEYBOARD: return; case WLR_DRAG_GRAB_KEYBOARD_POINTER: wlr_scene_node_set_position(node, cursor->x, cursor->y); break; case WLR_DRAG_GRAB_KEYBOARD_TOUCH:; struct wlr_touch_point *point = wlr_seat_touch_get_point(seat->wlr_seat, wlr_icon->drag->touch_id); if (point == NULL) { return; } wlr_scene_node_set_position(node, seat->touch_x, seat->touch_y); } } void drag_icons_update_position(struct sway_seat *seat) { struct wlr_scene_node *node; wl_list_for_each(node, &seat->drag_icons->children, link) { drag_icon_update_position(seat, node); } } static void drag_handle_destroy(struct wl_listener *listener, void *data) { struct sway_drag *drag = wl_container_of(listener, drag, destroy); // Focus enter isn't sent during drag, so refocus the focused node, layer // surface or unmanaged surface. struct sway_seat *seat = drag->seat; struct sway_node *focus = seat_get_focus(seat); if (focus) { seat_set_focus(seat, NULL); seat_set_focus(seat, focus); } else if (seat->focused_layer) { struct wlr_layer_surface_v1 *layer = seat->focused_layer; seat_set_focus_layer(seat, NULL); seat_set_focus_layer(seat, layer); } else { struct wlr_surface *unmanaged = seat->wlr_seat->keyboard_state.focused_surface; seat_set_focus_surface(seat, NULL, false); seat_set_focus_surface(seat, unmanaged, false); } drag->wlr_drag->data = NULL; wl_list_remove(&drag->destroy.link); free(drag); } static void handle_request_start_drag(struct wl_listener *listener, void *data) { struct sway_seat *seat = wl_container_of(listener, seat, request_start_drag); struct wlr_seat_request_start_drag_event *event = data; if (wlr_seat_validate_pointer_grab_serial(seat->wlr_seat, event->origin, event->serial)) { wlr_seat_start_pointer_drag(seat->wlr_seat, event->drag, event->serial); return; } struct wlr_touch_point *point; if (wlr_seat_validate_touch_grab_serial(seat->wlr_seat, event->origin, event->serial, &point)) { wlr_seat_start_touch_drag(seat->wlr_seat, event->drag, event->serial, point); return; } // TODO: tablet grabs sway_log(SWAY_DEBUG, "Ignoring start_drag request: " "could not validate pointer or touch serial %" PRIu32, event->serial); wlr_data_source_destroy(event->drag->source); } static void handle_start_drag(struct wl_listener *listener, void *data) { struct sway_seat *seat = wl_container_of(listener, seat, start_drag); struct wlr_drag *wlr_drag = data; struct sway_drag *drag = calloc(1, sizeof(struct sway_drag)); if (drag == NULL) { sway_log(SWAY_ERROR, "Allocation failed"); return; } drag->seat = seat; drag->wlr_drag = wlr_drag; wlr_drag->data = drag; drag->destroy.notify = drag_handle_destroy; wl_signal_add(&wlr_drag->events.destroy, &drag->destroy); struct wlr_drag_icon *wlr_drag_icon = wlr_drag->icon; if (wlr_drag_icon != NULL) { struct wlr_scene_tree *tree = wlr_scene_drag_icon_create(seat->drag_icons, wlr_drag_icon); if (!tree) { sway_log(SWAY_ERROR, "Failed to allocate a drag icon scene tree"); return; } if (!scene_descriptor_assign(&tree->node, SWAY_SCENE_DESC_DRAG_ICON, wlr_drag_icon)) { sway_log(SWAY_ERROR, "Failed to allocate a drag icon scene descriptor"); wlr_scene_node_destroy(&tree->node); return; } drag_icon_update_position(seat, &tree->node); } seatop_begin_default(seat); } static void handle_request_set_selection(struct wl_listener *listener, void *data) { struct sway_seat *seat = wl_container_of(listener, seat, request_set_selection); struct wlr_seat_request_set_selection_event *event = data; wlr_seat_set_selection(seat->wlr_seat, event->source, event->serial); } static void handle_request_set_primary_selection(struct wl_listener *listener, void *data) { struct sway_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->wlr_seat, event->source, event->serial); } static void collect_focus_iter(struct sway_node *node, void *data) { struct sway_seat *seat = data; struct sway_seat_node *seat_node = seat_node_from_node(seat, node); if (!seat_node) { return; } wl_list_remove(&seat_node->link); wl_list_insert(&seat->focus_stack, &seat_node->link); } static void collect_focus_workspace_iter(struct sway_workspace *workspace, void *data) { collect_focus_iter(&workspace->node, data); } static void collect_focus_container_iter(struct sway_container *container, void *data) { collect_focus_iter(&container->node, data); } struct sway_seat *seat_create(const char *seat_name) { struct sway_seat *seat = calloc(1, sizeof(struct sway_seat)); if (!seat) { return NULL; } bool failed = false; seat->scene_tree = alloc_scene_tree(root->layers.seat, &failed); seat->drag_icons = alloc_scene_tree(seat->scene_tree, &failed); if (failed) { wlr_scene_node_destroy(&seat->scene_tree->node); free(seat); return NULL; } seat->wlr_seat = wlr_seat_create(server.wl_display, seat_name); if (!sway_assert(seat->wlr_seat, "could not allocate seat")) { wlr_scene_node_destroy(&seat->scene_tree->node); free(seat); return NULL; } seat->wlr_seat->data = seat; seat->cursor = sway_cursor_create(seat); if (!seat->cursor) { wlr_scene_node_destroy(&seat->scene_tree->node); wlr_seat_destroy(seat->wlr_seat); free(seat); return NULL; } seat->destroy.notify = handle_seat_destroy; wl_signal_add(&seat->wlr_seat->events.destroy, &seat->destroy); seat->idle_inhibit_sources = seat->idle_wake_sources = IDLE_SOURCE_KEYBOARD | IDLE_SOURCE_POINTER | IDLE_SOURCE_TOUCH | IDLE_SOURCE_TABLET_PAD | IDLE_SOURCE_TABLET_TOOL | IDLE_SOURCE_SWITCH; // init the focus stack wl_list_init(&seat->focus_stack); wl_list_init(&seat->devices); root_for_each_workspace(collect_focus_workspace_iter, seat); root_for_each_container(collect_focus_container_iter, seat); seat->deferred_bindings = create_list(); wl_signal_add(&root->events.new_node, &seat->new_node); seat->new_node.notify = handle_new_node; wl_signal_add(&seat->wlr_seat->events.request_start_drag, &seat->request_start_drag); seat->request_start_drag.notify = handle_request_start_drag; wl_signal_add(&seat->wlr_seat->events.start_drag, &seat->start_drag); seat->start_drag.notify = handle_start_drag; wl_signal_add(&seat->wlr_seat->events.request_set_selection, &seat->request_set_selection); seat->request_set_selection.notify = handle_request_set_selection; wl_signal_add(&seat->wlr_seat->events.request_set_primary_selection, &seat->request_set_primary_selection); seat->request_set_primary_selection.notify = handle_request_set_primary_selection; wl_list_init(&seat->keyboard_groups); wl_list_init(&seat->keyboard_shortcuts_inhibitors); sway_input_method_relay_init(seat, &seat->im_relay); bool first = wl_list_empty(&server.input->seats); wl_list_insert(&server.input->seats, &seat->link); if (!first) { // Since this is not the first seat, attempt to set initial focus struct sway_seat *current_seat = input_manager_current_seat(); struct sway_node *current_focus = seat_get_focus_inactive(current_seat, &root->node); seat_set_focus(seat, current_focus); } seatop_begin_default(seat); return seat; } static void seat_update_capabilities(struct sway_seat *seat) { uint32_t caps = 0; uint32_t previous_caps = seat->wlr_seat->capabilities; struct sway_seat_device *seat_device; wl_list_for_each(seat_device, &seat->devices, link) { switch (seat_device->input_device->wlr_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; case WLR_INPUT_DEVICE_TABLET: caps |= WL_SEAT_CAPABILITY_POINTER; break; case WLR_INPUT_DEVICE_SWITCH: case WLR_INPUT_DEVICE_TABLET_PAD: break; } } // Hide cursor if seat doesn't have pointer capability. // We must call cursor_set_image while the wlr_seat has the capabilities // otherwise it's a no op. if ((caps & WL_SEAT_CAPABILITY_POINTER) == 0) { cursor_set_image(seat->cursor, NULL, NULL); wlr_seat_set_capabilities(seat->wlr_seat, caps); } else { wlr_seat_set_capabilities(seat->wlr_seat, caps); if ((previous_caps & WL_SEAT_CAPABILITY_POINTER) == 0) { cursor_set_image(seat->cursor, "default", NULL); } } } static void seat_reset_input_config(struct sway_seat *seat, struct sway_seat_device *sway_device) { sway_log(SWAY_DEBUG, "Resetting output mapping for input device %s", sway_device->input_device->identifier); wlr_cursor_map_input_to_output(seat->cursor->cursor, sway_device->input_device->wlr_device, NULL); } /** * Get the name of the built-in output, if any. Returns NULL if there isn't * exactly one built-in output. */ static const char *get_builtin_output_name(void) { const char *match = NULL; for (int i = 0; i < root->outputs->length; ++i) { struct sway_output *output = root->outputs->items[i]; const char *name = output->wlr_output->name; if (has_prefix(name, "eDP-") || has_prefix(name, "LVDS-") || has_prefix(name, "DSI-")) { if (match != NULL) { return NULL; } match = name; } } return match; } static bool is_touch_or_tablet_tool(struct sway_seat_device *seat_device) { switch (seat_device->input_device->wlr_device->type) { case WLR_INPUT_DEVICE_TOUCH: case WLR_INPUT_DEVICE_TABLET: return true; default: return false; } } static void seat_apply_input_mapping(struct sway_seat *seat, struct sway_seat_device *sway_device) { struct input_config *ic = input_device_get_config(sway_device->input_device); switch (sway_device->input_device->wlr_device->type) { case WLR_INPUT_DEVICE_POINTER: case WLR_INPUT_DEVICE_TOUCH: case WLR_INPUT_DEVICE_TABLET: break; default: return; // these devices don't support mappings } sway_log(SWAY_DEBUG, "Applying input mapping to %s", sway_device->input_device->identifier); const char *mapped_to_output = ic == NULL ? NULL : ic->mapped_to_output; struct wlr_box *mapped_to_region = ic == NULL ? NULL : ic->mapped_to_region; enum input_config_mapped_to mapped_to = ic == NULL ? MAPPED_TO_DEFAULT : ic->mapped_to; switch (mapped_to) { case MAPPED_TO_DEFAULT:; /* * If the wlroots backend provides an output name, use that. * * Otherwise, try to map built-in touch and pointer devices to the * built-in output. */ struct wlr_input_device *dev = sway_device->input_device->wlr_device; switch (dev->type) { case WLR_INPUT_DEVICE_POINTER: mapped_to_output = wlr_pointer_from_input_device(dev)->output_name; break; case WLR_INPUT_DEVICE_TOUCH: mapped_to_output = wlr_touch_from_input_device(dev)->output_name; break; default: mapped_to_output = NULL; break; } #if WLR_HAS_LIBINPUT_BACKEND if (mapped_to_output == NULL && is_touch_or_tablet_tool(sway_device) && sway_libinput_device_is_builtin(sway_device->input_device)) { mapped_to_output = get_builtin_output_name(); if (mapped_to_output) { sway_log(SWAY_DEBUG, "Auto-detected output '%s' for device '%s'", mapped_to_output, sway_device->input_device->identifier); } } #else (void)is_touch_or_tablet_tool; (void)get_builtin_output_name; #endif if (mapped_to_output == NULL) { return; } /* fallthrough */ case MAPPED_TO_OUTPUT: sway_log(SWAY_DEBUG, "Mapping input device %s to output %s", sway_device->input_device->identifier, mapped_to_output); if (strcmp("*", mapped_to_output) == 0) { wlr_cursor_map_input_to_output(seat->cursor->cursor, sway_device->input_device->wlr_device, NULL); wlr_cursor_map_input_to_region(seat->cursor->cursor, sway_device->input_device->wlr_device, NULL); sway_log(SWAY_DEBUG, "Reset output mapping"); return; } struct sway_output *output = output_by_name_or_id(mapped_to_output); if (!output) { sway_log(SWAY_DEBUG, "Requested output %s for device %s isn't present", mapped_to_output, sway_device->input_device->identifier); return; } wlr_cursor_map_input_to_output(seat->cursor->cursor, sway_device->input_device->wlr_device, output->wlr_output); wlr_cursor_map_input_to_region(seat->cursor->cursor, sway_device->input_device->wlr_device, NULL); sway_log(SWAY_DEBUG, "Mapped to output %s", output->wlr_output->name); return; case MAPPED_TO_REGION: sway_log(SWAY_DEBUG, "Mapping input device %s to %d,%d %dx%d", sway_device->input_device->identifier, mapped_to_region->x, mapped_to_region->y, mapped_to_region->width, mapped_to_region->height); wlr_cursor_map_input_to_output(seat->cursor->cursor, sway_device->input_device->wlr_device, NULL); wlr_cursor_map_input_to_region(seat->cursor->cursor, sway_device->input_device->wlr_device, mapped_to_region); return; } } static void seat_configure_pointer(struct sway_seat *seat, struct sway_seat_device *sway_device) { seat_configure_xcursor(seat); wlr_cursor_attach_input_device(seat->cursor->cursor, sway_device->input_device->wlr_device); wl_event_source_timer_update( seat->cursor->hide_source, cursor_get_timeout(seat->cursor)); } static void seat_configure_keyboard(struct sway_seat *seat, struct sway_seat_device *seat_device) { if (!seat_device->keyboard) { sway_keyboard_create(seat, seat_device); } sway_keyboard_configure(seat_device->keyboard); // We only need to update the current keyboard, as the rest will be updated // as they are activated. struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(seat_device->input_device->wlr_device); struct wlr_keyboard *current_keyboard = seat->wlr_seat->keyboard_state.keyboard; if (wlr_keyboard != current_keyboard) { return; } // Notify reenter to pick up the new configuration. This reuses // the current focused surface to avoid breaking input grabs. struct wlr_surface *surface = seat->wlr_seat->keyboard_state.focused_surface; if (surface) { seat_keyboard_notify_enter(seat, surface); } } static void seat_configure_switch(struct sway_seat *seat, struct sway_seat_device *seat_device) { if (!seat_device->switch_device) { sway_switch_create(seat, seat_device); } sway_switch_configure(seat_device->switch_device); } static void seat_configure_touch(struct sway_seat *seat, struct sway_seat_device *sway_device) { wlr_cursor_attach_input_device(seat->cursor->cursor, sway_device->input_device->wlr_device); } static void seat_configure_tablet_tool(struct sway_seat *seat, struct sway_seat_device *sway_device) { if (!sway_device->tablet) { sway_device->tablet = sway_tablet_create(seat, sway_device); } sway_configure_tablet(sway_device->tablet); wlr_cursor_attach_input_device(seat->cursor->cursor, sway_device->input_device->wlr_device); } static void seat_configure_tablet_pad(struct sway_seat *seat, struct sway_seat_device *sway_device) { if (!sway_device->tablet_pad) { sway_device->tablet_pad = sway_tablet_pad_create(seat, sway_device); } sway_configure_tablet_pad(sway_device->tablet_pad); } static struct sway_seat_device *seat_get_device(struct sway_seat *seat, struct sway_input_device *input_device) { struct sway_seat_device *seat_device = NULL; wl_list_for_each(seat_device, &seat->devices, link) { if (seat_device->input_device == input_device) { return seat_device; } } struct sway_keyboard_group *group = NULL; wl_list_for_each(group, &seat->keyboard_groups, link) { if (group->seat_device->input_device == input_device) { return group->seat_device; } } return NULL; } void seat_configure_device(struct sway_seat *seat, struct sway_input_device *input_device) { struct sway_seat_device *seat_device = seat_get_device(seat, input_device); if (!seat_device) { return; } switch (input_device->wlr_device->type) { case WLR_INPUT_DEVICE_POINTER: seat_configure_pointer(seat, seat_device); break; case WLR_INPUT_DEVICE_KEYBOARD: seat_configure_keyboard(seat, seat_device); break; case WLR_INPUT_DEVICE_SWITCH: seat_configure_switch(seat, seat_device); break; case WLR_INPUT_DEVICE_TOUCH: seat_configure_touch(seat, seat_device); break; case WLR_INPUT_DEVICE_TABLET: seat_configure_tablet_tool(seat, seat_device); break; case WLR_INPUT_DEVICE_TABLET_PAD: seat_configure_tablet_pad(seat, seat_device); break; } seat_apply_input_mapping(seat, seat_device); } void seat_configure_device_mapping(struct sway_seat *seat, struct sway_input_device *input_device) { struct sway_seat_device *seat_device = seat_get_device(seat, input_device); if (!seat_device) { return; } seat_apply_input_mapping(seat, seat_device); } void seat_reset_device(struct sway_seat *seat, struct sway_input_device *input_device) { struct sway_seat_device *seat_device = seat_get_device(seat, input_device); if (!seat_device) { return; } switch (input_device->wlr_device->type) { case WLR_INPUT_DEVICE_POINTER: seat_reset_input_config(seat, seat_device); break; case WLR_INPUT_DEVICE_KEYBOARD: sway_keyboard_disarm_key_repeat(seat_device->keyboard); sway_keyboard_configure(seat_device->keyboard); break; case WLR_INPUT_DEVICE_TOUCH: seat_reset_input_config(seat, seat_device); break; case WLR_INPUT_DEVICE_TABLET: seat_reset_input_config(seat, seat_device); break; case WLR_INPUT_DEVICE_TABLET_PAD: sway_log(SWAY_DEBUG, "TODO: reset tablet pad"); break; case WLR_INPUT_DEVICE_SWITCH: sway_log(SWAY_DEBUG, "TODO: reset switch device"); break; } } void seat_add_device(struct sway_seat *seat, struct sway_input_device *input_device) { if (seat_get_device(seat, input_device)) { seat_configure_device(seat, input_device); return; } struct sway_seat_device *seat_device = calloc(1, sizeof(struct sway_seat_device)); if (!seat_device) { sway_log(SWAY_DEBUG, "could not allocate seat device"); return; } sway_log(SWAY_DEBUG, "adding device %s to seat %s", input_device->identifier, seat->wlr_seat->name); seat_device->sway_seat = seat; seat_device->input_device = input_device; wl_list_insert(&seat->devices, &seat_device->link); seat_configure_device(seat, input_device); seat_update_capabilities(seat); } void seat_remove_device(struct sway_seat *seat, struct sway_input_device *input_device) { struct sway_seat_device *seat_device = seat_get_device(seat, input_device); if (!seat_device) { return; } sway_log(SWAY_DEBUG, "removing device %s from seat %s", input_device->identifier, seat->wlr_seat->name); seat_device_destroy(seat_device); seat_update_capabilities(seat); } static bool xcursor_manager_is_named(const struct wlr_xcursor_manager *manager, const char *name) { return (!manager->name && !name) || (name && manager->name && strcmp(name, manager->name) == 0); } void seat_configure_xcursor(struct sway_seat *seat) { unsigned cursor_size = 24; const char *cursor_theme = NULL; const struct seat_config *seat_config = seat_get_config(seat); if (!seat_config) { seat_config = seat_get_config_by_name("*"); } if (seat_config) { cursor_size = seat_config->xcursor_theme.size; cursor_theme = seat_config->xcursor_theme.name; } if (seat == input_manager_get_default_seat()) { char cursor_size_fmt[16]; snprintf(cursor_size_fmt, sizeof(cursor_size_fmt), "%u", cursor_size); setenv("XCURSOR_SIZE", cursor_size_fmt, 1); if (cursor_theme != NULL) { setenv("XCURSOR_THEME", cursor_theme, 1); } #if WLR_HAS_XWAYLAND if (server.xwayland.wlr_xwayland && (!server.xwayland.xcursor_manager || !xcursor_manager_is_named(server.xwayland.xcursor_manager, cursor_theme) || server.xwayland.xcursor_manager->size != cursor_size)) { wlr_xcursor_manager_destroy(server.xwayland.xcursor_manager); server.xwayland.xcursor_manager = wlr_xcursor_manager_create(cursor_theme, cursor_size); sway_assert(server.xwayland.xcursor_manager, "Cannot create XCursor manager for theme"); wlr_xcursor_manager_load(server.xwayland.xcursor_manager, 1); struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor( server.xwayland.xcursor_manager, "default", 1); if (xcursor != NULL) { struct wlr_xcursor_image *image = xcursor->images[0]; struct wlr_buffer *buffer = wlr_xcursor_image_get_buffer(image); wlr_xwayland_set_cursor( server.xwayland.wlr_xwayland, buffer, image->hotspot_x, image->hotspot_y); } } #endif } /* Create xcursor manager if we don't have one already, or if the * theme has changed */ if (!seat->cursor->xcursor_manager || !xcursor_manager_is_named( seat->cursor->xcursor_manager, cursor_theme) || seat->cursor->xcursor_manager->size != cursor_size) { wlr_xcursor_manager_destroy(seat->cursor->xcursor_manager); seat->cursor->xcursor_manager = wlr_xcursor_manager_create(cursor_theme, cursor_size); if (!seat->cursor->xcursor_manager) { sway_log(SWAY_ERROR, "Cannot create XCursor manager for theme '%s'", cursor_theme); } for (int i = 0; i < root->outputs->length; ++i) { struct sway_output *sway_output = root->outputs->items[i]; struct wlr_output *output = sway_output->wlr_output; bool result = wlr_xcursor_manager_load(seat->cursor->xcursor_manager, output->scale); if (!result) { sway_log(SWAY_ERROR, "Cannot load xcursor theme for output '%s' with scale %f", output->name, output->scale); } } // Reset the cursor so that we apply it to outputs that just appeared cursor_set_image(seat->cursor, NULL, NULL); cursor_set_image(seat->cursor, "default", NULL); wlr_cursor_warp(seat->cursor->cursor, NULL, seat->cursor->cursor->x, seat->cursor->cursor->y); } } bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface) { if (server.session_lock.lock) { return sway_session_lock_has_surface(server.session_lock.lock, surface); } return true; } static void send_unfocus(struct sway_container *con, void *data) { if (con->view) { view_set_activated(con->view, false); } } // Unfocus the container and any children (eg. when leaving `focus parent`) static void seat_send_unfocus(struct sway_node *node, struct sway_seat *seat) { sway_cursor_constrain(seat->cursor, NULL); wlr_seat_keyboard_notify_clear_focus(seat->wlr_seat); if (node->type == N_WORKSPACE) { workspace_for_each_container(node->sway_workspace, send_unfocus, seat); } else { send_unfocus(node->sway_container, seat); container_for_each_child(node->sway_container, send_unfocus, seat); } } static int handle_urgent_timeout(void *data) { struct sway_view *view = data; view_set_urgent(view, false); container_update_itself_and_parents(view->container); return 0; } static void set_workspace(struct sway_seat *seat, struct sway_workspace *new_ws) { if (seat->workspace == new_ws) { return; } if (seat->workspace) { free(seat->prev_workspace_name); seat->prev_workspace_name = strdup(seat->workspace->name); if (!seat->prev_workspace_name) { sway_log(SWAY_ERROR, "Unable to allocate previous workspace name"); } } ipc_event_workspace(seat->workspace, new_ws, "focus"); seat->workspace = new_ws; } void seat_set_raw_focus(struct sway_seat *seat, struct sway_node *node) { struct sway_seat_node *seat_node = seat_node_from_node(seat, node); wl_list_remove(&seat_node->link); wl_list_insert(&seat->focus_stack, &seat_node->link); node_set_dirty(node); // If focusing a scratchpad container that is fullscreen global, parent // will be NULL struct sway_node *parent = node_get_parent(node); if (parent) { node_set_dirty(parent); } } static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *node) { struct sway_node *last_focus = seat_get_focus(seat); if (last_focus == node) { return; } struct sway_workspace *last_workspace = seat_get_focused_workspace(seat); if (node == NULL) { // Close any popups on the old focus if (node_is_view(last_focus)) { view_close_popups(last_focus->sway_container->view); } seat_send_unfocus(last_focus, seat); sway_input_method_relay_set_focus(&seat->im_relay, NULL); seat->has_focus = false; return; } struct sway_workspace *new_workspace = node->type == N_WORKSPACE ? node->sway_workspace : node->sway_container->pending.workspace; struct sway_container *container = node->type == N_CONTAINER ? node->sway_container : NULL; // Deny setting focus to a view which is hidden by a fullscreen container or global if (container && container_obstructing_fullscreen_container(container)) { return; } // Deny setting focus to a workspace node when using fullscreen global if (root->fullscreen_global && !container && new_workspace) { return; } struct sway_output *new_output = new_workspace ? new_workspace->output : NULL; if (last_workspace != new_workspace && new_output) { node_set_dirty(&new_output->node); } // find new output's old workspace, which might have to be removed if empty struct sway_workspace *new_output_last_ws = new_output ? output_get_active_workspace(new_output) : NULL; // Unfocus the previous focus if (last_focus) { seat_send_unfocus(last_focus, seat); node_set_dirty(last_focus); struct sway_node *parent = node_get_parent(last_focus); if (parent) { node_set_dirty(parent); } } // Put the container parents on the focus stack, then the workspace, then // the focused container. if (container) { struct sway_container *parent = container->pending.parent; while (parent) { seat_set_raw_focus(seat, &parent->node); parent = parent->pending.parent; } } if (new_workspace) { seat_set_raw_focus(seat, &new_workspace->node); } if (container) { seat_set_raw_focus(seat, &container->node); seat_send_focus(&container->node, seat); } // emit ipc events set_workspace(seat, new_workspace); if (container && container->view) { ipc_event_window(container, "focus"); } // Move sticky containers to new workspace if (new_workspace && new_output_last_ws && new_workspace != new_output_last_ws) { for (int i = 0; i < new_output_last_ws->floating->length; ++i) { struct sway_container *floater = new_output_last_ws->floating->items[i]; if (container_is_sticky(floater)) { container_detach(floater); workspace_add_floating(new_workspace, floater); --i; } } } // Close any popups on the old focus if (last_focus && node_is_view(last_focus)) { view_close_popups(last_focus->sway_container->view); } // If urgent, either unset the urgency or start a timer to unset it if (container && container->view && view_is_urgent(container->view) && !container->view->urgent_timer) { struct sway_view *view = container->view; if (last_workspace && last_workspace != new_workspace && config->urgent_timeout > 0) { view->urgent_timer = wl_event_loop_add_timer(server.wl_event_loop, handle_urgent_timeout, view); if (view->urgent_timer) { wl_event_source_timer_update(view->urgent_timer, config->urgent_timeout); } else { sway_log_errno(SWAY_ERROR, "Unable to create urgency timer"); handle_urgent_timeout(view); } } else { view_set_urgent(view, false); } } if (new_output_last_ws) { workspace_consider_destroy(new_output_last_ws); } if (last_workspace && last_workspace != new_output_last_ws) { workspace_consider_destroy(last_workspace); } seat->has_focus = true; if (config->smart_gaps && new_workspace) { // When smart gaps is on, gaps may change when the focus changes so // the workspace needs to be arranged arrange_workspace(new_workspace); } } void seat_set_focus(struct sway_seat *seat, struct sway_node *node) { // Prevents the layer from losing focus if it has keyboard exclusivity if (seat->has_exclusive_layer) { struct wlr_layer_surface_v1 *layer = seat->focused_layer; seat_set_focus_layer(seat, NULL); seat_set_workspace_focus(seat, node); seat_set_focus_layer(seat, layer); } else if (seat->focused_layer) { seat_set_focus_layer(seat, NULL); seat_set_workspace_focus(seat, node); } else { seat_set_workspace_focus(seat, node); } if (server.session_lock.lock) { seat_set_focus_surface(seat, server.session_lock.lock->focused, false); } } void seat_set_focus_container(struct sway_seat *seat, struct sway_container *con) { seat_set_focus(seat, con ? &con->node : NULL); } void seat_set_focus_workspace(struct sway_seat *seat, struct sway_workspace *ws) { seat_set_focus(seat, ws ? &ws->node : NULL); } void seat_set_focus_surface(struct sway_seat *seat, struct wlr_surface *surface, bool unfocus) { if (seat->has_focus && unfocus) { struct sway_node *focus = seat_get_focus(seat); seat_send_unfocus(focus, seat); seat->has_focus = false; } if (surface) { seat_keyboard_notify_enter(seat, surface); } else { wlr_seat_keyboard_notify_clear_focus(seat->wlr_seat); } sway_input_method_relay_set_focus(&seat->im_relay, surface); seat_tablet_pads_set_focus(seat, surface); } void seat_set_focus_layer(struct sway_seat *seat, struct wlr_layer_surface_v1 *layer) { if (!layer && seat->focused_layer) { seat->focused_layer = NULL; struct sway_node *previous = seat_get_focus_inactive(seat, &root->node); if (previous) { // Hack to get seat to re-focus the return value of get_focus seat_set_focus(seat, NULL); seat_set_focus(seat, previous); } return; } else if (!layer) { return; } assert(layer->surface->mapped); if (layer->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP && layer->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) { seat->has_exclusive_layer = true; } if (seat->focused_layer == layer) { return; } seat_set_focus_surface(seat, layer->surface, true); seat->focused_layer = layer; } void seat_unfocus_unless_client(struct sway_seat *seat, struct wl_client *client) { if (seat->focused_layer) { if (wl_resource_get_client(seat->focused_layer->resource) != client) { seat_set_focus_layer(seat, NULL); } } if (seat->has_focus) { struct sway_node *focus = seat_get_focus(seat); if (node_is_view(focus) && wl_resource_get_client( focus->sway_container->view->surface->resource) != client) { seat_set_focus(seat, NULL); } } if (seat->wlr_seat->pointer_state.focused_client) { if (seat->wlr_seat->pointer_state.focused_client->client != client) { wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); } } struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); struct wlr_touch_point *point; wl_list_for_each(point, &seat->wlr_seat->touch_state.touch_points, link) { if (point->client->client != client) { wlr_seat_touch_point_clear_focus(seat->wlr_seat, now.tv_nsec / 1000, point->touch_id); } } } struct sway_node *seat_get_focus_inactive(struct sway_seat *seat, struct sway_node *node) { if (node_is_view(node)) { return node; } struct sway_seat_node *current; wl_list_for_each(current, &seat->focus_stack, link) { if (node_has_ancestor(current->node, node)) { return current->node; } } if (node->type == N_WORKSPACE) { return node; } return NULL; } struct sway_container *seat_get_focus_inactive_tiling(struct sway_seat *seat, struct sway_workspace *workspace) { if (!workspace->tiling->length) { return NULL; } struct sway_seat_node *current; wl_list_for_each(current, &seat->focus_stack, link) { struct sway_node *node = current->node; if (node->type == N_CONTAINER && !container_is_floating_or_child(node->sway_container) && node->sway_container->pending.workspace == workspace) { return node->sway_container; } } return NULL; } struct sway_container *seat_get_focus_inactive_floating(struct sway_seat *seat, struct sway_workspace *workspace) { if (!workspace->floating->length) { return NULL; } struct sway_seat_node *current; wl_list_for_each(current, &seat->focus_stack, link) { struct sway_node *node = current->node; if (node->type == N_CONTAINER && container_is_floating_or_child(node->sway_container) && node->sway_container->pending.workspace == workspace) { return node->sway_container; } } return NULL; } struct sway_node *seat_get_active_tiling_child(struct sway_seat *seat, struct sway_node *parent) { if (node_is_view(parent)) { return parent; } struct sway_seat_node *current; wl_list_for_each(current, &seat->focus_stack, link) { struct sway_node *node = current->node; if (node_get_parent(node) != parent) { continue; } if (parent->type == N_WORKSPACE) { // Only consider tiling children struct sway_workspace *ws = parent->sway_workspace; if (list_find(ws->tiling, node->sway_container) == -1) { continue; } } return node; } return NULL; } struct sway_node *seat_get_focus(struct sway_seat *seat) { if (!seat->has_focus) { return NULL; } sway_assert(!wl_list_empty(&seat->focus_stack), "focus_stack is empty, but has_focus is true"); struct sway_seat_node *current = wl_container_of(seat->focus_stack.next, current, link); return current->node; } struct sway_workspace *seat_get_focused_workspace(struct sway_seat *seat) { struct sway_node *focus = seat_get_focus_inactive(seat, &root->node); if (!focus) { return NULL; } if (focus->type == N_CONTAINER) { return focus->sway_container->pending.workspace; } if (focus->type == N_WORKSPACE) { return focus->sway_workspace; } return NULL; // output doesn't have a workspace yet } struct sway_workspace *seat_get_last_known_workspace(struct sway_seat *seat) { struct sway_seat_node *current; wl_list_for_each(current, &seat->focus_stack, link) { struct sway_node *node = current->node; if (node->type == N_CONTAINER && node->sway_container->pending.workspace) { return node->sway_container->pending.workspace; } else if (node->type == N_WORKSPACE) { return node->sway_workspace; } } return NULL; } struct sway_container *seat_get_focused_container(struct sway_seat *seat) { struct sway_node *focus = seat_get_focus(seat); if (focus && focus->type == N_CONTAINER) { return focus->sway_container; } return NULL; } void seat_apply_config(struct sway_seat *seat, struct seat_config *seat_config) { struct sway_seat_device *seat_device = NULL; if (!seat_config) { return; } seat->idle_inhibit_sources = seat_config->idle_inhibit_sources; seat->idle_wake_sources = seat_config->idle_wake_sources; wl_list_for_each(seat_device, &seat->devices, link) { seat_configure_device(seat, seat_device->input_device); cursor_handle_activity_from_device(seat->cursor, seat_device->input_device->wlr_device); } } struct seat_config *seat_get_config(struct sway_seat *seat) { struct seat_config *seat_config = NULL; for (int i = 0; i < config->seat_configs->length; ++i ) { seat_config = config->seat_configs->items[i]; if (strcmp(seat->wlr_seat->name, seat_config->name) == 0) { return seat_config; } } return NULL; } struct seat_config *seat_get_config_by_name(const char *name) { struct seat_config *seat_config = NULL; for (int i = 0; i < config->seat_configs->length; ++i ) { seat_config = config->seat_configs->items[i]; if (strcmp(name, seat_config->name) == 0) { return seat_config; } } return NULL; } void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec, uint32_t button, enum wl_pointer_button_state state) { seat->last_button_serial = wlr_seat_pointer_notify_button(seat->wlr_seat, time_msec, button, state); } void seat_consider_warp_to_focus(struct sway_seat *seat) { struct sway_node *focus = seat_get_focus(seat); if (config->mouse_warping == WARP_NO || !focus) { return; } if (config->mouse_warping == WARP_OUTPUT) { struct sway_output *output = node_get_output(focus); if (output) { struct wlr_box box; output_get_box(output, &box); if (wlr_box_contains_point(&box, seat->cursor->cursor->x, seat->cursor->cursor->y)) { return; } } } if (focus->type == N_CONTAINER) { cursor_warp_to_container(seat->cursor, focus->sway_container, false); } else { cursor_warp_to_workspace(seat->cursor, focus->sway_workspace); } } void seatop_unref(struct sway_seat *seat, struct sway_container *con) { if (seat->seatop_impl->unref) { seat->seatop_impl->unref(seat, con); } } void seatop_button(struct sway_seat *seat, uint32_t time_msec, struct wlr_input_device *device, uint32_t button, enum wl_pointer_button_state state) { if (seat->seatop_impl->button) { seat->seatop_impl->button(seat, time_msec, device, button, state); } } void seatop_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { if (seat->seatop_impl->pointer_motion) { seat->seatop_impl->pointer_motion(seat, time_msec); } } void seatop_pointer_axis(struct sway_seat *seat, struct wlr_pointer_axis_event *event) { if (seat->seatop_impl->pointer_axis) { seat->seatop_impl->pointer_axis(seat, event); } } void seatop_touch_motion(struct sway_seat *seat, struct wlr_touch_motion_event *event, double lx, double ly) { if (seat->seatop_impl->touch_motion) { seat->seatop_impl->touch_motion(seat, event, lx, ly); } } void seatop_touch_up(struct sway_seat *seat, struct wlr_touch_up_event *event) { if (seat->seatop_impl->touch_up) { seat->seatop_impl->touch_up(seat, event); } } void seatop_touch_down(struct sway_seat *seat, struct wlr_touch_down_event *event, double lx, double ly) { if (seat->seatop_impl->touch_down) { seat->seatop_impl->touch_down(seat, event, lx, ly); } } void seatop_touch_cancel(struct sway_seat *seat, struct wlr_touch_cancel_event *event) { if (seat->seatop_impl->touch_cancel) { seat->seatop_impl->touch_cancel(seat, event); } } void seatop_tablet_tool_tip(struct sway_seat *seat, struct sway_tablet_tool *tool, uint32_t time_msec, enum wlr_tablet_tool_tip_state state) { if (seat->seatop_impl->tablet_tool_tip) { seat->seatop_impl->tablet_tool_tip(seat, tool, time_msec, state); } } void seatop_tablet_tool_motion(struct sway_seat *seat, struct sway_tablet_tool *tool, uint32_t time_msec) { if (seat->seatop_impl->tablet_tool_motion) { seat->seatop_impl->tablet_tool_motion(seat, tool, time_msec); } else { seatop_pointer_motion(seat, time_msec); } } void seatop_hold_begin(struct sway_seat *seat, struct wlr_pointer_hold_begin_event *event) { if (seat->seatop_impl->hold_begin) { seat->seatop_impl->hold_begin(seat, event); } } void seatop_hold_end(struct sway_seat *seat, struct wlr_pointer_hold_end_event *event) { if (seat->seatop_impl->hold_end) { seat->seatop_impl->hold_end(seat, event); } } void seatop_pinch_begin(struct sway_seat *seat, struct wlr_pointer_pinch_begin_event *event) { if (seat->seatop_impl->pinch_begin) { seat->seatop_impl->pinch_begin(seat, event); } } void seatop_pinch_update(struct sway_seat *seat, struct wlr_pointer_pinch_update_event *event) { if (seat->seatop_impl->pinch_update) { seat->seatop_impl->pinch_update(seat, event); } } void seatop_pinch_end(struct sway_seat *seat, struct wlr_pointer_pinch_end_event *event) { if (seat->seatop_impl->pinch_end) { seat->seatop_impl->pinch_end(seat, event); } } void seatop_swipe_begin(struct sway_seat *seat, struct wlr_pointer_swipe_begin_event *event) { if (seat->seatop_impl->swipe_begin) { seat->seatop_impl->swipe_begin(seat, event); } } void seatop_swipe_update(struct sway_seat *seat, struct wlr_pointer_swipe_update_event *event) { if (seat->seatop_impl->swipe_update) { seat->seatop_impl->swipe_update(seat, event); } } void seatop_swipe_end(struct sway_seat *seat, struct wlr_pointer_swipe_end_event *event) { if (seat->seatop_impl->swipe_end) { seat->seatop_impl->swipe_end(seat, event); } } void seatop_rebase(struct sway_seat *seat, uint32_t time_msec) { if (seat->seatop_impl->rebase) { seat->seatop_impl->rebase(seat, time_msec); } } void seatop_end(struct sway_seat *seat) { if (seat->seatop_impl && seat->seatop_impl->end) { seat->seatop_impl->end(seat); } free(seat->seatop_data); seat->seatop_data = NULL; seat->seatop_impl = NULL; } bool seatop_allows_set_cursor(struct sway_seat *seat) { return seat->seatop_impl->allow_set_cursor; } struct sway_keyboard_shortcuts_inhibitor * keyboard_shortcuts_inhibitor_get_for_surface( const struct sway_seat *seat, const struct wlr_surface *surface) { struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor = NULL; wl_list_for_each(sway_inhibitor, &seat->keyboard_shortcuts_inhibitors, link) { if (sway_inhibitor->inhibitor->surface == surface) { return sway_inhibitor; } } return NULL; } struct sway_keyboard_shortcuts_inhibitor * keyboard_shortcuts_inhibitor_get_for_focused_surface( const struct sway_seat *seat) { return keyboard_shortcuts_inhibitor_get_for_surface(seat, seat->wlr_seat->keyboard_state.focused_surface); } ================================================ FILE: sway/input/seatop_default.c ================================================ #include #include #include #include #include #include #include "gesture.h" #include "sway/desktop/transaction.h" #include "sway/input/cursor.h" #include "sway/input/seat.h" #include "sway/input/tablet.h" #include "sway/layers.h" #include "sway/output.h" #include "sway/server.h" #include "sway/scene_descriptor.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "log.h" #include "util.h" #if WLR_HAS_XWAYLAND #include "sway/xwayland.h" #endif struct seatop_default_event { struct sway_node *previous_node; uint32_t pressed_buttons[SWAY_CURSOR_PRESSED_BUTTONS_CAP]; size_t pressed_button_count; struct gesture_tracker gestures; }; /*-----------------------------------------\ * Functions shared by multiple callbacks / *---------------------------------------*/ /** * Determine if the edge of the given container is on the edge of the * workspace/output. */ static bool edge_is_external(struct sway_container *cont, enum wlr_edges edge) { enum sway_container_layout layout = L_NONE; switch (edge) { case WLR_EDGE_TOP: case WLR_EDGE_BOTTOM: layout = L_VERT; break; case WLR_EDGE_LEFT: case WLR_EDGE_RIGHT: layout = L_HORIZ; break; case WLR_EDGE_NONE: sway_assert(false, "Never reached"); return false; } // Iterate the parents until we find one with the layout we want, // then check if the child has siblings between it and the edge. while (cont) { if (container_parent_layout(cont) == layout) { list_t *siblings = container_get_siblings(cont); if (!siblings) { return false; } int index = list_find(siblings, cont); if (index > 0 && (edge == WLR_EDGE_LEFT || edge == WLR_EDGE_TOP)) { return false; } if (index < siblings->length - 1 && (edge == WLR_EDGE_RIGHT || edge == WLR_EDGE_BOTTOM)) { return false; } } cont = cont->pending.parent; } return true; } static enum wlr_edges find_edge(struct sway_container *cont, struct wlr_surface *surface, struct sway_cursor *cursor) { if (!cont->view || (surface && cont->view->surface != surface)) { return WLR_EDGE_NONE; } if (cont->pending.border == B_NONE || !cont->pending.border_thickness || cont->pending.border == B_CSD) { return WLR_EDGE_NONE; } if (cont->pending.fullscreen_mode) { return WLR_EDGE_NONE; } enum wlr_edges edge = 0; if (cursor->cursor->x < cont->pending.x + cont->pending.border_thickness) { edge |= WLR_EDGE_LEFT; } if (cursor->cursor->y < cont->pending.y + cont->pending.border_thickness) { edge |= WLR_EDGE_TOP; } if (cursor->cursor->x >= cont->pending.x + cont->pending.width - cont->pending.border_thickness) { edge |= WLR_EDGE_RIGHT; } if (cursor->cursor->y >= cont->pending.y + cont->pending.height - cont->pending.border_thickness) { edge |= WLR_EDGE_BOTTOM; } return edge; } /** * If the cursor is over a _resizable_ edge, return the edge. * Edges that can't be resized are edges of the workspace. */ enum wlr_edges find_resize_edge(struct sway_container *cont, struct wlr_surface *surface, struct sway_cursor *cursor) { enum wlr_edges edge = find_edge(cont, surface, cursor); if (edge && !container_is_floating(cont) && edge_is_external(cont, edge)) { return WLR_EDGE_NONE; } return edge; } /** * Return the mouse binding which matches modifier, click location, release, * and pressed button state, otherwise return null. */ static struct sway_binding* get_active_mouse_binding( struct seatop_default_event *e, list_t *bindings, uint32_t modifiers, bool release, bool on_titlebar, bool on_border, bool on_content, bool on_workspace, const char *identifier) { uint32_t click_region = ((on_titlebar || on_workspace) ? BINDING_TITLEBAR : 0) | ((on_border || on_workspace) ? BINDING_BORDER : 0) | ((on_content || on_workspace) ? BINDING_CONTENTS : 0); struct sway_binding *current = NULL; for (int i = 0; i < bindings->length; ++i) { struct sway_binding *binding = bindings->items[i]; if (modifiers ^ binding->modifiers || e->pressed_button_count != (size_t)binding->keys->length || release != (binding->flags & BINDING_RELEASE) || !(click_region & binding->flags) || (on_workspace && (click_region & binding->flags) != click_region) || (strcmp(binding->input, identifier) != 0 && strcmp(binding->input, "*") != 0)) { continue; } bool match = true; for (size_t j = 0; j < e->pressed_button_count; j++) { uint32_t key = *(uint32_t *)binding->keys->items[j]; if (key != e->pressed_buttons[j]) { match = false; break; } } if (!match) { continue; } if (!current || strcmp(current->input, "*") == 0) { current = binding; if (strcmp(current->input, identifier) == 0) { // If a binding is found for the exact input, quit searching break; } } } return current; } /** * Remove a button (and duplicates) from the sorted list of currently pressed * buttons. */ static void state_erase_button(struct seatop_default_event *e, uint32_t button) { size_t j = 0; for (size_t i = 0; i < e->pressed_button_count; ++i) { if (i > j) { e->pressed_buttons[j] = e->pressed_buttons[i]; } if (e->pressed_buttons[i] != button) { ++j; } } while (e->pressed_button_count > j) { --e->pressed_button_count; e->pressed_buttons[e->pressed_button_count] = 0; } } /** * Add a button to the sorted list of currently pressed buttons, if there * is space. */ static void state_add_button(struct seatop_default_event *e, uint32_t button) { if (e->pressed_button_count >= SWAY_CURSOR_PRESSED_BUTTONS_CAP) { return; } size_t i = 0; while (i < e->pressed_button_count && e->pressed_buttons[i] < button) { ++i; } size_t j = e->pressed_button_count; while (j > i) { e->pressed_buttons[j] = e->pressed_buttons[j - 1]; --j; } e->pressed_buttons[i] = button; e->pressed_button_count++; } /*-------------------------------------------\ * Functions used by handle_tablet_tool_tip / *-----------------------------------------*/ static void handle_tablet_tool_tip(struct sway_seat *seat, struct sway_tablet_tool *tool, uint32_t time_msec, enum wlr_tablet_tool_tip_state state) { if (state == WLR_TABLET_TOOL_TIP_UP) { wlr_tablet_v2_tablet_tool_notify_up(tool->tablet_v2_tool); return; } struct sway_cursor *cursor = seat->cursor; struct wlr_surface *surface = NULL; double sx, sy; struct sway_node *node = node_at_coords(seat, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); if (!sway_assert(surface, "Expected null-surface tablet input to route through pointer emulation")) { return; } struct sway_container *cont = node && node->type == N_CONTAINER ? node->sway_container : NULL; struct wlr_layer_surface_v1 *layer; #if WLR_HAS_XWAYLAND struct wlr_xwayland_surface *xsurface; #endif if ((layer = wlr_layer_surface_v1_try_from_wlr_surface(surface)) && layer->current.keyboard_interactive) { // Handle tapping a layer surface seat_set_focus_layer(seat, layer); transaction_commit_dirty(); } else if (cont) { bool is_floating_or_child = container_is_floating_or_child(cont); bool is_fullscreen_or_child = container_is_fullscreen_or_child(cont); struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat); bool mod_pressed = keyboard && (wlr_keyboard_get_modifiers(keyboard) & config->floating_mod); // Handle beginning floating move if (is_floating_or_child && !is_fullscreen_or_child && mod_pressed) { seat_set_focus_container(seat, seat_get_focus_inactive_view(seat, &cont->node)); seatop_begin_move_floating(seat, container_toplevel_ancestor(cont)); return; } // Handle moving a tiling container if (config->tiling_drag && mod_pressed && !is_floating_or_child && cont->pending.fullscreen_mode == FULLSCREEN_NONE) { seatop_begin_move_tiling(seat, cont); return; } // Handle tapping on a container surface seat_set_focus_container(seat, cont); seatop_begin_down(seat, node->sway_container, sx, sy); } #if WLR_HAS_XWAYLAND // Handle tapping on an xwayland unmanaged view else if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(surface)) && xsurface->override_redirect && wlr_xwayland_surface_override_redirect_wants_focus(xsurface)) { struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; wlr_xwayland_set_seat(xwayland, seat->wlr_seat); seat_set_focus_surface(seat, xsurface->surface, false); transaction_commit_dirty(); } #endif wlr_tablet_v2_tablet_tool_notify_down(tool->tablet_v2_tool); wlr_tablet_tool_v2_start_implicit_grab(tool->tablet_v2_tool); } /*----------------------------------\ * Functions used by handle_button / *--------------------------------*/ static bool trigger_pointer_button_binding(struct sway_seat *seat, struct wlr_input_device *device, uint32_t button, enum wl_pointer_button_state state, uint32_t modifiers, bool on_titlebar, bool on_border, bool on_contents, bool on_workspace) { // We can reach this for non-pointer devices if we're currently emulating // pointer input for one. Emulated input should not trigger bindings. The // device can be NULL if this is synthetic (e.g. swaymsg-generated) input. if (device && device->type != WLR_INPUT_DEVICE_POINTER) { return false; } struct seatop_default_event *e = seat->seatop_data; char *device_identifier = device ? input_device_get_identifier(device) : strdup("*"); struct sway_binding *binding = NULL; if (state == WL_POINTER_BUTTON_STATE_PRESSED) { state_add_button(e, button); binding = get_active_mouse_binding(e, config->current_mode->mouse_bindings, modifiers, false, on_titlebar, on_border, on_contents, on_workspace, device_identifier); } else { binding = get_active_mouse_binding(e, config->current_mode->mouse_bindings, modifiers, true, on_titlebar, on_border, on_contents, on_workspace, device_identifier); state_erase_button(e, button); } free(device_identifier); if (binding) { seat_execute_command(seat, binding); return true; } return false; } static void handle_button(struct sway_seat *seat, uint32_t time_msec, struct wlr_input_device *device, uint32_t button, enum wl_pointer_button_state state) { struct sway_cursor *cursor = seat->cursor; // Determine what's under the cursor struct wlr_surface *surface = NULL; double sx, sy; struct sway_node *node = node_at_coords(seat, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); struct sway_container *cont = node && node->type == N_CONTAINER ? node->sway_container : NULL; bool is_floating = cont && container_is_floating(cont); bool is_floating_or_child = cont && container_is_floating_or_child(cont); bool is_fullscreen_or_child = cont && container_is_fullscreen_or_child(cont); enum wlr_edges edge = cont ? find_edge(cont, surface, cursor) : WLR_EDGE_NONE; enum wlr_edges resize_edge = cont && edge ? find_resize_edge(cont, surface, cursor) : WLR_EDGE_NONE; bool on_border = edge != WLR_EDGE_NONE; bool on_contents = cont && !on_border && surface; bool on_workspace = node && node->type == N_WORKSPACE; bool on_titlebar = cont && !on_border && !surface; struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat); uint32_t modifiers = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; bool mod_pressed = modifiers & config->floating_mod; uint32_t mod_move_btn = config->floating_mod_inverse ? BTN_RIGHT : BTN_LEFT; uint32_t mod_resize_btn = config->floating_mod_inverse ? BTN_LEFT : BTN_RIGHT; bool mod_move_btn_pressed = mod_pressed && button == mod_move_btn; bool mod_resize_btn_pressed = mod_pressed && button == mod_resize_btn; bool titlebar_left_btn_pressed = on_titlebar && button == BTN_LEFT; // Handle mouse bindings if (trigger_pointer_button_binding(seat, device, button, state, modifiers, on_titlebar, on_border, on_contents, on_workspace)) { return; } // Handle clicking an empty workspace if (node && node->type == N_WORKSPACE) { if (state == WL_POINTER_BUTTON_STATE_PRESSED) { seat_set_focus(seat, node); transaction_commit_dirty(); } seat_pointer_notify_button(seat, time_msec, button, state); return; } // Handle clicking a layer surface and its popups/subsurfaces struct wlr_layer_surface_v1 *layer = NULL; if ((layer = toplevel_layer_surface_from_surface(surface))) { if (layer->current.keyboard_interactive) { seat_set_focus_layer(seat, layer); transaction_commit_dirty(); } if (state == WL_POINTER_BUTTON_STATE_PRESSED) { seatop_begin_down_on_surface(seat, surface, sx, sy); } seat_pointer_notify_button(seat, time_msec, button, state); return; } // Handle tiling resize via border if (cont && resize_edge && button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED && !is_floating) { // If a resize is triggered on a tabbed or stacked container, change // focus to the tab which already had inactive focus -- otherwise, we'd // change the active tab when the user probably just wanted to resize. struct sway_container *cont_to_focus = cont; enum sway_container_layout layout = container_parent_layout(cont); if (layout == L_TABBED || layout == L_STACKED) { cont_to_focus = seat_get_focus_inactive_view(seat, &cont->pending.parent->node); } seat_set_focus_container(seat, cont_to_focus); seatop_begin_resize_tiling(seat, cont, edge); return; } // Handle tiling resize via mod if (cont && !is_floating_or_child && mod_pressed && mod_resize_btn_pressed && state == WL_POINTER_BUTTON_STATE_PRESSED) { edge = 0; edge |= cursor->cursor->x > cont->pending.x + cont->pending.width / 2 ? WLR_EDGE_RIGHT : WLR_EDGE_LEFT; edge |= cursor->cursor->y > cont->pending.y + cont->pending.height / 2 ? WLR_EDGE_BOTTOM : WLR_EDGE_TOP; const char *image = NULL; if (edge == (WLR_EDGE_LEFT | WLR_EDGE_TOP)) { image = "nw-resize"; } else if (edge == (WLR_EDGE_TOP | WLR_EDGE_RIGHT)) { image = "ne-resize"; } else if (edge == (WLR_EDGE_RIGHT | WLR_EDGE_BOTTOM)) { image = "se-resize"; } else if (edge == (WLR_EDGE_BOTTOM | WLR_EDGE_LEFT)) { image = "sw-resize"; } cursor_set_image(seat->cursor, image, NULL); seat_set_focus_container(seat, cont); seatop_begin_resize_tiling(seat, cont, edge); return; } // Handle changing focus when clicking on a container if (cont && state == WL_POINTER_BUTTON_STATE_PRESSED) { // Default case: focus the container that was just clicked. node = &cont->node; // If the container is a tab/stacked container and the click happened // on a tab, switch to the tab. If the tab contents were already // focused, focus the tab container itself. If the tab container was // already focused, cycle back to focusing the tab contents. if (on_titlebar) { struct sway_container *focus = seat_get_focused_container(seat); if (focus == cont || !container_has_ancestor(focus, cont)) { node = seat_get_focus_inactive(seat, &cont->node); } } seat_set_focus(seat, node); transaction_commit_dirty(); } // Handle beginning floating move if (cont && is_floating_or_child && !is_fullscreen_or_child && state == WL_POINTER_BUTTON_STATE_PRESSED && (mod_move_btn_pressed || titlebar_left_btn_pressed)) { seatop_begin_move_floating(seat, container_toplevel_ancestor(cont)); return; } // Handle beginning floating resize if (cont && is_floating_or_child && !is_fullscreen_or_child && state == WL_POINTER_BUTTON_STATE_PRESSED) { // Via border if (button == BTN_LEFT && resize_edge != WLR_EDGE_NONE) { seat_set_focus_container(seat, cont); seatop_begin_resize_floating(seat, cont, resize_edge); return; } // Via mod+click if (mod_resize_btn_pressed) { struct sway_container *floater = container_toplevel_ancestor(cont); edge = 0; edge |= cursor->cursor->x > floater->pending.x + floater->pending.width / 2 ? WLR_EDGE_RIGHT : WLR_EDGE_LEFT; edge |= cursor->cursor->y > floater->pending.y + floater->pending.height / 2 ? WLR_EDGE_BOTTOM : WLR_EDGE_TOP; seat_set_focus_container(seat, floater); seatop_begin_resize_floating(seat, floater, edge); return; } } // Handle moving a tiling container if (config->tiling_drag && (mod_move_btn_pressed || titlebar_left_btn_pressed) && state == WL_POINTER_BUTTON_STATE_PRESSED && !is_floating_or_child && cont && cont->pending.fullscreen_mode == FULLSCREEN_NONE) { // If moving a container by its title bar, use a threshold for the drag if (!mod_pressed && config->tiling_drag_threshold > 0) { seatop_begin_move_tiling_threshold(seat, cont); } else { seatop_begin_move_tiling(seat, cont); } return; } // Handle mousedown on a container surface if (surface && cont && state == WL_POINTER_BUTTON_STATE_PRESSED) { seatop_begin_down(seat, cont, sx, sy); seat_pointer_notify_button(seat, time_msec, button, WL_POINTER_BUTTON_STATE_PRESSED); return; } // Handle clicking a container surface or decorations if (cont && state == WL_POINTER_BUTTON_STATE_PRESSED) { seat_pointer_notify_button(seat, time_msec, button, state); return; } #if WLR_HAS_XWAYLAND // Handle clicking on xwayland unmanaged view struct wlr_xwayland_surface *xsurface; if (surface && (xsurface = wlr_xwayland_surface_try_from_wlr_surface(surface)) && xsurface->override_redirect && wlr_xwayland_surface_override_redirect_wants_focus(xsurface)) { struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; wlr_xwayland_set_seat(xwayland, seat->wlr_seat); seat_set_focus_surface(seat, xsurface->surface, false); transaction_commit_dirty(); seat_pointer_notify_button(seat, time_msec, button, state); } #endif seat_pointer_notify_button(seat, time_msec, button, state); } /*------------------------------------------\ * Functions used by handle_pointer_motion / *----------------------------------------*/ static void check_focus_follows_mouse(struct sway_seat *seat, struct seatop_default_event *e, struct sway_node *hovered_node) { struct sway_node *focus = seat_get_focus(seat); // This is the case if a layer-shell surface is hovered. // If it's on another output, focus the active workspace there. if (!hovered_node) { struct wlr_output *wlr_output = wlr_output_layout_output_at( root->output_layout, seat->cursor->cursor->x, seat->cursor->cursor->y); if (wlr_output == NULL) { return; } struct wlr_surface *surface = NULL; double sx, sy; node_at_coords(seat, seat->cursor->cursor->x, seat->cursor->cursor->y, &surface, &sx, &sy); // Focus topmost layer surface struct wlr_layer_surface_v1 *layer = NULL; if ((layer = toplevel_layer_surface_from_surface(surface)) && layer->current.keyboard_interactive) { seat_set_focus_layer(seat, layer); transaction_commit_dirty(); return; } struct sway_output *hovered_output = wlr_output->data; if (focus && hovered_output != node_get_output(focus)) { struct sway_workspace *ws = output_get_active_workspace(hovered_output); seat_set_focus(seat, &ws->node); transaction_commit_dirty(); } return; } // If a workspace node is hovered (eg. in the gap area), only set focus if // the workspace is on a different output to the previous focus. if (focus && hovered_node->type == N_WORKSPACE) { struct sway_output *focused_output = node_get_output(focus); struct sway_output *hovered_output = node_get_output(hovered_node); if (hovered_output != focused_output) { seat_set_focus(seat, seat_get_focus_inactive(seat, hovered_node)); transaction_commit_dirty(); } return; } // This is where we handle the common case. We don't want to focus inactive // tabs, hence the view_is_visible check. if (node_is_view(hovered_node) && view_is_visible(hovered_node->sway_container->view)) { // e->previous_node is the node which the cursor was over previously. // If focus_follows_mouse is yes and the cursor got over the view due // to, say, a workspace switch, we don't want to set the focus. // But if focus_follows_mouse is "always", we do. if (hovered_node != e->previous_node || config->focus_follows_mouse == FOLLOWS_ALWAYS) { seat_set_focus(seat, hovered_node); transaction_commit_dirty(); } } } static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { struct seatop_default_event *e = seat->seatop_data; struct sway_cursor *cursor = seat->cursor; struct wlr_surface *surface = NULL; double sx, sy; struct sway_node *node = node_at_coords(seat, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); if (config->focus_follows_mouse != FOLLOWS_NO) { check_focus_follows_mouse(seat, e, node); } if (surface) { if (seat_is_input_allowed(seat, surface)) { wlr_seat_pointer_notify_enter(seat->wlr_seat, surface, sx, sy); wlr_seat_pointer_notify_motion(seat->wlr_seat, time_msec, sx, sy); } } else { cursor_update_image(cursor, node); wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); } drag_icons_update_position(seat); e->previous_node = node; } static void handle_tablet_tool_motion(struct sway_seat *seat, struct sway_tablet_tool *tool, uint32_t time_msec) { struct seatop_default_event *e = seat->seatop_data; struct sway_cursor *cursor = seat->cursor; struct wlr_surface *surface = NULL; double sx, sy; struct sway_node *node = node_at_coords(seat, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); if (config->focus_follows_mouse != FOLLOWS_NO) { check_focus_follows_mouse(seat, e, node); } if (surface) { if (seat_is_input_allowed(seat, surface)) { wlr_tablet_v2_tablet_tool_notify_proximity_in(tool->tablet_v2_tool, tool->tablet->tablet_v2, surface); wlr_tablet_v2_tablet_tool_notify_motion(tool->tablet_v2_tool, sx, sy); } } else { cursor_update_image(cursor, node); wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tablet_v2_tool); } drag_icons_update_position(seat); e->previous_node = node; } static void handle_touch_down(struct sway_seat *seat, struct wlr_touch_down_event *event, double lx, double ly) { struct wlr_surface *surface = NULL; struct wlr_seat *wlr_seat = seat->wlr_seat; struct sway_cursor *cursor = seat->cursor; double sx, sy; node_at_coords(seat, seat->touch_x, seat->touch_y, &surface, &sx, &sy); if (surface && wlr_surface_accepts_touch(surface, wlr_seat)) { if (seat_is_input_allowed(seat, surface)) { cursor->simulating_pointer_from_touch = false; seatop_begin_touch_down(seat, surface, event, sx, sy, lx, ly); } } else if (!cursor->simulating_pointer_from_touch && (!surface || seat_is_input_allowed(seat, surface))) { // Fallback to cursor simulation. // The pointer_touch_id state is needed, so drags are not aborted when over // a surface supporting touch and multi touch events don't interfere. cursor->simulating_pointer_from_touch = true; cursor->pointer_touch_id = seat->touch_id; double dx, dy; dx = seat->touch_x - cursor->cursor->x; dy = seat->touch_y - cursor->cursor->y; pointer_motion(cursor, event->time_msec, &event->touch->base, dx, dy, dx, dy); dispatch_cursor_button(cursor, &event->touch->base, event->time_msec, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); } } /*----------------------------------------\ * Functions used by handle_pointer_axis / *--------------------------------------*/ static uint32_t wl_axis_to_button(struct wlr_pointer_axis_event *event) { switch (event->orientation) { case WL_POINTER_AXIS_VERTICAL_SCROLL: return event->delta < 0 ? SWAY_SCROLL_UP : SWAY_SCROLL_DOWN; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: return event->delta < 0 ? SWAY_SCROLL_LEFT : SWAY_SCROLL_RIGHT; default: sway_log(SWAY_DEBUG, "Unknown axis orientation"); return 0; } } static void handle_pointer_axis(struct sway_seat *seat, struct wlr_pointer_axis_event *event) { struct sway_input_device *input_device = event->pointer ? event->pointer->base.data : NULL; struct input_config *ic = input_device ? input_device_get_config(input_device) : NULL; struct sway_cursor *cursor = seat->cursor; struct seatop_default_event *e = seat->seatop_data; // Determine what's under the cursor struct wlr_surface *surface = NULL; double sx, sy; struct sway_node *node = node_at_coords(seat, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); struct sway_container *cont = node && node->type == N_CONTAINER ? node->sway_container : NULL; enum wlr_edges edge = cont ? find_edge(cont, surface, cursor) : WLR_EDGE_NONE; bool on_border = edge != WLR_EDGE_NONE; bool on_titlebar = cont && !on_border && !surface; bool on_titlebar_border = cont && on_border && cursor->cursor->y < cont->pending.content_y; bool on_contents = cont && !on_border && surface; bool on_workspace = node && node->type == N_WORKSPACE; float scroll_factor = (ic == NULL || ic->scroll_factor == FLT_MIN) ? 1.0f : ic->scroll_factor; bool handled = false; // Gather information needed for mouse bindings struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat); uint32_t modifiers = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; struct wlr_input_device *device = input_device ? input_device->wlr_device : NULL; char *dev_id = device ? input_device_get_identifier(device) : strdup("*"); uint32_t button = wl_axis_to_button(event); // Handle mouse bindings - x11 mouse buttons 4-7 - press event struct sway_binding *binding = NULL; state_add_button(e, button); binding = get_active_mouse_binding(e, config->current_mode->mouse_bindings, modifiers, false, on_titlebar, on_border, on_contents, on_workspace, dev_id); if (binding) { seat_execute_command(seat, binding); handled = true; } // Scrolling on a tabbed or stacked title bar (handled as press event) if (!handled && (on_titlebar || on_titlebar_border)) { struct sway_node *new_focus; enum sway_container_layout layout = container_parent_layout(cont); if (layout == L_TABBED || layout == L_STACKED) { struct sway_node *tabcontainer = node_get_parent(node); struct sway_node *active = seat_get_active_tiling_child(seat, tabcontainer); list_t *siblings = container_get_siblings(cont); int desired = list_find(siblings, active->sway_container) + roundf(scroll_factor * event->delta_discrete / WLR_POINTER_AXIS_DISCRETE_STEP); if (desired < 0) { desired = 0; } else if (desired >= siblings->length) { desired = siblings->length - 1; } struct sway_container *new_sibling_con = siblings->items[desired]; struct sway_node *new_sibling = &new_sibling_con->node; // Use the focused child of the tabbed/stacked container, not the // container the user scrolled on. new_focus = seat_get_focus_inactive(seat, new_sibling); } else { new_focus = seat_get_focus_inactive(seat, &cont->node); } seat_set_focus(seat, new_focus); transaction_commit_dirty(); handled = true; } // Handle mouse bindings - x11 mouse buttons 4-7 - release event binding = get_active_mouse_binding(e, config->current_mode->mouse_bindings, modifiers, true, on_titlebar, on_border, on_contents, on_workspace, dev_id); state_erase_button(e, button); if (binding) { seat_execute_command(seat, binding); handled = true; } free(dev_id); if (!handled) { wlr_seat_pointer_notify_axis(cursor->seat->wlr_seat, event->time_msec, event->orientation, scroll_factor * event->delta, roundf(scroll_factor * event->delta_discrete), event->source, event->relative_direction); } } /*------------------------------------\ * Functions used by gesture support / *----------------------------------*/ /** * Check gesture binding for a specific gesture type and finger count. * Returns true if binding is present, false otherwise */ static bool gesture_binding_check(list_t *bindings, enum gesture_type type, uint8_t fingers, struct sway_input_device *device) { char *input = device ? input_device_get_identifier(device->wlr_device) : strdup("*"); for (int i = 0; i < bindings->length; ++i) { struct sway_gesture_binding *binding = bindings->items[i]; // Check type and finger count if (!gesture_check(&binding->gesture, type, fingers)) { continue; } // Check that input matches if (strcmp(binding->input, "*") != 0 && strcmp(binding->input, input) != 0) { continue; } free(input); return true; } free(input); return false; } /** * Return the gesture binding which matches gesture type, finger count * and direction, otherwise return null. */ static struct sway_gesture_binding* gesture_binding_match( list_t *bindings, struct gesture *gesture, const char *input) { struct sway_gesture_binding *current = NULL; // Find best matching binding for (int i = 0; i < bindings->length; ++i) { struct sway_gesture_binding *binding = bindings->items[i]; bool exact = binding->flags & BINDING_EXACT; // Check gesture matching if (!gesture_match(&binding->gesture, gesture, exact)) { continue; } // Check input matching if (strcmp(binding->input, "*") != 0 && strcmp(binding->input, input) != 0) { continue; } // If we already have a match ... if (current) { // ... check if input matching is equivalent if (strcmp(current->input, binding->input) == 0) { // ... - do not override an exact binding if (!exact && current->flags & BINDING_EXACT) { continue; } // ... - and ensure direction matching is better or equal if (gesture_compare(¤t->gesture, &binding->gesture) > 0) { continue; } } else if (strcmp(binding->input, "*") == 0) { // ... do not accept worse input match continue; } } // Accept newer or better match current = binding; // If exact binding and input is found, quit search if (strcmp(current->input, input) == 0 && gesture_compare(¤t->gesture, gesture) == 0) { break; } } // for all gesture bindings return current; } // Wrapper around gesture_tracker_end to use tracker with sway bindings static struct sway_gesture_binding* gesture_tracker_end_and_match( struct gesture_tracker *tracker, struct sway_input_device* device) { // Determine name of input that received gesture char *input = device ? input_device_get_identifier(device->wlr_device) : strdup("*"); // Match tracking result to binding struct gesture *gesture = gesture_tracker_end(tracker); struct sway_gesture_binding *binding = gesture_binding_match( config->current_mode->gesture_bindings, gesture, input); free(gesture); free(input); return binding; } // Small wrapper around seat_execute_command to work on gesture bindings static void gesture_binding_execute(struct sway_seat *seat, struct sway_gesture_binding *binding) { struct sway_binding *dummy_binding = calloc(1, sizeof(struct sway_binding)); dummy_binding->type = BINDING_GESTURE; dummy_binding->command = binding->command; char *description = gesture_to_string(&binding->gesture); sway_log(SWAY_DEBUG, "executing gesture binding: %s", description); free(description); seat_execute_command(seat, dummy_binding); free(dummy_binding); } static void handle_hold_begin(struct sway_seat *seat, struct wlr_pointer_hold_begin_event *event) { // Start tracking gesture if there is a matching binding ... struct sway_input_device *device = event->pointer ? event->pointer->base.data : NULL; list_t *bindings = config->current_mode->gesture_bindings; if (gesture_binding_check(bindings, GESTURE_TYPE_HOLD, event->fingers, device)) { struct seatop_default_event *seatop = seat->seatop_data; gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_HOLD, event->fingers); } else { // ... otherwise forward to client struct sway_cursor *cursor = seat->cursor; wlr_pointer_gestures_v1_send_hold_begin( server.input->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->fingers); } } static void handle_hold_end(struct sway_seat *seat, struct wlr_pointer_hold_end_event *event) { // Ensure that gesture is being tracked and was not cancelled struct seatop_default_event *seatop = seat->seatop_data; if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_HOLD)) { struct sway_cursor *cursor = seat->cursor; wlr_pointer_gestures_v1_send_hold_end( server.input->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->cancelled); return; } if (event->cancelled) { gesture_tracker_cancel(&seatop->gestures); return; } // End gesture tracking and execute matched binding struct sway_input_device *device = event->pointer ? event->pointer->base.data : NULL; struct sway_gesture_binding *binding = gesture_tracker_end_and_match( &seatop->gestures, device); if (binding) { gesture_binding_execute(seat, binding); } } static void handle_pinch_begin(struct sway_seat *seat, struct wlr_pointer_pinch_begin_event *event) { // Start tracking gesture if there is a matching binding ... struct sway_input_device *device = event->pointer ? event->pointer->base.data : NULL; list_t *bindings = config->current_mode->gesture_bindings; if (gesture_binding_check(bindings, GESTURE_TYPE_PINCH, event->fingers, device)) { struct seatop_default_event *seatop = seat->seatop_data; gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_PINCH, event->fingers); } else { // ... otherwise forward to client struct sway_cursor *cursor = seat->cursor; wlr_pointer_gestures_v1_send_pinch_begin( server.input->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->fingers); } } static void handle_pinch_update(struct sway_seat *seat, struct wlr_pointer_pinch_update_event *event) { // Update any ongoing tracking ... struct seatop_default_event *seatop = seat->seatop_data; if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_PINCH)) { gesture_tracker_update(&seatop->gestures, event->dx, event->dy, event->scale, event->rotation); } else { // ... otherwise forward to client struct sway_cursor *cursor = seat->cursor; wlr_pointer_gestures_v1_send_pinch_update( server.input->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->dx, event->dy, event->scale, event->rotation); } } static void handle_pinch_end(struct sway_seat *seat, struct wlr_pointer_pinch_end_event *event) { // Ensure that gesture is being tracked and was not cancelled struct seatop_default_event *seatop = seat->seatop_data; if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_PINCH)) { struct sway_cursor *cursor = seat->cursor; wlr_pointer_gestures_v1_send_pinch_end( server.input->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->cancelled); return; } if (event->cancelled) { gesture_tracker_cancel(&seatop->gestures); return; } // End gesture tracking and execute matched binding struct sway_input_device *device = event->pointer ? event->pointer->base.data : NULL; struct sway_gesture_binding *binding = gesture_tracker_end_and_match( &seatop->gestures, device); if (binding) { gesture_binding_execute(seat, binding); } } static void handle_swipe_begin(struct sway_seat *seat, struct wlr_pointer_swipe_begin_event *event) { // Start tracking gesture if there is a matching binding ... struct sway_input_device *device = event->pointer ? event->pointer->base.data : NULL; list_t *bindings = config->current_mode->gesture_bindings; if (gesture_binding_check(bindings, GESTURE_TYPE_SWIPE, event->fingers, device)) { struct seatop_default_event *seatop = seat->seatop_data; gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_SWIPE, event->fingers); } else { // ... otherwise forward to client struct sway_cursor *cursor = seat->cursor; wlr_pointer_gestures_v1_send_swipe_begin( server.input->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->fingers); } } static void handle_swipe_update(struct sway_seat *seat, struct wlr_pointer_swipe_update_event *event) { // Update any ongoing tracking ... struct seatop_default_event *seatop = seat->seatop_data; if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_SWIPE)) { gesture_tracker_update(&seatop->gestures, event->dx, event->dy, NAN, NAN); } else { // ... otherwise forward to client struct sway_cursor *cursor = seat->cursor; wlr_pointer_gestures_v1_send_swipe_update( server.input->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->dx, event->dy); } } static void handle_swipe_end(struct sway_seat *seat, struct wlr_pointer_swipe_end_event *event) { // Ensure gesture is being tracked and was not cancelled struct seatop_default_event *seatop = seat->seatop_data; if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_SWIPE)) { struct sway_cursor *cursor = seat->cursor; wlr_pointer_gestures_v1_send_swipe_end(server.input->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->cancelled); return; } if (event->cancelled) { gesture_tracker_cancel(&seatop->gestures); return; } // End gesture tracking and execute matched binding struct sway_input_device *device = event->pointer ? event->pointer->base.data : NULL; struct sway_gesture_binding *binding = gesture_tracker_end_and_match( &seatop->gestures, device); if (binding) { gesture_binding_execute(seat, binding); } } /*----------------------------------\ * Functions used by handle_rebase / *--------------------------------*/ static void handle_rebase(struct sway_seat *seat, uint32_t time_msec) { struct seatop_default_event *e = seat->seatop_data; struct sway_cursor *cursor = seat->cursor; struct wlr_surface *surface = NULL; double sx = 0.0, sy = 0.0; e->previous_node = node_at_coords(seat, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); if (surface) { if (seat_is_input_allowed(seat, surface) && !cursor->hidden) { wlr_seat_pointer_notify_enter(seat->wlr_seat, surface, sx, sy); wlr_seat_pointer_notify_motion(seat->wlr_seat, time_msec, sx, sy); } } else { cursor_update_image(cursor, e->previous_node); wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); } } static const struct sway_seatop_impl seatop_impl = { .button = handle_button, .pointer_motion = handle_pointer_motion, .pointer_axis = handle_pointer_axis, .tablet_tool_tip = handle_tablet_tool_tip, .tablet_tool_motion = handle_tablet_tool_motion, .hold_begin = handle_hold_begin, .hold_end = handle_hold_end, .pinch_begin = handle_pinch_begin, .pinch_update = handle_pinch_update, .pinch_end = handle_pinch_end, .swipe_begin = handle_swipe_begin, .swipe_update = handle_swipe_update, .swipe_end = handle_swipe_end, .touch_down = handle_touch_down, .rebase = handle_rebase, .allow_set_cursor = true, }; void seatop_begin_default(struct sway_seat *seat) { seatop_end(seat); struct seatop_default_event *e = calloc(1, sizeof(struct seatop_default_event)); sway_assert(e, "Unable to allocate seatop_default_event"); seat->seatop_impl = &seatop_impl; seat->seatop_data = e; uint32_t time_msec = get_current_time_in_msec(); seatop_rebase(seat, time_msec); } ================================================ FILE: sway/input/seatop_down.c ================================================ #include #include #include #include #include "sway/input/cursor.h" #include "sway/input/seat.h" #include "sway/tree/view.h" #include "sway/desktop/transaction.h" #include "log.h" struct seatop_touch_point_event { double ref_lx, ref_ly; // touch's x/y at start of op double ref_con_lx, ref_con_ly; // container's x/y at start of op int32_t touch_id; struct wl_list link; }; struct seatop_down_event { struct sway_container *con; struct sway_seat *seat; struct wl_listener surface_destroy; struct wlr_surface *surface; double ref_lx, ref_ly; // cursor's x/y at start of op double ref_con_lx, ref_con_ly; // container's x/y at start of op struct wl_list point_events; // seatop_touch_point_event::link }; static void handle_touch_motion(struct sway_seat *seat, struct wlr_touch_motion_event *event, double lx, double ly) { struct seatop_down_event *e = seat->seatop_data; struct seatop_touch_point_event *point_event; bool found = false; wl_list_for_each(point_event, &e->point_events, link) { if (point_event->touch_id == event->touch_id) { found = true; break; } } if (!found) { return; // Probably not a point_event from this seatop_down } double moved_x = lx - point_event->ref_lx; double moved_y = ly - point_event->ref_ly; double sx = point_event->ref_con_lx + moved_x; double sy = point_event->ref_con_ly + moved_y; wlr_seat_touch_notify_motion(seat->wlr_seat, event->time_msec, event->touch_id, sx, sy); } static void handle_touch_up(struct sway_seat *seat, struct wlr_touch_up_event *event) { struct seatop_down_event *e = seat->seatop_data; struct seatop_touch_point_event *point_event, *tmp; wl_list_for_each_safe(point_event, tmp, &e->point_events, link) { if (point_event->touch_id == event->touch_id) { wl_list_remove(&point_event->link); free(point_event); break; } } wlr_seat_touch_notify_up(seat->wlr_seat, event->time_msec, event->touch_id); if (wl_list_empty(&e->point_events)) { seatop_begin_default(seat); } } static void handle_touch_down(struct sway_seat *seat, struct wlr_touch_down_event *event, double lx, double ly) { struct seatop_down_event *e = seat->seatop_data; double sx, sy; struct wlr_surface *surface = NULL; struct sway_node *focused_node = node_at_coords(seat, seat->touch_x, seat->touch_y, &surface, &sx, &sy); if (!surface || surface != e->surface) { // Must start from the initial surface return; } struct seatop_touch_point_event *point_event = calloc(1, sizeof(struct seatop_touch_point_event)); if (!sway_assert(point_event, "Unable to allocate point_event")) { return; } point_event->touch_id = event->touch_id; point_event->ref_lx = lx; point_event->ref_ly = ly; point_event->ref_con_lx = sx; point_event->ref_con_ly = sy; wl_list_insert(&e->point_events, &point_event->link); wlr_seat_touch_notify_down(seat->wlr_seat, surface, event->time_msec, event->touch_id, sx, sy); if (focused_node) { seat_set_focus(seat, focused_node); transaction_commit_dirty(); } } static void handle_touch_cancel(struct sway_seat *seat, struct wlr_touch_cancel_event *event) { struct seatop_down_event *e = seat->seatop_data; struct seatop_touch_point_event *point_event, *tmp; wl_list_for_each_safe(point_event, tmp, &e->point_events, link) { if (point_event->touch_id == event->touch_id) { wl_list_remove(&point_event->link); free(point_event); break; } } if (e->surface) { struct wl_client *client = wl_resource_get_client(e->surface->resource); struct wlr_seat_client *seat_client = wlr_seat_client_for_wl_client(seat->wlr_seat, client); if (seat_client != NULL) { wlr_seat_touch_notify_cancel(seat->wlr_seat, seat_client); } } if (wl_list_empty(&e->point_events)) { seatop_begin_default(seat); } } static void handle_pointer_axis(struct sway_seat *seat, struct wlr_pointer_axis_event *event) { struct sway_input_device *input_device = event->pointer ? event->pointer->base.data : NULL; struct input_config *ic = input_device ? input_device_get_config(input_device) : NULL; float scroll_factor = (ic == NULL || ic->scroll_factor == FLT_MIN) ? 1.0f : ic->scroll_factor; wlr_seat_pointer_notify_axis(seat->wlr_seat, event->time_msec, event->orientation, scroll_factor * event->delta, roundf(scroll_factor * event->delta_discrete), event->source, event->relative_direction); } static void handle_button(struct sway_seat *seat, uint32_t time_msec, struct wlr_input_device *device, uint32_t button, enum wl_pointer_button_state state) { seat_pointer_notify_button(seat, time_msec, button, state); if (seat->cursor->pressed_button_count == 0) { seatop_begin_default(seat); } } static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { struct seatop_down_event *e = seat->seatop_data; if (seat_is_input_allowed(seat, e->surface)) { double moved_x = seat->cursor->cursor->x - e->ref_lx; double moved_y = seat->cursor->cursor->y - e->ref_ly; double sx = e->ref_con_lx + moved_x; double sy = e->ref_con_ly + moved_y; wlr_seat_pointer_notify_motion(seat->wlr_seat, time_msec, sx, sy); } } static void handle_tablet_tool_tip(struct sway_seat *seat, struct sway_tablet_tool *tool, uint32_t time_msec, enum wlr_tablet_tool_tip_state state) { if (state == WLR_TABLET_TOOL_TIP_UP) { wlr_tablet_v2_tablet_tool_notify_up(tool->tablet_v2_tool); seatop_begin_default(seat); } } static void handle_tablet_tool_motion(struct sway_seat *seat, struct sway_tablet_tool *tool, uint32_t time_msec) { struct seatop_down_event *e = seat->seatop_data; if (seat_is_input_allowed(seat, e->surface)) { double moved_x = seat->cursor->cursor->x - e->ref_lx; double moved_y = seat->cursor->cursor->y - e->ref_ly; double sx = e->ref_con_lx + moved_x; double sy = e->ref_con_ly + moved_y; wlr_tablet_v2_tablet_tool_notify_motion(tool->tablet_v2_tool, sx, sy); } } static void handle_destroy(struct wl_listener *listener, void *data) { struct seatop_down_event *e = wl_container_of(listener, e, surface_destroy); if (e) { seatop_begin_default(e->seat); } } static void handle_unref(struct sway_seat *seat, struct sway_container *con) { struct seatop_down_event *e = seat->seatop_data; if (e->con == con) { seatop_begin_default(seat); } } static void handle_end(struct sway_seat *seat) { struct seatop_down_event *e = seat->seatop_data; wl_list_remove(&e->surface_destroy.link); } static const struct sway_seatop_impl seatop_impl = { .button = handle_button, .pointer_motion = handle_pointer_motion, .pointer_axis = handle_pointer_axis, .tablet_tool_tip = handle_tablet_tool_tip, .tablet_tool_motion = handle_tablet_tool_motion, .touch_motion = handle_touch_motion, .touch_up = handle_touch_up, .touch_down = handle_touch_down, .touch_cancel = handle_touch_cancel, .unref = handle_unref, .end = handle_end, .allow_set_cursor = true, }; void seatop_begin_down(struct sway_seat *seat, struct sway_container *con, double sx, double sy) { seatop_begin_down_on_surface(seat, con->view->surface, sx, sy); struct seatop_down_event *e = seat->seatop_data; e->con = con; container_raise_floating(con); transaction_commit_dirty(); } void seatop_begin_touch_down(struct sway_seat *seat, struct wlr_surface *surface, struct wlr_touch_down_event *event, double sx, double sy, double lx, double ly) { seatop_begin_down_on_surface(seat, surface, sx, sy); handle_touch_down(seat, event, lx, ly); } void seatop_begin_down_on_surface(struct sway_seat *seat, struct wlr_surface *surface, double sx, double sy) { seatop_end(seat); struct seatop_down_event *e = calloc(1, sizeof(struct seatop_down_event)); if (!sway_assert(e, "Unable to allocate e")) { return; } e->con = NULL; e->seat = seat; e->surface = surface; wl_signal_add(&e->surface->events.destroy, &e->surface_destroy); e->surface_destroy.notify = handle_destroy; e->ref_lx = seat->cursor->cursor->x; e->ref_ly = seat->cursor->cursor->y; e->ref_con_lx = sx; e->ref_con_ly = sy; wl_list_init(&e->point_events); seat->seatop_impl = &seatop_impl; seat->seatop_data = e; } ================================================ FILE: sway/input/seatop_move_floating.c ================================================ #include #include "sway/desktop/transaction.h" #include "sway/input/cursor.h" #include "sway/input/seat.h" struct seatop_move_floating_event { struct sway_container *con; double dx, dy; // cursor offset in container }; static void finalize_move(struct sway_seat *seat) { struct seatop_move_floating_event *e = seat->seatop_data; // We "move" the container to its own location // so it discovers its output again. container_floating_move_to(e->con, e->con->pending.x, e->con->pending.y); transaction_commit_dirty(); seatop_begin_default(seat); } static void handle_button(struct sway_seat *seat, uint32_t time_msec, struct wlr_input_device *device, uint32_t button, enum wl_pointer_button_state state) { if (seat->cursor->pressed_button_count == 0) { finalize_move(seat); } } static void handle_tablet_tool_tip(struct sway_seat *seat, struct sway_tablet_tool *tool, uint32_t time_msec, enum wlr_tablet_tool_tip_state state) { if (state == WLR_TABLET_TOOL_TIP_UP) { finalize_move(seat); } } static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { struct seatop_move_floating_event *e = seat->seatop_data; struct wlr_cursor *cursor = seat->cursor->cursor; container_floating_move_to(e->con, cursor->x - e->dx, cursor->y - e->dy); transaction_commit_dirty(); } static void handle_unref(struct sway_seat *seat, struct sway_container *con) { struct seatop_move_floating_event *e = seat->seatop_data; if (e->con == con) { seatop_begin_default(seat); } } static const struct sway_seatop_impl seatop_impl = { .button = handle_button, .pointer_motion = handle_pointer_motion, .tablet_tool_tip = handle_tablet_tool_tip, .unref = handle_unref, }; void seatop_begin_move_floating(struct sway_seat *seat, struct sway_container *con) { seatop_end(seat); struct sway_cursor *cursor = seat->cursor; struct seatop_move_floating_event *e = calloc(1, sizeof(struct seatop_move_floating_event)); if (!e) { return; } e->con = con; e->dx = cursor->cursor->x - con->pending.x; e->dy = cursor->cursor->y - con->pending.y; seat->seatop_impl = &seatop_impl; seat->seatop_data = e; container_raise_floating(con); transaction_commit_dirty(); cursor_set_image(cursor, "grab", NULL); wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); } ================================================ FILE: sway/input/seatop_move_tiling.c ================================================ #include #include #include #include "sway/desktop/transaction.h" #include "sway/input/cursor.h" #include "sway/input/seat.h" #include "sway/ipc-server.h" #include "sway/output.h" #include "sway/tree/arrange.h" #include "sway/tree/node.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" // Thickness of the dropzone when dragging to the edge of a layout container #define DROP_LAYOUT_BORDER 30 // Thickness of indicator when dropping onto a titlebar. This should be a // multiple of 2. #define DROP_SPLIT_INDICATOR 10 struct seatop_move_tiling_event { struct sway_container *con; struct sway_node *target_node; enum wlr_edges target_edge; double ref_lx, ref_ly; // cursor's x/y at start of op bool threshold_reached; bool split_target; bool insert_after_target; struct wlr_scene_rect *indicator_rect; }; static void handle_end(struct sway_seat *seat) { struct seatop_move_tiling_event *e = seat->seatop_data; wlr_scene_node_destroy(&e->indicator_rect->node); e->indicator_rect = NULL; } static void handle_motion_prethreshold(struct sway_seat *seat) { struct seatop_move_tiling_event *e = seat->seatop_data; double cx = seat->cursor->cursor->x; double cy = seat->cursor->cursor->y; double sx = e->ref_lx; double sy = e->ref_ly; // Get the scaled threshold for the output. Even if the operation goes // across multiple outputs of varying scales, just use the scale for the // output that the cursor is currently on for simplicity. struct wlr_output *wlr_output = wlr_output_layout_output_at( root->output_layout, cx, cy); double output_scale = wlr_output ? wlr_output->scale : 1; double threshold = config->tiling_drag_threshold * output_scale; threshold *= threshold; // If the threshold has been exceeded, start the actual drag if ((cx - sx) * (cx - sx) + (cy - sy) * (cy - sy) > threshold) { wlr_scene_node_set_enabled(&e->indicator_rect->node, true); e->threshold_reached = true; cursor_set_image(seat->cursor, "grab", NULL); } } static void resize_box(struct wlr_box *box, enum wlr_edges edge, int thickness) { switch (edge) { case WLR_EDGE_TOP: box->height = thickness; break; case WLR_EDGE_LEFT: box->width = thickness; break; case WLR_EDGE_RIGHT: box->x = box->x + box->width - thickness; box->width = thickness; break; case WLR_EDGE_BOTTOM: box->y = box->y + box->height - thickness; box->height = thickness; break; case WLR_EDGE_NONE: box->x += thickness; box->y += thickness; box->width -= thickness * 2; box->height -= thickness * 2; break; } } static void split_border(double pos, int offset, int len, int n_children, int avoid, int *out_pos, bool *out_after) { int region = 2 * n_children * (pos - offset) / len; // If the cursor is over the right side of a left-adjacent titlebar, or the // left side of a right-adjacent titlebar, it's position when dropped will // be the same. To avoid this, shift the region for adjacent containers. if (avoid >= 0) { if (region == 2 * avoid - 1 || region == 2 * avoid) { region--; } else if (region == 2 * avoid + 1 || region == 2 * avoid + 2) { region++; } } int child_index = (region + 1) / 2; *out_after = region % 2; // When dropping at the beginning or end of a container, show the drop // region within the container boundary, otherwise show it on top of the // border between two titlebars. if (child_index == 0) { *out_pos = offset; } else if (child_index == n_children) { *out_pos = offset + len - DROP_SPLIT_INDICATOR; } else { *out_pos = offset + child_index * len / n_children - DROP_SPLIT_INDICATOR / 2; } } static bool split_titlebar(struct sway_node *node, struct sway_container *avoid, struct wlr_cursor *cursor, struct wlr_box *title_box, bool *after) { struct sway_container *con = node->sway_container; struct sway_node *parent = &con->pending.parent->node; int title_height = container_titlebar_height(); struct wlr_box box; int n_children, avoid_index; enum sway_container_layout layout = parent ? node_get_layout(parent) : L_NONE; if (layout == L_TABBED || layout == L_STACKED) { node_get_box(parent, &box); n_children = node_get_children(parent)->length; avoid_index = list_find(node_get_children(parent), avoid); } else { node_get_box(node, &box); n_children = 1; avoid_index = -1; } if (layout == L_STACKED && cursor->y < box.y + title_height * n_children) { // Drop into stacked titlebars. title_box->width = box.width; title_box->height = DROP_SPLIT_INDICATOR; title_box->x = box.x; split_border(cursor->y, box.y, title_height * n_children, n_children, avoid_index, &title_box->y, after); return true; } else if (layout != L_STACKED && cursor->y < box.y + title_height) { // Drop into side-by-side titlebars. title_box->width = DROP_SPLIT_INDICATOR; title_box->height = title_height; title_box->y = box.y; split_border(cursor->x, box.x, box.width, n_children, avoid_index, &title_box->x, after); return true; } return false; } static void update_indicator(struct seatop_move_tiling_event *e, struct wlr_box *box) { wlr_scene_node_set_position(&e->indicator_rect->node, box->x, box->y); wlr_scene_rect_set_size(e->indicator_rect, box->width, box->height); } static void handle_motion_postthreshold(struct sway_seat *seat) { struct seatop_move_tiling_event *e = seat->seatop_data; e->split_target = false; struct wlr_surface *surface = NULL; double sx, sy; struct sway_cursor *cursor = seat->cursor; struct sway_node *node = node_at_coords(seat, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); if (!node) { // Eg. hovered over a layer surface such as swaybar e->target_node = NULL; e->target_edge = WLR_EDGE_NONE; return; } if (node->type == N_WORKSPACE) { // Empty workspace e->target_node = node; e->target_edge = WLR_EDGE_NONE; struct wlr_box drop_box; workspace_get_box(node->sway_workspace, &drop_box); update_indicator(e, &drop_box); return; } // Deny moving within own workspace if this is the only child struct sway_container *con = node->sway_container; if (workspace_num_tiling_views(e->con->pending.workspace) == 1 && con->pending.workspace == e->con->pending.workspace) { e->target_node = NULL; e->target_edge = WLR_EDGE_NONE; return; } struct wlr_box drop_box = { .x = con->pending.content_x, .y = con->pending.content_y, .width = con->pending.content_width, .height = con->pending.content_height, }; // Check if the cursor is over a tilebar only if the destination // container is not a descendant of the source container. if (!surface && !container_has_ancestor(con, e->con) && split_titlebar(node, e->con, cursor->cursor, &drop_box, &e->insert_after_target)) { // Don't allow dropping over the source container's titlebar // to give users a chance to cancel a drag operation. if (con == e->con) { e->target_node = NULL; } else { e->target_node = node; e->split_target = true; } e->target_edge = WLR_EDGE_NONE; update_indicator(e, &drop_box); return; } // Traverse the ancestors, trying to find a layout container perpendicular // to the edge. Eg. close to the top or bottom of a horiz layout. int thresh_top = con->pending.content_y + DROP_LAYOUT_BORDER; int thresh_bottom = con->pending.content_y + con->pending.content_height - DROP_LAYOUT_BORDER; int thresh_left = con->pending.content_x + DROP_LAYOUT_BORDER; int thresh_right = con->pending.content_x + con->pending.content_width - DROP_LAYOUT_BORDER; while (con) { enum wlr_edges edge = WLR_EDGE_NONE; enum sway_container_layout layout = container_parent_layout(con); struct wlr_box box; node_get_box(node_get_parent(&con->node), &box); if (layout == L_HORIZ || layout == L_TABBED) { if (cursor->cursor->y < thresh_top) { edge = WLR_EDGE_TOP; box.height = thresh_top - box.y; } else if (cursor->cursor->y > thresh_bottom) { edge = WLR_EDGE_BOTTOM; box.height = box.y + box.height - thresh_bottom; box.y = thresh_bottom; } } else if (layout == L_VERT || layout == L_STACKED) { if (cursor->cursor->x < thresh_left) { edge = WLR_EDGE_LEFT; box.width = thresh_left - box.x; } else if (cursor->cursor->x > thresh_right) { edge = WLR_EDGE_RIGHT; box.width = box.x + box.width - thresh_right; box.x = thresh_right; } } if (edge) { e->target_node = node_get_parent(&con->node); if (e->target_node == &e->con->node) { e->target_node = node_get_parent(e->target_node); } e->target_edge = edge; update_indicator(e, &box); return; } con = con->pending.parent; } // Use the hovered view - but we must be over the actual surface con = node->sway_container; if (!con->view || !con->view->surface || node == &e->con->node || node_has_ancestor(node, &e->con->node)) { e->target_node = NULL; e->target_edge = WLR_EDGE_NONE; return; } // Find the closest edge size_t thickness = fmin(con->pending.content_width, con->pending.content_height) * 0.3; size_t closest_dist = INT_MAX; size_t dist; e->target_edge = WLR_EDGE_NONE; if ((dist = cursor->cursor->y - con->pending.y) < closest_dist) { closest_dist = dist; e->target_edge = WLR_EDGE_TOP; } if ((dist = cursor->cursor->x - con->pending.x) < closest_dist) { closest_dist = dist; e->target_edge = WLR_EDGE_LEFT; } if ((dist = con->pending.x + con->pending.width - cursor->cursor->x) < closest_dist) { closest_dist = dist; e->target_edge = WLR_EDGE_RIGHT; } if ((dist = con->pending.y + con->pending.height - cursor->cursor->y) < closest_dist) { closest_dist = dist; e->target_edge = WLR_EDGE_BOTTOM; } if (closest_dist > thickness) { e->target_edge = WLR_EDGE_NONE; } e->target_node = node; resize_box(&drop_box, e->target_edge, thickness); update_indicator(e, &drop_box); } static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { struct seatop_move_tiling_event *e = seat->seatop_data; if (e->threshold_reached) { handle_motion_postthreshold(seat); } else { handle_motion_prethreshold(seat); } transaction_commit_dirty(); } static bool is_parallel(enum sway_container_layout layout, enum wlr_edges edge) { bool layout_is_horiz = layout == L_HORIZ || layout == L_TABBED; bool edge_is_horiz = edge == WLR_EDGE_LEFT || edge == WLR_EDGE_RIGHT; return layout_is_horiz == edge_is_horiz; } static void finalize_move(struct sway_seat *seat) { struct seatop_move_tiling_event *e = seat->seatop_data; if (!e->target_node) { seatop_begin_default(seat); return; } struct sway_container *con = e->con; struct sway_container *old_parent = con->pending.parent; struct sway_workspace *old_ws = con->pending.workspace; struct sway_node *target_node = e->target_node; struct sway_workspace *new_ws = target_node->type == N_WORKSPACE ? target_node->sway_workspace : target_node->sway_container->pending.workspace; enum wlr_edges edge = e->target_edge; int after = edge != WLR_EDGE_TOP && edge != WLR_EDGE_LEFT; bool swap = edge == WLR_EDGE_NONE && target_node->type == N_CONTAINER && !e->split_target; if (!swap) { container_detach(con); } // Moving container into empty workspace if (target_node->type == N_WORKSPACE && edge == WLR_EDGE_NONE) { con = workspace_add_tiling(new_ws, con); } else if (e->split_target) { struct sway_container *target = target_node->sway_container; enum sway_container_layout layout = container_parent_layout(target); if (layout != L_TABBED && layout != L_STACKED) { container_split(target, L_TABBED); } container_add_sibling(target, con, e->insert_after_target); ipc_event_window(con, "move"); } else if (target_node->type == N_CONTAINER) { // Moving container before/after another struct sway_container *target = target_node->sway_container; if (swap) { container_swap(target_node->sway_container, con); } else { enum sway_container_layout layout = container_parent_layout(target); if (edge && !is_parallel(layout, edge)) { enum sway_container_layout new_layout = edge == WLR_EDGE_TOP || edge == WLR_EDGE_BOTTOM ? L_VERT : L_HORIZ; container_split(target, new_layout); } container_add_sibling(target, con, after); ipc_event_window(con, "move"); } } else { // Target is a workspace which requires splitting enum sway_container_layout new_layout = edge == WLR_EDGE_TOP || edge == WLR_EDGE_BOTTOM ? L_VERT : L_HORIZ; workspace_split(new_ws, new_layout); workspace_insert_tiling(new_ws, con, after); } if (old_parent) { container_reap_empty(old_parent); } // This is a bit dirty, but we'll set the dimensions to that of a sibling. // I don't think there's any other way to make it consistent without // changing how we auto-size containers. list_t *siblings = container_get_siblings(con); if (siblings->length > 1) { int index = list_find(siblings, con); struct sway_container *sibling = index == 0 ? siblings->items[1] : siblings->items[index - 1]; con->pending.width = sibling->pending.width; con->pending.height = sibling->pending.height; con->width_fraction = sibling->width_fraction; con->height_fraction = sibling->height_fraction; } arrange_workspace(old_ws); if (new_ws != old_ws) { arrange_workspace(new_ws); } transaction_commit_dirty(); seatop_begin_default(seat); } static void handle_button(struct sway_seat *seat, uint32_t time_msec, struct wlr_input_device *device, uint32_t button, enum wl_pointer_button_state state) { if (seat->cursor->pressed_button_count == 0) { finalize_move(seat); } } static void handle_tablet_tool_tip(struct sway_seat *seat, struct sway_tablet_tool *tool, uint32_t time_msec, enum wlr_tablet_tool_tip_state state) { if (state == WLR_TABLET_TOOL_TIP_UP) { finalize_move(seat); } } static void handle_unref(struct sway_seat *seat, struct sway_container *con) { struct seatop_move_tiling_event *e = seat->seatop_data; if (e->target_node == &con->node) { // Drop target e->target_node = NULL; } if (e->con == con) { // The container being moved seatop_begin_default(seat); } } static const struct sway_seatop_impl seatop_impl = { .button = handle_button, .pointer_motion = handle_pointer_motion, .tablet_tool_tip = handle_tablet_tool_tip, .unref = handle_unref, .end = handle_end, }; void seatop_begin_move_tiling_threshold(struct sway_seat *seat, struct sway_container *con) { seatop_end(seat); struct seatop_move_tiling_event *e = calloc(1, sizeof(struct seatop_move_tiling_event)); if (!e) { return; } const float *indicator = config->border_colors.focused.indicator; float color[4] = { indicator[0] * .5, indicator[1] * .5, indicator[2] * .5, indicator[3] * .5, }; e->indicator_rect = wlr_scene_rect_create(seat->scene_tree, 0, 0, color); if (!e->indicator_rect) { free(e); return; } e->con = con; e->ref_lx = seat->cursor->cursor->x; e->ref_ly = seat->cursor->cursor->y; seat->seatop_impl = &seatop_impl; seat->seatop_data = e; container_raise_floating(con); transaction_commit_dirty(); wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); } void seatop_begin_move_tiling(struct sway_seat *seat, struct sway_container *con) { seatop_begin_move_tiling_threshold(seat, con); struct seatop_move_tiling_event *e = seat->seatop_data; if (e) { e->threshold_reached = true; cursor_set_image(seat->cursor, "grab", NULL); } } ================================================ FILE: sway/input/seatop_resize_floating.c ================================================ #include #include #include #include "sway/desktop/transaction.h" #include "sway/input/cursor.h" #include "sway/input/seat.h" #include "sway/tree/arrange.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "sway/tree/container.h" struct seatop_resize_floating_event { struct sway_container *con; enum wlr_edges edge; bool preserve_ratio; double ref_lx, ref_ly; // cursor's x/y at start of op double ref_width, ref_height; // container's size at start of op double ref_con_lx, ref_con_ly; // container's x/y at start of op }; static void handle_button(struct sway_seat *seat, uint32_t time_msec, struct wlr_input_device *device, uint32_t button, enum wl_pointer_button_state state) { struct seatop_resize_floating_event *e = seat->seatop_data; struct sway_container *con = e->con; if (seat->cursor->pressed_button_count == 0) { container_set_resizing(con, false); arrange_container(con); // Send configure w/o resizing hint transaction_commit_dirty(); seatop_begin_default(seat); } } static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { struct seatop_resize_floating_event *e = seat->seatop_data; struct sway_container *con = e->con; enum wlr_edges edge = e->edge; struct sway_cursor *cursor = seat->cursor; // The amount the mouse has moved since the start of the resize operation // Positive is down/right double mouse_move_x = cursor->cursor->x - e->ref_lx; double mouse_move_y = cursor->cursor->y - e->ref_ly; if (edge == WLR_EDGE_TOP || edge == WLR_EDGE_BOTTOM) { mouse_move_x = 0; } if (edge == WLR_EDGE_LEFT || edge == WLR_EDGE_RIGHT) { mouse_move_y = 0; } double grow_width = edge & WLR_EDGE_LEFT ? -mouse_move_x : mouse_move_x; double grow_height = edge & WLR_EDGE_TOP ? -mouse_move_y : mouse_move_y; if (e->preserve_ratio) { double x_multiplier = grow_width / e->ref_width; double y_multiplier = grow_height / e->ref_height; double max_multiplier = fmax(x_multiplier, y_multiplier); grow_width = e->ref_width * max_multiplier; grow_height = e->ref_height * max_multiplier; } struct sway_container_state *state = &con->current; double border_width = 0.0; if (con->current.border == B_NORMAL || con->current.border == B_PIXEL) { border_width = state->border_thickness * 2; } double border_height = 0.0; if (con->current.border == B_NORMAL) { border_height += container_titlebar_height(); border_height += state->border_thickness; } else if (con->current.border == B_PIXEL) { border_height += state->border_thickness * 2; } // Determine new width/height, and accommodate for floating min/max values double width = e->ref_width + grow_width; double height = e->ref_height + grow_height; int min_width, max_width, min_height, max_height; floating_calculate_constraints(&min_width, &max_width, &min_height, &max_height); width = fmin(width, max_width - border_width); width = fmax(width, min_width + border_width); width = fmax(width, 1); height = fmin(height, max_height - border_height); height = fmax(height, min_height + border_height); height = fmax(height, 1); // Apply the view's min/max size if (con->view) { double view_min_width, view_max_width, view_min_height, view_max_height; view_get_constraints(con->view, &view_min_width, &view_max_width, &view_min_height, &view_max_height); width = fmin(width, view_max_width - border_width); width = fmax(width, view_min_width + border_width); width = fmax(width, 1); height = fmin(height, view_max_height - border_height); height = fmax(height, view_min_height + border_height); height = fmax(height, 1); } // Recalculate these, in case we hit a min/max limit grow_width = width - e->ref_width; grow_height = height - e->ref_height; // Determine grow x/y values - these are relative to the container's x/y at // the start of the resize operation. double grow_x = 0, grow_y = 0; if (edge & WLR_EDGE_LEFT) { grow_x = -grow_width; } else if (edge & WLR_EDGE_RIGHT) { grow_x = 0; } else { grow_x = -grow_width / 2; } if (edge & WLR_EDGE_TOP) { grow_y = -grow_height; } else if (edge & WLR_EDGE_BOTTOM) { grow_y = 0; } else { grow_y = -grow_height / 2; } // Determine the amounts we need to bump everything relative to the current // size. int relative_grow_width = width - con->pending.width; int relative_grow_height = height - con->pending.height; int relative_grow_x = (e->ref_con_lx + grow_x) - con->pending.x; int relative_grow_y = (e->ref_con_ly + grow_y) - con->pending.y; // Actually resize stuff con->pending.x += relative_grow_x; con->pending.y += relative_grow_y; con->pending.width += relative_grow_width; con->pending.height += relative_grow_height; con->pending.content_x += relative_grow_x; con->pending.content_y += relative_grow_y; con->pending.content_width += relative_grow_width; con->pending.content_height += relative_grow_height; arrange_container(con); transaction_commit_dirty(); } static void handle_unref(struct sway_seat *seat, struct sway_container *con) { struct seatop_resize_floating_event *e = seat->seatop_data; if (e->con == con) { seatop_begin_default(seat); } } static const struct sway_seatop_impl seatop_impl = { .button = handle_button, .pointer_motion = handle_pointer_motion, .unref = handle_unref, }; void seatop_begin_resize_floating(struct sway_seat *seat, struct sway_container *con, enum wlr_edges edge) { seatop_end(seat); struct seatop_resize_floating_event *e = calloc(1, sizeof(struct seatop_resize_floating_event)); if (!e) { return; } e->con = con; struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat); e->preserve_ratio = keyboard && (wlr_keyboard_get_modifiers(keyboard) & WLR_MODIFIER_SHIFT); e->edge = edge == WLR_EDGE_NONE ? WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT : edge; e->ref_lx = seat->cursor->cursor->x; e->ref_ly = seat->cursor->cursor->y; e->ref_con_lx = con->pending.x; e->ref_con_ly = con->pending.y; e->ref_width = con->pending.width; e->ref_height = con->pending.height; seat->seatop_impl = &seatop_impl; seat->seatop_data = e; container_set_resizing(con, true); container_raise_floating(con); transaction_commit_dirty(); const char *image = edge == WLR_EDGE_NONE ? "se-resize" : wlr_xcursor_get_resize_name(edge); cursor_set_image(seat->cursor, image, NULL); wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); } ================================================ FILE: sway/input/seatop_resize_tiling.c ================================================ #include #include #include "sway/commands.h" #include "sway/desktop/transaction.h" #include "sway/input/cursor.h" #include "sway/input/seat.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/view.h" struct seatop_resize_tiling_event { struct sway_container *con; // leaf container // con, or ancestor of con which will be resized horizontally/vertically struct sway_container *h_con; struct sway_container *v_con; // sibling con(s) that will be resized to accommodate struct sway_container *h_sib; struct sway_container *v_sib; enum wlr_edges edge; enum wlr_edges edge_x, edge_y; double ref_lx, ref_ly; // cursor's x/y at start of op double h_con_orig_width; // width of the horizontal ancestor at start double v_con_orig_height; // height of the vertical ancestor at start }; static struct sway_container *container_get_resize_sibling( struct sway_container *con, uint32_t edge) { if (!con) { return NULL; } list_t *siblings = container_get_siblings(con); int index = container_sibling_index(con); int offset = edge & (WLR_EDGE_TOP | WLR_EDGE_LEFT) ? -1 : 1; if (siblings->length == 1) { return NULL; } else { return siblings->items[index + offset]; } } static void handle_button(struct sway_seat *seat, uint32_t time_msec, struct wlr_input_device *device, uint32_t button, enum wl_pointer_button_state state) { struct seatop_resize_tiling_event *e = seat->seatop_data; if (seat->cursor->pressed_button_count == 0) { if (e->h_con) { container_set_resizing(e->h_con, false); container_set_resizing(e->h_sib, false); if (e->h_con->pending.parent) { arrange_container(e->h_con->pending.parent); } else { arrange_workspace(e->h_con->pending.workspace); } } if (e->v_con) { container_set_resizing(e->v_con, false); container_set_resizing(e->v_sib, false); if (e->v_con->pending.parent) { arrange_container(e->v_con->pending.parent); } else { arrange_workspace(e->v_con->pending.workspace); } } transaction_commit_dirty(); seatop_begin_default(seat); } } static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { struct seatop_resize_tiling_event *e = seat->seatop_data; int amount_x = 0; int amount_y = 0; int moved_x = seat->cursor->cursor->x - e->ref_lx; int moved_y = seat->cursor->cursor->y - e->ref_ly; if (e->h_con) { if (e->edge & WLR_EDGE_LEFT) { amount_x = (e->h_con_orig_width - moved_x) - e->h_con->pending.width; } else if (e->edge & WLR_EDGE_RIGHT) { amount_x = (e->h_con_orig_width + moved_x) - e->h_con->pending.width; } } if (e->v_con) { if (e->edge & WLR_EDGE_TOP) { amount_y = (e->v_con_orig_height - moved_y) - e->v_con->pending.height; } else if (e->edge & WLR_EDGE_BOTTOM) { amount_y = (e->v_con_orig_height + moved_y) - e->v_con->pending.height; } } if (amount_x != 0) { container_resize_tiled(e->h_con, e->edge_x, amount_x); } if (amount_y != 0) { container_resize_tiled(e->v_con, e->edge_y, amount_y); } transaction_commit_dirty(); } static void handle_unref(struct sway_seat *seat, struct sway_container *con) { struct seatop_resize_tiling_event *e = seat->seatop_data; if (e->con == con || e->h_sib == con || e->v_sib == con) { seatop_begin_default(seat); } } static const struct sway_seatop_impl seatop_impl = { .button = handle_button, .pointer_motion = handle_pointer_motion, .unref = handle_unref, }; void seatop_begin_resize_tiling(struct sway_seat *seat, struct sway_container *con, enum wlr_edges edge) { seatop_end(seat); struct seatop_resize_tiling_event *e = calloc(1, sizeof(struct seatop_resize_tiling_event)); if (!e) { return; } e->con = con; e->edge = edge; e->ref_lx = seat->cursor->cursor->x; e->ref_ly = seat->cursor->cursor->y; if (edge & (WLR_EDGE_LEFT | WLR_EDGE_RIGHT)) { e->edge_x = edge & (WLR_EDGE_LEFT | WLR_EDGE_RIGHT); e->h_con = container_find_resize_parent(e->con, e->edge_x); e->h_sib = container_get_resize_sibling(e->h_con, e->edge_x); if (e->h_con) { container_set_resizing(e->h_con, true); container_set_resizing(e->h_sib, true); e->h_con_orig_width = e->h_con->pending.width; } } if (edge & (WLR_EDGE_TOP | WLR_EDGE_BOTTOM)) { e->edge_y = edge & (WLR_EDGE_TOP | WLR_EDGE_BOTTOM); e->v_con = container_find_resize_parent(e->con, e->edge_y); e->v_sib = container_get_resize_sibling(e->v_con, e->edge_y); if (e->v_con) { container_set_resizing(e->v_con, true); container_set_resizing(e->v_sib, true); e->v_con_orig_height = e->v_con->pending.height; } } seat->seatop_impl = &seatop_impl; seat->seatop_data = e; transaction_commit_dirty(); wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); } ================================================ FILE: sway/input/switch.c ================================================ #include "sway/config.h" #include "sway/input/switch.h" #include "sway/server.h" #include "log.h" struct sway_switch *sway_switch_create(struct sway_seat *seat, struct sway_seat_device *device) { struct sway_switch *switch_device = calloc(1, sizeof(struct sway_switch)); if (!sway_assert(switch_device, "could not allocate switch")) { return NULL; } device->switch_device = switch_device; switch_device->wlr = wlr_switch_from_input_device(device->input_device->wlr_device); switch_device->seat_device = device; switch_device->state = WLR_SWITCH_STATE_OFF; wl_list_init(&switch_device->switch_toggle.link); sway_log(SWAY_DEBUG, "Allocated switch for device"); return switch_device; } static bool sway_switch_trigger_test(enum sway_switch_trigger trigger, enum wlr_switch_state state) { switch (trigger) { case SWAY_SWITCH_TRIGGER_ON: return state == WLR_SWITCH_STATE_ON; case SWAY_SWITCH_TRIGGER_OFF: return state == WLR_SWITCH_STATE_OFF; case SWAY_SWITCH_TRIGGER_TOGGLE: return true; } abort(); // unreachable } static void execute_binding(struct sway_switch *sway_switch) { struct sway_seat *seat = sway_switch->seat_device->sway_seat; bool locked = server.session_lock.lock; list_t *bindings = config->current_mode->switch_bindings; struct sway_switch_binding *matched_binding = NULL; for (int i = 0; i < bindings->length; ++i) { struct sway_switch_binding *binding = bindings->items[i]; if (binding->type != sway_switch->type) { continue; } if (!sway_switch_trigger_test(binding->trigger, sway_switch->state)) { continue; } if (config->reloading && (binding->trigger == SWAY_SWITCH_TRIGGER_TOGGLE || (binding->flags & BINDING_RELOAD) == 0)) { continue; } bool binding_locked = binding->flags & BINDING_LOCKED; if (!binding_locked && locked) { continue; } matched_binding = binding; if (binding_locked == locked) { break; } } if (matched_binding) { struct sway_binding *dummy_binding = calloc(1, sizeof(struct sway_binding)); dummy_binding->type = BINDING_SWITCH; dummy_binding->flags = matched_binding->flags; dummy_binding->command = matched_binding->command; seat_execute_command(seat, dummy_binding); free(dummy_binding); } } static void handle_switch_toggle(struct wl_listener *listener, void *data) { struct sway_switch *sway_switch = wl_container_of(listener, sway_switch, switch_toggle); struct wlr_switch_toggle_event *event = data; struct sway_seat *seat = sway_switch->seat_device->sway_seat; seat_idle_notify_activity(seat, IDLE_SOURCE_SWITCH); struct wlr_input_device *wlr_device = sway_switch->seat_device->input_device->wlr_device; char *device_identifier = input_device_get_identifier(wlr_device); sway_log(SWAY_DEBUG, "%s: type %d state %d", device_identifier, event->switch_type, event->switch_state); free(device_identifier); sway_switch->type = event->switch_type; sway_switch->state = event->switch_state; execute_binding(sway_switch); } void sway_switch_configure(struct sway_switch *sway_switch) { wl_list_remove(&sway_switch->switch_toggle.link); wl_signal_add(&sway_switch->wlr->events.toggle, &sway_switch->switch_toggle); sway_switch->switch_toggle.notify = handle_switch_toggle; sway_log(SWAY_DEBUG, "Configured switch for device"); } void sway_switch_destroy(struct sway_switch *sway_switch) { if (!sway_switch) { return; } wl_list_remove(&sway_switch->switch_toggle.link); free(sway_switch); } void sway_switch_retrigger_bindings_for_all(void) { struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { struct sway_seat_device *seat_device; wl_list_for_each(seat_device, &seat->devices, link) { struct sway_input_device *input_device = seat_device->input_device; if (input_device->wlr_device->type != WLR_INPUT_DEVICE_SWITCH) { continue; } execute_binding(seat_device->switch_device); }; } } ================================================ FILE: sway/input/tablet.c ================================================ #include #include #include #include #include #include "log.h" #include "sway/input/cursor.h" #include "sway/input/seat.h" #include "sway/input/tablet.h" #include "sway/server.h" #if WLR_HAS_LIBINPUT_BACKEND #include #endif static void handle_pad_tablet_destroy(struct wl_listener *listener, void *data) { struct sway_tablet_pad *pad = wl_container_of(listener, pad, tablet_destroy); pad->tablet = NULL; wl_list_remove(&pad->tablet_destroy.link); wl_list_init(&pad->tablet_destroy.link); } static void attach_tablet_pad(struct sway_tablet_pad *tablet_pad, struct sway_tablet *tablet) { sway_log(SWAY_DEBUG, "Attaching tablet pad \"%s\" to tablet tool \"%s\"", tablet_pad->seat_device->input_device->wlr_device->name, tablet->seat_device->input_device->wlr_device->name); tablet_pad->tablet = tablet; wl_list_remove(&tablet_pad->tablet_destroy.link); tablet_pad->tablet_destroy.notify = handle_pad_tablet_destroy; wl_signal_add(&tablet->seat_device->input_device->wlr_device->events.destroy, &tablet_pad->tablet_destroy); } struct sway_tablet *sway_tablet_create(struct sway_seat *seat, struct sway_seat_device *device) { struct sway_tablet *tablet = calloc(1, sizeof(struct sway_tablet)); if (!sway_assert(tablet, "could not allocate sway tablet for seat")) { return NULL; } wl_list_insert(&seat->cursor->tablets, &tablet->link); device->tablet = tablet; tablet->seat_device = device; return tablet; } void sway_configure_tablet(struct sway_tablet *tablet) { struct wlr_input_device *device = tablet->seat_device->input_device->wlr_device; struct sway_seat *seat = tablet->seat_device->sway_seat; seat_configure_xcursor(seat); if (!tablet->tablet_v2) { tablet->tablet_v2 = wlr_tablet_create(server.tablet_v2, seat->wlr_seat, device); } #if WLR_HAS_LIBINPUT_BACKEND /* Search for a sibling tablet pad */ if (!wlr_input_device_is_libinput(device)) { /* We can only do this on libinput devices */ return; } struct libinput_device_group *group = libinput_device_get_device_group(wlr_libinput_get_device_handle(device)); struct sway_tablet_pad *tablet_pad; wl_list_for_each(tablet_pad, &seat->cursor->tablet_pads, link) { struct wlr_input_device *pad_device = tablet_pad->seat_device->input_device->wlr_device; if (!wlr_input_device_is_libinput(pad_device)) { continue; } struct libinput_device_group *pad_group = libinput_device_get_device_group(wlr_libinput_get_device_handle(pad_device)); if (pad_group == group) { attach_tablet_pad(tablet_pad, tablet); break; } } #endif } void sway_tablet_destroy(struct sway_tablet *tablet) { if (!tablet) { return; } wl_list_remove(&tablet->link); free(tablet); } static void handle_tablet_tool_set_cursor(struct wl_listener *listener, void *data) { struct sway_tablet_tool *tool = wl_container_of(listener, tool, set_cursor); struct wlr_tablet_v2_event_cursor *event = data; struct sway_cursor *cursor = tool->seat->cursor; if (!seatop_allows_set_cursor(cursor->seat)) { return; } struct wl_client *focused_client = NULL; struct wlr_surface *focused_surface = tool->tablet_v2_tool->focused_surface; if (focused_surface != NULL) { focused_client = wl_resource_get_client(focused_surface->resource); } // TODO: check cursor mode if (focused_client == NULL || event->seat_client->client != focused_client) { sway_log(SWAY_DEBUG, "denying request to set cursor from unfocused client"); return; } cursor_set_image_surface(cursor, event->surface, event->hotspot_x, event->hotspot_y, focused_client); } static void handle_tablet_tool_destroy(struct wl_listener *listener, void *data) { struct sway_tablet_tool *tool = wl_container_of(listener, tool, tool_destroy); wl_list_remove(&tool->tool_destroy.link); wl_list_remove(&tool->set_cursor.link); free(tool); } void sway_tablet_tool_configure(struct sway_tablet *tablet, struct wlr_tablet_tool *wlr_tool) { struct sway_tablet_tool *tool = calloc(1, sizeof(struct sway_tablet_tool)); if (!sway_assert(tool, "could not allocate sway tablet tool for tablet")) { return; } switch (wlr_tool->type) { case WLR_TABLET_TOOL_TYPE_LENS: case WLR_TABLET_TOOL_TYPE_MOUSE: tool->mode = SWAY_TABLET_TOOL_MODE_RELATIVE; break; default: tool->mode = SWAY_TABLET_TOOL_MODE_ABSOLUTE; struct input_config *ic = input_device_get_config( tablet->seat_device->input_device); if (!ic) { break; } for (int i = 0; i < ic->tools->length; i++) { struct input_config_tool *tool_config = ic->tools->items[i]; if (tool_config->type == wlr_tool->type) { tool->mode = tool_config->mode; break; } } } tool->seat = tablet->seat_device->sway_seat; tool->tablet = tablet; tool->tablet_v2_tool = wlr_tablet_tool_create(server.tablet_v2, tablet->seat_device->sway_seat->wlr_seat, wlr_tool); tool->tool_destroy.notify = handle_tablet_tool_destroy; wl_signal_add(&wlr_tool->events.destroy, &tool->tool_destroy); tool->set_cursor.notify = handle_tablet_tool_set_cursor; wl_signal_add(&tool->tablet_v2_tool->events.set_cursor, &tool->set_cursor); wlr_tool->data = tool; } static void handle_tablet_pad_attach(struct wl_listener *listener, void *data) { struct sway_tablet_pad *pad = wl_container_of(listener, pad, attach); struct wlr_tablet_tool *wlr_tool = data; struct sway_tablet_tool *tool = wlr_tool->data; if (!tool) { return; } attach_tablet_pad(pad, tool->tablet); } static void handle_tablet_pad_ring(struct wl_listener *listener, void *data) { struct sway_tablet_pad *pad = wl_container_of(listener, pad, ring); struct wlr_tablet_pad_ring_event *event = data; if (!pad->current_surface) { return; } wlr_tablet_v2_tablet_pad_notify_ring(pad->tablet_v2_pad, event->ring, event->position, event->source == WLR_TABLET_PAD_RING_SOURCE_FINGER, event->time_msec); } static void handle_tablet_pad_strip(struct wl_listener *listener, void *data) { struct sway_tablet_pad *pad = wl_container_of(listener, pad, strip); struct wlr_tablet_pad_strip_event *event = data; if (!pad->current_surface) { return; } wlr_tablet_v2_tablet_pad_notify_strip(pad->tablet_v2_pad, event->strip, event->position, event->source == WLR_TABLET_PAD_STRIP_SOURCE_FINGER, event->time_msec); } static void handle_tablet_pad_button(struct wl_listener *listener, void *data) { struct sway_tablet_pad *pad = wl_container_of(listener, pad, button); struct wlr_tablet_pad_button_event *event = data; if (!pad->current_surface) { return; } wlr_tablet_v2_tablet_pad_notify_mode(pad->tablet_v2_pad, event->group, event->mode, event->time_msec); wlr_tablet_v2_tablet_pad_notify_button(pad->tablet_v2_pad, event->button, event->time_msec, (enum zwp_tablet_pad_v2_button_state)event->state); } struct sway_tablet_pad *sway_tablet_pad_create(struct sway_seat *seat, struct sway_seat_device *device) { struct sway_tablet_pad *tablet_pad = calloc(1, sizeof(struct sway_tablet_pad)); if (!sway_assert(tablet_pad, "could not allocate sway tablet")) { return NULL; } tablet_pad->wlr = wlr_tablet_pad_from_input_device(device->input_device->wlr_device); tablet_pad->seat_device = device; wl_list_init(&tablet_pad->attach.link); wl_list_init(&tablet_pad->button.link); wl_list_init(&tablet_pad->strip.link); wl_list_init(&tablet_pad->ring.link); wl_list_init(&tablet_pad->surface_destroy.link); wl_list_init(&tablet_pad->tablet_destroy.link); wl_list_insert(&seat->cursor->tablet_pads, &tablet_pad->link); return tablet_pad; } void sway_configure_tablet_pad(struct sway_tablet_pad *tablet_pad) { struct wlr_input_device *wlr_device = tablet_pad->seat_device->input_device->wlr_device; struct sway_seat *seat = tablet_pad->seat_device->sway_seat; if (!tablet_pad->tablet_v2_pad) { tablet_pad->tablet_v2_pad = wlr_tablet_pad_create(server.tablet_v2, seat->wlr_seat, wlr_device); } wl_list_remove(&tablet_pad->attach.link); tablet_pad->attach.notify = handle_tablet_pad_attach; wl_signal_add(&tablet_pad->wlr->events.attach_tablet, &tablet_pad->attach); wl_list_remove(&tablet_pad->button.link); tablet_pad->button.notify = handle_tablet_pad_button; wl_signal_add(&tablet_pad->wlr->events.button, &tablet_pad->button); wl_list_remove(&tablet_pad->strip.link); tablet_pad->strip.notify = handle_tablet_pad_strip; wl_signal_add(&tablet_pad->wlr->events.strip, &tablet_pad->strip); wl_list_remove(&tablet_pad->ring.link); tablet_pad->ring.notify = handle_tablet_pad_ring; wl_signal_add(&tablet_pad->wlr->events.ring, &tablet_pad->ring); #if WLR_HAS_LIBINPUT_BACKEND /* Search for a sibling tablet */ if (!wlr_input_device_is_libinput(wlr_device)) { /* We can only do this on libinput devices */ return; } struct libinput_device_group *group = libinput_device_get_device_group(wlr_libinput_get_device_handle(wlr_device)); struct sway_tablet *tool; wl_list_for_each(tool, &seat->cursor->tablets, link) { struct wlr_input_device *tablet = tool->seat_device->input_device->wlr_device; if (!wlr_input_device_is_libinput(tablet)) { continue; } struct libinput_device_group *tablet_group = libinput_device_get_device_group(wlr_libinput_get_device_handle(tablet)); if (tablet_group == group) { attach_tablet_pad(tablet_pad, tool); break; } } #endif } void sway_tablet_pad_destroy(struct sway_tablet_pad *tablet_pad) { if (!tablet_pad) { return; } wl_list_remove(&tablet_pad->link); wl_list_remove(&tablet_pad->attach.link); wl_list_remove(&tablet_pad->button.link); wl_list_remove(&tablet_pad->strip.link); wl_list_remove(&tablet_pad->ring.link); wl_list_remove(&tablet_pad->surface_destroy.link); wl_list_remove(&tablet_pad->tablet_destroy.link); free(tablet_pad); } static void handle_pad_tablet_surface_destroy(struct wl_listener *listener, void *data) { struct sway_tablet_pad *tablet_pad = wl_container_of(listener, tablet_pad, surface_destroy); sway_tablet_pad_set_focus(tablet_pad, NULL); } void sway_tablet_pad_set_focus(struct sway_tablet_pad *tablet_pad, struct wlr_surface *surface) { if (!tablet_pad || !tablet_pad->tablet) { return; } if (surface == tablet_pad->current_surface) { return; } /* Leave current surface */ if (tablet_pad->current_surface) { wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad->tablet_v2_pad, tablet_pad->current_surface); wl_list_remove(&tablet_pad->surface_destroy.link); wl_list_init(&tablet_pad->surface_destroy.link); tablet_pad->current_surface = NULL; } if (surface == NULL || !wlr_surface_accepts_tablet_v2(surface, tablet_pad->tablet->tablet_v2)) { return; } wlr_tablet_v2_tablet_pad_notify_enter(tablet_pad->tablet_v2_pad, tablet_pad->tablet->tablet_v2, surface); tablet_pad->current_surface = surface; tablet_pad->surface_destroy.notify = handle_pad_tablet_surface_destroy; wl_signal_add(&surface->events.destroy, &tablet_pad->surface_destroy); } ================================================ FILE: sway/input/text_input.c ================================================ #include #include #include "log.h" #include "sway/input/seat.h" #include "sway/scene_descriptor.h" #include "sway/tree/root.h" #include "sway/tree/view.h" #include "sway/output.h" #include "sway/input/text_input.h" #include "sway/input/text_input_popup.h" #include "sway/layers.h" #include "sway/server.h" #include static struct sway_text_input *relay_get_focusable_text_input( struct sway_input_method_relay *relay) { struct sway_text_input *text_input = NULL; wl_list_for_each(text_input, &relay->text_inputs, link) { if (text_input->pending_focused_surface) { return text_input; } } return NULL; } static struct sway_text_input *relay_get_focused_text_input( struct sway_input_method_relay *relay) { struct sway_text_input *text_input = NULL; wl_list_for_each(text_input, &relay->text_inputs, link) { if (text_input->input->focused_surface) { return text_input; } } return NULL; } static void handle_im_commit(struct wl_listener *listener, void *data) { struct sway_input_method_relay *relay = wl_container_of(listener, relay, input_method_commit); struct sway_text_input *text_input = relay_get_focused_text_input(relay); if (!text_input) { return; } if (relay->input_method->current.preedit.text) { wlr_text_input_v3_send_preedit_string(text_input->input, relay->input_method->current.preedit.text, relay->input_method->current.preedit.cursor_begin, relay->input_method->current.preedit.cursor_end); } if (relay->input_method->current.commit_text) { wlr_text_input_v3_send_commit_string(text_input->input, relay->input_method->current.commit_text); } if (relay->input_method->current.delete.before_length || relay->input_method->current.delete.after_length) { wlr_text_input_v3_send_delete_surrounding_text(text_input->input, relay->input_method->current.delete.before_length, relay->input_method->current.delete.after_length); } wlr_text_input_v3_send_done(text_input->input); } static void handle_im_keyboard_grab_destroy(struct wl_listener *listener, void *data) { struct sway_input_method_relay *relay = wl_container_of(listener, relay, input_method_keyboard_grab_destroy); struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = relay->input_method->keyboard_grab; struct wlr_seat *wlr_seat = keyboard_grab->input_method->seat; wl_list_remove(&relay->input_method_keyboard_grab_destroy.link); if (keyboard_grab->keyboard) { // send modifier state to original client wlr_seat_set_keyboard(wlr_seat, keyboard_grab->keyboard); wlr_seat_keyboard_notify_modifiers(wlr_seat, &keyboard_grab->keyboard->modifiers); } } static void handle_im_grab_keyboard(struct wl_listener *listener, void *data) { struct sway_input_method_relay *relay = wl_container_of(listener, relay, input_method_grab_keyboard); struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data; // send modifier state to grab struct wlr_keyboard *active_keyboard = wlr_seat_get_keyboard(relay->seat->wlr_seat); wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab, active_keyboard); wl_signal_add(&keyboard_grab->events.destroy, &relay->input_method_keyboard_grab_destroy); relay->input_method_keyboard_grab_destroy.notify = handle_im_keyboard_grab_destroy; } static void text_input_set_pending_focused_surface( struct sway_text_input *text_input, struct wlr_surface *surface) { wl_list_remove(&text_input->pending_focused_surface_destroy.link); text_input->pending_focused_surface = surface; if (surface) { wl_signal_add(&surface->events.destroy, &text_input->pending_focused_surface_destroy); } else { wl_list_init(&text_input->pending_focused_surface_destroy.link); } } static void handle_im_destroy(struct wl_listener *listener, void *data) { struct sway_input_method_relay *relay = wl_container_of(listener, relay, input_method_destroy); wl_list_remove(&relay->input_method_commit.link); wl_list_remove(&relay->input_method_grab_keyboard.link); wl_list_remove(&relay->input_method_destroy.link); wl_list_remove(&relay->input_method_new_popup_surface.link); relay->input_method = NULL; struct sway_text_input *text_input = relay_get_focused_text_input(relay); if (text_input) { // keyboard focus is still there, so keep the surface at hand in case // the input method returns text_input_set_pending_focused_surface(text_input, text_input->input->focused_surface); wlr_text_input_v3_send_leave(text_input->input); } } static void constrain_popup(struct sway_input_popup *popup) { struct sway_text_input *text_input = relay_get_focused_text_input(popup->relay); if (!popup->desc.relative) { return; } struct wlr_box parent = {0}; wlr_scene_node_coords(&popup->desc.relative->parent->node, &parent.x, &parent.y); struct wlr_box geo = {0}; struct wlr_output *output; if (popup->desc.view) { struct sway_view *view = popup->desc.view; output = wlr_output_layout_output_at(root->output_layout, view->container->pending.content_x + view->geometry.x, view->container->pending.content_y + view->geometry.y); parent.width = view->geometry.width; parent.height = view->geometry.height; geo = view->geometry; } else { output = popup->fixed_output; } struct wlr_box output_box; wlr_output_layout_get_box(root->output_layout, output, &output_box); bool cursor_rect = text_input->input->current.features & WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE; struct wlr_box cursor_area; if (cursor_rect) { cursor_area = text_input->input->current.cursor_rectangle; } else { cursor_area = (struct wlr_box) { .width = parent.width, .height = parent.height, }; } int popup_width = popup->popup_surface->surface->current.width; int popup_height = popup->popup_surface->surface->current.height; int x1 = parent.x + cursor_area.x; int x2 = parent.x + cursor_area.x + cursor_area.width; int y1 = parent.y + cursor_area.y; int y2 = parent.y + cursor_area.y + cursor_area.height; int x = x1; int y = y2; int available_right = output_box.x + output_box.width - x1; int available_left = x2 - output_box.x; if (available_right < popup_width && available_left > available_right) { x = x2 - popup_width; } int available_down = output_box.y + output_box.height - y2; int available_up = y1 - output_box.y; if (available_down < popup_height && available_up > available_down) { y = y1 - popup_height; } wlr_scene_node_set_position(popup->desc.relative, x - parent.x - geo.x, y - parent.y - geo.y); if (cursor_rect) { struct wlr_box box = { .x = x1 - x, .y = y1 - y, .width = cursor_area.width, .height = cursor_area.height, }; wlr_input_popup_surface_v2_send_text_input_rectangle( popup->popup_surface, &box); } if (popup->scene_tree) { wlr_scene_node_set_position(&popup->scene_tree->node, x - geo.x, y - geo.y); } } static void input_popup_set_focus(struct sway_input_popup *popup, struct wlr_surface *surface); static void relay_send_im_state(struct sway_input_method_relay *relay, struct wlr_text_input_v3 *input) { struct wlr_input_method_v2 *input_method = relay->input_method; if (!input_method) { sway_log(SWAY_INFO, "Sending IM_DONE but im is gone"); return; } // TODO: only send each of those if they were modified if (input->active_features & WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT) { wlr_input_method_v2_send_surrounding_text(input_method, input->current.surrounding.text, input->current.surrounding.cursor, input->current.surrounding.anchor); } wlr_input_method_v2_send_text_change_cause(input_method, input->current.text_change_cause); if (input->active_features & WLR_TEXT_INPUT_V3_FEATURE_CONTENT_TYPE) { wlr_input_method_v2_send_content_type(input_method, input->current.content_type.hint, input->current.content_type.purpose); } struct sway_text_input *text_input = relay_get_focused_text_input(relay); struct sway_input_popup *popup; wl_list_for_each(popup, &relay->input_popups, link) { if (text_input != NULL) { input_popup_set_focus(popup, text_input->input->focused_surface); } else { input_popup_set_focus(popup, NULL); } } wlr_input_method_v2_send_done(input_method); // TODO: pass intent, display popup size } static void handle_text_input_enable(struct wl_listener *listener, void *data) { struct sway_text_input *text_input = wl_container_of(listener, text_input, text_input_enable); if (text_input->input->focused_surface == NULL) { sway_log(SWAY_DEBUG, "Enabling text input, but no longer focused"); return; } if (text_input->relay->input_method == NULL) { sway_log(SWAY_INFO, "Enabling text input when input method is gone"); return; } wlr_input_method_v2_send_activate(text_input->relay->input_method); relay_send_im_state(text_input->relay, text_input->input); } static void handle_text_input_commit(struct wl_listener *listener, void *data) { struct sway_text_input *text_input = wl_container_of(listener, text_input, text_input_commit); if (text_input->input->focused_surface == NULL) { sway_log(SWAY_DEBUG, "Unfocused text input tried to commit an update"); return; } if (!text_input->input->current_enabled) { sway_log(SWAY_INFO, "Inactive text input tried to commit an update"); return; } sway_log(SWAY_DEBUG, "Text input committed update"); if (text_input->relay->input_method == NULL) { sway_log(SWAY_INFO, "Text input committed, but input method is gone"); return; } relay_send_im_state(text_input->relay, text_input->input); } static void relay_disable_text_input(struct sway_input_method_relay *relay, struct sway_text_input *text_input) { if (relay->input_method == NULL) { sway_log(SWAY_DEBUG, "Disabling text input, but input method is gone"); return; } wlr_input_method_v2_send_deactivate(relay->input_method); relay_send_im_state(relay, text_input->input); } static void handle_text_input_disable(struct wl_listener *listener, void *data) { struct sway_text_input *text_input = wl_container_of(listener, text_input, text_input_disable); if (text_input->input->focused_surface == NULL) { sway_log(SWAY_DEBUG, "Disabling text input, but no longer focused"); return; } relay_disable_text_input(text_input->relay, text_input); } static void handle_text_input_destroy(struct wl_listener *listener, void *data) { struct sway_text_input *text_input = wl_container_of(listener, text_input, text_input_destroy); if (text_input->input->current_enabled) { relay_disable_text_input(text_input->relay, text_input); } text_input_set_pending_focused_surface(text_input, NULL); wl_list_remove(&text_input->text_input_commit.link); wl_list_remove(&text_input->text_input_destroy.link); wl_list_remove(&text_input->text_input_disable.link); wl_list_remove(&text_input->text_input_enable.link); wl_list_remove(&text_input->link); free(text_input); } static void handle_pending_focused_surface_destroy(struct wl_listener *listener, void *data) { struct sway_text_input *text_input = wl_container_of(listener, text_input, pending_focused_surface_destroy); text_input->pending_focused_surface = NULL; wl_list_remove(&text_input->pending_focused_surface_destroy.link); wl_list_init(&text_input->pending_focused_surface_destroy.link); } struct sway_text_input *sway_text_input_create( struct sway_input_method_relay *relay, struct wlr_text_input_v3 *text_input) { struct sway_text_input *input = calloc(1, sizeof(struct sway_text_input)); if (!input) { return NULL; } input->input = text_input; input->relay = relay; wl_list_insert(&relay->text_inputs, &input->link); input->text_input_enable.notify = handle_text_input_enable; wl_signal_add(&text_input->events.enable, &input->text_input_enable); input->text_input_commit.notify = handle_text_input_commit; wl_signal_add(&text_input->events.commit, &input->text_input_commit); input->text_input_disable.notify = handle_text_input_disable; wl_signal_add(&text_input->events.disable, &input->text_input_disable); input->text_input_destroy.notify = handle_text_input_destroy; wl_signal_add(&text_input->events.destroy, &input->text_input_destroy); input->pending_focused_surface_destroy.notify = handle_pending_focused_surface_destroy; wl_list_init(&input->pending_focused_surface_destroy.link); return input; } static void relay_handle_text_input(struct wl_listener *listener, void *data) { struct sway_input_method_relay *relay = wl_container_of(listener, relay, text_input_new); struct wlr_text_input_v3 *wlr_text_input = data; if (relay->seat->wlr_seat != wlr_text_input->seat) { return; } sway_text_input_create(relay, wlr_text_input); } static void input_popup_set_focus(struct sway_input_popup *popup, struct wlr_surface *surface) { wl_list_remove(&popup->focused_surface_unmap.link); if (!popup->scene_tree) { wl_list_init(&popup->focused_surface_unmap.link); return; } if (popup->desc.relative) { scene_descriptor_destroy(&popup->scene_tree->node, SWAY_SCENE_DESC_POPUP); wlr_scene_node_destroy(popup->desc.relative); popup->desc.relative = NULL; } if (surface == NULL) { wl_list_init(&popup->focused_surface_unmap.link); wlr_scene_node_set_enabled(&popup->scene_tree->node, false); return; } struct wlr_layer_surface_v1 *layer_surface = wlr_layer_surface_v1_try_from_wlr_surface(surface); struct wlr_session_lock_surface_v1 *lock_surface = wlr_session_lock_surface_v1_try_from_wlr_surface(surface); struct wlr_scene_tree *relative_parent; if (layer_surface) { wl_signal_add(&layer_surface->surface->events.unmap, &popup->focused_surface_unmap); struct sway_layer_surface *layer = layer_surface->data; if (layer == NULL) { return; } relative_parent = layer->scene->tree; popup->desc.view = NULL; // we don't need to add an event here to NULL out this field because // this field will only be initialized if the popup is part of a layer // surface. Layer surfaces get destroyed as part of the output being // destroyed, thus also trickling down to popups. popup->fixed_output = layer->layer_surface->output; } else if (lock_surface) { wl_signal_add(&lock_surface->surface->events.unmap, &popup->focused_surface_unmap); struct sway_layer_surface *lock = lock_surface->data; if (lock == NULL) { return; } relative_parent = lock->scene->tree; popup->desc.view = NULL; // we don't need to add an event here to NULL out this field because // this field will only be initialized if the popup is part of a layer // surface. Layer surfaces get destroyed as part of the output being // destroyed, thus also trickling down to popups. popup->fixed_output = lock->layer_surface->output; } else { struct sway_view *view = view_from_wlr_surface(surface); // In the future there may be other shells been added, so we also need to check here. if (view == NULL) { sway_log(SWAY_DEBUG, "Unsupported IME focus surface"); return; } wl_signal_add(&view->events.unmap, &popup->focused_surface_unmap); relative_parent = view->scene_tree; popup->desc.view = view; } struct wlr_scene_tree *relative = wlr_scene_tree_create(relative_parent); popup->desc.relative = &relative->node; if (!scene_descriptor_assign(&popup->scene_tree->node, SWAY_SCENE_DESC_POPUP, &popup->desc)) { wlr_scene_node_destroy(&popup->scene_tree->node); popup->scene_tree = NULL; return; } constrain_popup(popup); wlr_scene_node_set_enabled(&popup->scene_tree->node, true); } static void handle_im_popup_destroy(struct wl_listener *listener, void *data) { struct sway_input_popup *popup = wl_container_of(listener, popup, popup_destroy); wlr_scene_node_destroy(&popup->scene_tree->node); wl_list_remove(&popup->focused_surface_unmap.link); wl_list_remove(&popup->popup_surface_commit.link); wl_list_remove(&popup->popup_surface_map.link); wl_list_remove(&popup->popup_surface_unmap.link); wl_list_remove(&popup->popup_destroy.link); wl_list_remove(&popup->link); free(popup); } static void handle_im_popup_surface_map(struct wl_listener *listener, void *data) { struct sway_input_popup *popup = wl_container_of(listener, popup, popup_surface_map); struct sway_text_input *text_input = relay_get_focused_text_input(popup->relay); if (text_input != NULL) { input_popup_set_focus(popup, text_input->input->focused_surface); } else { input_popup_set_focus(popup, NULL); } } static void handle_im_popup_surface_unmap(struct wl_listener *listener, void *data) { struct sway_input_popup *popup = wl_container_of(listener, popup, popup_surface_unmap); scene_descriptor_destroy(&popup->scene_tree->node, SWAY_SCENE_DESC_POPUP); // relative should already be freed as it should be a child of the just unmapped scene popup->desc.relative = NULL; input_popup_set_focus(popup, NULL); } static void handle_im_popup_surface_commit(struct wl_listener *listener, void *data) { struct sway_input_popup *popup = wl_container_of(listener, popup, popup_surface_commit); constrain_popup(popup); } static void handle_im_focused_surface_unmap( struct wl_listener *listener, void *data) { struct sway_input_popup *popup = wl_container_of(listener, popup, focused_surface_unmap); input_popup_set_focus(popup, NULL); } static void handle_im_new_popup_surface(struct wl_listener *listener, void *data) { struct sway_input_method_relay *relay = wl_container_of(listener, relay, input_method_new_popup_surface); struct sway_input_popup *popup = calloc(1, sizeof(*popup)); if (!popup) { sway_log(SWAY_ERROR, "Failed to allocate an input method popup"); return; } popup->relay = relay; popup->popup_surface = data; popup->popup_surface->data = popup; popup->scene_tree = wlr_scene_tree_create(root->layers.popup); if (!popup->scene_tree) { sway_log(SWAY_ERROR, "Failed to allocate scene tree"); free(popup); return; } if (!wlr_scene_subsurface_tree_create(popup->scene_tree, popup->popup_surface->surface)) { sway_log(SWAY_ERROR, "Failed to allocate subsurface tree"); wlr_scene_node_destroy(&popup->scene_tree->node); free(popup); return; } wl_signal_add(&popup->popup_surface->events.destroy, &popup->popup_destroy); popup->popup_destroy.notify = handle_im_popup_destroy; wl_signal_add(&popup->popup_surface->surface->events.commit, &popup->popup_surface_commit); popup->popup_surface_commit.notify = handle_im_popup_surface_commit; wl_signal_add(&popup->popup_surface->surface->events.map, &popup->popup_surface_map); popup->popup_surface_map.notify = handle_im_popup_surface_map; wl_signal_add(&popup->popup_surface->surface->events.unmap, &popup->popup_surface_unmap); popup->popup_surface_unmap.notify = handle_im_popup_surface_unmap; wl_list_init(&popup->focused_surface_unmap.link); popup->focused_surface_unmap.notify = handle_im_focused_surface_unmap; struct sway_text_input *text_input = relay_get_focused_text_input(relay); if (text_input != NULL) { input_popup_set_focus(popup, text_input->input->focused_surface); } else { input_popup_set_focus(popup, NULL); } wl_list_insert(&relay->input_popups, &popup->link); } static void text_input_send_enter(struct sway_text_input *text_input, struct wlr_surface *surface) { wlr_text_input_v3_send_enter(text_input->input, surface); struct sway_input_popup *popup; wl_list_for_each(popup, &text_input->relay->input_popups, link) { input_popup_set_focus(popup, surface); } } static void relay_handle_input_method(struct wl_listener *listener, void *data) { struct sway_input_method_relay *relay = wl_container_of(listener, relay, input_method_new); struct wlr_input_method_v2 *input_method = data; if (relay->seat->wlr_seat != input_method->seat) { return; } if (relay->input_method != NULL) { sway_log(SWAY_INFO, "Attempted to connect second input method to a seat"); wlr_input_method_v2_send_unavailable(input_method); return; } relay->input_method = input_method; wl_signal_add(&relay->input_method->events.commit, &relay->input_method_commit); relay->input_method_commit.notify = handle_im_commit; wl_signal_add(&relay->input_method->events.grab_keyboard, &relay->input_method_grab_keyboard); relay->input_method_grab_keyboard.notify = handle_im_grab_keyboard; wl_signal_add(&relay->input_method->events.destroy, &relay->input_method_destroy); relay->input_method_destroy.notify = handle_im_destroy; wl_signal_add(&relay->input_method->events.new_popup_surface, &relay->input_method_new_popup_surface); relay->input_method_new_popup_surface.notify = handle_im_new_popup_surface; struct sway_text_input *text_input = relay_get_focusable_text_input(relay); if (text_input) { text_input_send_enter(text_input, text_input->pending_focused_surface); text_input_set_pending_focused_surface(text_input, NULL); } } static void sway_input_method_relay_finish_text_input(struct sway_input_method_relay *relay) { wl_list_remove(&relay->text_input_new.link); wl_list_remove(&relay->text_input_manager_destroy.link); wl_list_init(&relay->text_input_new.link); wl_list_init(&relay->text_input_manager_destroy.link); } static void relay_handle_text_input_manager_destroy(struct wl_listener *listener, void *data) { struct sway_input_method_relay *relay = wl_container_of(listener, relay, text_input_manager_destroy); sway_input_method_relay_finish_text_input(relay); } static void sway_input_method_relay_finish_input_method(struct sway_input_method_relay *relay) { wl_list_remove(&relay->input_method_new.link); wl_list_remove(&relay->input_method_manager_destroy.link); wl_list_init(&relay->input_method_new.link); wl_list_init(&relay->input_method_manager_destroy.link); } static void relay_handle_input_method_manager_destroy(struct wl_listener *listener, void *data) { struct sway_input_method_relay *relay = wl_container_of(listener, relay, input_method_manager_destroy); sway_input_method_relay_finish_input_method(relay); } void sway_input_method_relay_init(struct sway_seat *seat, struct sway_input_method_relay *relay) { relay->seat = seat; wl_list_init(&relay->text_inputs); wl_list_init(&relay->input_popups); relay->text_input_new.notify = relay_handle_text_input; wl_signal_add(&server.text_input->events.new_text_input, &relay->text_input_new); relay->text_input_manager_destroy.notify = relay_handle_text_input_manager_destroy; wl_signal_add(&server.text_input->events.destroy, &relay->text_input_manager_destroy); relay->input_method_new.notify = relay_handle_input_method; wl_signal_add( &server.input_method->events.new_input_method, &relay->input_method_new); relay->input_method_manager_destroy.notify = relay_handle_input_method_manager_destroy; wl_signal_add(&server.input_method->events.destroy, &relay->input_method_manager_destroy); } void sway_input_method_relay_finish(struct sway_input_method_relay *relay) { sway_input_method_relay_finish_text_input(relay); sway_input_method_relay_finish_input_method(relay); } void sway_input_method_relay_set_focus(struct sway_input_method_relay *relay, struct wlr_surface *surface) { struct sway_text_input *text_input; wl_list_for_each(text_input, &relay->text_inputs, link) { if (text_input->pending_focused_surface) { assert(text_input->input->focused_surface == NULL); if (surface != text_input->pending_focused_surface) { text_input_set_pending_focused_surface(text_input, NULL); } } else if (text_input->input->focused_surface) { assert(text_input->pending_focused_surface == NULL); if (surface != text_input->input->focused_surface) { relay_disable_text_input(relay, text_input); wlr_text_input_v3_send_leave(text_input->input); } else { sway_log(SWAY_DEBUG, "IM relay set_focus already focused"); continue; } } if (surface && wl_resource_get_client(text_input->input->resource) == wl_resource_get_client(surface->resource)) { if (relay->input_method) { wlr_text_input_v3_send_enter(text_input->input, surface); } else { text_input_set_pending_focused_surface(text_input, surface); } } } } ================================================ FILE: sway/ipc-json.c ================================================ #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "log.h" #include "sway/config.h" #include "sway/ipc-json.h" #include "sway/server.h" #include "sway/tree/container.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "sway/output.h" #include "sway/input/input-manager.h" #include "sway/input/cursor.h" #include "sway/input/seat.h" #include "wlr-layer-shell-unstable-v1-protocol.h" #include "sway/desktop/idle_inhibit_v1.h" #if WLR_HAS_LIBINPUT_BACKEND #include #endif static const int i3_output_id = INT32_MAX; static const int i3_scratch_id = INT32_MAX - 1; static const char *ipc_json_node_type_description(enum sway_node_type node_type) { switch (node_type) { case N_ROOT: return "root"; case N_OUTPUT: return "output"; case N_WORKSPACE: return "workspace"; case N_CONTAINER: return "con"; } return "none"; } static const char *ipc_json_layout_description(enum sway_container_layout l) { switch (l) { case L_VERT: return "splitv"; case L_HORIZ: return "splith"; case L_TABBED: return "tabbed"; case L_STACKED: return "stacked"; case L_NONE: break; } return "none"; } static const char *ipc_json_orientation_description(enum sway_container_layout l) { switch (l) { case L_VERT: return "vertical"; case L_HORIZ: return "horizontal"; default: return "none"; } } static const char *ipc_json_border_description(enum sway_container_border border) { switch (border) { case B_NONE: return "none"; case B_PIXEL: return "pixel"; case B_NORMAL: return "normal"; case B_CSD: return "csd"; } return "unknown"; } static const char *ipc_json_output_transform_description(enum wl_output_transform transform) { switch (transform) { case WL_OUTPUT_TRANSFORM_NORMAL: return "normal"; case WL_OUTPUT_TRANSFORM_90: // Sway uses clockwise transforms, while WL_OUTPUT_TRANSFORM_* describes // anti-clockwise transforms. return "270"; case WL_OUTPUT_TRANSFORM_180: return "180"; case WL_OUTPUT_TRANSFORM_270: // Transform also inverted here. return "90"; case WL_OUTPUT_TRANSFORM_FLIPPED: return "flipped"; case WL_OUTPUT_TRANSFORM_FLIPPED_90: // Inverted. return "flipped-270"; case WL_OUTPUT_TRANSFORM_FLIPPED_180: return "flipped-180"; case WL_OUTPUT_TRANSFORM_FLIPPED_270: // Inverted. return "flipped-90"; } return NULL; } static const char *ipc_json_output_adaptive_sync_status_description( enum wlr_output_adaptive_sync_status status) { switch (status) { case WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED: return "disabled"; case WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED: return "enabled"; } return NULL; } static const char *ipc_json_output_mode_aspect_ratio_description( enum wlr_output_mode_aspect_ratio aspect_ratio) { switch (aspect_ratio) { case WLR_OUTPUT_MODE_ASPECT_RATIO_NONE: return "none"; case WLR_OUTPUT_MODE_ASPECT_RATIO_4_3: return "4:3"; case WLR_OUTPUT_MODE_ASPECT_RATIO_16_9: return "16:9"; case WLR_OUTPUT_MODE_ASPECT_RATIO_64_27: return "64:27"; case WLR_OUTPUT_MODE_ASPECT_RATIO_256_135: return "256:135"; } return NULL; } static json_object *ipc_json_output_mode_description( const struct wlr_output_mode *mode) { const char *pic_ar = ipc_json_output_mode_aspect_ratio_description(mode->picture_aspect_ratio); json_object *mode_object = json_object_new_object(); json_object_object_add(mode_object, "width", json_object_new_int(mode->width)); json_object_object_add(mode_object, "height", json_object_new_int(mode->height)); json_object_object_add(mode_object, "refresh", json_object_new_int(mode->refresh)); json_object_object_add(mode_object, "picture_aspect_ratio", json_object_new_string(pic_ar)); return mode_object; } #if WLR_HAS_XWAYLAND static const char *ipc_json_xwindow_type_description(struct sway_view *view) { struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface; struct sway_xwayland *xwayland = &server.xwayland; for (size_t i = 0; i < surface->window_type_len; ++i) { xcb_atom_t type = surface->window_type[i]; if (type == xwayland->atoms[NET_WM_WINDOW_TYPE_NORMAL]) { return "normal"; } else if (type == xwayland->atoms[NET_WM_WINDOW_TYPE_DIALOG]) { return "dialog"; } else if (type == xwayland->atoms[NET_WM_WINDOW_TYPE_UTILITY]) { return "utility"; } else if (type == xwayland->atoms[NET_WM_WINDOW_TYPE_TOOLBAR]) { return "toolbar"; } else if (type == xwayland->atoms[NET_WM_WINDOW_TYPE_SPLASH]) { return "splash"; } else if (type == xwayland->atoms[NET_WM_WINDOW_TYPE_MENU]) { return "menu"; } else if (type == xwayland->atoms[NET_WM_WINDOW_TYPE_DROPDOWN_MENU]) { return "dropdown_menu"; } else if (type == xwayland->atoms[NET_WM_WINDOW_TYPE_POPUP_MENU]) { return "popup_menu"; } else if (type == xwayland->atoms[NET_WM_WINDOW_TYPE_TOOLTIP]) { return "tooltip"; } else if (type == xwayland->atoms[NET_WM_WINDOW_TYPE_NOTIFICATION]) { return "notification"; } else { return "unknown"; } } return "unknown"; } #endif static const char *ipc_json_user_idle_inhibitor_description(enum sway_idle_inhibit_mode mode) { switch (mode) { case INHIBIT_IDLE_FOCUS: return "focus"; case INHIBIT_IDLE_FULLSCREEN: return "fullscreen"; case INHIBIT_IDLE_OPEN: return "open"; case INHIBIT_IDLE_VISIBLE: return "visible"; case INHIBIT_IDLE_APPLICATION: return NULL; } return NULL; } static const char *ipc_json_content_type_description(enum wp_content_type_v1_type type) { switch (type) { case WP_CONTENT_TYPE_V1_TYPE_NONE: return "none"; case WP_CONTENT_TYPE_V1_TYPE_PHOTO: return "photo"; case WP_CONTENT_TYPE_V1_TYPE_VIDEO: return "video"; case WP_CONTENT_TYPE_V1_TYPE_GAME: return "game"; } return NULL; } json_object *ipc_json_get_version(void) { int major = 0, minor = 0, patch = 0; json_object *version = json_object_new_object(); sscanf(SWAY_VERSION, "%d.%d.%d", &major, &minor, &patch); json_object_object_add(version, "human_readable", json_object_new_string(SWAY_VERSION)); json_object_object_add(version, "variant", json_object_new_string("sway")); json_object_object_add(version, "major", json_object_new_int(major)); json_object_object_add(version, "minor", json_object_new_int(minor)); json_object_object_add(version, "patch", json_object_new_int(patch)); json_object_object_add(version, "loaded_config_file_name", json_object_new_string(config->current_config_path)); return version; } static json_object *ipc_json_create_rect(struct wlr_box *box) { json_object *rect = json_object_new_object(); json_object_object_add(rect, "x", json_object_new_int(box->x)); json_object_object_add(rect, "y", json_object_new_int(box->y)); json_object_object_add(rect, "width", json_object_new_int(box->width)); json_object_object_add(rect, "height", json_object_new_int(box->height)); return rect; } static json_object *ipc_json_create_empty_rect(void) { struct wlr_box empty = {0, 0, 0, 0}; return ipc_json_create_rect(&empty); } static json_object *ipc_json_create_node(int id, const char* type, char *name, bool focused, json_object *focus, struct wlr_box *box) { json_object *object = json_object_new_object(); json_object_object_add(object, "id", json_object_new_int(id)); json_object_object_add(object, "type", json_object_new_string(type)); json_object_object_add(object, "orientation", json_object_new_string( ipc_json_orientation_description(L_HORIZ))); json_object_object_add(object, "percent", NULL); json_object_object_add(object, "urgent", json_object_new_boolean(false)); json_object_object_add(object, "marks", json_object_new_array()); json_object_object_add(object, "focused", json_object_new_boolean(focused)); json_object_object_add(object, "layout", json_object_new_string( ipc_json_layout_description(L_HORIZ))); // set default values to be compatible with i3 json_object_object_add(object, "border", json_object_new_string( ipc_json_border_description(B_NONE))); json_object_object_add(object, "current_border_width", json_object_new_int(0)); json_object_object_add(object, "rect", ipc_json_create_rect(box)); json_object_object_add(object, "deco_rect", ipc_json_create_empty_rect()); json_object_object_add(object, "window_rect", ipc_json_create_empty_rect()); json_object_object_add(object, "geometry", ipc_json_create_empty_rect()); json_object_object_add(object, "name", name ? json_object_new_string(name) : NULL); json_object_object_add(object, "window", NULL); json_object_object_add(object, "nodes", json_object_new_array()); json_object_object_add(object, "floating_nodes", json_object_new_array()); json_object_object_add(object, "focus", focus); json_object_object_add(object, "fullscreen_mode", json_object_new_int(0)); json_object_object_add(object, "sticky", json_object_new_boolean(false)); json_object_object_add(object, "floating", NULL); json_object_object_add(object, "scratchpad_state", NULL); return object; } static void ipc_json_describe_wlr_output(struct wlr_output *wlr_output, json_object *object) { json_object_object_add(object, "primary", json_object_new_boolean(false)); json_object_object_add(object, "make", json_object_new_string(wlr_output->make ? wlr_output->make : "Unknown")); json_object_object_add(object, "model", json_object_new_string(wlr_output->model ? wlr_output->model : "Unknown")); json_object_object_add(object, "serial", json_object_new_string(wlr_output->serial ? wlr_output->serial : "Unknown")); json_object *modes_array = json_object_new_array(); struct wlr_output_mode *mode; wl_list_for_each(mode, &wlr_output->modes, link) { json_object *mode_object = json_object_new_object(); json_object_object_add(mode_object, "width", json_object_new_int(mode->width)); json_object_object_add(mode_object, "height", json_object_new_int(mode->height)); json_object_object_add(mode_object, "refresh", json_object_new_int(mode->refresh)); json_object_array_add(modes_array, mode_object); } json_object_object_add(object, "modes", modes_array); json_object *features_object = json_object_new_object(); json_object_object_add(features_object, "adaptive_sync", json_object_new_boolean(wlr_output->adaptive_sync_supported || wlr_output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED)); json_object_object_add(features_object, "hdr", json_object_new_boolean(output_supports_hdr(wlr_output, NULL))); json_object_object_add(object, "features", features_object); } static void ipc_json_describe_output(struct sway_output *output, json_object *object) { ipc_json_describe_wlr_output(output->wlr_output, object); } static void ipc_json_describe_enabled_output(struct sway_output *output, json_object *object) { ipc_json_describe_output(output, object); struct wlr_output *wlr_output = output->wlr_output; json_object_object_add(object, "non_desktop", json_object_new_boolean(false)); json_object_object_add(object, "active", json_object_new_boolean(true)); json_object_object_add(object, "dpms", json_object_new_boolean(wlr_output->enabled)); json_object_object_add(object, "power", json_object_new_boolean(wlr_output->enabled)); json_object_object_add(object, "layout", json_object_new_string("output")); json_object_object_add(object, "orientation", json_object_new_string( ipc_json_orientation_description(L_NONE))); json_object_object_add(object, "scale", json_object_new_double(wlr_output->scale)); json_object_object_add(object, "scale_filter", json_object_new_string( sway_output_scale_filter_to_string(output->scale_filter))); json_object_object_add(object, "transform", json_object_new_string( ipc_json_output_transform_description(wlr_output->transform))); const char *adaptive_sync_status = ipc_json_output_adaptive_sync_status_description( wlr_output->adaptive_sync_status); json_object_object_add(object, "adaptive_sync_status", json_object_new_string(adaptive_sync_status)); struct sway_workspace *ws = output_get_active_workspace(output); if (!sway_assert(ws, "Expected output to have a workspace")) { return; } json_object_object_add(object, "current_workspace", json_object_new_string(ws->name)); json_object *modes_array = json_object_new_array(); struct wlr_output_mode *mode; wl_list_for_each(mode, &wlr_output->modes, link) { json_object *mode_object = ipc_json_output_mode_description(mode); json_object_array_add(modes_array, mode_object); } json_object_object_add(object, "modes", modes_array); json_object *current_mode_object; if (wlr_output->current_mode != NULL) { current_mode_object = ipc_json_output_mode_description(wlr_output->current_mode); } else { current_mode_object = json_object_new_object(); json_object_object_add(current_mode_object, "width", json_object_new_int(wlr_output->width)); json_object_object_add(current_mode_object, "height", json_object_new_int(wlr_output->height)); json_object_object_add(current_mode_object, "refresh", json_object_new_int(wlr_output->refresh)); } json_object_object_add(object, "current_mode", current_mode_object); struct sway_node *parent = node_get_parent(&output->node); struct wlr_box parent_box = {0, 0, 0, 0}; if (parent != NULL) { node_get_box(parent, &parent_box); } if (parent_box.width != 0 && parent_box.height != 0) { double percent = ((double)output->width / parent_box.width) * ((double)output->height / parent_box.height); json_object_object_add(object, "percent", json_object_new_double(percent)); } json_object_object_add(object, "max_render_time", json_object_new_int(output->max_render_time)); json_object_object_add(object, "allow_tearing", json_object_new_boolean(output->allow_tearing)); json_object_object_add(object, "hdr", json_object_new_boolean(output->hdr)); } json_object *ipc_json_describe_disabled_output(struct sway_output *output) { struct wlr_output *wlr_output = output->wlr_output; json_object *object = json_object_new_object(); ipc_json_describe_output(output, object); json_object_object_add(object, "non_desktop", json_object_new_boolean(false)); json_object_object_add(object, "type", json_object_new_string("output")); json_object_object_add(object, "name", json_object_new_string(wlr_output->name)); json_object_object_add(object, "active", json_object_new_boolean(false)); json_object_object_add(object, "dpms", json_object_new_boolean(false)); json_object_object_add(object, "power", json_object_new_boolean(false)); json_object_object_add(object, "current_workspace", NULL); json_object *rect_object = json_object_new_object(); json_object_object_add(rect_object, "x", json_object_new_int(0)); json_object_object_add(rect_object, "y", json_object_new_int(0)); json_object_object_add(rect_object, "width", json_object_new_int(0)); json_object_object_add(rect_object, "height", json_object_new_int(0)); json_object_object_add(object, "rect", rect_object); json_object_object_add(object, "percent", NULL); return object; } json_object *ipc_json_describe_non_desktop_output(struct sway_output_non_desktop *output) { struct wlr_output *wlr_output = output->wlr_output; json_object *object = json_object_new_object(); ipc_json_describe_wlr_output(wlr_output, object); json_object_object_add(object, "non_desktop", json_object_new_boolean(true)); json_object_object_add(object, "type", json_object_new_string("output")); json_object_object_add(object, "name", json_object_new_string(wlr_output->name)); return object; } static json_object *ipc_json_describe_scratchpad_output(void) { struct wlr_box box; root_get_box(root, &box); // Create focus stack for __i3_scratch workspace json_object *workspace_focus = json_object_new_array(); for (int i = root->scratchpad->length - 1; i >= 0; --i) { struct sway_container *container = root->scratchpad->items[i]; json_object_array_add(workspace_focus, json_object_new_int(container->node.id)); } json_object *workspace = ipc_json_create_node(i3_scratch_id, "workspace", "__i3_scratch", false, workspace_focus, &box); json_object_object_add(workspace, "fullscreen_mode", json_object_new_int(1)); // List all hidden scratchpad containers as floating nodes json_object *floating_array = json_object_new_array(); for (int i = 0; i < root->scratchpad->length; ++i) { struct sway_container *container = root->scratchpad->items[i]; if (container_is_scratchpad_hidden(container)) { json_object_array_add(floating_array, ipc_json_describe_node_recursive(&container->node)); } } json_object_object_add(workspace, "floating_nodes", floating_array); // Create focus stack for __i3 output json_object *output_focus = json_object_new_array(); json_object_array_add(output_focus, json_object_new_int(i3_scratch_id)); json_object *output = ipc_json_create_node(i3_output_id, "output", "__i3", false, output_focus, &box); json_object_object_add(output, "layout", json_object_new_string("output")); json_object *nodes = json_object_new_array(); json_object_array_add(nodes, workspace); json_object_object_add(output, "nodes", nodes); return output; } static void ipc_json_describe_workspace(struct sway_workspace *workspace, json_object *object) { int num; if (isdigit(workspace->name[0])) { errno = 0; char *endptr = NULL; long long parsed_num = strtoll(workspace->name, &endptr, 10); if (errno != 0 || parsed_num > INT32_MAX || parsed_num < 0 || endptr == workspace->name) { num = -1; } else { num = (int) parsed_num; } } else { num = -1; } json_object_object_add(object, "num", json_object_new_int(num)); json_object_object_add(object, "fullscreen_mode", json_object_new_int(1)); json_object_object_add(object, "output", workspace->output ? json_object_new_string(workspace->output->wlr_output->name) : NULL); json_object_object_add(object, "urgent", json_object_new_boolean(workspace->urgent)); json_object_object_add(object, "representation", workspace->representation ? json_object_new_string(workspace->representation) : NULL); json_object_object_add(object, "layout", json_object_new_string( ipc_json_layout_description(workspace->layout))); json_object_object_add(object, "orientation", json_object_new_string( ipc_json_orientation_description(workspace->layout))); // Floating json_object *floating_array = json_object_new_array(); for (int i = 0; i < workspace->floating->length; ++i) { struct sway_container *floater = workspace->floating->items[i]; json_object_array_add(floating_array, ipc_json_describe_node_recursive(&floater->node)); } json_object_object_add(object, "floating_nodes", floating_array); } static void get_deco_rect(struct sway_container *c, struct wlr_box *deco_rect) { enum sway_container_layout parent_layout = container_parent_layout(c); list_t *siblings = container_get_siblings(c); bool tab_or_stack = (parent_layout == L_TABBED || parent_layout == L_STACKED) && ((siblings && siblings->length > 1) || !config->hide_lone_tab); if (((!tab_or_stack || container_is_floating(c)) && c->current.border != B_NORMAL) || c->pending.fullscreen_mode != FULLSCREEN_NONE || c->pending.workspace == NULL) { deco_rect->x = deco_rect->y = deco_rect->width = deco_rect->height = 0; return; } if (c->pending.parent) { deco_rect->x = c->pending.x - c->pending.parent->pending.x; deco_rect->y = c->pending.y - c->pending.parent->pending.y; } else { deco_rect->x = c->pending.x - c->pending.workspace->x; deco_rect->y = c->pending.y - c->pending.workspace->y; } deco_rect->width = c->pending.width; deco_rect->height = container_titlebar_height(); if (!container_is_floating(c)) { if (parent_layout == L_TABBED) { deco_rect->width = c->pending.parent ? c->pending.parent->pending.width / c->pending.parent->pending.children->length : c->pending.workspace->width / c->pending.workspace->tiling->length; deco_rect->x += deco_rect->width * container_sibling_index(c); } else if (parent_layout == L_STACKED) { if (!c->view) { size_t siblings = container_get_siblings(c)->length; deco_rect->y -= deco_rect->height * siblings; } deco_rect->y += deco_rect->height * container_sibling_index(c); } } } static void ipc_json_describe_view(struct sway_container *c, json_object *object) { json_object_object_add(object, "pid", json_object_new_int(c->view->pid)); const char *app_id = view_get_app_id(c->view); json_object_object_add(object, "app_id", app_id ? json_object_new_string(app_id) : NULL); json_object_object_add(object, "foreign_toplevel_identifier", c->view->ext_foreign_toplevel ? json_object_new_string(c->view->ext_foreign_toplevel->identifier) : NULL); bool visible = view_is_visible(c->view); json_object_object_add(object, "visible", json_object_new_boolean(visible)); bool has_titlebar = c->title_bar.tree->node.enabled; struct wlr_box window_box = { c->pending.content_x - c->pending.x, has_titlebar ? 0 : c->pending.content_y - c->pending.y, c->pending.content_width, c->pending.content_height }; json_object_object_add(object, "window_rect", ipc_json_create_rect(&window_box)); struct wlr_box geometry = {0, 0, c->view->natural_width, c->view->natural_height}; json_object_object_add(object, "geometry", ipc_json_create_rect(&geometry)); json_object_object_add(object, "max_render_time", json_object_new_int(c->view->max_render_time)); json_object_object_add(object, "allow_tearing", json_object_new_boolean(view_can_tear(c->view))); json_object_object_add(object, "shell", json_object_new_string(view_get_shell(c->view))); json_object_object_add(object, "inhibit_idle", json_object_new_boolean(view_inhibit_idle(c->view))); const char *sandbox_engine = view_get_sandbox_engine(c->view); json_object_object_add(object, "sandbox_engine", sandbox_engine ? json_object_new_string(sandbox_engine) : NULL); const char *sandbox_app_id = view_get_sandbox_app_id(c->view); json_object_object_add(object, "sandbox_app_id", sandbox_app_id ? json_object_new_string(sandbox_app_id) : NULL); const char *sandbox_instance_id = view_get_sandbox_instance_id(c->view); json_object_object_add(object, "sandbox_instance_id", sandbox_instance_id ? json_object_new_string(sandbox_instance_id) : NULL); const char *tag = view_get_tag(c->view); json_object_object_add(object, "tag", tag ? json_object_new_string(tag) : NULL); json_object *idle_inhibitors = json_object_new_object(); struct sway_idle_inhibitor_v1 *user_inhibitor = sway_idle_inhibit_v1_user_inhibitor_for_view(c->view); if (user_inhibitor) { json_object_object_add(idle_inhibitors, "user", json_object_new_string( ipc_json_user_idle_inhibitor_description(user_inhibitor->mode))); } else { json_object_object_add(idle_inhibitors, "user", json_object_new_string("none")); } struct sway_idle_inhibitor_v1 *application_inhibitor = sway_idle_inhibit_v1_application_inhibitor_for_view(c->view); if (application_inhibitor) { json_object_object_add(idle_inhibitors, "application", json_object_new_string("enabled")); } else { json_object_object_add(idle_inhibitors, "application", json_object_new_string("none")); } json_object_object_add(object, "idle_inhibitors", idle_inhibitors); enum wp_content_type_v1_type content_type = WP_CONTENT_TYPE_V1_TYPE_NONE; if (c->view->surface != NULL) { content_type = wlr_surface_get_content_type_v1(server.content_type_manager_v1, c->view->surface); } if (content_type != WP_CONTENT_TYPE_V1_TYPE_NONE) { json_object_object_add(object, "content_type", json_object_new_string(ipc_json_content_type_description(content_type))); } #if WLR_HAS_XWAYLAND if (c->view->type == SWAY_VIEW_XWAYLAND) { json_object_object_add(object, "window", json_object_new_int(view_get_x11_window_id(c->view))); json_object *window_props = json_object_new_object(); const char *class = view_get_class(c->view); if (class) { json_object_object_add(window_props, "class", json_object_new_string(class)); } const char *instance = view_get_instance(c->view); if (instance) { json_object_object_add(window_props, "instance", json_object_new_string(instance)); } if (c->title) { json_object_object_add(window_props, "title", json_object_new_string(c->title)); } // the transient_for key is always present in i3's output uint32_t parent_id = view_get_x11_parent_id(c->view); json_object_object_add(window_props, "transient_for", parent_id ? json_object_new_int(parent_id) : NULL); const char *role = view_get_window_role(c->view); if (role) { json_object_object_add(window_props, "window_role", json_object_new_string(role)); } uint32_t window_type = view_get_window_type(c->view); if (window_type) { json_object_object_add(window_props, "window_type", json_object_new_string( ipc_json_xwindow_type_description(c->view))); } json_object_object_add(object, "window_properties", window_props); } #endif } static void ipc_json_describe_container(struct sway_container *c, json_object *object) { json_object_object_add(object, "name", c->title ? json_object_new_string(c->title) : NULL); bool floating = container_is_floating(c); if (floating) { json_object_object_add(object, "type", json_object_new_string("floating_con")); } json_object_object_add(object, "layout", json_object_new_string( ipc_json_layout_description(c->pending.layout))); json_object_object_add(object, "orientation", json_object_new_string( ipc_json_orientation_description(c->pending.layout))); bool urgent = c->view ? view_is_urgent(c->view) : container_has_urgent_child(c); json_object_object_add(object, "urgent", json_object_new_boolean(urgent)); json_object_object_add(object, "sticky", json_object_new_boolean(c->is_sticky)); // sway doesn't track the floating reason, so we can't use "auto_on" or "user_off" json_object_object_add(object, "floating", json_object_new_string(floating ? "user_on" : "auto_off")); json_object_object_add(object, "fullscreen_mode", json_object_new_int(c->pending.fullscreen_mode)); // sway doesn't track if window was resized in scratchpad, so we can't use "changed" json_object_object_add(object, "scratchpad_state", json_object_new_string(!c->scratchpad ? "none" : "fresh")); struct sway_node *parent = node_get_parent(&c->node); struct wlr_box parent_box = {0, 0, 0, 0}; if (parent != NULL) { node_get_box(parent, &parent_box); } if (parent_box.width != 0 && parent_box.height != 0) { double percent = ((double)c->pending.width / parent_box.width) * ((double)c->pending.height / parent_box.height); json_object_object_add(object, "percent", json_object_new_double(percent)); } json_object_object_add(object, "border", json_object_new_string( ipc_json_border_description(c->current.border))); json_object_object_add(object, "current_border_width", json_object_new_int(c->current.border_thickness)); json_object_object_add(object, "floating_nodes", json_object_new_array()); struct wlr_box deco_box = {0, 0, 0, 0}; get_deco_rect(c, &deco_box); json_object_object_add(object, "deco_rect", ipc_json_create_rect(&deco_box)); json_object *marks = json_object_new_array(); list_t *con_marks = c->marks; for (int i = 0; i < con_marks->length; ++i) { json_object_array_add(marks, json_object_new_string(con_marks->items[i])); } json_object_object_add(object, "marks", marks); if (c->view) { ipc_json_describe_view(c, object); } } struct focus_inactive_data { struct sway_node *node; json_object *object; }; static void focus_inactive_children_iterator(struct sway_node *node, void *_data) { struct focus_inactive_data *data = _data; json_object *focus = data->object; if (data->node == &root->node) { struct sway_output *output = node_get_output(node); if (output == NULL) { return; } size_t id = output->node.id; int len = json_object_array_length(focus); for (int i = 0; i < len; ++i) { if ((size_t) json_object_get_int(json_object_array_get_idx(focus, i)) == id) { return; } } node = &output->node; } else if (node_get_parent(node) != data->node) { return; } json_object_array_add(focus, json_object_new_int(node->id)); } json_object *ipc_json_describe_node(struct sway_node *node) { struct sway_seat *seat = input_manager_get_default_seat(); bool focused = seat_get_focus(seat) == node; char *name = node_get_name(node); struct wlr_box box; node_get_box(node, &box); if (node->type == N_CONTAINER) { struct wlr_box deco_rect = {0, 0, 0, 0}; get_deco_rect(node->sway_container, &deco_rect); size_t count = 1; if (container_parent_layout(node->sway_container) == L_STACKED) { count = container_get_siblings(node->sway_container)->length; } box.y += deco_rect.height * count; box.height -= deco_rect.height * count; } json_object *focus = json_object_new_array(); struct focus_inactive_data data = { .node = node, .object = focus, }; seat_for_each_node(seat, focus_inactive_children_iterator, &data); json_object *object = ipc_json_create_node((int)node->id, ipc_json_node_type_description(node->type), name, focused, focus, &box); switch (node->type) { case N_ROOT: break; case N_OUTPUT: ipc_json_describe_enabled_output(node->sway_output, object); break; case N_CONTAINER: ipc_json_describe_container(node->sway_container, object); break; case N_WORKSPACE: ipc_json_describe_workspace(node->sway_workspace, object); break; } return object; } json_object *ipc_json_describe_node_recursive(struct sway_node *node) { json_object *object = ipc_json_describe_node(node); int i; json_object *children = json_object_new_array(); switch (node->type) { case N_ROOT: json_object_array_add(children, ipc_json_describe_scratchpad_output()); for (i = 0; i < root->outputs->length; ++i) { struct sway_output *output = root->outputs->items[i]; json_object_array_add(children, ipc_json_describe_node_recursive(&output->node)); } break; case N_OUTPUT: for (i = 0; i < node->sway_output->workspaces->length; ++i) { struct sway_workspace *ws = node->sway_output->workspaces->items[i]; json_object_array_add(children, ipc_json_describe_node_recursive(&ws->node)); } break; case N_WORKSPACE: for (i = 0; i < node->sway_workspace->tiling->length; ++i) { struct sway_container *con = node->sway_workspace->tiling->items[i]; json_object_array_add(children, ipc_json_describe_node_recursive(&con->node)); } break; case N_CONTAINER: if (node->sway_container->pending.children) { for (i = 0; i < node->sway_container->pending.children->length; ++i) { struct sway_container *child = node->sway_container->pending.children->items[i]; json_object_array_add(children, ipc_json_describe_node_recursive(&child->node)); } } break; } json_object_object_add(object, "nodes", children); return object; } #if WLR_HAS_LIBINPUT_BACKEND static json_object *describe_libinput_device(struct libinput_device *device) { json_object *object = json_object_new_object(); const char *events = "unknown"; switch (libinput_device_config_send_events_get_mode(device)) { case LIBINPUT_CONFIG_SEND_EVENTS_ENABLED: events = "enabled"; break; case LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE: events = "disabled_on_external_mouse"; break; case LIBINPUT_CONFIG_SEND_EVENTS_DISABLED: events = "disabled"; break; } json_object_object_add(object, "send_events", json_object_new_string(events)); if (libinput_device_config_tap_get_finger_count(device) > 0) { const char *tap = "unknown"; switch (libinput_device_config_tap_get_enabled(device)) { case LIBINPUT_CONFIG_TAP_ENABLED: tap = "enabled"; break; case LIBINPUT_CONFIG_TAP_DISABLED: tap = "disabled"; break; } json_object_object_add(object, "tap", json_object_new_string(tap)); const char *button_map = "unknown"; switch (libinput_device_config_tap_get_button_map(device)) { case LIBINPUT_CONFIG_TAP_MAP_LRM: button_map = "lrm"; break; case LIBINPUT_CONFIG_TAP_MAP_LMR: button_map = "lmr"; break; } json_object_object_add(object, "tap_button_map", json_object_new_string(button_map)); const char* drag = "unknown"; switch (libinput_device_config_tap_get_drag_enabled(device)) { case LIBINPUT_CONFIG_DRAG_ENABLED: drag = "enabled"; break; case LIBINPUT_CONFIG_DRAG_DISABLED: drag = "disabled"; break; } json_object_object_add(object, "tap_drag", json_object_new_string(drag)); const char *drag_lock = "unknown"; switch (libinput_device_config_tap_get_drag_lock_enabled(device)) { case LIBINPUT_CONFIG_DRAG_LOCK_ENABLED: drag_lock = "enabled"; break; case LIBINPUT_CONFIG_DRAG_LOCK_DISABLED: drag_lock = "disabled"; break; #if HAVE_LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY case LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY: drag_lock = "enabled_sticky"; break; #endif } json_object_object_add(object, "tap_drag_lock", json_object_new_string(drag_lock)); } if (libinput_device_config_accel_is_available(device)) { double accel = libinput_device_config_accel_get_speed(device); json_object_object_add(object, "accel_speed", json_object_new_double(accel)); const char *accel_profile = "unknown"; switch (libinput_device_config_accel_get_profile(device)) { case LIBINPUT_CONFIG_ACCEL_PROFILE_NONE: accel_profile = "none"; break; case LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT: accel_profile = "flat"; break; case LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE: accel_profile = "adaptive"; break; #if HAVE_LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM case LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM: accel_profile = "custom"; break; #endif } json_object_object_add(object, "accel_profile", json_object_new_string(accel_profile)); } if (libinput_device_config_scroll_has_natural_scroll(device)) { const char *natural_scroll = "disabled"; if (libinput_device_config_scroll_get_natural_scroll_enabled(device)) { natural_scroll = "enabled"; } json_object_object_add(object, "natural_scroll", json_object_new_string(natural_scroll)); } if (libinput_device_config_left_handed_is_available(device)) { const char *left_handed = "disabled"; if (libinput_device_config_left_handed_get(device) != 0) { left_handed = "enabled"; } json_object_object_add(object, "left_handed", json_object_new_string(left_handed)); } uint32_t click_methods = libinput_device_config_click_get_methods(device); if ((click_methods & ~LIBINPUT_CONFIG_CLICK_METHOD_NONE) != 0) { const char *click_method = "unknown"; switch (libinput_device_config_click_get_method(device)) { case LIBINPUT_CONFIG_CLICK_METHOD_NONE: click_method = "none"; break; case LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS: click_method = "button_areas"; break; case LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER: click_method = "clickfinger"; break; } json_object_object_add(object, "click_method", json_object_new_string(click_method)); const char *button_map = "unknown"; switch (libinput_device_config_click_get_clickfinger_button_map(device)) { case LIBINPUT_CONFIG_CLICKFINGER_MAP_LRM: button_map = "lrm"; break; case LIBINPUT_CONFIG_CLICKFINGER_MAP_LMR: button_map = "lmr"; break; } json_object_object_add(object, "clickfinger_button_map", json_object_new_string(button_map)); } if (libinput_device_config_middle_emulation_is_available(device)) { const char *middle_emulation = "unknown"; switch (libinput_device_config_middle_emulation_get_enabled(device)) { case LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED: middle_emulation = "enabled"; break; case LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED: middle_emulation = "disabled"; break; } json_object_object_add(object, "middle_emulation", json_object_new_string(middle_emulation)); } uint32_t scroll_methods = libinput_device_config_scroll_get_methods(device); if ((scroll_methods & ~LIBINPUT_CONFIG_SCROLL_NO_SCROLL) != 0) { const char *scroll_method = "unknown"; switch (libinput_device_config_scroll_get_method(device)) { case LIBINPUT_CONFIG_SCROLL_NO_SCROLL: scroll_method = "none"; break; case LIBINPUT_CONFIG_SCROLL_2FG: scroll_method = "two_finger"; break; case LIBINPUT_CONFIG_SCROLL_EDGE: scroll_method = "edge"; break; case LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN: scroll_method = "on_button_down"; break; } json_object_object_add(object, "scroll_method", json_object_new_string(scroll_method)); if ((scroll_methods & LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) != 0) { uint32_t button = libinput_device_config_scroll_get_button(device); json_object_object_add(object, "scroll_button", json_object_new_int(button)); const char *lock = "unknown"; switch (libinput_device_config_scroll_get_button_lock(device)) { case LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED: lock = "enabled"; break; case LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED: lock = "disabled"; break; } json_object_object_add(object, "scroll_button_lock", json_object_new_string(lock)); } } if (libinput_device_config_dwt_is_available(device)) { const char *dwt = "unknown"; switch (libinput_device_config_dwt_get_enabled(device)) { case LIBINPUT_CONFIG_DWT_ENABLED: dwt = "enabled"; break; case LIBINPUT_CONFIG_DWT_DISABLED: dwt = "disabled"; break; } json_object_object_add(object, "dwt", json_object_new_string(dwt)); } if (libinput_device_config_dwtp_is_available(device)) { const char *dwtp = "unknown"; switch (libinput_device_config_dwtp_get_enabled(device)) { case LIBINPUT_CONFIG_DWTP_ENABLED: dwtp = "enabled"; break; case LIBINPUT_CONFIG_DWTP_DISABLED: dwtp = "disabled"; break; } json_object_object_add(object, "dwtp", json_object_new_string(dwtp)); } if (libinput_device_config_calibration_has_matrix(device)) { float matrix[6]; libinput_device_config_calibration_get_matrix(device, matrix); struct json_object* array = json_object_new_array(); struct json_object* x; for (int i = 0; i < 6; i++) { x = json_object_new_double(matrix[i]); json_object_array_add(array, x); } json_object_object_add(object, "calibration_matrix", array); } return object; } #endif json_object *ipc_json_describe_input(struct sway_input_device *device) { if (!(sway_assert(device, "Device must not be null"))) { return NULL; } json_object *object = json_object_new_object(); json_object_object_add(object, "identifier", json_object_new_string(device->identifier)); json_object_object_add(object, "name", json_object_new_string(device->wlr_device->name)); json_object_object_add(object, "type", json_object_new_string( input_device_get_type(device))); if (device->wlr_device->type == WLR_INPUT_DEVICE_KEYBOARD) { struct wlr_keyboard *keyboard = wlr_keyboard_from_input_device(device->wlr_device); struct xkb_keymap *keymap = keyboard->keymap; struct xkb_state *state = keyboard->xkb_state; json_object_object_add(object, "repeat_delay", json_object_new_int(keyboard->repeat_info.delay)); json_object_object_add(object, "repeat_rate", json_object_new_int(keyboard->repeat_info.rate)); json_object *layouts_arr = json_object_new_array(); json_object_object_add(object, "xkb_layout_names", layouts_arr); xkb_layout_index_t num_layouts = keymap ? xkb_keymap_num_layouts(keymap) : 0; // Virtual keyboards might have null keymap xkb_layout_index_t layout_idx; for (layout_idx = 0; layout_idx < num_layouts; layout_idx++) { const char *layout = xkb_keymap_layout_get_name(keymap, layout_idx); json_object_array_add(layouts_arr, layout ? json_object_new_string(layout) : NULL); bool is_active = xkb_state_layout_index_is_active(state, layout_idx, XKB_STATE_LAYOUT_EFFECTIVE); if (is_active) { json_object_object_add(object, "xkb_active_layout_index", json_object_new_int(layout_idx)); json_object_object_add(object, "xkb_active_layout_name", layout ? json_object_new_string(layout) : NULL); } } } if (device->wlr_device->type == WLR_INPUT_DEVICE_POINTER) { struct input_config *ic = input_device_get_config(device); float scroll_factor = 1.0f; if (ic != NULL && !isnan(ic->scroll_factor) && ic->scroll_factor != FLT_MIN) { scroll_factor = ic->scroll_factor; } json_object_object_add(object, "scroll_factor", json_object_new_double(scroll_factor)); } #if WLR_HAS_LIBINPUT_BACKEND if (wlr_input_device_is_libinput(device->wlr_device)) { struct libinput_device *libinput_dev; libinput_dev = wlr_libinput_get_device_handle(device->wlr_device); json_object_object_add(object, "libinput", describe_libinput_device(libinput_dev)); json_object_object_add(object, "vendor", json_object_new_int(libinput_device_get_id_vendor(libinput_dev))); json_object_object_add(object, "product", json_object_new_int(libinput_device_get_id_product(libinput_dev))); } #endif return object; } json_object *ipc_json_describe_seat(struct sway_seat *seat) { if (!(sway_assert(seat, "Seat must not be null"))) { return NULL; } json_object *object = json_object_new_object(); struct sway_node *focus = seat_get_focus(seat); json_object_object_add(object, "name", json_object_new_string(seat->wlr_seat->name)); json_object_object_add(object, "capabilities", json_object_new_int(seat->wlr_seat->capabilities)); json_object_object_add(object, "focus", json_object_new_int(focus ? focus->id : 0)); json_object *devices = json_object_new_array(); struct sway_seat_device *device = NULL; wl_list_for_each(device, &seat->devices, link) { json_object_array_add(devices, ipc_json_describe_input(device->input_device)); } json_object_object_add(object, "devices", devices); return object; } static uint32_t event_to_x11_button(uint32_t event) { switch (event) { case BTN_LEFT: return 1; case BTN_MIDDLE: return 2; case BTN_RIGHT: return 3; case SWAY_SCROLL_UP: return 4; case SWAY_SCROLL_DOWN: return 5; case SWAY_SCROLL_LEFT: return 6; case SWAY_SCROLL_RIGHT: return 7; case BTN_SIDE: return 8; case BTN_EXTRA: return 9; default: return 0; } } json_object *ipc_json_describe_bar_config(struct bar_config *bar) { if (!sway_assert(bar, "Bar must not be NULL")) { return NULL; } json_object *json = json_object_new_object(); json_object_object_add(json, "id", json_object_new_string(bar->id)); json_object_object_add(json, "mode", json_object_new_string(bar->mode)); json_object_object_add(json, "hidden_state", json_object_new_string(bar->hidden_state)); json_object_object_add(json, "position", json_object_new_string(bar->position)); json_object_object_add(json, "status_command", bar->status_command ? json_object_new_string(bar->status_command) : NULL); json_object_object_add(json, "font", json_object_new_string((bar->font) ? bar->font : config->font)); json_object *gaps = json_object_new_object(); json_object_object_add(gaps, "top", json_object_new_int(bar->gaps.top)); json_object_object_add(gaps, "right", json_object_new_int(bar->gaps.right)); json_object_object_add(gaps, "bottom", json_object_new_int(bar->gaps.bottom)); json_object_object_add(gaps, "left", json_object_new_int(bar->gaps.left)); json_object_object_add(json, "gaps", gaps); if (bar->separator_symbol) { json_object_object_add(json, "separator_symbol", json_object_new_string(bar->separator_symbol)); } json_object_object_add(json, "bar_height", json_object_new_int(bar->height)); json_object_object_add(json, "status_padding", json_object_new_int(bar->status_padding)); json_object_object_add(json, "status_edge_padding", json_object_new_int(bar->status_edge_padding)); json_object_object_add(json, "wrap_scroll", json_object_new_boolean(bar->wrap_scroll)); json_object_object_add(json, "workspace_buttons", json_object_new_boolean(bar->workspace_buttons)); json_object_object_add(json, "strip_workspace_numbers", json_object_new_boolean(bar->strip_workspace_numbers)); json_object_object_add(json, "strip_workspace_name", json_object_new_boolean(bar->strip_workspace_name)); json_object_object_add(json, "workspace_min_width", json_object_new_int(bar->workspace_min_width)); json_object_object_add(json, "binding_mode_indicator", json_object_new_boolean(bar->binding_mode_indicator)); json_object_object_add(json, "verbose", json_object_new_boolean(bar->verbose)); json_object_object_add(json, "pango_markup", json_object_new_boolean(bar->pango_markup == PANGO_MARKUP_DEFAULT ? config->pango_markup : bar->pango_markup)); json_object *colors = json_object_new_object(); json_object_object_add(colors, "background", json_object_new_string(bar->colors.background)); json_object_object_add(colors, "statusline", json_object_new_string(bar->colors.statusline)); json_object_object_add(colors, "separator", json_object_new_string(bar->colors.separator)); if (bar->colors.focused_background) { json_object_object_add(colors, "focused_background", json_object_new_string(bar->colors.focused_background)); } else { json_object_object_add(colors, "focused_background", json_object_new_string(bar->colors.background)); } if (bar->colors.focused_statusline) { json_object_object_add(colors, "focused_statusline", json_object_new_string(bar->colors.focused_statusline)); } else { json_object_object_add(colors, "focused_statusline", json_object_new_string(bar->colors.statusline)); } if (bar->colors.focused_separator) { json_object_object_add(colors, "focused_separator", json_object_new_string(bar->colors.focused_separator)); } else { json_object_object_add(colors, "focused_separator", json_object_new_string(bar->colors.separator)); } json_object_object_add(colors, "focused_workspace_border", json_object_new_string(bar->colors.focused_workspace_border)); json_object_object_add(colors, "focused_workspace_bg", json_object_new_string(bar->colors.focused_workspace_bg)); json_object_object_add(colors, "focused_workspace_text", json_object_new_string(bar->colors.focused_workspace_text)); json_object_object_add(colors, "inactive_workspace_border", json_object_new_string(bar->colors.inactive_workspace_border)); json_object_object_add(colors, "inactive_workspace_bg", json_object_new_string(bar->colors.inactive_workspace_bg)); json_object_object_add(colors, "inactive_workspace_text", json_object_new_string(bar->colors.inactive_workspace_text)); json_object_object_add(colors, "active_workspace_border", json_object_new_string(bar->colors.active_workspace_border)); json_object_object_add(colors, "active_workspace_bg", json_object_new_string(bar->colors.active_workspace_bg)); json_object_object_add(colors, "active_workspace_text", json_object_new_string(bar->colors.active_workspace_text)); json_object_object_add(colors, "urgent_workspace_border", json_object_new_string(bar->colors.urgent_workspace_border)); json_object_object_add(colors, "urgent_workspace_bg", json_object_new_string(bar->colors.urgent_workspace_bg)); json_object_object_add(colors, "urgent_workspace_text", json_object_new_string(bar->colors.urgent_workspace_text)); if (bar->colors.binding_mode_border) { json_object_object_add(colors, "binding_mode_border", json_object_new_string(bar->colors.binding_mode_border)); } else { json_object_object_add(colors, "binding_mode_border", json_object_new_string(bar->colors.urgent_workspace_border)); } if (bar->colors.binding_mode_bg) { json_object_object_add(colors, "binding_mode_bg", json_object_new_string(bar->colors.binding_mode_bg)); } else { json_object_object_add(colors, "binding_mode_bg", json_object_new_string(bar->colors.urgent_workspace_bg)); } if (bar->colors.binding_mode_text) { json_object_object_add(colors, "binding_mode_text", json_object_new_string(bar->colors.binding_mode_text)); } else { json_object_object_add(colors, "binding_mode_text", json_object_new_string(bar->colors.urgent_workspace_text)); } json_object_object_add(json, "colors", colors); if (bar->bindings->length > 0) { json_object *bindings = json_object_new_array(); for (int i = 0; i < bar->bindings->length; ++i) { struct bar_binding *binding = bar->bindings->items[i]; json_object *bind = json_object_new_object(); json_object_object_add(bind, "input_code", json_object_new_int(event_to_x11_button(binding->button))); json_object_object_add(bind, "event_code", json_object_new_int(binding->button)); json_object_object_add(bind, "command", json_object_new_string(binding->command)); json_object_object_add(bind, "release", json_object_new_boolean(binding->release)); json_object_array_add(bindings, bind); } json_object_object_add(json, "bindings", bindings); } // Add outputs if defined if (bar->outputs && bar->outputs->length > 0) { json_object *outputs = json_object_new_array(); for (int i = 0; i < bar->outputs->length; ++i) { const char *name = bar->outputs->items[i]; json_object_array_add(outputs, json_object_new_string(name)); } json_object_object_add(json, "outputs", outputs); } #if HAVE_TRAY // Add tray outputs if defined if (bar->tray_outputs && bar->tray_outputs->length > 0) { json_object *tray_outputs = json_object_new_array(); for (int i = 0; i < bar->tray_outputs->length; ++i) { const char *name = bar->tray_outputs->items[i]; json_object_array_add(tray_outputs, json_object_new_string(name)); } json_object_object_add(json, "tray_outputs", tray_outputs); } json_object *tray_bindings = json_object_new_array(); struct tray_binding *tray_bind = NULL; wl_list_for_each(tray_bind, &bar->tray_bindings, link) { json_object *bind = json_object_new_object(); json_object_object_add(bind, "input_code", json_object_new_int(event_to_x11_button(tray_bind->button))); json_object_object_add(bind, "event_code", json_object_new_int(tray_bind->button)); json_object_object_add(bind, "command", json_object_new_string(tray_bind->command)); json_object_array_add(tray_bindings, bind); } if (json_object_array_length(tray_bindings) > 0) { json_object_object_add(json, "tray_bindings", tray_bindings); } else { json_object_put(tray_bindings); } if (bar->icon_theme) { json_object_object_add(json, "icon_theme", json_object_new_string(bar->icon_theme)); } json_object_object_add(json, "tray_padding", json_object_new_int(bar->tray_padding)); #endif return json; } json_object *ipc_json_get_binding_mode(void) { json_object *current_mode = json_object_new_object(); json_object_object_add(current_mode, "name", json_object_new_string(config->current_mode->name)); return current_mode; } ================================================ FILE: sway/ipc-server.c ================================================ // See https://i3wm.org/docs/ipc.html for protocol information #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sway/commands.h" #include "sway/config.h" #include "sway/desktop/transaction.h" #include "sway/ipc-json.h" #include "sway/ipc-server.h" #include "sway/output.h" #include "sway/server.h" #include "sway/input/input-manager.h" #include "sway/input/keyboard.h" #include "sway/input/seat.h" #include "sway/tree/root.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "list.h" #include "log.h" #include "util.h" static int ipc_socket = -1; static struct wl_event_source *ipc_event_source = NULL; static struct sockaddr_un *ipc_sockaddr = NULL; static list_t *ipc_client_list = NULL; static struct wl_listener ipc_display_destroy; static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'}; #define IPC_HEADER_SIZE (sizeof(ipc_magic) + 8) struct ipc_client { struct wl_event_source *event_source; struct wl_event_source *writable_event_source; struct sway_server *server; int fd; enum ipc_command_type subscribed_events; size_t write_buffer_len; size_t write_buffer_size; char *write_buffer; // The following are for storing data between event_loop calls uint32_t pending_length; enum ipc_command_type pending_type; }; int ipc_handle_connection(int fd, uint32_t mask, void *data); int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data); int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data); void ipc_client_disconnect(struct ipc_client *client); void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_length, enum ipc_command_type payload_type); bool ipc_send_reply(struct ipc_client *client, enum ipc_command_type payload_type, const char *payload, uint32_t payload_length); static void handle_display_destroy(struct wl_listener *listener, void *data) { if (ipc_event_source) { wl_event_source_remove(ipc_event_source); } close(ipc_socket); unlink(ipc_sockaddr->sun_path); while (ipc_client_list->length) { ipc_client_disconnect(ipc_client_list->items[ipc_client_list->length-1]); } list_free(ipc_client_list); free(ipc_sockaddr); wl_list_remove(&ipc_display_destroy.link); } void ipc_init(struct sway_server *server) { ipc_socket = socket(AF_UNIX, SOCK_STREAM, 0); if (ipc_socket == -1) { sway_abort("Unable to create IPC socket"); } if (fcntl(ipc_socket, F_SETFD, FD_CLOEXEC) == -1) { sway_abort("Unable to set CLOEXEC on IPC socket"); } if (fcntl(ipc_socket, F_SETFL, O_NONBLOCK) == -1) { sway_abort("Unable to set NONBLOCK on IPC socket"); } ipc_sockaddr = ipc_user_sockaddr(); // We want to use socket name set by user, not existing socket from another sway instance. if (getenv("SWAYSOCK") != NULL && access(getenv("SWAYSOCK"), F_OK) == -1) { strncpy(ipc_sockaddr->sun_path, getenv("SWAYSOCK"), sizeof(ipc_sockaddr->sun_path) - 1); ipc_sockaddr->sun_path[sizeof(ipc_sockaddr->sun_path) - 1] = 0; } unlink(ipc_sockaddr->sun_path); if (bind(ipc_socket, (struct sockaddr *)ipc_sockaddr, sizeof(*ipc_sockaddr)) == -1) { sway_abort("Unable to bind IPC socket"); } if (listen(ipc_socket, 3) == -1) { sway_abort("Unable to listen on IPC socket"); } // Set i3 IPC socket path so that i3-msg works out of the box setenv("I3SOCK", ipc_sockaddr->sun_path, 1); setenv("SWAYSOCK", ipc_sockaddr->sun_path, 1); ipc_client_list = create_list(); ipc_display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->wl_display, &ipc_display_destroy); ipc_event_source = wl_event_loop_add_fd(server->wl_event_loop, ipc_socket, WL_EVENT_READABLE, ipc_handle_connection, server); } struct sockaddr_un *ipc_user_sockaddr(void) { struct sockaddr_un *ipc_sockaddr = malloc(sizeof(struct sockaddr_un)); if (ipc_sockaddr == NULL) { sway_abort("Can't allocate ipc_sockaddr"); } ipc_sockaddr->sun_family = AF_UNIX; int path_size = sizeof(ipc_sockaddr->sun_path); // Env var typically set by logind, e.g. "/run/user/" const char *dir = getenv("XDG_RUNTIME_DIR"); if (!dir) { dir = "/tmp"; } if (path_size <= snprintf(ipc_sockaddr->sun_path, path_size, "%s/sway-ipc.%u.%i.sock", dir, getuid(), getpid())) { sway_abort("Socket path won't fit into ipc_sockaddr->sun_path"); } return ipc_sockaddr; } int ipc_handle_connection(int fd, uint32_t mask, void *data) { (void) fd; struct sway_server *server = data; assert(mask == WL_EVENT_READABLE); int client_fd = accept(ipc_socket, NULL, NULL); if (client_fd == -1) { sway_log_errno(SWAY_ERROR, "Unable to accept IPC client connection"); return 0; } int flags; if ((flags = fcntl(client_fd, F_GETFD)) == -1 || fcntl(client_fd, F_SETFD, flags|FD_CLOEXEC) == -1) { sway_log_errno(SWAY_ERROR, "Unable to set CLOEXEC on IPC client socket"); close(client_fd); return 0; } if ((flags = fcntl(client_fd, F_GETFL)) == -1 || fcntl(client_fd, F_SETFL, flags|O_NONBLOCK) == -1) { sway_log_errno(SWAY_ERROR, "Unable to set NONBLOCK on IPC client socket"); close(client_fd); return 0; } struct ipc_client *client = malloc(sizeof(struct ipc_client)); if (!client) { sway_log(SWAY_ERROR, "Unable to allocate ipc client"); close(client_fd); return 0; } client->server = server; client->pending_length = 0; client->fd = client_fd; client->subscribed_events = 0; client->event_source = wl_event_loop_add_fd(server->wl_event_loop, client_fd, WL_EVENT_READABLE, ipc_client_handle_readable, client); client->writable_event_source = NULL; client->write_buffer_size = 128; client->write_buffer_len = 0; client->write_buffer = malloc(client->write_buffer_size); if (!client->write_buffer) { sway_log(SWAY_ERROR, "Unable to allocate ipc client write buffer"); close(client_fd); return 0; } sway_log(SWAY_DEBUG, "New client: fd %d", client_fd); list_add(ipc_client_list, client); return 0; } int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) { struct ipc_client *client = data; if (mask & WL_EVENT_ERROR) { sway_log(SWAY_ERROR, "IPC Client socket error, removing client"); ipc_client_disconnect(client); return 0; } if (mask & WL_EVENT_HANGUP) { ipc_client_disconnect(client); return 0; } int read_available; if (ioctl(client_fd, FIONREAD, &read_available) == -1) { sway_log_errno(SWAY_INFO, "Unable to read IPC socket buffer size"); ipc_client_disconnect(client); return 0; } // Wait for the rest of the command payload in case the header has already been read if (client->pending_length > 0) { if ((uint32_t)read_available >= client->pending_length) { // Reset pending values. uint32_t pending_length = client->pending_length; enum ipc_command_type pending_type = client->pending_type; client->pending_length = 0; ipc_client_handle_command(client, pending_length, pending_type); } return 0; } if (read_available < (int) IPC_HEADER_SIZE) { return 0; } uint8_t buf[IPC_HEADER_SIZE]; // Should be fully available, because read_available >= IPC_HEADER_SIZE ssize_t received = recv(client_fd, buf, IPC_HEADER_SIZE, 0); if (received == -1) { sway_log_errno(SWAY_INFO, "Unable to receive header from IPC client"); ipc_client_disconnect(client); return 0; } if (memcmp(buf, ipc_magic, sizeof(ipc_magic)) != 0) { sway_log(SWAY_DEBUG, "IPC header check failed"); ipc_client_disconnect(client); return 0; } memcpy(&client->pending_length, buf + sizeof(ipc_magic), sizeof(uint32_t)); memcpy(&client->pending_type, buf + sizeof(ipc_magic) + sizeof(uint32_t), sizeof(uint32_t)); if (read_available - received >= (long)client->pending_length) { // Reset pending values. uint32_t pending_length = client->pending_length; enum ipc_command_type pending_type = client->pending_type; client->pending_length = 0; ipc_client_handle_command(client, pending_length, pending_type); } return 0; } static bool ipc_has_event_listeners(enum ipc_command_type event) { for (int i = 0; i < ipc_client_list->length; i++) { struct ipc_client *client = ipc_client_list->items[i]; if ((client->subscribed_events & event_mask(event)) != 0) { return true; } } return false; } static void ipc_send_event(const char *json_string, enum ipc_command_type event) { struct ipc_client *client; for (int i = 0; i < ipc_client_list->length; i++) { client = ipc_client_list->items[i]; if ((client->subscribed_events & event_mask(event)) == 0) { continue; } if (!ipc_send_reply(client, event, json_string, (uint32_t)strlen(json_string))) { sway_log_errno(SWAY_INFO, "Unable to send reply to IPC client"); /* ipc_send_reply destroys client on error, which also * removes it from the list, so we need to process * current index again */ i--; } } } void ipc_event_workspace(struct sway_workspace *old, struct sway_workspace *new, const char *change) { if (!ipc_has_event_listeners(IPC_EVENT_WORKSPACE)) { return; } sway_log(SWAY_DEBUG, "Sending workspace::%s event", change); json_object *obj = json_object_new_object(); json_object_object_add(obj, "change", json_object_new_string(change)); if (old) { json_object_object_add(obj, "old", ipc_json_describe_node_recursive(&old->node)); } else { json_object_object_add(obj, "old", NULL); } if (new) { json_object_object_add(obj, "current", ipc_json_describe_node_recursive(&new->node)); } else { json_object_object_add(obj, "current", NULL); } const char *json_string = json_object_to_json_string(obj); ipc_send_event(json_string, IPC_EVENT_WORKSPACE); json_object_put(obj); } void ipc_event_window(struct sway_container *window, const char *change) { if (!ipc_has_event_listeners(IPC_EVENT_WINDOW)) { return; } sway_log(SWAY_DEBUG, "Sending window::%s event", change); json_object *obj = json_object_new_object(); json_object_object_add(obj, "change", json_object_new_string(change)); json_object_object_add(obj, "container", ipc_json_describe_node_recursive(&window->node)); const char *json_string = json_object_to_json_string(obj); ipc_send_event(json_string, IPC_EVENT_WINDOW); json_object_put(obj); } void ipc_event_barconfig_update(struct bar_config *bar) { if (!ipc_has_event_listeners(IPC_EVENT_BARCONFIG_UPDATE)) { return; } sway_log(SWAY_DEBUG, "Sending barconfig_update event"); json_object *json = ipc_json_describe_bar_config(bar); const char *json_string = json_object_to_json_string(json); ipc_send_event(json_string, IPC_EVENT_BARCONFIG_UPDATE); json_object_put(json); } void ipc_event_bar_state_update(struct bar_config *bar) { if (!ipc_has_event_listeners(IPC_EVENT_BAR_STATE_UPDATE)) { return; } sway_log(SWAY_DEBUG, "Sending bar_state_update event"); json_object *json = json_object_new_object(); json_object_object_add(json, "id", json_object_new_string(bar->id)); json_object_object_add(json, "visible_by_modifier", json_object_new_boolean(bar->visible_by_modifier)); const char *json_string = json_object_to_json_string(json); ipc_send_event(json_string, IPC_EVENT_BAR_STATE_UPDATE); json_object_put(json); } void ipc_event_mode(const char *mode, bool pango) { if (!ipc_has_event_listeners(IPC_EVENT_MODE)) { return; } sway_log(SWAY_DEBUG, "Sending mode::%s event", mode); json_object *obj = json_object_new_object(); json_object_object_add(obj, "change", json_object_new_string(mode)); json_object_object_add(obj, "pango_markup", json_object_new_boolean(pango)); const char *json_string = json_object_to_json_string(obj); ipc_send_event(json_string, IPC_EVENT_MODE); json_object_put(obj); } void ipc_event_shutdown(const char *reason) { if (!ipc_has_event_listeners(IPC_EVENT_SHUTDOWN)) { return; } sway_log(SWAY_DEBUG, "Sending shutdown::%s event", reason); json_object *json = json_object_new_object(); json_object_object_add(json, "change", json_object_new_string(reason)); const char *json_string = json_object_to_json_string(json); ipc_send_event(json_string, IPC_EVENT_SHUTDOWN); json_object_put(json); } void ipc_event_binding(struct sway_binding *binding) { if (!ipc_has_event_listeners(IPC_EVENT_BINDING)) { return; } sway_log(SWAY_DEBUG, "Sending binding event"); json_object *json_binding = json_object_new_object(); json_object_object_add(json_binding, "command", json_object_new_string(binding->command)); const char *names[10]; int len = get_modifier_names(names, binding->modifiers); json_object *modifiers = json_object_new_array(); for (int i = 0; i < len; ++i) { json_object_array_add(modifiers, json_object_new_string(names[i])); } json_object_object_add(json_binding, "event_state_mask", modifiers); json_object *input_codes = json_object_new_array(); int input_code = 0; json_object *symbols = json_object_new_array(); json_object *symbol = NULL; switch (binding->type) { case BINDING_KEYCODE:; // bindcode: populate input_codes uint32_t keycode; for (int i = 0; i < binding->keys->length; ++i) { keycode = *(uint32_t *)binding->keys->items[i]; json_object_array_add(input_codes, json_object_new_int(keycode)); if (i == 0) { input_code = keycode; } } break; case BINDING_KEYSYM: case BINDING_MOUSESYM: case BINDING_MOUSECODE:; // bindsym/mouse: populate symbols uint32_t keysym; char buffer[64]; for (int i = 0; i < binding->keys->length; ++i) { keysym = *(uint32_t *)binding->keys->items[i]; if (keysym >= BTN_LEFT && keysym <= BTN_LEFT + 8) { snprintf(buffer, 64, "button%u", keysym - BTN_LEFT + 1); } else if (xkb_keysym_get_name(keysym, buffer, 64) < 0) { continue; } json_object *str = json_object_new_string(buffer); if (i == 0) { // str is owned by both symbol and symbols. Make sure // to bump the ref count. json_object_array_add(symbols, json_object_get(str)); symbol = str; } else { json_object_array_add(symbols, str); } } break; default: sway_log(SWAY_DEBUG, "Unsupported ipc binding event"); json_object_put(input_codes); json_object_put(symbols); json_object_put(json_binding); return; // do not send any event } json_object_object_add(json_binding, "input_codes", input_codes); json_object_object_add(json_binding, "input_code", json_object_new_int(input_code)); json_object_object_add(json_binding, "symbols", symbols); json_object_object_add(json_binding, "symbol", symbol); bool mouse = binding->type == BINDING_MOUSECODE || binding->type == BINDING_MOUSESYM; json_object_object_add(json_binding, "input_type", mouse ? json_object_new_string("mouse") : json_object_new_string("keyboard")); json_object *json = json_object_new_object(); json_object_object_add(json, "change", json_object_new_string("run")); json_object_object_add(json, "binding", json_binding); const char *json_string = json_object_to_json_string(json); ipc_send_event(json_string, IPC_EVENT_BINDING); json_object_put(json); } static void ipc_event_tick(const char *payload) { if (!ipc_has_event_listeners(IPC_EVENT_TICK)) { return; } sway_log(SWAY_DEBUG, "Sending tick event"); json_object *json = json_object_new_object(); json_object_object_add(json, "first", json_object_new_boolean(false)); json_object_object_add(json, "payload", json_object_new_string(payload)); const char *json_string = json_object_to_json_string(json); ipc_send_event(json_string, IPC_EVENT_TICK); json_object_put(json); } void ipc_event_input(const char *change, struct sway_input_device *device) { if (!ipc_has_event_listeners(IPC_EVENT_INPUT)) { return; } sway_log(SWAY_DEBUG, "Sending input event"); json_object *json = json_object_new_object(); json_object_object_add(json, "change", json_object_new_string(change)); json_object_object_add(json, "input", ipc_json_describe_input(device)); const char *json_string = json_object_to_json_string(json); ipc_send_event(json_string, IPC_EVENT_INPUT); json_object_put(json); } void ipc_event_output(void) { if (!ipc_has_event_listeners(IPC_EVENT_OUTPUT)) { return; } sway_log(SWAY_DEBUG, "Sending output event"); json_object *json = json_object_new_object(); json_object_object_add(json, "change", json_object_new_string("unspecified")); const char *json_string = json_object_to_json_string(json); ipc_send_event(json_string, IPC_EVENT_OUTPUT); json_object_put(json); } int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) { struct ipc_client *client = data; if (mask & WL_EVENT_ERROR) { sway_log(SWAY_ERROR, "IPC Client socket error, removing client"); ipc_client_disconnect(client); return 0; } if (mask & WL_EVENT_HANGUP) { ipc_client_disconnect(client); return 0; } if (client->write_buffer_len <= 0) { return 0; } ssize_t written = write(client->fd, client->write_buffer, client->write_buffer_len); if (written == -1 && errno == EAGAIN) { return 0; } else if (written == -1) { sway_log_errno(SWAY_INFO, "Unable to send data from queue to IPC client"); ipc_client_disconnect(client); return 0; } memmove(client->write_buffer, client->write_buffer + written, client->write_buffer_len - written); client->write_buffer_len -= written; if (client->write_buffer_len == 0 && client->writable_event_source) { wl_event_source_remove(client->writable_event_source); client->writable_event_source = NULL; } return 0; } void ipc_client_disconnect(struct ipc_client *client) { if (!sway_assert(client != NULL, "client != NULL")) { return; } shutdown(client->fd, SHUT_RDWR); sway_log(SWAY_INFO, "IPC Client %d disconnected", client->fd); wl_event_source_remove(client->event_source); if (client->writable_event_source) { wl_event_source_remove(client->writable_event_source); } int i = 0; while (i < ipc_client_list->length && ipc_client_list->items[i] != client) { i++; } list_del(ipc_client_list, i); free(client->write_buffer); close(client->fd); free(client); } static void ipc_get_workspaces_callback(struct sway_workspace *workspace, void *data) { json_object *workspace_json = ipc_json_describe_node(&workspace->node); // override the default focused indicator because // it's set differently for the get_workspaces reply struct sway_seat *seat = input_manager_get_default_seat(); struct sway_workspace *focused_ws = seat_get_focused_workspace(seat); bool focused = workspace == focused_ws; json_object_object_del(workspace_json, "focused"); json_object_object_add(workspace_json, "focused", json_object_new_boolean(focused)); json_object_array_add((json_object *)data, workspace_json); focused_ws = output_get_active_workspace(workspace->output); bool visible = workspace == focused_ws; json_object_object_add(workspace_json, "visible", json_object_new_boolean(visible)); } static void ipc_get_marks_callback(struct sway_container *con, void *data) { json_object *marks = (json_object *)data; for (int i = 0; i < con->marks->length; ++i) { char *mark = (char *)con->marks->items[i]; json_object_array_add(marks, json_object_new_string(mark)); } } void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_length, enum ipc_command_type payload_type) { if (!sway_assert(client != NULL, "client != NULL")) { return; } char *buf = malloc(payload_length + 1); if (!buf) { sway_log_errno(SWAY_INFO, "Unable to allocate IPC payload"); ipc_client_disconnect(client); return; } if (payload_length > 0) { // Payload should be fully available ssize_t received = recv(client->fd, buf, payload_length, 0); if (received == -1) { sway_log_errno(SWAY_INFO, "Unable to receive payload from IPC client"); ipc_client_disconnect(client); free(buf); return; } } buf[payload_length] = '\0'; switch (payload_type) { case IPC_COMMAND: { char *line = strtok(buf, "\n"); while (line) { size_t line_length = strlen(line); if (line + line_length >= buf + payload_length) { break; } line[line_length] = ';'; line = strtok(NULL, "\n"); } list_t *res_list = execute_command(buf, NULL, NULL); if (modeset_is_pending()) { // IPC expects commands to have taken immediate effect, so we need // to force a modeset after output commands. We do a single modeset // here to avoid modesetting for every output command in sequence. force_modeset(); } transaction_commit_dirty(); char *json = cmd_results_to_json(res_list); int length = strlen(json); ipc_send_reply(client, payload_type, json, (uint32_t)length); free(json); while (res_list->length) { struct cmd_results *results = res_list->items[0]; free_cmd_results(results); list_del(res_list, 0); } list_free(res_list); goto exit_cleanup; } case IPC_SEND_TICK: { ipc_event_tick(buf); ipc_send_reply(client, payload_type, "{\"success\": true}", 17); goto exit_cleanup; } case IPC_GET_OUTPUTS: { json_object *outputs = json_object_new_array(); for (int i = 0; i < root->outputs->length; ++i) { struct sway_output *output = root->outputs->items[i]; json_object *output_json = ipc_json_describe_node(&output->node); // override the default focused indicator because it's set // differently for the get_outputs reply struct sway_seat *seat = input_manager_get_default_seat(); struct sway_workspace *focused_ws = seat_get_focused_workspace(seat); bool focused = focused_ws && output == focused_ws->output; json_object_object_del(output_json, "focused"); json_object_object_add(output_json, "focused", json_object_new_boolean(focused)); const char *subpixel = sway_wl_output_subpixel_to_string(output->wlr_output->subpixel); json_object_object_add(output_json, "subpixel_hinting", json_object_new_string(subpixel)); json_object_array_add(outputs, output_json); } struct sway_output *output; wl_list_for_each(output, &root->all_outputs, link) { if (!output->enabled && output != root->fallback_output) { json_object_array_add(outputs, ipc_json_describe_disabled_output(output)); } } for (int i = 0; i < root->non_desktop_outputs->length; i++) { struct sway_output_non_desktop *non_desktop_output = root->non_desktop_outputs->items[i]; json_object_array_add(outputs, ipc_json_describe_non_desktop_output(non_desktop_output)); } const char *json_string = json_object_to_json_string(outputs); ipc_send_reply(client, payload_type, json_string, (uint32_t)strlen(json_string)); json_object_put(outputs); // free goto exit_cleanup; } case IPC_GET_WORKSPACES: { json_object *workspaces = json_object_new_array(); root_for_each_workspace(ipc_get_workspaces_callback, workspaces); const char *json_string = json_object_to_json_string(workspaces); ipc_send_reply(client, payload_type, json_string, (uint32_t)strlen(json_string)); json_object_put(workspaces); // free goto exit_cleanup; } case IPC_SUBSCRIBE: { // TODO: Check if they're permitted to use these events struct json_object *request = json_tokener_parse(buf); if (request == NULL || !json_object_is_type(request, json_type_array)) { const char msg[] = "{\"success\": false}"; ipc_send_reply(client, payload_type, msg, strlen(msg)); sway_log(SWAY_INFO, "Failed to parse subscribe request"); goto exit_cleanup; } bool is_tick = false; // parse requested event types for (size_t i = 0; i < json_object_array_length(request); i++) { const char *event_type = json_object_get_string(json_object_array_get_idx(request, i)); if (strcmp(event_type, "workspace") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_WORKSPACE); } else if (strcmp(event_type, "output") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_OUTPUT); } else if (strcmp(event_type, "barconfig_update") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_BARCONFIG_UPDATE); } else if (strcmp(event_type, "bar_state_update") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_BAR_STATE_UPDATE); } else if (strcmp(event_type, "mode") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_MODE); } else if (strcmp(event_type, "shutdown") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_SHUTDOWN); } else if (strcmp(event_type, "window") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_WINDOW); } else if (strcmp(event_type, "binding") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_BINDING); } else if (strcmp(event_type, "tick") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_TICK); is_tick = true; } else if (strcmp(event_type, "input") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_INPUT); } else { const char msg[] = "{\"success\": false}"; ipc_send_reply(client, payload_type, msg, strlen(msg)); json_object_put(request); sway_log(SWAY_INFO, "Unsupported event type in subscribe request"); goto exit_cleanup; } } json_object_put(request); const char msg[] = "{\"success\": true}"; ipc_send_reply(client, payload_type, msg, strlen(msg)); if (is_tick) { const char tickmsg[] = "{\"first\": true, \"payload\": \"\"}"; ipc_send_reply(client, IPC_EVENT_TICK, tickmsg, strlen(tickmsg)); } goto exit_cleanup; } case IPC_GET_INPUTS: { json_object *inputs = json_object_new_array(); struct sway_input_device *device = NULL; wl_list_for_each(device, &server.input->devices, link) { json_object_array_add(inputs, ipc_json_describe_input(device)); } const char *json_string = json_object_to_json_string(inputs); ipc_send_reply(client, payload_type, json_string, (uint32_t)strlen(json_string)); json_object_put(inputs); // free goto exit_cleanup; } case IPC_GET_SEATS: { json_object *seats = json_object_new_array(); struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { json_object_array_add(seats, ipc_json_describe_seat(seat)); } const char *json_string = json_object_to_json_string(seats); ipc_send_reply(client, payload_type, json_string, (uint32_t)strlen(json_string)); json_object_put(seats); // free goto exit_cleanup; } case IPC_GET_TREE: { json_object *tree = ipc_json_describe_node_recursive(&root->node); const char *json_string = json_object_to_json_string(tree); ipc_send_reply(client, payload_type, json_string, (uint32_t)strlen(json_string)); json_object_put(tree); goto exit_cleanup; } case IPC_GET_MARKS: { json_object *marks = json_object_new_array(); root_for_each_container(ipc_get_marks_callback, marks); const char *json_string = json_object_to_json_string(marks); ipc_send_reply(client, payload_type, json_string, (uint32_t)strlen(json_string)); json_object_put(marks); goto exit_cleanup; } case IPC_GET_VERSION: { json_object *version = ipc_json_get_version(); const char *json_string = json_object_to_json_string(version); ipc_send_reply(client, payload_type, json_string, (uint32_t)strlen(json_string)); json_object_put(version); // free goto exit_cleanup; } case IPC_GET_BAR_CONFIG: { if (!buf[0]) { // Send list of configured bar IDs json_object *bars = json_object_new_array(); for (int i = 0; i < config->bars->length; ++i) { struct bar_config *bar = config->bars->items[i]; json_object_array_add(bars, json_object_new_string(bar->id)); } const char *json_string = json_object_to_json_string(bars); ipc_send_reply(client, payload_type, json_string, (uint32_t)strlen(json_string)); json_object_put(bars); // free } else { // Send particular bar's details struct bar_config *bar = NULL; for (int i = 0; i < config->bars->length; ++i) { bar = config->bars->items[i]; if (strcmp(buf, bar->id) == 0) { break; } bar = NULL; } if (!bar) { const char *error = "{ \"success\": false, \"error\": \"No bar with that ID\" }"; ipc_send_reply(client, payload_type, error, (uint32_t)strlen(error)); goto exit_cleanup; } json_object *json = ipc_json_describe_bar_config(bar); const char *json_string = json_object_to_json_string(json); ipc_send_reply(client, payload_type, json_string, (uint32_t)strlen(json_string)); json_object_put(json); // free } goto exit_cleanup; } case IPC_GET_BINDING_MODES: { json_object *modes = json_object_new_array(); for (int i = 0; i < config->modes->length; i++) { struct sway_mode *mode = config->modes->items[i]; json_object_array_add(modes, json_object_new_string(mode->name)); } const char *json_string = json_object_to_json_string(modes); ipc_send_reply(client, payload_type, json_string, (uint32_t)strlen(json_string)); json_object_put(modes); // free goto exit_cleanup; } case IPC_GET_BINDING_STATE: { json_object *current_mode = ipc_json_get_binding_mode(); const char *json_string = json_object_to_json_string(current_mode); ipc_send_reply(client, payload_type, json_string, (uint32_t)strlen(json_string)); json_object_put(current_mode); // free goto exit_cleanup; } case IPC_GET_CONFIG: { json_object *json = json_object_new_object(); json_object_object_add(json, "config", json_object_new_string(config->current_config)); const char *json_string = json_object_to_json_string(json); ipc_send_reply(client, payload_type, json_string, (uint32_t)strlen(json_string)); json_object_put(json); // free goto exit_cleanup; } case IPC_SYNC: { // It was decided sway will not support this, just return success:false const char msg[] = "{\"success\": false}"; ipc_send_reply(client, payload_type, msg, strlen(msg)); goto exit_cleanup; } default: sway_log(SWAY_INFO, "Unknown IPC command type %x", payload_type); goto exit_cleanup; } exit_cleanup: free(buf); } bool ipc_send_reply(struct ipc_client *client, enum ipc_command_type payload_type, const char *payload, uint32_t payload_length) { assert(payload); char data[IPC_HEADER_SIZE]; memcpy(data, ipc_magic, sizeof(ipc_magic)); memcpy(data + sizeof(ipc_magic), &payload_length, sizeof(payload_length)); memcpy(data + sizeof(ipc_magic) + sizeof(payload_length), &payload_type, sizeof(payload_type)); while (client->write_buffer_len + IPC_HEADER_SIZE + payload_length >= client->write_buffer_size) { client->write_buffer_size *= 2; } if (client->write_buffer_size > 4e6) { // 4 MB sway_log(SWAY_ERROR, "Client write buffer too big (%zu), disconnecting client", client->write_buffer_size); ipc_client_disconnect(client); return false; } char *new_buffer = realloc(client->write_buffer, client->write_buffer_size); if (!new_buffer) { sway_log(SWAY_ERROR, "Unable to reallocate ipc client write buffer"); ipc_client_disconnect(client); return false; } client->write_buffer = new_buffer; memcpy(client->write_buffer + client->write_buffer_len, data, IPC_HEADER_SIZE); client->write_buffer_len += IPC_HEADER_SIZE; memcpy(client->write_buffer + client->write_buffer_len, payload, payload_length); client->write_buffer_len += payload_length; if (!client->writable_event_source) { client->writable_event_source = wl_event_loop_add_fd( server.wl_event_loop, client->fd, WL_EVENT_WRITABLE, ipc_client_handle_writable, client); } return true; } ================================================ FILE: sway/lock.c ================================================ #include #include #include #include "log.h" #include "sway/input/cursor.h" #include "sway/input/keyboard.h" #include "sway/input/seat.h" #include "sway/layers.h" #include "sway/output.h" #include "sway/server.h" #include "sway/lock.h" struct sway_session_lock_output { struct wlr_scene_tree *tree; struct wlr_scene_rect *background; struct sway_session_lock *lock; struct sway_output *output; struct wl_list link; // sway_session_lock::outputs struct wl_listener destroy; struct wlr_session_lock_surface_v1 *surface; // invalid if surface is NULL struct wl_listener surface_destroy; struct wl_listener surface_map; }; static void focus_surface(struct sway_session_lock *lock, struct wlr_surface *focused) { lock->focused = focused; struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { seat_set_focus_surface(seat, focused, false); } } static void refocus_output(struct sway_session_lock_output *output) { // Move the seat focus to another surface if one is available if (output->lock->focused == output->surface->surface) { struct wlr_surface *next_focus = NULL; struct sway_session_lock_output *candidate; wl_list_for_each(candidate, &output->lock->outputs, link) { if (candidate == output || !candidate->surface) { continue; } if (candidate->surface->surface->mapped) { next_focus = candidate->surface->surface; break; } } focus_surface(output->lock, next_focus); } } static void handle_surface_map(struct wl_listener *listener, void *data) { struct sway_session_lock_output *surf = wl_container_of(listener, surf, surface_map); if (surf->lock->focused == NULL) { focus_surface(surf->lock, surf->surface->surface); } cursor_rebase_all(); } static void handle_surface_destroy(struct wl_listener *listener, void *data) { struct sway_session_lock_output *output = wl_container_of(listener, output, surface_destroy); refocus_output(output); sway_assert(output->surface, "Trying to destroy a surface that the lock doesn't think exists"); output->surface = NULL; wl_list_remove(&output->surface_destroy.link); wl_list_remove(&output->surface_map.link); } static void lock_output_reconfigure(struct sway_session_lock_output *output) { int width = output->output->width; int height = output->output->height; wlr_scene_rect_set_size(output->background, width, height); if (output->surface) { wlr_session_lock_surface_v1_configure(output->surface, width, height); } } void arrange_locks(void) { if (server.session_lock.lock == NULL) { return; } struct sway_session_lock_output *lock_output; wl_list_for_each(lock_output, &server.session_lock.lock->outputs, link) { lock_output_reconfigure(lock_output); } } static void handle_new_surface(struct wl_listener *listener, void *data) { struct sway_session_lock *lock = wl_container_of(listener, lock, new_surface); struct wlr_session_lock_surface_v1 *lock_surface = data; struct sway_output *output = lock_surface->output->data; sway_log(SWAY_DEBUG, "new lock layer surface"); struct sway_session_lock_output *current_lock_output, *lock_output = NULL; wl_list_for_each(current_lock_output, &lock->outputs, link) { if (current_lock_output->output == output) { lock_output = current_lock_output; break; } } sway_assert(lock_output, "Couldn't find output to lock"); sway_assert(!lock_output->surface, "Tried to reassign a surface to an existing output"); lock_output->surface = lock_surface; wlr_scene_subsurface_tree_create(lock_output->tree, lock_surface->surface); lock_output->surface_destroy.notify = handle_surface_destroy; wl_signal_add(&lock_surface->events.destroy, &lock_output->surface_destroy); lock_output->surface_map.notify = handle_surface_map; wl_signal_add(&lock_surface->surface->events.map, &lock_output->surface_map); lock_output_reconfigure(lock_output); } static void sway_session_lock_output_destroy(struct sway_session_lock_output *output) { if (output->surface) { refocus_output(output); wl_list_remove(&output->surface_destroy.link); wl_list_remove(&output->surface_map.link); } wl_list_remove(&output->destroy.link); wl_list_remove(&output->link); free(output); } static void lock_node_handle_destroy(struct wl_listener *listener, void *data) { struct sway_session_lock_output *output = wl_container_of(listener, output, destroy); sway_session_lock_output_destroy(output); } static struct sway_session_lock_output *session_lock_output_create( struct sway_session_lock *lock, struct sway_output *output) { struct sway_session_lock_output *lock_output = calloc(1, sizeof(*lock_output)); if (!lock_output) { sway_log(SWAY_ERROR, "failed to allocate a session lock output"); return NULL; } struct wlr_scene_tree *tree = wlr_scene_tree_create(output->layers.session_lock); if (!tree) { sway_log(SWAY_ERROR, "failed to allocate a session lock output scene tree"); free(lock_output); return NULL; } struct wlr_scene_rect *background = wlr_scene_rect_create(tree, 0, 0, (float[4]){ lock->abandoned ? 1.f : 0.f, 0.f, 0.f, 1.f, }); if (!background) { sway_log(SWAY_ERROR, "failed to allocate a session lock output scene background"); wlr_scene_node_destroy(&tree->node); free(lock_output); return NULL; } lock_output->output = output; lock_output->tree = tree; lock_output->background = background; lock_output->lock = lock; lock_output->destroy.notify = lock_node_handle_destroy; wl_signal_add(&tree->node.events.destroy, &lock_output->destroy); lock_output_reconfigure(lock_output); wl_list_insert(&lock->outputs, &lock_output->link); return lock_output; } static void sway_session_lock_destroy(struct sway_session_lock* lock) { struct sway_session_lock_output *lock_output, *tmp_lock_output; wl_list_for_each_safe(lock_output, tmp_lock_output, &lock->outputs, link) { // destroying the node will also destroy the whole lock output wlr_scene_node_destroy(&lock_output->tree->node); } if (server.session_lock.lock == lock) { server.session_lock.lock = NULL; } if (!lock->abandoned) { wl_list_remove(&lock->destroy.link); wl_list_remove(&lock->unlock.link); wl_list_remove(&lock->new_surface.link); } free(lock); } static void handle_unlock(struct wl_listener *listener, void *data) { struct sway_session_lock *lock = wl_container_of(listener, lock, unlock); sway_log(SWAY_DEBUG, "session unlocked"); sway_session_lock_destroy(lock); struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { // copied from seat_set_focus_layer -- deduplicate? struct sway_node *previous = seat_get_focus_inactive(seat, &root->node); if (previous) { // Hack to get seat to re-focus the return value of get_focus seat_set_focus(seat, NULL); seat_set_focus(seat, previous); } } // Triggers a refocus of the topmost surface layer if necessary // TODO: Make layer surface focus per-output based on cursor position for (int i = 0; i < root->outputs->length; ++i) { struct sway_output *output = root->outputs->items[i]; arrange_layers(output); } // Views are now visible, so check if we need to activate inhibition again. sway_idle_inhibit_v1_check_active(); } static void handle_abandon(struct wl_listener *listener, void *data) { struct sway_session_lock *lock = wl_container_of(listener, lock, destroy); sway_log(SWAY_INFO, "session lock abandoned"); struct sway_session_lock_output *lock_output; wl_list_for_each(lock_output, &lock->outputs, link) { wlr_scene_rect_set_color(lock_output->background, (float[4]){ 1.f, 0.f, 0.f, 1.f }); } lock->abandoned = true; wl_list_remove(&lock->destroy.link); wl_list_remove(&lock->unlock.link); wl_list_remove(&lock->new_surface.link); } static void handle_session_lock(struct wl_listener *listener, void *data) { struct wlr_session_lock_v1 *lock = data; struct wl_client *client = wl_resource_get_client(lock->resource); if (server.session_lock.lock) { if (server.session_lock.lock->abandoned) { sway_log(SWAY_INFO, "Replacing abandoned lock"); sway_session_lock_destroy(server.session_lock.lock); } else { sway_log(SWAY_ERROR, "Cannot lock an already locked session"); wlr_session_lock_v1_destroy(lock); return; } } struct sway_session_lock *sway_lock = calloc(1, sizeof(*sway_lock)); if (!sway_lock) { sway_log(SWAY_ERROR, "failed to allocate a session lock object"); wlr_session_lock_v1_destroy(lock); return; } wl_list_init(&sway_lock->outputs); sway_log(SWAY_DEBUG, "session locked"); struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { seat_unfocus_unless_client(seat, client); } struct sway_output *output; wl_list_for_each(output, &root->all_outputs, link) { sway_session_lock_add_output(sway_lock, output); } sway_lock->new_surface.notify = handle_new_surface; wl_signal_add(&lock->events.new_surface, &sway_lock->new_surface); sway_lock->unlock.notify = handle_unlock; wl_signal_add(&lock->events.unlock, &sway_lock->unlock); sway_lock->destroy.notify = handle_abandon; wl_signal_add(&lock->events.destroy, &sway_lock->destroy); wlr_session_lock_v1_send_locked(lock); server.session_lock.lock = sway_lock; // The lock screen covers everything, so check if any active inhibition got // deactivated due to lost visibility. sway_idle_inhibit_v1_check_active(); } static void handle_session_lock_destroy(struct wl_listener *listener, void *data) { // if the server shuts down while a lock is active, destroy the lock if (server.session_lock.lock) { sway_session_lock_destroy(server.session_lock.lock); } wl_list_remove(&server.session_lock.new_lock.link); wl_list_remove(&server.session_lock.manager_destroy.link); server.session_lock.manager = NULL; } void sway_session_lock_add_output(struct sway_session_lock *lock, struct sway_output *output) { struct sway_session_lock_output *lock_output = session_lock_output_create(lock, output); // if we run out of memory while trying to lock the screen, the best we // can do is kill the sway process. Security conscious users will have // the sway session fall back to a login shell. if (!lock_output) { sway_log(SWAY_ERROR, "aborting: failed to allocate a lock output"); abort(); } } bool sway_session_lock_has_surface(struct sway_session_lock *lock, struct wlr_surface *surface) { struct sway_session_lock_output *lock_output; wl_list_for_each(lock_output, &lock->outputs, link) { if (lock_output->surface && lock_output->surface->surface == surface) { return true; } } return false; } void sway_session_lock_init(void) { server.session_lock.manager = wlr_session_lock_manager_v1_create(server.wl_display); server.session_lock.new_lock.notify = handle_session_lock; server.session_lock.manager_destroy.notify = handle_session_lock_destroy; wl_signal_add(&server.session_lock.manager->events.new_lock, &server.session_lock.new_lock); wl_signal_add(&server.session_lock.manager->events.destroy, &server.session_lock.manager_destroy); } ================================================ FILE: sway/main.c ================================================ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sway/commands.h" #include "sway/config.h" #include "sway/server.h" #include "sway/swaynag.h" #include "sway/desktop/transaction.h" #include "sway/tree/root.h" #include "sway/ipc-server.h" #include "ipc-client.h" #include "log.h" #include "stringop.h" #include "util.h" static bool terminate_request = false; static int exit_value = 0; static struct rlimit original_nofile_rlimit = {0}; struct sway_server server = {0}; struct sway_debug debug = {0}; void sway_terminate(int exit_code) { if (!server.wl_display) { // Running as IPC client exit(exit_code); } else { // Running as server terminate_request = true; exit_value = exit_code; ipc_event_shutdown("exit"); wl_display_terminate(server.wl_display); } } void run_as_ipc_client(char *command, char *socket_path) { int socketfd = ipc_open_socket(socket_path); uint32_t len = strlen(command); char *resp = ipc_single_command(socketfd, IPC_COMMAND, command, &len); printf("%s\n", resp); free(resp); close(socketfd); } static void log_env(void) { const char *log_vars[] = { "LD_LIBRARY_PATH", "LD_PRELOAD", "PATH", "SWAYSOCK", }; for (size_t i = 0; i < sizeof(log_vars) / sizeof(char *); ++i) { char *value = getenv(log_vars[i]); sway_log(SWAY_INFO, "%s=%s", log_vars[i], value != NULL ? value : ""); } } static void log_file(FILE *f) { char *line = NULL; size_t line_size = 0; ssize_t nread; while ((nread = getline(&line, &line_size, f)) != -1) { if (line[nread - 1] == '\n') { line[nread - 1] = '\0'; } sway_log(SWAY_INFO, "%s", line); } free(line); } static void log_distro(void) { const char *paths[] = { "/etc/lsb-release", "/etc/os-release", "/etc/debian_version", "/etc/redhat-release", "/etc/gentoo-release", }; for (size_t i = 0; i < sizeof(paths) / sizeof(char *); ++i) { FILE *f = fopen(paths[i], "r"); if (f) { sway_log(SWAY_INFO, "Contents of %s:", paths[i]); log_file(f); fclose(f); } } } static void log_kernel(void) { FILE *f = popen("uname -a", "r"); if (!f) { sway_log(SWAY_INFO, "Unable to determine kernel version"); return; } log_file(f); pclose(f); } static void restore_nofile_limit(void) { if (original_nofile_rlimit.rlim_cur == 0) { return; } if (setrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) { sway_log_errno(SWAY_ERROR, "Failed to restore max open files limit: " "setrlimit(NOFILE) failed"); } } static void increase_nofile_limit(void) { if (getrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) { sway_log_errno(SWAY_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) { sway_log_errno(SWAY_ERROR, "Failed to bump max open files limit: " "setrlimit(NOFILE) failed"); sway_log(SWAY_INFO, "Running with %d max open files", (int)original_nofile_rlimit.rlim_cur); return; } pthread_atfork(NULL, NULL, restore_nofile_limit); } static int term_signal(int signal, void *data) { sway_terminate(EXIT_SUCCESS); return 0; } static void restore_signals(void) { sigset_t set; sigemptyset(&set); sigprocmask(SIG_SETMASK, &set, NULL); struct sigaction sa_dfl = { .sa_handler = SIG_DFL }; sigaction(SIGCHLD, &sa_dfl, NULL); sigaction(SIGPIPE, &sa_dfl, NULL); } static void init_signals(void) { wl_event_loop_add_signal(server.wl_event_loop, SIGTERM, term_signal, NULL); wl_event_loop_add_signal(server.wl_event_loop, SIGINT, term_signal, NULL); struct sigaction sa_ign = { .sa_handler = SIG_IGN }; // avoid need to reap children sigaction(SIGCHLD, &sa_ign, NULL); // prevent ipc write errors from crashing sway sigaction(SIGPIPE, &sa_ign, NULL); pthread_atfork(NULL, NULL, restore_signals); } void enable_debug_flag(const char *flag) { if (strcmp(flag, "noatomic") == 0) { debug.noatomic = true; } else if (strcmp(flag, "txn-wait") == 0) { debug.txn_wait = true; } else if (strcmp(flag, "txn-timings") == 0) { debug.txn_timings = true; } else if (has_prefix(flag, "txn-timeout=")) { server.txn_timeout_ms = atoi(&flag[strlen("txn-timeout=")]); } else { sway_log(SWAY_ERROR, "Unknown debug flag: %s", flag); } } static sway_log_importance_t convert_wlr_log_importance( enum wlr_log_importance importance) { switch (importance) { case WLR_ERROR: return SWAY_ERROR; case WLR_INFO: return SWAY_INFO; default: return SWAY_DEBUG; } } static void handle_wlr_log(enum wlr_log_importance importance, const char *fmt, va_list args) { static char sway_fmt[1024]; snprintf(sway_fmt, sizeof(sway_fmt), "[wlr] %s", fmt); _sway_vlog(convert_wlr_log_importance(importance), sway_fmt, args); } static const struct option long_options[] = { {"help", no_argument, NULL, 'h'}, {"config", required_argument, NULL, 'c'}, {"validate", no_argument, NULL, 'C'}, {"debug", no_argument, NULL, 'd'}, {"version", no_argument, NULL, 'v'}, {"verbose", no_argument, NULL, 'V'}, {"get-socketpath", no_argument, NULL, 'p'}, {"unsupported-gpu", no_argument, NULL, 'u'}, {0, 0, 0, 0} }; static const char usage[] = "Usage: sway [options] [command]\n" "\n" " -h, --help Show help message and quit.\n" " -c, --config Specify a config file.\n" " -C, --validate Check the validity of the config file, then exit.\n" " -d, --debug Enables full logging, including debug information.\n" " -v, --version Show the version number and quit.\n" " -V, --verbose Enables more verbose logging.\n" " --get-socketpath Gets the IPC socket path and prints it, then exits.\n" "\n"; int main(int argc, char **argv) { bool verbose = false, debug = false, validate = false, allow_unsupported_gpu = false; char *config_path = NULL; int c; while (1) { int option_index = 0; c = getopt_long(argc, argv, "hCdD:vVc:", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'h': // help printf("%s", usage); exit(EXIT_SUCCESS); break; case 'c': // config free(config_path); config_path = strdup(optarg); break; case 'C': // validate validate = true; break; case 'd': // debug debug = true; break; case 'D': // extended debug options enable_debug_flag(optarg); break; case 'u': allow_unsupported_gpu = true; break; case 'v': // version printf("sway version " SWAY_VERSION "\n"); exit(EXIT_SUCCESS); break; case 'V': // verbose verbose = true; break; case 'p': // --get-socketpath if (getenv("SWAYSOCK")) { printf("%s\n", getenv("SWAYSOCK")); exit(EXIT_SUCCESS); } else { fprintf(stderr, "sway socket not detected.\n"); exit(EXIT_FAILURE); } break; default: fprintf(stderr, "%s", usage); exit(EXIT_FAILURE); } } // Since wayland requires XDG_RUNTIME_DIR to be set, abort with just the // clear error message (when not running as an IPC client). if (!getenv("XDG_RUNTIME_DIR") && optind == argc) { fprintf(stderr, "XDG_RUNTIME_DIR is not set in the environment. Aborting.\n"); exit(EXIT_FAILURE); } char *unsupported_gpu_env = getenv("SWAY_UNSUPPORTED_GPU"); // we let the flag override the environment variable if (!allow_unsupported_gpu && unsupported_gpu_env) { allow_unsupported_gpu = parse_boolean(unsupported_gpu_env, false); } // As the 'callback' function for wlr_log is equivalent to that for // sway, we do not need to override it. if (debug) { sway_log_init(SWAY_DEBUG, sway_terminate); wlr_log_init(WLR_DEBUG, handle_wlr_log); } else if (verbose) { sway_log_init(SWAY_INFO, sway_terminate); wlr_log_init(WLR_INFO, handle_wlr_log); } else { sway_log_init(SWAY_ERROR, sway_terminate); wlr_log_init(WLR_ERROR, handle_wlr_log); } sway_log(SWAY_INFO, "Sway version " SWAY_VERSION); sway_log(SWAY_INFO, "wlroots version " WLR_VERSION_STR); log_kernel(); log_distro(); log_env(); if (optind < argc) { // Behave as IPC client if (optind != 1) { sway_log(SWAY_ERROR, "Detected both options and positional arguments. If you " "are trying to use the IPC client, options are not " "supported. Otherwise, check the provided arguments for " "issues. See `man 1 sway` or `sway -h` for usage. If you " "are trying to generate a debug log, use " "`sway -d 2>sway.log`."); exit(EXIT_FAILURE); } char *socket_path = getenv("SWAYSOCK"); if (!socket_path) { sway_log(SWAY_ERROR, "Unable to retrieve socket path"); exit(EXIT_FAILURE); } char *command = join_args(argv + optind, argc - optind); run_as_ipc_client(command, socket_path); free(command); return 0; } increase_nofile_limit(); sway_log(SWAY_INFO, "Starting sway version " SWAY_VERSION); if (!server_init(&server)) { return 1; } init_signals(); if (server.linux_dmabuf_v1) { wlr_scene_set_linux_dmabuf_v1(root->root_scene, server.linux_dmabuf_v1); } if (validate) { bool valid = load_main_config(config_path, false, true); free(config_path); return valid ? 0 : 1; } ipc_init(&server); setenv("WAYLAND_DISPLAY", server.socket, true); if (!load_main_config(config_path, false, false)) { sway_terminate(EXIT_FAILURE); goto shutdown; } set_rr_scheduling(); if (!server_start(&server)) { sway_terminate(EXIT_FAILURE); goto shutdown; } config->active = true; force_modeset(); load_swaybars(); run_deferred_commands(); run_deferred_bindings(); transaction_commit_dirty(); if (config->swaynag_config_errors.client != NULL) { swaynag_show(&config->swaynag_config_errors); } struct swaynag_instance nag_gpu = (struct swaynag_instance){ .args = "--type error " "--message 'Proprietary GPU drivers are not supported by sway. Do not report issues.' " "--detailed-message", .detailed = true, }; if (unsupported_gpu_detected && !allow_unsupported_gpu) { swaynag_log(config->swaynag_command, &nag_gpu, "To remove this message, launch sway with --unsupported-gpu " "or set the environment variable SWAY_UNSUPPORTED_GPU=true."); swaynag_show(&nag_gpu); } server_run(&server); shutdown: sway_log(SWAY_INFO, "Shutting down sway"); server_fini(&server); root_destroy(root); root = NULL; free(config_path); free_config(config); if (nag_gpu.client != NULL) { wl_client_destroy(nag_gpu.client); } pango_cairo_font_map_set_default(NULL); return exit_value; } ================================================ FILE: sway/meson.build ================================================ sway_sources = files( 'commands.c', 'config.c', 'criteria.c', 'decoration.c', 'ipc-json.c', 'ipc-server.c', 'lock.c', 'main.c', 'realtime.c', 'scene_descriptor.c', 'server.c', 'sway_text_node.c', 'swaynag.c', 'xdg_activation_v1.c', 'xdg_decoration.c', 'desktop/idle_inhibit_v1.c', 'desktop/layer_shell.c', 'desktop/output.c', 'desktop/tearing.c', 'desktop/transaction.c', 'desktop/xdg_shell.c', 'desktop/launcher.c', 'input/input-manager.c', 'input/cursor.c', 'input/keyboard.c', 'input/seat.c', 'input/seatop_default.c', 'input/seatop_down.c', 'input/seatop_move_floating.c', 'input/seatop_move_tiling.c', 'input/seatop_resize_floating.c', 'input/seatop_resize_tiling.c', 'input/switch.c', 'input/tablet.c', 'input/text_input.c', 'config/bar.c', 'config/output.c', 'config/seat.c', 'config/input.c', 'commands/allow_tearing.c', 'commands/assign.c', 'commands/bar.c', 'commands/bind.c', 'commands/border.c', 'commands/client.c', 'commands/create_output.c', 'commands/default_border.c', 'commands/default_floating_border.c', 'commands/default_orientation.c', 'commands/exit.c', 'commands/exec.c', 'commands/exec_always.c', 'commands/floating.c', 'commands/floating_minmax_size.c', 'commands/floating_modifier.c', 'commands/focus.c', 'commands/focus_follows_mouse.c', 'commands/focus_on_window_activation.c', 'commands/focus_wrapping.c', 'commands/font.c', 'commands/for_window.c', 'commands/force_display_urgency_hint.c', 'commands/force_focus_wrapping.c', 'commands/fullscreen.c', 'commands/gaps.c', 'commands/gesture.c', 'commands/hide_edge_borders.c', 'commands/inhibit_idle.c', 'commands/kill.c', 'commands/mark.c', 'commands/max_render_time.c', 'commands/opacity.c', 'commands/include.c', 'commands/input.c', 'commands/layout.c', 'commands/mode.c', 'commands/mouse_warping.c', 'commands/move.c', 'commands/new_float.c', 'commands/new_window.c', 'commands/no_focus.c', 'commands/nop.c', 'commands/output.c', 'commands/popup_during_fullscreen.c', 'commands/primary_selection.c', 'commands/reload.c', 'commands/rename.c', 'commands/resize.c', 'commands/scratchpad.c', 'commands/seat.c', 'commands/seat/attach.c', 'commands/seat/cursor.c', 'commands/seat/fallback.c', 'commands/seat/hide_cursor.c', 'commands/seat/idle.c', 'commands/seat/keyboard_grouping.c', 'commands/seat/pointer_constraint.c', 'commands/seat/shortcuts_inhibitor.c', 'commands/seat/xcursor_theme.c', 'commands/set.c', 'commands/show_marks.c', 'commands/shortcuts_inhibitor.c', 'commands/smart_borders.c', 'commands/smart_gaps.c', 'commands/split.c', 'commands/sticky.c', 'commands/swaybg_command.c', 'commands/swaynag_command.c', 'commands/swap.c', 'commands/tiling_drag.c', 'commands/tiling_drag_threshold.c', 'commands/title_align.c', 'commands/title_format.c', 'commands/titlebar_border_thickness.c', 'commands/titlebar_padding.c', 'commands/unmark.c', 'commands/urgent.c', 'commands/workspace.c', 'commands/workspace_layout.c', 'commands/ws_auto_back_and_forth.c', 'commands/xwayland.c', 'commands/bar/bind.c', 'commands/bar/binding_mode_indicator.c', 'commands/bar/colors.c', 'commands/bar/font.c', 'commands/bar/gaps.c', 'commands/bar/height.c', 'commands/bar/hidden_state.c', 'commands/bar/icon_theme.c', 'commands/bar/id.c', 'commands/bar/mode.c', 'commands/bar/modifier.c', 'commands/bar/output.c', 'commands/bar/pango_markup.c', 'commands/bar/position.c', 'commands/bar/separator_symbol.c', 'commands/bar/status_command.c', 'commands/bar/status_edge_padding.c', 'commands/bar/status_padding.c', 'commands/bar/strip_workspace_numbers.c', 'commands/bar/strip_workspace_name.c', 'commands/bar/swaybar_command.c', 'commands/bar/tray_bind.c', 'commands/bar/tray_output.c', 'commands/bar/tray_padding.c', 'commands/bar/workspace_buttons.c', 'commands/bar/workspace_min_width.c', 'commands/bar/wrap_scroll.c', 'commands/input/accel_profile.c', 'commands/input/calibration_matrix.c', 'commands/input/click_method.c', 'commands/input/clickfinger_button_map.c', 'commands/input/drag.c', 'commands/input/drag_lock.c', 'commands/input/dwt.c', 'commands/input/dwtp.c', 'commands/input/events.c', 'commands/input/left_handed.c', 'commands/input/map_from_region.c', 'commands/input/map_to_output.c', 'commands/input/map_to_region.c', 'commands/input/middle_emulation.c', 'commands/input/natural_scroll.c', 'commands/input/pointer_accel.c', 'commands/input/rotation_angle.c', 'commands/input/repeat_delay.c', 'commands/input/repeat_rate.c', 'commands/input/scroll_button.c', 'commands/input/scroll_button_lock.c', 'commands/input/scroll_factor.c', 'commands/input/scroll_method.c', 'commands/input/tap.c', 'commands/input/tap_button_map.c', 'commands/input/tool_mode.c', 'commands/input/xkb_capslock.c', 'commands/input/xkb_file.c', 'commands/input/xkb_layout.c', 'commands/input/xkb_model.c', 'commands/input/xkb_numlock.c', 'commands/input/xkb_options.c', 'commands/input/xkb_rules.c', 'commands/input/xkb_switch_layout.c', 'commands/input/xkb_variant.c', 'commands/output/adaptive_sync.c', 'commands/output/allow_tearing.c', 'commands/output/background.c', 'commands/output/disable.c', 'commands/output/dpms.c', 'commands/output/enable.c', 'commands/output/hdr.c', 'commands/output/max_render_time.c', 'commands/output/mode.c', 'commands/output/position.c', 'commands/output/power.c', 'commands/output/render_bit_depth.c', 'commands/output/scale.c', 'commands/output/scale_filter.c', 'commands/output/subpixel.c', 'commands/output/toggle.c', 'commands/output/transform.c', 'commands/output/unplug.c', 'commands/output/color_profile.c', 'tree/arrange.c', 'tree/container.c', 'tree/node.c', 'tree/root.c', 'tree/view.c', 'tree/workspace.c', 'tree/output.c', ) sway_deps = [ cairo, drm, jsonc, libevdev, libinput, libudev, math, pango, pcre2, pixman, threads, wayland_server, wlroots, xkbcommon, xcb, xcb_icccm, ] if wlroots_features['xwayland'] sway_sources += 'desktop/xwayland.c' endif if wlroots_features['libinput_backend'] sway_sources += 'input/libinput.c' endif executable( 'sway', sway_sources + wl_protos_src, include_directories: [sway_inc], dependencies: sway_deps, link_with: [lib_sway_common], install: true ) ================================================ FILE: sway/realtime.c ================================================ #include #include #include #include #include "sway/server.h" #include "log.h" static void child_fork_callback(void) { struct sched_param param; param.sched_priority = 0; int ret = pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶m); if (ret != 0) { sway_log(SWAY_ERROR, "Failed to reset scheduler policy on fork"); } } void set_rr_scheduling(void) { int prio = sched_get_priority_min(SCHED_RR); int old_policy; int ret; struct sched_param param; ret = pthread_getschedparam(pthread_self(), &old_policy, ¶m); if (ret != 0) { sway_log(SWAY_DEBUG, "Failed to get old scheduling priority"); return; } param.sched_priority = prio; ret = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); if (ret != 0) { sway_log(SWAY_INFO, "Failed to set scheduling priority to %d", prio); return; } pthread_atfork(NULL, NULL, child_fork_callback); } ================================================ FILE: sway/scene_descriptor.c ================================================ #include #include #include "log.h" #include "sway/scene_descriptor.h" struct scene_descriptor { void *data; struct wlr_addon addon; }; static const struct wlr_addon_interface addon_interface; static struct scene_descriptor *scene_node_get_descriptor( struct wlr_scene_node *node, enum sway_scene_descriptor_type type) { struct wlr_addon *addon = wlr_addon_find(&node->addons, (void *)type, &addon_interface); if (!addon) { return NULL; } struct scene_descriptor *desc = wl_container_of(addon, desc, addon); return desc; } static void descriptor_destroy(struct scene_descriptor *desc) { wlr_addon_finish(&desc->addon); free(desc); } void *scene_descriptor_try_get(struct wlr_scene_node *node, enum sway_scene_descriptor_type type) { struct scene_descriptor *desc = scene_node_get_descriptor(node, type); if (!desc) { return NULL; } return desc->data; } void scene_descriptor_destroy(struct wlr_scene_node *node, enum sway_scene_descriptor_type type) { struct scene_descriptor *desc = scene_node_get_descriptor(node, type); if (!desc) { return; } descriptor_destroy(desc); } static void addon_handle_destroy(struct wlr_addon *addon) { struct scene_descriptor *desc = wl_container_of(addon, desc, addon); descriptor_destroy(desc); } static const struct wlr_addon_interface addon_interface = { .name = "sway_scene_descriptor", .destroy = addon_handle_destroy, }; bool scene_descriptor_assign(struct wlr_scene_node *node, enum sway_scene_descriptor_type type, void *data) { struct scene_descriptor *desc = calloc(1, sizeof(*desc)); if (!desc) { sway_log(SWAY_ERROR, "Could not allocate a scene descriptor"); return false; } wlr_addon_init(&desc->addon, &node->addons, (void *)type, &addon_interface); desc->data = data; return true; } ================================================ FILE: sway/server.c ================================================ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "list.h" #include "log.h" #include "sway/config.h" #include "sway/desktop/idle_inhibit_v1.h" #include "sway/input/input-manager.h" #include "sway/output.h" #include "sway/server.h" #include "sway/input/cursor.h" #include "sway/tree/root.h" #if WLR_HAS_XWAYLAND #include #include "sway/xwayland.h" #endif #if WLR_HAS_DRM_BACKEND #include #endif #define SWAY_XDG_SHELL_VERSION 5 #define SWAY_LAYER_SHELL_VERSION 5 #define SWAY_FOREIGN_TOPLEVEL_LIST_VERSION 1 #define SWAY_PRESENTATION_VERSION 2 bool unsupported_gpu_detected = false; #if WLR_HAS_DRM_BACKEND static void handle_drm_lease_request(struct wl_listener *listener, void *data) { /* We only offer non-desktop outputs, but in the future we might want to do * more logic here. */ struct wlr_drm_lease_request_v1 *req = data; struct wlr_drm_lease_v1 *lease = wlr_drm_lease_request_v1_grant(req); if (!lease) { sway_log(SWAY_ERROR, "Failed to grant lease request"); wlr_drm_lease_request_v1_reject(req); } } #endif static bool is_privileged(const struct wl_global *global) { #if WLR_HAS_DRM_BACKEND if (server.drm_lease_manager != NULL) { struct wlr_drm_lease_device_v1 *drm_lease_dev; wl_list_for_each(drm_lease_dev, &server.drm_lease_manager->devices, link) { if (drm_lease_dev->global == global) { return true; } } } #endif return global == server.output_manager_v1->global || global == server.output_power_manager_v1->global || global == server.input_method->global || global == server.foreign_toplevel_list->global || global == server.foreign_toplevel_manager->global || global == server.wlr_data_control_manager_v1->global || global == server.ext_data_control_manager_v1->global || global == server.screencopy_manager_v1->global || global == server.ext_image_copy_capture_manager_v1->global || global == server.export_dmabuf_manager_v1->global || global == server.security_context_manager_v1->global || global == server.gamma_control_manager_v1->global || global == server.layer_shell->global || global == server.session_lock.manager->global || global == server.input->keyboard_shortcuts_inhibit->global || global == server.input->virtual_keyboard->global || global == server.input->virtual_pointer->global || global == server.input->transient_seat_manager->global || global == server.xdg_output_manager_v1->global; } static bool filter_global(const struct wl_client *client, const struct wl_global *global, void *data) { #if WLR_HAS_XWAYLAND struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; if (xwayland && global == xwayland->shell_v1->global) { return xwayland->server != NULL && client == xwayland->server->client; } #endif // Restrict usage of privileged protocols to unsandboxed clients // TODO: add a way for users to configure an allow-list const struct wlr_security_context_v1_state *security_context = wlr_security_context_manager_v1_lookup_client( server.security_context_manager_v1, (struct wl_client *)client); if (is_privileged(global)) { return security_context == NULL; } return true; } static void detect_proprietary(struct wlr_backend *backend, void *data) { int drm_fd = wlr_backend_get_drm_fd(backend); if (drm_fd < 0) { return; } drmVersion *version = drmGetVersion(drm_fd); if (version == NULL) { sway_log(SWAY_ERROR, "drmGetVersion() failed"); return; } if (strcmp(version->name, "nvidia-drm") == 0) { unsupported_gpu_detected = true; sway_log(SWAY_ERROR, "!!! Proprietary Nvidia drivers are in use !!!"); } if (strcmp(version->name, "evdi") == 0) { unsupported_gpu_detected = true; sway_log(SWAY_ERROR, "!!! Proprietary DisplayLink drivers are in use !!!"); } drmFreeVersion(version); } static void do_renderer_recreate(void *data) { struct sway_server *server = data; server->recreating_renderer = NULL; sway_log(SWAY_INFO, "Re-creating renderer after GPU reset"); struct wlr_renderer *renderer = wlr_renderer_autocreate(server->backend); if (renderer == NULL) { sway_log(SWAY_ERROR, "Unable to create renderer"); return; } struct wlr_allocator *allocator = wlr_allocator_autocreate(server->backend, renderer); if (allocator == NULL) { sway_log(SWAY_ERROR, "Unable to create allocator"); wlr_renderer_destroy(renderer); return; } struct wlr_renderer *old_renderer = server->renderer; struct wlr_allocator *old_allocator = server->allocator; server->renderer = renderer; server->allocator = allocator; wl_list_remove(&server->renderer_lost.link); wl_signal_add(&server->renderer->events.lost, &server->renderer_lost); wlr_compositor_set_renderer(server->compositor, renderer); struct sway_output *output; wl_list_for_each(output, &root->all_outputs, link) { wlr_output_init_render(output->wlr_output, server->allocator, server->renderer); } wlr_allocator_destroy(old_allocator); wlr_renderer_destroy(old_renderer); } static void handle_renderer_lost(struct wl_listener *listener, void *data) { struct sway_server *server = wl_container_of(listener, server, renderer_lost); if (server->recreating_renderer != NULL) { sway_log(SWAY_DEBUG, "Re-creation of renderer already scheduled"); return; } sway_log(SWAY_INFO, "Scheduling re-creation of renderer after GPU reset"); server->recreating_renderer = wl_event_loop_add_idle(server->wl_event_loop, do_renderer_recreate, server); } static void handle_new_foreign_toplevel_capture_request(struct wl_listener *listener, void *data) { struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request = data; struct sway_view *view = request->toplevel_handle->data; if (view->image_capture_source == NULL) { view->image_capture_source = wlr_ext_image_capture_source_v1_create_with_scene_node( &view->image_capture_scene->tree.node, server.wl_event_loop, server.allocator, server.renderer); if (view->image_capture_source == NULL) { return; } } wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_accept(request, view->image_capture_source); } bool server_init(struct sway_server *server) { sway_log(SWAY_DEBUG, "Initializing Wayland server"); server->wl_display = wl_display_create(); server->wl_event_loop = wl_display_get_event_loop(server->wl_display); wl_display_set_global_filter(server->wl_display, filter_global, NULL); wl_display_set_default_max_buffer_size(server->wl_display, 1024 * 1024); wlr_fixes_create(server->wl_display, 1); root = root_create(server->wl_display); server->backend = wlr_backend_autocreate(server->wl_event_loop, &server->session); if (!server->backend) { sway_log(SWAY_ERROR, "Unable to create backend"); return false; } wlr_multi_for_each_backend(server->backend, detect_proprietary, NULL); server->renderer = wlr_renderer_autocreate(server->backend); if (!server->renderer) { sway_log(SWAY_ERROR, "Failed to create renderer"); return false; } server->renderer_lost.notify = handle_renderer_lost; wl_signal_add(&server->renderer->events.lost, &server->renderer_lost); wlr_renderer_init_wl_shm(server->renderer, server->wl_display); if (wlr_renderer_get_texture_formats(server->renderer, WLR_BUFFER_CAP_DMABUF) != NULL) { server->linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer( server->wl_display, 5, server->renderer); } if (wlr_renderer_get_drm_fd(server->renderer) >= 0 && server->renderer->features.timeline && server->backend->features.timeline) { wlr_linux_drm_syncobj_manager_v1_create(server->wl_display, 1, wlr_renderer_get_drm_fd(server->renderer)); } server->allocator = wlr_allocator_autocreate(server->backend, server->renderer); if (!server->allocator) { sway_log(SWAY_ERROR, "Failed to create allocator"); return false; } server->compositor = wlr_compositor_create(server->wl_display, 6, server->renderer); wlr_subcompositor_create(server->wl_display); server->data_device_manager = wlr_data_device_manager_create(server->wl_display); server->gamma_control_manager_v1 = wlr_gamma_control_manager_v1_create(server->wl_display); wlr_scene_set_gamma_control_manager_v1(root->root_scene, server->gamma_control_manager_v1); server->new_output.notify = handle_new_output; wl_signal_add(&server->backend->events.new_output, &server->new_output); server->xdg_output_manager_v1 = wlr_xdg_output_manager_v1_create(server->wl_display, root->output_layout); server->idle_notifier_v1 = wlr_idle_notifier_v1_create(server->wl_display); sway_idle_inhibit_manager_v1_init(); server->layer_shell = wlr_layer_shell_v1_create(server->wl_display, SWAY_LAYER_SHELL_VERSION); wl_signal_add(&server->layer_shell->events.new_surface, &server->layer_shell_surface); server->layer_shell_surface.notify = handle_layer_shell_surface; server->xdg_shell = wlr_xdg_shell_create(server->wl_display, SWAY_XDG_SHELL_VERSION); wl_signal_add(&server->xdg_shell->events.new_toplevel, &server->xdg_shell_toplevel); server->xdg_shell_toplevel.notify = handle_xdg_shell_toplevel; server->tablet_v2 = wlr_tablet_v2_create(server->wl_display); server->server_decoration_manager = wlr_server_decoration_manager_create(server->wl_display); wlr_server_decoration_manager_set_default_mode( server->server_decoration_manager, WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); wl_signal_add(&server->server_decoration_manager->events.new_decoration, &server->server_decoration); server->server_decoration.notify = handle_server_decoration; wl_list_init(&server->decorations); server->xdg_decoration_manager = wlr_xdg_decoration_manager_v1_create(server->wl_display); wl_signal_add( &server->xdg_decoration_manager->events.new_toplevel_decoration, &server->xdg_decoration); server->xdg_decoration.notify = handle_xdg_decoration; wl_list_init(&server->xdg_decorations); server->relative_pointer_manager = wlr_relative_pointer_manager_v1_create(server->wl_display); server->pointer_constraints = wlr_pointer_constraints_v1_create(server->wl_display); server->pointer_constraint.notify = handle_pointer_constraint; wl_signal_add(&server->pointer_constraints->events.new_constraint, &server->pointer_constraint); wlr_presentation_create(server->wl_display, server->backend, SWAY_PRESENTATION_VERSION); wlr_alpha_modifier_v1_create(server->wl_display); server->output_manager_v1 = wlr_output_manager_v1_create(server->wl_display); server->output_manager_apply.notify = handle_output_manager_apply; wl_signal_add(&server->output_manager_v1->events.apply, &server->output_manager_apply); server->output_manager_test.notify = handle_output_manager_test; wl_signal_add(&server->output_manager_v1->events.test, &server->output_manager_test); server->output_power_manager_v1 = wlr_output_power_manager_v1_create(server->wl_display); server->output_power_manager_set_mode.notify = handle_output_power_manager_set_mode; wl_signal_add(&server->output_power_manager_v1->events.set_mode, &server->output_power_manager_set_mode); server->input_method = wlr_input_method_manager_v2_create(server->wl_display); server->text_input = wlr_text_input_manager_v3_create(server->wl_display); server->foreign_toplevel_list = wlr_ext_foreign_toplevel_list_v1_create(server->wl_display, SWAY_FOREIGN_TOPLEVEL_LIST_VERSION); server->foreign_toplevel_manager = wlr_foreign_toplevel_manager_v1_create(server->wl_display); sway_session_lock_init(); #if WLR_HAS_DRM_BACKEND 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 { sway_log(SWAY_DEBUG, "Failed to create wlr_drm_lease_device_v1"); sway_log(SWAY_INFO, "VR will not be available"); } #endif server->export_dmabuf_manager_v1 = wlr_export_dmabuf_manager_v1_create(server->wl_display); server->screencopy_manager_v1 = wlr_screencopy_manager_v1_create(server->wl_display); server->ext_image_copy_capture_manager_v1 = wlr_ext_image_copy_capture_manager_v1_create(server->wl_display, 1); wlr_ext_output_image_capture_source_manager_v1_create(server->wl_display, 1); server->wlr_data_control_manager_v1 = wlr_data_control_manager_v1_create(server->wl_display); server->ext_data_control_manager_v1 = wlr_ext_data_control_manager_v1_create(server->wl_display, 1); server->security_context_manager_v1 = wlr_security_context_manager_v1_create(server->wl_display); wlr_viewporter_create(server->wl_display); wlr_single_pixel_buffer_manager_v1_create(server->wl_display); server->content_type_manager_v1 = wlr_content_type_manager_v1_create(server->wl_display, 1); wlr_fractional_scale_manager_v1_create(server->wl_display, 1); server->ext_foreign_toplevel_image_capture_source_manager_v1 = wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(server->wl_display, 1); server->new_foreign_toplevel_capture_request.notify = handle_new_foreign_toplevel_capture_request; wl_signal_add(&server->ext_foreign_toplevel_image_capture_source_manager_v1->events.new_request, &server->new_foreign_toplevel_capture_request); server->tearing_control_v1 = wlr_tearing_control_manager_v1_create(server->wl_display, 1); server->tearing_control_new_object.notify = handle_new_tearing_hint; wl_signal_add(&server->tearing_control_v1->events.new_object, &server->tearing_control_new_object); wl_list_init(&server->tearing_controllers); struct wlr_xdg_foreign_registry *foreign_registry = wlr_xdg_foreign_registry_create(server->wl_display); wlr_xdg_foreign_v1_create(server->wl_display, foreign_registry); wlr_xdg_foreign_v2_create(server->wl_display, foreign_registry); server->xdg_activation_v1 = wlr_xdg_activation_v1_create(server->wl_display); server->xdg_activation_v1_request_activate.notify = xdg_activation_v1_handle_request_activate; wl_signal_add(&server->xdg_activation_v1->events.request_activate, &server->xdg_activation_v1_request_activate); server->xdg_activation_v1_new_token.notify = xdg_activation_v1_handle_new_token; wl_signal_add(&server->xdg_activation_v1->events.new_token, &server->xdg_activation_v1_new_token); struct wlr_xdg_toplevel_tag_manager_v1 *xdg_toplevel_tag_manager_v1 = wlr_xdg_toplevel_tag_manager_v1_create(server->wl_display, 1); server->xdg_toplevel_tag_manager_v1_set_tag.notify = xdg_toplevel_tag_manager_v1_handle_set_tag; wl_signal_add(&xdg_toplevel_tag_manager_v1->events.set_tag, &server->xdg_toplevel_tag_manager_v1_set_tag); struct wlr_cursor_shape_manager_v1 *cursor_shape_manager = wlr_cursor_shape_manager_v1_create(server->wl_display, 2); server->request_set_cursor_shape.notify = handle_request_set_cursor_shape; wl_signal_add(&cursor_shape_manager->events.request_set_shape, &server->request_set_cursor_shape); if (server->renderer->features.input_color_transform) { const enum wp_color_manager_v1_render_intent render_intents[] = { WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL, }; size_t transfer_functions_len = 0; enum wp_color_manager_v1_transfer_function *transfer_functions = wlr_color_manager_v1_transfer_function_list_from_renderer(server->renderer, &transfer_functions_len); size_t primaries_len = 0; enum wp_color_manager_v1_primaries *primaries = wlr_color_manager_v1_primaries_list_from_renderer(server->renderer, &primaries_len); struct wlr_color_manager_v1 *cm = wlr_color_manager_v1_create( server->wl_display, 2, &(struct wlr_color_manager_v1_options){ .features = { .parametric = true, .set_mastering_display_primaries = true, }, .render_intents = render_intents, .render_intents_len = sizeof(render_intents) / sizeof(render_intents[0]), .transfer_functions = transfer_functions, .transfer_functions_len = transfer_functions_len, .primaries = primaries, .primaries_len = primaries_len, }); free(transfer_functions); free(primaries); wlr_scene_set_color_manager_v1(root->root_scene, cm); } wlr_color_representation_manager_v1_create_with_renderer( server->wl_display, 1, server->renderer); wl_list_init(&server->pending_launcher_ctxs); // Avoid using "wayland-0" as display socket char name_candidate[16]; for (unsigned int i = 1; i <= 32; ++i) { snprintf(name_candidate, sizeof(name_candidate), "wayland-%u", i); if (wl_display_add_socket(server->wl_display, name_candidate) >= 0) { server->socket = strdup(name_candidate); break; } } if (!server->socket) { sway_log(SWAY_ERROR, "Unable to open wayland socket"); wlr_backend_destroy(server->backend); return false; } server->headless_backend = wlr_headless_backend_create(server->wl_event_loop); if (!server->headless_backend) { sway_log(SWAY_ERROR, "Failed to create secondary headless backend"); wlr_backend_destroy(server->backend); return false; } else { wlr_multi_backend_add(server->backend, server->headless_backend); } struct wlr_output *wlr_output = wlr_headless_add_output(server->headless_backend, 800, 600); wlr_output_set_name(wlr_output, "FALLBACK"); root->fallback_output = output_create(wlr_output); // This may have been set already via -Dtxn-timeout if (!server->txn_timeout_ms) { server->txn_timeout_ms = 200; } server->dirty_nodes = create_list(); server->input = input_manager_create(server); input_manager_get_default_seat(); // create seat0 return true; } void server_fini(struct sway_server *server) { // remove listeners wl_list_remove(&server->renderer_lost.link); wl_list_remove(&server->new_output.link); wl_list_remove(&server->layer_shell_surface.link); wl_list_remove(&server->xdg_shell_toplevel.link); wl_list_remove(&server->server_decoration.link); wl_list_remove(&server->xdg_decoration.link); wl_list_remove(&server->pointer_constraint.link); wl_list_remove(&server->output_manager_apply.link); wl_list_remove(&server->output_manager_test.link); wl_list_remove(&server->output_power_manager_set_mode.link); #if WLR_HAS_DRM_BACKEND if (server->drm_lease_manager) { wl_list_remove(&server->drm_lease_request.link); } #endif wl_list_remove(&server->tearing_control_new_object.link); wl_list_remove(&server->xdg_activation_v1_request_activate.link); wl_list_remove(&server->xdg_activation_v1_new_token.link); wl_list_remove(&server->xdg_toplevel_tag_manager_v1_set_tag.link); wl_list_remove(&server->request_set_cursor_shape.link); wl_list_remove(&server->new_foreign_toplevel_capture_request.link); input_manager_finish(server->input); // TODO: free sway-specific resources #if WLR_HAS_XWAYLAND if (server->xwayland.wlr_xwayland != NULL) { wl_list_remove(&server->xwayland_surface.link); wl_list_remove(&server->xwayland_ready.link); wlr_xwayland_destroy(server->xwayland.wlr_xwayland); } #endif wl_display_destroy_clients(server->wl_display); wlr_backend_destroy(server->backend); wl_display_destroy(server->wl_display); list_free(server->dirty_nodes); free(server->socket); } bool server_start(struct sway_server *server) { #if WLR_HAS_XWAYLAND if (config->xwayland != XWAYLAND_MODE_DISABLED) { sway_log(SWAY_DEBUG, "Initializing Xwayland (lazy=%d)", config->xwayland == XWAYLAND_MODE_LAZY); server->xwayland.wlr_xwayland = wlr_xwayland_create(server->wl_display, server->compositor, config->xwayland == XWAYLAND_MODE_LAZY); if (!server->xwayland.wlr_xwayland) { sway_log(SWAY_ERROR, "Failed to start Xwayland"); unsetenv("DISPLAY"); } else { wl_signal_add(&server->xwayland.wlr_xwayland->events.new_surface, &server->xwayland_surface); server->xwayland_surface.notify = handle_xwayland_surface; wl_signal_add(&server->xwayland.wlr_xwayland->events.ready, &server->xwayland_ready); server->xwayland_ready.notify = handle_xwayland_ready; setenv("DISPLAY", server->xwayland.wlr_xwayland->display_name, true); /* xcursor configured by the default seat */ } } #endif if (config->primary_selection) { wlr_primary_selection_v1_device_manager_create(server->wl_display); } sway_log(SWAY_INFO, "Starting backend on wayland display '%s'", server->socket); if (!wlr_backend_start(server->backend)) { sway_log(SWAY_ERROR, "Failed to start backend"); wlr_backend_destroy(server->backend); return false; } return true; } void server_run(struct sway_server *server) { sway_log(SWAY_INFO, "Running compositor on wayland display '%s'", server->socket); wl_display_run(server->wl_display); } ================================================ FILE: sway/sway-bar.5.scd ================================================ sway-bar(5) # NAME sway-bar - bar configuration file and commands # DESCRIPTION Sway allows configuring swaybar in the sway configuration file. # COMMANDS The following commands may only be used in the configuration file. *id* Sets the ID of the bar. *swaybar_command* Executes custom bar command. Default is _swaybar_. The following commands may be used either in the configuration file or at runtime. *bindcode* [--release] Executes _command_ when the mouse button has been pressed (or if _released_ is given, when the button has been released). The buttons can be given as an event code, which can be obtaining from *libinput debug-events*. To disable the default behavior for a button, use the command _nop_. *bindsym* [--release] button[1-9]| Executes _command_ when the mouse button has been pressed (or if _released_ is given, when the button has been released). The buttons can be given as a x11 button number or an event name, which can be obtained from *libinput debug-events*. To disable the default behavior for a button, use the command _nop_. *binding_mode_indicator* yes|no Enable or disable binding mode indicator. Default is _yes_. *font* Specifies the font to be used in the bar. _font_ should be specified as a pango font description. For more information on pango font descriptions, see https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html#description *gaps* | | Sets the gaps from the edge of the screen for the bar. Gaps can either be set all at once, per direction, or per side. Note that only sides that touch an edge of the screen can have gaps. For the side that does not touch an edge of the screen, per-side outer gaps for workspaces may be of use. *height* Sets the height of the bar. Default height (0) will match the font size. *hidden_state* hide|show [] Specifies the behaviour of the bar when it is in _hide_ mode. When the hidden state is _hide_, then it is normally hidden, and only unhidden by pressing the modifier key or in case of urgency hints. When the hidden state is _show_, then it is permanently visible, drawn on top of the currently visible workspace. Default is _hide_. For compatibility with i3, _bar hidden_state hide|show []_ is supported along with the sway only _bar hidden_state hide|show_ syntax. When using the i3 syntax, if _bar-id_ is omitted, the hidden_state will be changed for all bars. Attempting to use _bar hidden_state hide|show _ will result in an error due to conflicting bar ids. *mode* dock|hide|invisible|overlay [] Specifies the visibility of the bar. In _dock_ mode, it is permanently visible at one edge of the screen. In _hide_ mode, it is hidden unless the modifier key is pressed, though this behaviour depends on the hidden state. In _invisible_ mode, it is permanently hidden. In _overlay_ mode, it is permanently visible on top of other windows. (In _overlay_ mode the bar is transparent to input events.) Default is _dock_. For compatibility with i3, _bar mode []_ syntax is supported along with the sway only _bar mode _ syntax. When using the i3 syntax, if _bar-id_ is omitted, the mode will be changed for all bars. Attempting to use _bar mode _ will result in an error due to conflicting bar ids. *modifier* |none Specifies the modifier key that shows a hidden bar. Default is _Mod4_. *output* |\* Restrict the bar to a certain output, can be specified multiple times. If the output command is omitted, the bar will be displayed on all outputs. _\*_ can be given at any point to reset it back to all outputs. *pango_markup* enabled|disabled Enables or disables pango markup for status lines. This has no effect on status lines using the i3bar JSON protocol. *position* top|bottom Sets position of the bar. Default is _bottom_. *separator_symbol* Specifies the separator symbol to separate blocks on the bar. *status_command* Executes the bar _status command_ with _sh -c_. Each line of text printed to stdout from this command will be displayed in the status area of the bar. You may also use swaybar's JSON status line protocol. See *swaybar-protocol*(7) for more information on the protocol If running this command via IPC, you can disable a running status command by setting the command to a single dash: _swaybar bar bar-0 status\_command -_ *status_edge_padding* Sets the padding that is used when the status line is at the right edge of the bar. This value will be multiplied by the output scale. The default is _3_. *status_padding* Sets the vertical padding that is used for the status line. The default is _1_. If _padding_ is _0_, blocks will be able to take up the full height of the bar. This value will be multiplied by the output scale. *strip_workspace_name* yes|no If set to _yes_, then workspace names will be omitted from the workspace button and only the custom number will be shown. Default is _no_. *strip_workspace_numbers* yes|no If set to _yes_, then workspace numbers will be omitted from the workspace button and only the custom name will be shown. Default is _no_. *unbindcode* [--release] Removes the binding with the given . *unbindsym* [--release] button[1-9]| Removes the binding with the given