wlmaker-0.8.1/ 0000755 0001751 0001751 00000000000 15222270710 012566 5 ustar runner runner wlmaker-0.8.1/STYLE.md 0000644 0001751 0001751 00000005477 15222270710 014025 0 ustar runner runner # CMake Style Guidelines
This document outlines the style conventions used in the CMake configuration files for this project.
## 1. Command and Function Casing
* **Lowercase commands:** All built-in CMake commands and functions are written in lowercase (e.g., `cmake_minimum_required`, `set`, `add_executable`, `target_link_libraries`, `install`).
## 2. Indentation, Control Flow, and Line Wrapping
* **Indentation:** 2-space indentation is used for commands inside control blocks (like `if` / `endif`).
* **Control Flow:** Closing commands (e.g., `endif()`, `endforeach()`, `endmacro()`, `endfunction()`) must not repeat the condition or arguments of the opening command (e.g., use `endif()` instead of `endif(config_DEBUG)`).
* **Argument Wrapping:**
* Short, simple commands are kept on a single line (e.g., `set(CMAKE_C_STANDARD 11)`).
* Long commands or those with multiple arguments/keywords are wrapped, with each argument placed on a new line and indented by 2 spaces relative to the command (e.g., `add_executable`, `target_include_directories`).
* The closing parenthesis `)` is either placed on the same line as the last argument or on its own line aligned with the command name (usually in block-style commands like `install` or `set_target_properties`).
## 3. Naming Conventions
* **Local and private variables:** Written in uppercase in legacy project code (e.g., `PUBLIC_HEADER_FILES`, `SOURCES`, `DOXYGEN_IN`), but official guidance recommends using **lowercase** or **snake_case** (e.g., `public_header_files`, `sources`) to distinguish them from CMake's built-in variables.
* **Options/Cache variables:** Formatted using either a lowercase/mixed-case prefix (e.g., `config_DEBUG`, `config_WERROR`) or full uppercase (e.g., `IWYU_MODE`).
## 4. Quoting
* **Variable expansion and paths:** Double quotes are used for file paths, generator expressions, and strings containing variable expansion (e.g., `"${CMAKE_CURRENT_BINARY_DIR}/analyzer.c"`, `"${CURSES_INCLUDE_DIRS}"`).
* **Identifiers and keywords:** Unquoted for target names, libraries, command keywords, and constant literals (e.g., `libbase`, `STATIC`, `PUBLIC`, `FATAL_ERROR`).
## 5. File Header and Comments
* **Headers:** Every CMake file begins with a standardized Apache 2.0 license and copyright header.
* **Comments:** Standard `#` comments are placed above the relevant block or line. Inline comments are generally lowercase-first or capitalized, providing short descriptions.
## 6. Target-Based Configuration
* **Prefer target-based configuration:** Define compile options, compile definitions, include directories, and link libraries on specific targets using `target_*` commands (e.g., `target_include_directories`, `target_compile_options`, `target_compile_definitions`, `target_link_libraries`) rather than modifying global/directory-wide settings (e.g., `add_compile_options`).
wlmaker-0.8.1/README.md 0000644 0001751 0001751 00000007311 15222270710 014047 0 ustar runner runner #
WaylandMaker - wlmaker
A [Wayland](https://wayland.freedesktop.org/) compositor inspired by
[Window Maker](https://www.windowmaker.org/).
Key features:
* Compositor for windows in stacking mode.
* Supports multiple workspaces.
* Appearance inspired by Window Maker, following the look and feel of NeXTSTEP.
* Easy to use, lightweight, low gimmicks and fast.
* Dock and clip, to be extended for dockable apps.
How it looks, running in a window using the default theme:

### Current status
**Early access**: Wayland Maker covers elementary compositor functionality on single-monitor output. Please report what's missing or broken!
See [here](doc/FEATURES.md) for a detailed list of implemented or planned
features, or the [roadmap](doc/ROADMAP.md) for what's planned for the upcoming
versions.
Highlights for current version ([0.8.1](https://github.com/phkaeser/wlmaker/releases/tag/v0.8.1)):
* *new* Bugfixes ([#509](https://github.com/phkaeser/wlmaker/issues/509), [#508](https://github.com/phkaeser/wlmaker/issues/508), [#504](https://github.com/phkaeser/wlmaker/issues/504))
* Support to change Theme while running, shown in the root menu.
* Window placement.
* DockApp (`wlmbattery`) to show laptop battery and power status.
* Improved Touchpad support: Configuration options, and right-click emulation.
* Various bug fixes ([#449](https://github.com/phkaeser/wlmaker/issues/449), [#475](https://github.com/phkaeser/wlmaker/issues/475), [495](https://github.com/phkaeser/wlmaker/issues/495)).
* Builds with [wlroots 0.18, 0.19 and 0.20](https://gitlab.freedesktop.org/wlroots/wlroots/-/tags).
* Support for multiple outputs, configuable through `wlr-output-management-unstable-v1` protocol (eg. `wlr-randr`, `wdisplays`).
* [Root menu](https://phkaeser.github.io/wlmaker/root_menu.html) can be generated from XDG repository ([#90](https://github.com/phkaeser/wlmaker/issues/90), eg. `wlmtool` or `wmmenugen`), and can be navigated by keyboard.
* Screen saver support, through `ext-session-lock-v1` and `idle-inhibit-unstable-v1` protocols.
* Hot corners with configurable actions, default to 'lock' or 'inhibit' locking.
* Configurable through plist text files: [base configuration](etc/Config.plist),
[style](share/Themes/Default.plist), [root menu](etc/RootMenu.plist) and
[docks & workspaces](etc/State.plist).
* wlr layer shell support (`wlr-layer-shell-unstable-v1`), fully implemented & tested.
* Appearance matches Window Maker: Decorations, dock, clip.
* Support for Wayland XDG shell (mostly complete. Bug reports welcome).
* Initial support for X11 applications (positioning and specific modes are missing).
Use `--start_xwayland` argument to enable XWayland, it's off by default.
* DockApps (`apps/wlmclock`, `apps/wlmbattery`, `apps/wlmcpugraph`, ...).
### All the details?
See http://phkaeser.github.io/wlmaker for:
* How to configure wlmaker.
* Startup options.
* Supported Wayland protocols.
### Build & use it!
* From source: Please follow the [detailed build instructions](doc/BUILD.md)
for a step-by-step guide.
* Once compiled, see the [these instructions](doc/RUN.md) on how to run
Wayland Maker in a window or standalone, and to configure it for your needs.
* Alternatively, use a pre-built package:
[](https://repology.org/project/wlmaker/versions)
## Contributing
Contributions, help and bug reports are highly welcome! See
[`CONTRIBUTING.md`](CONTRIBUTING.md) for details, and
[code of conduct](CODE_OF_CONDUCT.md) for more.
## License
Apache 2.0; see [`LICENSE`](LICENSE) for details.
wlmaker-0.8.1/third_party/ 0000755 0001751 0001751 00000000000 15222270710 015117 5 ustar runner runner wlmaker-0.8.1/third_party/protocols/ 0000755 0001751 0001751 00000000000 15222270710 017143 5 ustar runner runner wlmaker-0.8.1/third_party/protocols/wlr-layer-shell-unstable-v1.xml 0000644 0001751 0001751 00000044036 15222270710 025056 0 ustar runner runner
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.
wlmaker-0.8.1/third_party/protocols/CMakeLists.txt 0000644 0001751 0001751 00000003221 15222270710 021701 0 ustar runner runner # Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cmake_minimum_required(VERSION 3.13)
find_program(
WAYLAND_SCANNER_EXECUTABLE
NAMES wayland-scanner
REQUIRED)
if(NOT WAYLAND_SCANNER_EXECUTABLE)
message(FATAL_ERROR "'wayland-scanner' executable not found.")
endif()
pkg_get_variable(protocol_dir wayland-protocols pkgdatadir)
add_custom_command(
OUTPUT wlr-layer-shell-unstable-v1-protocol.h
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${CMAKE_CURRENT_SOURCE_DIR}/../protocols/wlr-layer-shell-unstable-v1.xml" "wlr-layer-shell-unstable-v1-protocol.h"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../protocols/wlr-layer-shell-unstable-v1.xml"
VERBATIM)
add_custom_command(
OUTPUT xdg-shell-protocol.h
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${protocol_dir}/stable/xdg-shell/xdg-shell.xml" "xdg-shell-protocol.h"
DEPENDS "${protocol_dir}/stable/xdg-shell/xdg-shell.xml"
VERBATIM)
add_library(
protocol_headers
OBJECT
wlr-layer-shell-unstable-v1-protocol.h
xdg-shell-protocol.h)
set_target_properties(
protocol_headers PROPERTIES
LINKER_LANGUAGE C)
wlmaker-0.8.1/build-cscope-index.sh 0000755 0001751 0001751 00000001034 15222270710 016601 0 ustar runner runner #! /bin/sh
# Generates Cscope index for wlmaker and all dependencies.
set -o errexit
SUBPATHS="\
dependencies \
include \
src \
submodules"
base_path="$(readlink -f "$(dirname "${0}")")"
rm -f "${base_path}/cscope.files"
for p in ${SUBPATHS} ; do
echo "Processing ${base_path}/${p} ..."
find "${base_path}/${p}" -name "*.h" -o -name "*.c" -o -name "*.cpp" | xargs etags
find "${base_path}/${p}" -name "*.h" -o -name "*.c" -o -name "*.cpp" >> cscope.files
done
cscope -Rbkq -i cscope.files 2>/dev/null
echo "Done."
exit 0
wlmaker-0.8.1/cmake/ 0000755 0001751 0001751 00000000000 15222270710 013646 5 ustar runner runner wlmaker-0.8.1/cmake/embed_binary.h.in 0000644 0001751 0001751 00000002325 15222270710 017046 0 ustar runner runner /* ========================================================================= */
/**
* @file @output_header@
* Generated from "@binary_file@"
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __EMBEDDED_@prefix_uppercase@_H__
#define __EMBEDDED_@prefix_uppercase@_H__
#include
#include
/** Embedded content of "@binary_file@". */
extern const uint8_t embedded_binary_@prefix@_data[];
/** Size of "@binary_file@". */
extern const size_t embedded_binary_@prefix@_size;
#endif // __EMBEDDED_@prefix_uppercase@_H__
/* == End of @output_header ================================================ */
wlmaker-0.8.1/cmake/EmbedBinary.cmake 0000644 0001751 0001751 00000004635 15222270710 017041 0 ustar runner runner # Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cmake_minimum_required(VERSION 3.13)
# During the INCLUDE() call, CMAKE_CURRENT_LIST_DIR is this file's path.
# Store it for later, during the FUNCTION() call it'll be the caller's path.
set(embed_binary_template_dir "${CMAKE_CURRENT_LIST_DIR}")
# Adds a static C library to embed the binary file with the specified prefix.
#
# Generates a C source and header file from the contents of `binary_file`, and
# adds both of these to a new library target `library_target`.
#
# The embedded data can then be accessed as external variables:
#
# #include ".h"
#
# * uint8_t embedded_binary__data[];
# * const size_t embedded_binary__size;
#
# Declared with lowercase name according to coding style.
function(embedbinary_add_library library_target prefix binary_file)
set(output_basename "${CMAKE_CURRENT_BINARY_DIR}/${prefix}")
set(output_source "${output_basename}.c")
set(output_header "${output_basename}.h")
set(generated_cmake "${output_basename}.cmake")
set(template_header "${embed_binary_template_dir}/embed_binary.h.in")
set(template_source "${embed_binary_template_dir}/embed_binary.c.in")
configure_file(
"${embed_binary_template_dir}/embed.cmake.in"
"${generated_cmake}"
@ONLY)
add_custom_command(
OUTPUT "${output_source}" "${output_header}"
COMMAND "${CMAKE_COMMAND}" -P "${generated_cmake}"
MAIN_DEPENDENCY "${binary_file}"
DEPENDS "${generated_cmake}" "${template_header}" "${template_source}"
)
add_library(${library_target} STATIC)
target_sources(
${library_target} PRIVATE
"${output_source}"
"${output_header}")
target_include_directories(
${library_target} PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")
set_target_properties(
${library_target}
PROPERTIES VERSION 1.0
PUBLIC_HEADER "${output_header}")
endfunction()
wlmaker-0.8.1/cmake/embed_binary.c.in 0000644 0001751 0001751 00000002054 15222270710 017040 0 ustar runner runner /* ========================================================================= */
/**
* @file @output_source@
* Generated from "@binary_file@"
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "@output_header@"
const uint8_t embedded_binary_@prefix@_data[] = {@output_data@};
const size_t embedded_binary_@prefix@_size = sizeof(
embedded_binary_@prefix@_data);
/* == End of @output_source ================================================ */
wlmaker-0.8.1/cmake/WaylandProtocol.cmake 0000644 0001751 0001751 00000007040 15222270710 017772 0 ustar runner runner # Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
find_program(
WaylandScanner_EXECUTABLE
NAMES wayland-scanner)
mark_as_advanced(WaylandScanner_EXECUTABLE)
find_package_handle_standard_args(
WaylandScanner
FOUND_VAR WaylandScanner_FOUND
REQUIRED_VARS WaylandScanner_EXECUTABLE)
# -----------------------------------------------------------------------------
# Builds a library for the protocol, and adds as dependency to target_var.
function(waylandprotocol_add target_var)
if(NOT WaylandScanner_EXECUTABLE)
message(FATAL_ERROR "'wayland-scanner' executable required, not found.")
endif()
# Parse and verify arguments.
set(one_value_args PROTOCOL_FILE BASE_NAME SIDE)
cmake_parse_arguments(args "" "${one_value_args}" "" ${ARGN})
if(NOT "${args_SIDE}" STREQUAL "client" AND NOT "${args_SIDE}" STREQUAL "server")
message(FATAL_ERROR "SIDE arg must be \"client\" or \"server\".")
endif()
if(args_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "Unknown args passed to _wayland_protocol_add: \"${args_UNPARSED_ARGUMENTS}\"")
endif()
get_filename_component(_protocol_file "${args_PROTOCOL_FILE}" ABSOLUTE)
# Generate the client header.
set(_header "${CMAKE_CURRENT_BINARY_DIR}/${args_BASE_NAME}-${args_SIDE}-protocol.h")
set_source_files_properties("${_header}" GENERATED)
add_custom_command(
OUTPUT "${_header}"
COMMAND "${WaylandScanner_EXECUTABLE}" "${args_SIDE}-header" "${_protocol_file}" "${_header}"
DEPENDS "${WaylandScanner_EXECUTABLE}" "${_protocol_file}"
VERBATIM)
# Generate the glue code.
set(_glue_code "${CMAKE_CURRENT_BINARY_DIR}/${args_BASE_NAME}-protocol.c")
set_source_files_properties("${_glue_code}" GENERATED)
add_custom_command(
OUTPUT "${_glue_code}"
COMMAND "${WaylandScanner_EXECUTABLE}" private-code "${_protocol_file}" "${_glue_code}"
DEPENDS "${WaylandScanner_EXECUTABLE}" "${_protocol_file}"
VERBATIM)
set(lib_name "lib-${args_BASE_NAME}-${args_SIDE}")
add_library("${lib_name}" STATIC)
add_dependencies("${target_var}" "${lib_name}")
target_sources("${lib_name}" PRIVATE "${_glue_code}" "${_header}")
set_target_properties("${lib_name}" PROPERTIES VERSION 1.0 PUBLIC_HEADER "${_header}")
# Add dependencies.
if("${args_SIDE}" STREQUAL "client")
pkg_check_modules(WAYLAND_CLIENT REQUIRED IMPORTED_TARGET wayland-client>=1.22.0)
target_include_directories(
"${lib_name}" PRIVATE
"${WAYLAND_CLIENT_INCLUDE_DIRS}")
else()
pkg_check_modules(WAYLAND_SERVER REQUIRED IMPORTED_TARGET wayland-server>=1.22.0)
target_include_directories(
"${lib_name}" PRIVATE
"${WAYLAND_SERVER_INCLUDE_DIRS}")
endif()
# The target may be an INTERFACE library. That needs INTERFACE linking.
get_property(target_type TARGET "${target_var}" PROPERTY TYPE)
if(target_type STREQUAL "INTERFACE_LIBRARY")
target_link_libraries(
"${target_var}"
INTERFACE
"${lib_name}")
else()
target_link_libraries(
"${target_var}"
"${lib_name}")
endif()
endfunction()
wlmaker-0.8.1/cmake/embed.cmake.in 0000644 0001751 0001751 00000002665 15222270710 016342 0 ustar runner runner # -*- cmake -*-
# Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
file(READ "@binary_file@" binary_content HEX)
string(REGEX MATCHALL "([A-Fa-f0-9][A-Fa-f0-9])" separated_content "${binary_content}")
set(prefix "@prefix@")
string(TOUPPER "@prefix@" prefix_uppercase)
set(binary_file "@binary_file@")
set(output_header "@output_header@")
set(output_source "@output_source@")
set(output_data "")
set(bytes 0)
foreach(token IN LISTS separated_content)
if(NOT bytes EQUAL 0)
string(APPEND output_data ", ")
endif()
math(EXPR line_pos "${bytes} & 0xF")
if(line_pos EQUAL 0)
string(APPEND output_data "\n ")
endif()
string(APPEND output_data "0x${token}")
math(EXPR bytes "${bytes} + 1")
endforeach()
configure_file("@template_header@" "@output_header@" @ONLY)
configure_file("@template_source@" "@output_source@" @ONLY)
file(WRITE "@OUTPUT_BASENAME@.c" "${output}")
wlmaker-0.8.1/.gitmodules 0000644 0001751 0001751 00000001013 15222270710 014736 0 ustar runner runner [submodule "submodules/libbase"]
path = submodules/libbase
url = https://github.com/phkaeser/libbase.git
branch = main
update = rebase
[submodule "examples/gtk-layer-shell"]
path = examples/gtk-layer-shell
url = https://github.com/wmww/gtk-layer-shell.git
[submodule "dependencies"]
path = dependencies
url = https://github.com/phkaeser/wlmaker-dependencies.git
[submodule "submodules/inih"]
path = submodules/inih
url = https://github.com/benhoyt/inih.git
wlmaker-0.8.1/RELEASE.md 0000644 0001751 0001751 00000001135 15222270710 014170 0 ustar runner runner # Instructions for releasing wlmaker
* Update [CMakeLists.txt]:
* Set a new `VERSION` in the `PROJECT` directive.
* Update [doc/ROADMAP.md]:
* Updates the roadmap's section title to a link to the new release tag.
* Update [README.md]:
* Update section with highlights for the current version.
* Remove the `*new*` prefix from the earlier version's highlights.
* On https://github.com/phkaeser/wlmaker/releases, *Draft a new release*
* Create a new version tag, in format `v.`
* Update the release title to `v. - `
Pending: Generate release notes.
wlmaker-0.8.1/tool/ 0000755 0001751 0001751 00000000000 15222270710 013543 5 ustar runner runner wlmaker-0.8.1/tool/menu.h 0000644 0001751 0001751 00000003656 15222270710 014672 0 ustar runner runner /* ========================================================================= */
/**
* @file menu.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright (c) 2026 Google LLC and Philipp Kaeser
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __WLMAKER_MENU_H__
#define __WLMAKER_MENU_H__
#include
#include
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Generates a menu for the Themes files found at XDG data directories.
*
* @param path_ptr Optional: Path to read from. If NULL, use the XDG
* data directories.
*
* @return A Plist array, or NULL on error.
*/
bspl_array_t *wlmtool_menu_generate_appearance(const char *path_ptr);
/**
* Generates an Applications menu from parsed `.desktop` files.
*
* @param path_ptr Optional: Path to read from. If NULL, use the XDG
* data directories.
* @param locale_ptr The locale for LC_MESSAGES, or NULL.
*
* @return a Plist array, or NULL on error.
*/
bspl_array_t *wlmtool_menu_generate_applications(
const char *path_ptr,
const char *locale_ptr);
/** Unit tests for the menu generator. */
extern const bs_test_set_t wlmtool_menu_test_set;
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif // __WLMAKER_MENU_H__
/* == End of menu.h ======================================================== */
wlmaker-0.8.1/tool/item.h 0000644 0001751 0001751 00000006366 15222270710 014665 0 ustar runner runner /* ========================================================================= */
/**
* @file item.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright (c) 2026 Google LLC and Philipp Kaeser
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __WLMAKER_ITEM_H__
#define __WLMAKER_ITEM_H__
#include
#include
#include
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/* == Declarations ========================================================= */
struct wlmtool_item;
struct wlmtool_menu;
/** Destroys the item. */
void wlmtool_item_destroy(struct wlmtool_item *item_ptr);
/** Returns a plist array representing the item (or menu). */
bspl_array_t *wlmtool_item_create_array(struct wlmtool_item *item_ptr);
/**
* Creates a menu.
*
* @param title_ptr
*
* @return The menu, or NULL on failure. To release associates, the function
* @ref wlmtool_item_destroy must be called, on the menu's item
* (@see wlmtool_item_from_menu).
*/
struct wlmtool_menu *wlmtool_menu_create(const char *title_ptr);
/** @return the parent item handle for the menu. */
struct wlmtool_item *wlmtool_item_from_menu(struct wlmtool_menu *menu_ptr);
/**
* Adds an item to this menu.
*
* @param menu_ptr
* @param item_ptr The item to add. The menu will take ownership.
*
* @return true on success.
*/
bool wlmtool_menu_add_item(
struct wlmtool_menu *menu_ptr,
struct wlmtool_item *item_ptr);
/**
* Obtain (or create) the submenu with the provided key within a menu.
*
* @param menu_ptr
* @param key_ptr
*
* @return The submenu, or NULL on error.
*/
struct wlmtool_menu *wlmtool_menu_get_or_create_submenu(
struct wlmtool_menu *menu_ptr,
const void *key_ptr);
/**
* Creates a "file action" menu item.
*
* @param title_ptr Title to use for the menu entry.
* @param action_ptr Action command to use.
* @param resolved_path_ptr Fully resolved path to the file to act on.
*
* @return Menu item, or NULL on error.
*/
struct wlmtool_item *wlmtool_file_action_create(
const char *title_ptr,
const char *action_ptr,
const char *resolved_path_ptr);
/**
* Adds a menu entry.
*
* @param key_ptr Key that is expected unique within the submenu.
* @param args_ptr NULL-terminted array to build the entry from.
*
* @return Menu item, or NULL on error.
*/
struct wlmtool_item *wlmtool_entry_create(
const char *key_ptr,
const char **args_ptr);
/** Unit tests for the menu item classes. */
extern const bs_test_set_t wlmtool_item_test_set;
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif // __WLMAKER_ITEM_H__
/* == End of item.h =========================================================== */
wlmaker-0.8.1/tool/wlmtool.c 0000644 0001751 0001751 00000016454 15222270710 015416 0 ustar runner runner /* ========================================================================= */
/**
* @file wlmtool.c
*
* Command-line tool for Wayland Maker.
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright (c) 2026 Google LLC and Philipp Kaeser
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include
#include
#include
#include
#include
#include
#include
#include "menu.h"
/* == Declarations ========================================================= */
static bool print_version(int argc, const char **argv);
static bool print_help(int argc, const char **argv);
static bool generate_applications_menu(int argc, const char **argv);
static bool generate_themes_menu(int argc, const char **argv);
#if !defined(WLMAKER_VERSION_MAJOR) || !defined(WLMAKER_VERSION_MINOR) || !defined(WLMAKER_VERSION_FULL)
#eror "WLMAKER_VERSION_... not defined!"
#else
static const char *wlmaker_version_major = WLMAKER_VERSION_MAJOR;
static const char *wlmaker_version_minor = WLMAKER_VERSION_MINOR;
static const char *wlmaker_version_full = WLMAKER_VERSION_FULL;
#endif
/** Command descriptor. */
struct command_desc {
/** Command that can be invoked. */
const char *command_ptr;
/** Description of the command. */
const char *description_ptr;
/** The operation executed by the command. */
bool (*op)(int argc, const char **argv);
};
/** Locale, when specified as `--locale` argument to the commandline. */
static char *wlmtool_locale_ptr = NULL;
/* == Data ================================================================= */
/** Definition of commandline arguments. */
static const bs_arg_t wlmtool_args[] = {
BS_ARG_STRING(
"locale",
"Optional: Override the locale for generating the menu. Uses the "
"environment's setting for LC_MESSAGES, if not set.",
NULL,
&wlmtool_locale_ptr),
BS_ARG_SENTINEL()
};
/** List of available commands. */
static const struct command_desc commands[] = {
{
.command_ptr = "genmenu",
.description_ptr =
"Generates the \"Applications\" menu for Wayland Maker, in .plist "
"text format. DEPRECATED. Use \"GenerateApplicationsMenu\" instead.",
.op = generate_applications_menu
},
{
.command_ptr = "GenerateApplicationsMenu",
.description_ptr =
"Generates the \"Applications\" menu for Wayland Maker, in .plist "
"text format.",
.op = generate_applications_menu
},
{
.command_ptr = "GenerateThemesMenu",
.description_ptr =
"Generates the \"Themes\" menu for Wayland Maker, in .plist text "
"format.",
.op = generate_themes_menu
},
{
.command_ptr = "--help",
.description_ptr = "Prints usage information.",
.op = print_help
},
{
.command_ptr = "--version",
.description_ptr = "Prints version information.",
.op = print_version
},
{} // Sentinel.
};
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/** Prints version information. */
bool print_version(__UNUSED__ int argc, __UNUSED__ const char **argv)
{
fprintf(stdout, "Wayland Maker wlmtool version %s.%s (%s)\n",
wlmaker_version_major,
wlmaker_version_minor,
wlmaker_version_full);
return true;
}
/* ------------------------------------------------------------------------- */
/** Prints help. */
bool print_help(__UNUSED__ int argc, __UNUSED__ const char **argv)
{
fprintf(stdout, "Wayland Maker wlmtool version %s.%s (%s)\n",
wlmaker_version_major,
wlmaker_version_minor,
wlmaker_version_full);
fprintf(stderr, "\nAvailable commands and options:\n");
for (const struct command_desc *d = commands; d->command_ptr; ++d) {
fprintf(stdout, "%s: %s\n", d->command_ptr, d->description_ptr);
}
bs_arg_print_usage(stdout, wlmtool_args);
return true;
}
/* ------------------------------------------------------------------------- */
/** Generates the the "Applications" section for the root menu. */
bool generate_applications_menu(int argc, const char **argv)
{
const char *path_ptr = NULL;
if (1 > argc) {
fprintf(stderr, "Usage: wlmtool GenerateApplicationsMenu [PATH]\n");
return false;
} else if (2 <= argc) {
path_ptr = argv[1];
}
bspl_array_t *array_ptr = wlmtool_menu_generate_applications(
path_ptr, wlmtool_locale_ptr);
if (NULL == array_ptr) return false;
bs_dynbuf_t buf = {};
bool rv = bs_dynbuf_init(&buf, 1024, SIZE_MAX);
if (rv) {
rv = bspl_object_write(bspl_object_from_array(array_ptr), &buf);
if (rv) fprintf(stdout, "%.*s", (int)buf.length, (char*)buf.data_ptr);
}
bspl_array_unref(array_ptr);
bs_dynbuf_fini(&buf);
return rv;
}
/* ------------------------------------------------------------------------- */
/** Generates the the "Appearance" menu from Themes plist files. */
bool generate_themes_menu(__UNUSED__ int argc, __UNUSED__ const char **argv)
{
bspl_array_t *array_ptr = NULL;
if (1 >= argc) {
array_ptr = wlmtool_menu_generate_appearance(NULL);
} else {
array_ptr = wlmtool_menu_generate_appearance(argv[1]);
}
if (NULL == array_ptr) return false;
bs_dynbuf_t buf = {};
bool rv = bs_dynbuf_init(&buf, 1024, SIZE_MAX);
if (rv) {
rv = bspl_object_write(bspl_object_from_array(array_ptr), &buf);
if (rv) fprintf(stdout, "%.*s", (int)buf.length, (char*)buf.data_ptr);
}
bspl_array_unref(array_ptr);
bs_dynbuf_fini(&buf);
return rv;
}
/* == Main program ========================================================= */
/** The main program. */
int main(int argc, const char **argv)
{
if (!bs_arg_parse(wlmtool_args, BS_ARG_MODE_EXTRA_ARGS, &argc, argv)) {
fprintf(stderr, "Failed to parse commandline arguments.\n");
bs_arg_print_usage(stderr, wlmtool_args);
return EXIT_FAILURE;
}
if (1 < argc) {
for (const struct command_desc *d = commands; d->command_ptr; ++d) {
if (0 == strcmp(argv[1], d->command_ptr)) {
return d->op(argc - 1, argv + 1) ? EXIT_SUCCESS : EXIT_FAILURE;
}
}
fprintf(stderr, "Unknown command: %s.\n", argv[1]);
} else {
fprintf(stderr, "Missing command.\n");
}
fprintf(stderr, "\nAvailable commands:\n");
for (const struct command_desc *d = commands; d->command_ptr; ++d) {
fprintf(stderr, "%s: %s\n", d->command_ptr, d->description_ptr);
}
bs_arg_print_usage(stderr, wlmtool_args);
return EXIT_FAILURE;
}
/* == End of wlmtool.c ===================================================== */
wlmaker-0.8.1/tool/item.c 0000644 0001751 0001751 00000037104 15222270710 014652 0 ustar runner runner /* ========================================================================= */
/**
* @file item.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright (c) 2026 Google LLC and Philipp Kaeser
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "item.h"
#include
#include
#include
#include
/* == Declarations ========================================================= */
/** Abstract definition of a menu item. Can hold a submenu, or a file item. */
struct wlmtool_item {
/** To prevent duplicate entries. Node of @ref wlmtool_menu::tree_ptr. */
bs_avltree_node_t avlnode;
/** Item of @ref wlmtool_menu::items. */
bs_dllist_node_t dlnode;
/** Key used for @ref wlmtool_menu::tree_ptr. */
const char *key_ptr;
/** Item title, used for sorting in the double-linked list. */
const char *title_ptr;
/** Dtor for this item. */
void (*destroy)(struct wlmtool_item *item_ptr);
/** Constructs an array from this item. */
bspl_array_t *(*create_array)(struct wlmtool_item *item_ptr);
};
/** Menu item: A menu. */
struct wlmtool_menu {
/** Parent class: An item. */
struct wlmtool_item menu_item;
/** To disambiguate duplicate entries, and to lookup by key. */
bs_avltree_t *tree_ptr;
/** Items, through @ref wlmtool_item::dlnode. Defines the order. */
bs_dllist_t items;
/** The menu's title. */
bspl_string_t *title_ptr;
};
/** Menu item. */
struct wlmtool_entry {
/** Parent class: An item. */
struct wlmtool_item menu_item;
/** Plist array describing the item: (Title, Action, ...). */
bspl_array_t *array_ptr;
/** Duplicate of the lookup key to use. To take ownership.*/
char *key_ptr;
};
static void _wlmtool_item_destroy_dlnode(
bs_dllist_node_t *dlnode_ptr,
void *ud_ptr);
static int _wlmtool_item_cmp(
const bs_avltree_node_t *node_ptr,
const void *key_ptr);
static int _wlmtool_item_dlnode_cmp(
const bs_dllist_node_t *n1,
const bs_dllist_node_t *n2);
static bool _wlmtool_item_dlnode_add_to_array(
bs_dllist_node_t *dlnode_ptr,
void *ud_ptr);
static void _wlmtool_menu_destroy_item(struct wlmtool_item *item_ptr);
static bspl_array_t *_wlmtool_menu_create_array(
struct wlmtool_item *item_ptr);
static void _wlmtool_entry_destroy(struct wlmtool_item *item_ptr);
static bspl_array_t *_wlmtool_entry_create_array(
struct wlmtool_item *item_ptr);
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
void wlmtool_item_destroy(struct wlmtool_item *item_ptr)
{
item_ptr->destroy(item_ptr);
}
/* ------------------------------------------------------------------------- */
bspl_array_t *wlmtool_item_create_array(struct wlmtool_item *item_ptr)
{
return item_ptr->create_array(item_ptr);
}
/* ------------------------------------------------------------------------- */
struct wlmtool_menu *wlmtool_menu_create(const char *title_ptr)
{
struct wlmtool_menu *menu_ptr = logged_calloc(1, sizeof(*menu_ptr));
if (NULL == menu_ptr) return NULL;
menu_ptr->menu_item.destroy = _wlmtool_menu_destroy_item;
menu_ptr->menu_item.create_array = _wlmtool_menu_create_array;
menu_ptr->tree_ptr = bs_avltree_create(_wlmtool_item_cmp, NULL);
if (NULL == menu_ptr->tree_ptr) {
wlmtool_item_destroy(&menu_ptr->menu_item);
return NULL;
}
menu_ptr->title_ptr = bspl_string_create(title_ptr);
if (NULL == menu_ptr->title_ptr) {
wlmtool_item_destroy(&menu_ptr->menu_item);
return NULL;
}
menu_ptr->menu_item.key_ptr = bspl_string_value(menu_ptr->title_ptr);
menu_ptr->menu_item.title_ptr = bspl_string_value(menu_ptr->title_ptr);
return menu_ptr;
}
/* ------------------------------------------------------------------------- */
struct wlmtool_item *wlmtool_item_from_menu(struct wlmtool_menu *menu_ptr)
{
return &menu_ptr->menu_item;
}
/* ------------------------------------------------------------------------- */
/** Adds an item to the menu. */
bool wlmtool_menu_add_item(
struct wlmtool_menu *menu_ptr,
struct wlmtool_item *item_ptr)
{
if (!bs_avltree_insert(menu_ptr->tree_ptr,
item_ptr->key_ptr,
&item_ptr->avlnode,
false)) return false;
bs_dllist_push_back(&menu_ptr->items, &item_ptr->dlnode);
return true;
}
/* ------------------------------------------------------------------------- */
/** Returns a submenu for key, if it exists. If not, create it. */
struct wlmtool_menu *wlmtool_menu_get_or_create_submenu(
struct wlmtool_menu *menu_ptr,
const void *key_ptr)
{
bs_avltree_node_t *a_ptr = bs_avltree_lookup(menu_ptr->tree_ptr, key_ptr);
if (NULL != a_ptr) {
return BS_CONTAINER_OF(a_ptr, struct wlmtool_menu, menu_item.avlnode);
}
struct wlmtool_menu *submenu_ptr = wlmtool_menu_create(key_ptr);
if (NULL == submenu_ptr) return NULL;
if (!wlmtool_menu_add_item(menu_ptr, &submenu_ptr->menu_item)) {
bs_log(BS_ERROR, "Failed to add category \"%s\"",
(const char*)key_ptr);
wlmtool_item_destroy(&submenu_ptr->menu_item);
return NULL;
}
return submenu_ptr;
}
/* ------------------------------------------------------------------------- */
struct wlmtool_item *wlmtool_file_action_create(
const char *title_ptr,
const char *action_ptr,
const char *resolved_path_ptr)
{
const char *args[4] = {
[0] = title_ptr,
[1] = action_ptr,
[2] = resolved_path_ptr,
[3] = NULL
};
return wlmtool_entry_create(resolved_path_ptr, args);
}
/* ------------------------------------------------------------------------- */
struct wlmtool_item *wlmtool_entry_create(
const char *key_ptr,
const char **args_ptr)
{
if (NULL == args_ptr || NULL == *args_ptr) return NULL;
struct wlmtool_entry *entry_ptr = logged_calloc(1, sizeof(*entry_ptr));
if (NULL == entry_ptr) return NULL;
entry_ptr->menu_item.destroy = _wlmtool_entry_destroy;
entry_ptr->menu_item.create_array = _wlmtool_entry_create_array;
entry_ptr->array_ptr = bspl_array_create();
if (NULL == entry_ptr->array_ptr) goto error;
for (const char **i_pp = args_ptr; NULL != *i_pp; ++i_pp) {
bspl_string_t *s = bspl_string_create(*i_pp);
if (NULL == s) goto error;
if (!bspl_array_push_back(entry_ptr->array_ptr,
bspl_object_from_string(s))) goto error;
if (*i_pp == *args_ptr) {
entry_ptr->menu_item.title_ptr = bspl_string_value(s);
}
if (key_ptr == *i_pp) {
entry_ptr->menu_item.key_ptr = bspl_string_value(s);
}
bspl_string_unref(s);
}
if (NULL == entry_ptr->menu_item.key_ptr) {
entry_ptr->key_ptr = logged_strdup(key_ptr);
if (NULL == entry_ptr->key_ptr) goto error;
entry_ptr->menu_item.key_ptr = entry_ptr->key_ptr;
}
return &entry_ptr->menu_item;
error:
_wlmtool_entry_destroy(&entry_ptr->menu_item);
return NULL;
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/** Callback for bs_dllist_each(), destroy the item at `dlnode_ptr`. */
void _wlmtool_item_destroy_dlnode(
bs_dllist_node_t *dlnode_ptr,
__UNUSED__ void *ud_ptr)
{
struct wlmtool_item *item_ptr = BS_CONTAINER_OF(
dlnode_ptr, struct wlmtool_item, dlnode);
item_ptr->destroy(item_ptr);
}
/* ------------------------------------------------------------------------- */
/** Comparator for the AVL tree. */
int _wlmtool_item_cmp(
const bs_avltree_node_t *node_ptr,
const void *key_ptr)
{
struct wlmtool_item *item_ptr = BS_CONTAINER_OF(
node_ptr, struct wlmtool_item, avlnode);
return strcmp(item_ptr->key_ptr, key_ptr);
}
/* ------------------------------------------------------------------------- */
/** Comparator for the double-linked list. */
int _wlmtool_item_dlnode_cmp(
const bs_dllist_node_t *n1,
const bs_dllist_node_t *n2)
{
struct wlmtool_item *i1 = BS_CONTAINER_OF(n1, struct wlmtool_item, dlnode);
struct wlmtool_item *i2 = BS_CONTAINER_OF(n2, struct wlmtool_item, dlnode);
return strcmp(i1->title_ptr, i2->title_ptr);
}
/* ------------------------------------------------------------------------- */
/** Creates array from item at `dlnode_ptr` and adds to array at `ud_ptr`. */
bool _wlmtool_item_dlnode_add_to_array(
bs_dllist_node_t *dlnode_ptr,
void *ud_ptr)
{
struct wlmtool_item *item_ptr = BS_CONTAINER_OF(
dlnode_ptr, struct wlmtool_item, dlnode);
bspl_array_t *parent_array_ptr = ud_ptr;
bspl_array_t *array_ptr = item_ptr->create_array(item_ptr);
if (NULL == array_ptr) return false;
bspl_array_push_back(parent_array_ptr, bspl_object_from_array(array_ptr));
bspl_array_unref(array_ptr);
return true;
}
/* ------------------------------------------------------------------------- */
/** Implements @ref wlmtool_item::destroy. */
void _wlmtool_menu_destroy_item(struct wlmtool_item *item_ptr)
{
struct wlmtool_menu *menu_ptr = BS_CONTAINER_OF(
item_ptr, struct wlmtool_menu, menu_item);
if (NULL != menu_ptr->title_ptr) {
bspl_string_unref(menu_ptr->title_ptr);
menu_ptr->title_ptr = NULL;
}
if (NULL != menu_ptr->tree_ptr) {
bs_avltree_destroy(menu_ptr->tree_ptr);
menu_ptr->tree_ptr = NULL;
}
bs_dllist_for_each(&menu_ptr->items, _wlmtool_item_destroy_dlnode, NULL);
free(menu_ptr);
}
/* ------------------------------------------------------------------------- */
/**
* Creates a plist array representing the menu and it's items.
*
* @note The menu's items will be sorted, meaning: will change order.
*
* @param item_ptr
*
* @return the plist array, or NULL on error.
*/
bspl_array_t *_wlmtool_menu_create_array(struct wlmtool_item *item_ptr)
{
struct wlmtool_menu *menu_ptr = BS_CONTAINER_OF(
item_ptr, struct wlmtool_menu, menu_item);
bspl_array_t *array_ptr = bspl_array_create();
if (NULL == array_ptr) return NULL;
bspl_array_push_back(
array_ptr, bspl_object_from_string(menu_ptr->title_ptr));
bs_dllist_sort(&menu_ptr->items, _wlmtool_item_dlnode_cmp);
if (bs_dllist_all(
&menu_ptr->items,
_wlmtool_item_dlnode_add_to_array,
array_ptr)) return array_ptr;
bs_log(BS_ERROR, "Failed to create plist array from menu \"%s\"",
bspl_string_value(menu_ptr->title_ptr));
bspl_array_unref(array_ptr);
return NULL;
}
/* ------------------------------------------------------------------------- */
/** Implements @ref wlmtool_item::destroy. Dtor for the entry. */
void _wlmtool_entry_destroy(struct wlmtool_item *item_ptr)
{
struct wlmtool_entry *entry_ptr = BS_CONTAINER_OF(
item_ptr, struct wlmtool_entry, menu_item);
if (NULL != entry_ptr->array_ptr) {
bspl_array_unref(entry_ptr->array_ptr);
entry_ptr->array_ptr = NULL;
}
if (NULL != entry_ptr->key_ptr) {
free(entry_ptr->key_ptr);
entry_ptr->key_ptr = NULL;
}
free(entry_ptr);
}
/* ------------------------------------------------------------------------- */
/** @return A plist array for the entry, or NULL on error. */
bspl_array_t *_wlmtool_entry_create_array(
struct wlmtool_item *item_ptr)
{
struct wlmtool_entry *entry_ptr = BS_CONTAINER_OF(
item_ptr, struct wlmtool_entry, menu_item);
return bspl_array_ref(entry_ptr->array_ptr);
}
/* == Unit Tests =========================================================== */
static void _wlmaker_item_test_create(bs_test_t *test_ptr);
static void _wlmaker_item_test_array(bs_test_t *test_ptr);
/** Test cases for the menu item classes. */
static const bs_test_case_t _wlmtool_item_test_cases[] = {
{ true, "create", _wlmaker_item_test_create },
{ true, "array", _wlmaker_item_test_array },
BS_TEST_CASE_SENTINEL(),
};
const bs_test_set_t wlmtool_item_test_set = BS_TEST_SET(
true, "item", _wlmtool_item_test_cases);
/* ------------------------------------------------------------------------- */
/** Tests item creation and destruction. */
void _wlmaker_item_test_create(bs_test_t *test_ptr)
{
struct wlmtool_menu *menu_ptr = wlmtool_menu_create("Menu");
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, menu_ptr);
struct wlmtool_menu *submenu_ptr = wlmtool_menu_get_or_create_submenu(
menu_ptr, "Submenu");
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, submenu_ptr);
// Second "get_or_create" must return the same.
BS_TEST_VERIFY_EQ(
test_ptr,
submenu_ptr,
wlmtool_menu_get_or_create_submenu(menu_ptr, "Submenu"));
struct wlmtool_item *item_ptr = wlmtool_file_action_create("a", "b", "c");
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, item_ptr);
BS_TEST_VERIFY_TRUE(test_ptr, wlmtool_menu_add_item(menu_ptr, item_ptr));
// Second addition with same "file" must fail.
item_ptr = wlmtool_file_action_create("x", "y", "c");
BS_TEST_VERIFY_FALSE_OR_RETURN(
test_ptr, wlmtool_menu_add_item(menu_ptr, item_ptr));
wlmtool_item_destroy(item_ptr);
wlmtool_item_destroy(wlmtool_item_from_menu(menu_ptr));
}
/* ------------------------------------------------------------------------- */
/** Tests plist array creation. */
void _wlmaker_item_test_array(bs_test_t *test_ptr)
{
struct wlmtool_menu *m = wlmtool_menu_create("Menu");
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, m);
struct wlmtool_item *i0 = wlmtool_file_action_create("a0", "b0", "c0");
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, i0);
struct wlmtool_item *i1 = wlmtool_file_action_create("a1", "b1", "c1");
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, i1);
BS_TEST_VERIFY_TRUE(test_ptr, wlmtool_menu_add_item(m, i0));
BS_TEST_VERIFY_TRUE(test_ptr, wlmtool_menu_add_item(m, i1));
bspl_array_t *a = wlmtool_item_create_array(wlmtool_item_from_menu(m));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, a);
BS_TEST_VERIFY_STREQ(test_ptr, "Menu", bspl_array_string_value_at(a, 0));
bspl_array_t *ai = bspl_array_from_object(bspl_array_at(a, 1));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ai);
BS_TEST_VERIFY_STREQ(test_ptr, "a0", bspl_array_string_value_at(ai, 0));
BS_TEST_VERIFY_STREQ(test_ptr, "b0", bspl_array_string_value_at(ai, 1));
BS_TEST_VERIFY_STREQ(test_ptr, "c0", bspl_array_string_value_at(ai, 2));
ai = bspl_array_from_object(bspl_array_at(a, 2));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ai);
BS_TEST_VERIFY_STREQ(test_ptr, "a1", bspl_array_string_value_at(ai, 0));
BS_TEST_VERIFY_STREQ(test_ptr, "b1", bspl_array_string_value_at(ai, 1));
BS_TEST_VERIFY_STREQ(test_ptr, "c1", bspl_array_string_value_at(ai, 2));
bspl_array_unref(a);
wlmtool_item_destroy(wlmtool_item_from_menu(m));
}
/* == End of item.c ======================================================== */
wlmaker-0.8.1/tool/menu.c 0000644 0001751 0001751 00000045077 15222270710 014670 0 ustar runner runner /* ========================================================================= */
/**
* @file menu.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright (c) 2026 Google LLC and Philipp Kaeser
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "menu.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "item.h"
/* == Declarations ========================================================= */
struct wlmtool_menu;
static bool _wlmtool_menu_add_themes_path(
struct wlmtool_menu *menu_ptr,
const char *path_ptr);
static bool _wlmtool_menu_add_themes_file(
const char *resolved_path_ptr,
const FTSENT *ftsent_ptr,
void *ud_ptr);
static bool _wlmtool_menu_add_desktop_path(
struct wlmtool_menu *menu_ptr,
const char *path_ptr,
const char *locale_ptr);
static bool _wlmtool_menu_add_desktop_file(
const char *resolved_path_ptr,
const FTSENT *ftsent_ptr,
void *ud_ptr);
static char *_wlmtool_menu_themes_name_from_plist_file(const char *path_ptr);
static const char *_wlmtool_menu_category_from_entry(
const struct desktop_entry *desktop_entry_ptr);
/** Lookup table for category names */
struct _category_translation {
/** Category that is used in a `.desktop` file. */
const char *desktop_category_ptr;
/** Category name to use in the menu. */
const char *menu_category_ptr;
};
/** Arguments to @ref _wlmtool_menu_add_desktop_file */
struct _wlmtool_menu_add_desktop_file_arg {
/** Parser for the `.desktop` files. */
struct desktop_parser *desktop_parser_ptr;
/** Root menu. */
struct wlmtool_menu *root_menu_ptr;
/** Chosen locale. */
const char *locale_ptr;
};
/* == Data ================================================================= */
/** Command to load a theme file. */
static const char* _wlmtool_action_load_theme_from_file = "ThemeLoadFromFile";
/** Recognized categories. */
// TODO(kaeser@gubbe.ch): Internationalize this.
static const struct _category_translation _wlmtool_menu_category_table[] = {
{ "AudioVideo", "Audio & Video" },
{ "Video", "Video" },
{ "Development", "Development" },
{ "Education", "Education" },
{ "Game", "Game" },
{ "Graphics", "Graphics" },
{ "Network", "Network" },
{ "Office", "Office" },
{ "Science", "Science" },
{ "Settings", "Settings" },
{ "System", "System" },
{ "Utility", "Utility" },
// Reserved categories.
{ "Screensaver", "Screensaver" },
{ "TrayIcon", "Tray Icon" },
{ "Applet", "Applet" },
{ "Shell", "Shell" },
{ NULL, NULL },
};
/** Category to use for any entry that is not categorized. */
// TODO(kaeser@gubbe.ch): Internationalize.
static const char *_wlmtool_menu_category_other_ptr = "Other";
/** Command to use for terminal. */
// TODO(kaeser@gubbe.ch): This should be configurable.
static const char *_wlmtool_menu_terminal_command = "/usr/bin/foot";
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
bspl_array_t *wlmtool_menu_generate_appearance(const char *path_ptr)
{
struct wlmtool_menu *root_menu_ptr = wlmtool_menu_create("Appearance");
if (NULL == root_menu_ptr) return NULL;
struct wlmtool_menu *menu_ptr = NULL;
menu_ptr = wlmtool_menu_get_or_create_submenu(
root_menu_ptr, "System Themes");
if (NULL == menu_ptr) {
wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr));
return NULL;
}
if (NULL != path_ptr) {
if (!_wlmtool_menu_add_themes_path(root_menu_ptr, path_ptr)) {
wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr));
return NULL;
}
} else {
xdgHandle xdg_handle;
if (NULL == xdgInitHandle(&xdg_handle)) {
wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr));
return NULL;
}
bs_ptr_set_t *dir_set_ptr = bs_ptr_set_create();
const char* const *dirs_ptr = xdgDataDirectories(&xdg_handle);
for (; NULL != dirs_ptr && NULL != *dirs_ptr; ++dirs_ptr) {
bs_ptr_set_insert(dir_set_ptr, (void*)*dirs_ptr);
char *p = bs_strdupf("%s/%s", *dirs_ptr, "wlmaker/Themes");
if (NULL == p) {
wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr));
return NULL;
}
bool rv = _wlmtool_menu_add_themes_path(menu_ptr, p);
free(p);
if (!rv) {
wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr));
return NULL;
}
}
menu_ptr = wlmtool_menu_get_or_create_submenu(
root_menu_ptr, "User Themes");
char *p = bs_strdupf("%s/%s", xdgDataHome(&xdg_handle), "wlmaker/Themes");
if (NULL == p) {
wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr));
return NULL;
}
bool rv = _wlmtool_menu_add_themes_path(menu_ptr, p);
free(p);
if (!rv) {
wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr));
return NULL;
}
menu_ptr = wlmtool_menu_get_or_create_submenu(
root_menu_ptr, "User Themes");
p = bs_strdupf("%s/%s", xdgConfigHome(&xdg_handle), "wlmaker/Themes");
if (NULL == p) {
wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr));
return NULL;
}
rv = _wlmtool_menu_add_themes_path(menu_ptr, p);
free(p);
if (!rv) {
wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr));
return NULL;
}
bs_ptr_set_destroy(dir_set_ptr);
xdgWipeHandle(&xdg_handle);
}
bspl_array_t *array_ptr = wlmtool_item_create_array(
wlmtool_item_from_menu(root_menu_ptr));
wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr));
return array_ptr;
}
/* ------------------------------------------------------------------------- */
bspl_array_t *wlmtool_menu_generate_applications(
const char *path_ptr,
const char *locale_ptr)
{
struct wlmtool_menu *menu_ptr = wlmtool_menu_create("Applications");
if (NULL == menu_ptr) return NULL;
if (NULL != path_ptr) {
if (!_wlmtool_menu_add_desktop_path(menu_ptr, path_ptr, locale_ptr)) {
wlmtool_item_destroy(wlmtool_item_from_menu(menu_ptr));
return NULL;
}
} else {
// No path provided. Iterate over XDG data directories.
xdgHandle xdg_handle;
if (NULL == xdgInitHandle(&xdg_handle)) return NULL;
const char* const *dirs_ptr = xdgSearchableDataDirectories(&xdg_handle);
for (; NULL != dirs_ptr && NULL != *dirs_ptr; ++dirs_ptr) {
char *p = bs_strdupf("%s/applications", *dirs_ptr);
if (NULL == p) return NULL;
bool rv = _wlmtool_menu_add_desktop_path(menu_ptr, p, locale_ptr);
if (!rv) {
bs_log(BS_ERROR, "Failed to add desktop files from %s", p);
}
free(p);
if (!rv) return NULL;
}
xdgWipeHandle(&xdg_handle);
}
bspl_array_t *array_ptr = wlmtool_item_create_array(
wlmtool_item_from_menu(menu_ptr));
wlmtool_item_destroy(wlmtool_item_from_menu(menu_ptr));
return array_ptr;
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/** Convenience wrapper to add themes file from `path_ptr` to `menu_ptr`. */
bool _wlmtool_menu_add_themes_path(
struct wlmtool_menu *menu_ptr,
const char *path_ptr)
{
if (bs_file_walk_tree(
path_ptr, "*.plist", S_IFREG, false,
_wlmtool_menu_add_themes_file, menu_ptr)) return true;
bs_log(BS_ERROR, "Failed to add Themes files from %s", path_ptr);
return false;
}
/* ------------------------------------------------------------------------- */
/** Adds a menu entry for the plist Themes file at `resolved_path_ptr`. */
bool _wlmtool_menu_add_themes_file(
const char *resolved_path_ptr,
__UNUSED__ const FTSENT *ftsent_ptr,
void *ud_ptr)
{
struct wlmtool_menu *menu_ptr = ud_ptr;
// Creates a "Theme" menu item from the plist file.
char *n = _wlmtool_menu_themes_name_from_plist_file(resolved_path_ptr);
if (NULL == n) return false;
struct wlmtool_item *item_ptr = wlmtool_file_action_create(
n, "ThemeLoadFromFile", resolved_path_ptr);
free(n);
if (NULL == item_ptr) return false;
if (wlmtool_menu_add_item(menu_ptr, item_ptr)) return true;
bs_log(BS_INFO, "Skipping already added file \"%s\"", resolved_path_ptr);
wlmtool_item_destroy(item_ptr);
return true;
}
/* ------------------------------------------------------------------------- */
/** Convenience wrapper to add desktop files from `path_ptr` to `menu_ptr`. */
bool _wlmtool_menu_add_desktop_path(
struct wlmtool_menu *menu_ptr,
const char *path_ptr,
const char *locale_ptr)
{
struct _wlmtool_menu_add_desktop_file_arg arg = {
.desktop_parser_ptr = desktop_parser_create(locale_ptr),
.root_menu_ptr = menu_ptr,
.locale_ptr = locale_ptr
};
if (NULL == arg.desktop_parser_ptr) return false;
bool rv = bs_file_walk_tree(
path_ptr, "*.desktop", S_IFREG, false,
_wlmtool_menu_add_desktop_file, &arg);
if (!rv) bs_log(BS_ERROR, "Failed to add desktop files from %s", path_ptr);
desktop_parser_destroy(arg.desktop_parser_ptr);
return rv;
}
/* ------------------------------------------------------------------------- */
/** Adds a menu entry for a desktop file. */
bool _wlmtool_menu_add_desktop_file(
const char *resolved_path_ptr,
__UNUSED__ const FTSENT *ftsent_ptr,
void *ud_ptr)
{
struct _wlmtool_menu_add_desktop_file_arg *arg_ptr = ud_ptr;
struct desktop_entry de = {};
char *cmd_ptr = NULL;
int error_line = desktop_parser_file_to_entry(
arg_ptr->desktop_parser_ptr,
resolved_path_ptr,
&de);
if (0 != error_line) {
bs_log(BS_WARNING,
"Failed desktop_parser_file_to_entry(%p, %s, %p) at line %d",
arg_ptr->desktop_parser_ptr,
resolved_path_ptr,
&de, error_line);
return false;
}
if (DESKTOP_ENTRY_TYPE_APPLICATION != de.type ||
de.no_display ||
de.hidden) {
desktop_parser_entry_release(&de);
return true;
}
const char *c = _wlmtool_menu_category_from_entry(&de);
if (NULL == c) {
bs_log(BS_WARNING, "Failed to lookup category for %s",
resolved_path_ptr);
goto error;
}
struct wlmtool_menu *cat_menu_ptr = wlmtool_menu_get_or_create_submenu(
arg_ptr->root_menu_ptr, c);
if (NULL == cat_menu_ptr) goto error;
if (NULL == de.try_exec_ptr && NULL == de.exec_ptr) {
bs_log(BS_WARNING, "Missing 'Exec' and 'TryExec' in desktop file %s",
resolved_path_ptr);
goto error;
}
if (de.terminal) {
cmd_ptr = bs_strdupf(
"%s %s",
_wlmtool_menu_terminal_command,
de.try_exec_ptr ? de.try_exec_ptr : de.exec_ptr);
} else {
cmd_ptr = logged_strdup(
de.try_exec_ptr ? de.try_exec_ptr : de.exec_ptr);
}
if (NULL == cmd_ptr) goto error;
if (NULL == de.name_ptr) {
bs_log(BS_WARNING, "Missing 'Name' in desktop file %s",
resolved_path_ptr);
goto error;
}
const char *a[] = { de.name_ptr, "ShellExecute", cmd_ptr, NULL };
struct wlmtool_item *item_ptr = wlmtool_entry_create(resolved_path_ptr, a);
if (NULL == item_ptr) goto error;
if (!wlmtool_menu_add_item(cat_menu_ptr, item_ptr)) {
bs_log(BS_WARNING, "Duplicate? Failed to add entry for %s",
resolved_path_ptr);
goto error;
}
free(cmd_ptr);
desktop_parser_entry_release(&de);
return true;
error:
if (NULL != cmd_ptr) free(cmd_ptr);
desktop_parser_entry_release(&de);
return false;
}
/* ------------------------------------------------------------------------- */
/**
* Returns a string holding name to show in the menu for the file action.
*
* This will attempt to load the file as a PList, and extract a string value
* keyed "Name" from the toplevel dict. If `path_ptr` is not a plist file, or
* does not hold a Plist dict as toplevel object, it returns NULL.
* If there is no "Name" entry in the toplevel dict, the function returns the
* basename of `path_ptr`.
*
* @param path_ptr
*
* @return An allocated string, or NULL on error. The allocated string must be
* released by calling free().
*/
char *_wlmtool_menu_themes_name_from_plist_file(const char *path_ptr)
{
// Load the plist, and lookup the value of "Name" in the toplevel dict.
bspl_object_t *object_ptr = bspl_create_object_from_plist_file(path_ptr);
if (NULL == object_ptr) return NULL;
bspl_dict_t *dict_ptr = bspl_dict_from_object(object_ptr);
if (NULL == dict_ptr) {
bs_log(BS_WARNING, "Not a Plist dict in %s", path_ptr);
bspl_object_unref(object_ptr);
return NULL;
}
char *name_ptr = NULL;
const char *plist_name_ptr = bspl_dict_get_string_value(dict_ptr, "Name");
if (NULL != plist_name_ptr) name_ptr = logged_strdup(plist_name_ptr);
bspl_object_unref(object_ptr);
if (NULL != name_ptr) return name_ptr;
// No such value in the plist file. Use the file's basename.
char *p = logged_strdup(path_ptr);
if (NULL == p) return false;
char *basename_ptr = basename(p);
name_ptr = logged_strdup(basename_ptr);
free(p);
return name_ptr;
}
/* ------------------------------------------------------------------------- */
/** Looks up the category name for a desktop's file menu entry. */
const char *_wlmtool_menu_category_from_entry(
const struct desktop_entry *desktop_entry_ptr)
{
if (NULL != desktop_entry_ptr->category_ptrs) {
for (char **c = desktop_entry_ptr->category_ptrs; *c; ++c) {
for (const struct _category_translation *ct =
&_wlmtool_menu_category_table[0];
ct->desktop_category_ptr != NULL;
++ct) {
if (0 == strcmp(*c, ct->desktop_category_ptr)) {
return ct->menu_category_ptr;
}
}
}
}
return _wlmtool_menu_category_other_ptr;
}
/* == Unit Tests =========================================================== */
static void _wlmtool_menu_test_generate_themes(bs_test_t *test_ptr);
static void _wlmtool_menu_test_generate_applications(bs_test_t *test_ptr);
/** Test cases for the menu generator. */
static const bs_test_case_t _wlmtool_menu_test_cases[] = {
{ true, "generate_themes", _wlmtool_menu_test_generate_themes },
{ true, "applications", _wlmtool_menu_test_generate_applications },
BS_TEST_CASE_SENTINEL(),
};
const bs_test_set_t wlmtool_menu_test_set = BS_TEST_SET(
true, "menu", _wlmtool_menu_test_cases);
/* ------------------------------------------------------------------------- */
/** Verifies themes menu generation from the test files. */
void _wlmtool_menu_test_generate_themes(bs_test_t *test_ptr)
{
struct wlmtool_menu *m = wlmtool_menu_create("Appearance");
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, m);
const char *p = bs_test_data_path(test_ptr, "Themes");
BS_TEST_VERIFY_TRUE(test_ptr, _wlmtool_menu_add_themes_path(m, p));
bspl_array_t *a = wlmtool_item_create_array(wlmtool_item_from_menu(m));
wlmtool_item_destroy(wlmtool_item_from_menu(m));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, a);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, a);
BS_TEST_VERIFY_EQ(test_ptr, 3, bspl_array_size(a));
BS_TEST_VERIFY_STREQ(test_ptr, "Appearance", bspl_array_string_value_at(a, 0));
const char *load = _wlmtool_action_load_theme_from_file;
bspl_array_t *aa = bspl_array_from_object(bspl_array_at(a, 1));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, aa);
BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 3, bspl_array_size(aa));
BS_TEST_VERIFY_STREQ(test_ptr, "ThemeA", bspl_array_string_value_at(aa, 0));
BS_TEST_VERIFY_STREQ(test_ptr, load, bspl_array_string_value_at(aa, 1));
aa = bspl_array_from_object(bspl_array_at(a, 2));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, aa);
BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 3, bspl_array_size(aa));
BS_TEST_VERIFY_STREQ(test_ptr, "ThemeB", bspl_array_string_value_at(aa, 0));
BS_TEST_VERIFY_STREQ(test_ptr, load, bspl_array_string_value_at(aa, 1));
bspl_array_unref(a);
}
/* ------------------------------------------------------------------------- */
/** Tests generation of the 'Applications' menu. */
void _wlmtool_menu_test_generate_applications(bs_test_t *test_ptr)
{
const char *p = bs_test_data_path(test_ptr, "%s", "");
bspl_array_t *a = wlmtool_menu_generate_applications(p, NULL);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, a);
BS_TEST_VERIFY_EQ(test_ptr, 2, bspl_array_size(a));
BS_TEST_VERIFY_STREQ(
test_ptr, "Applications", bspl_array_string_value_at(a, 0));
bspl_array_t *aa = bspl_array_from_object(bspl_array_at(a, 1));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, aa);
BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 2, bspl_array_size(aa));
BS_TEST_VERIFY_STREQ(
test_ptr, "System", bspl_array_string_value_at(aa, 0));
bspl_array_t *aaa = bspl_array_from_object(bspl_array_at(aa, 1));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, aaa);
BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 3, bspl_array_size(aaa));
BS_TEST_VERIFY_STREQ(
test_ptr, "WaylandMaker", bspl_array_string_value_at(aaa, 0));
BS_TEST_VERIFY_STREQ(
test_ptr, "ShellExecute", bspl_array_string_value_at(aaa, 1));
BS_TEST_VERIFY_STREQ(
test_ptr, "/usr/bin/foot ./wlmaker",
bspl_array_string_value_at(aaa, 2));
bspl_array_unref(a);
}
/* == End of menu.c ======================================================== */
wlmaker-0.8.1/tool/desktop-parser/ 0000755 0001751 0001751 00000000000 15222270710 016506 5 ustar runner runner wlmaker-0.8.1/tool/desktop-parser/desktop-parser.c 0000644 0001751 0001751 00000101230 15222270710 021612 0 ustar runner runner /* ========================================================================= */
/**
* @file desktop-parser.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright (c) 2025 Google LLC and Philipp Kaeser
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "desktop-parser.h"
#include
#include
#include
#include
#include
#include
#include
/* == Declarations ========================================================= */
/** State for the DFA automaton. */
enum _dfa_state {
/** Normal mode. */
_DFA_STATE_NORMAL,
/** Escape mode, after a \ character. */
_DFA_STATE_ESCAPED,
/** Terminate. */
_DFA_STATE_HALT
};
/** Character classes for the DFA automaton. */
enum _dfa_class {
/** Backslash. */
_DFA_CLASS_BACKSLASH = 0,
/** Semicolon. */
_DFA_CLASS_SEMICOLON = 1,
/** NUL character. */
_DFA_CLASS_NUL = 2,
/** Anything else. */
_DFA_CLASS_REGULAR = 3,
};
/** Transition for the DFA automaton. */
struct _dfa_transition {
/** Next state. */
enum _dfa_state next_state;
/**
* Function to apply for the current input character.
*
* @param dest_ptr Current position in output string.
* @param dest_ptr Current input character.
*
* @return New position in output string.
*/
char *(*process)(char *dest_ptr, const char c);
};
/** Encapsulates both parser and entry as argument to the inih handler. */
struct _desktop_parser_handler_arg {
/** Parser. */
const struct desktop_parser *parser;
/** The entry to parse into. */
struct desktop_entry *entry_ptr;
};
static int _desktop_parser_handler(
void *userdata_ptr,
const char *section_ptr,
const char *name_ptr,
const char *value_ptr);
static char *_create_locale_key(const char *l, const char *t, const char *m);
static char *_dfa_next_strings_element(const char **word_ptr);
static char *_dfa_append_char(char *dest_ptr, const char c);
static char *_dfa_append_escaped(char *dest_ptr, const char c);
static char *_dfa_append_nul(char *dest_ptr, const char c);
static char *_dfa_append_bslash_nul(char *dest_ptr, const char c);
/** Parser handle. */
struct desktop_parser {
/** Describes a key entry, and extracts the optional localization key. */
regex_t key_regex;
/** Lookup keys for localized strings, ordered in decreasing priority. */
char *localization_key[4];
/** Length of each of the localized strings. */
size_t localization_key_len[4];
};
static bool _desktop_parser_translate_type(
const char *value_ptr,
void *dest_ptr);
static bool _desktop_parser_translate_boolean(
const char *value_ptr,
void *dest_ptr);
static bool _desktop_parser_translate_string(
const char *value_ptr,
void *dest_ptr);
static bool _desktop_parser_translate_strings(
const char *value_ptr,
void *dest_ptr);
static bool _desktop_parser_translate_exec(
const char *value_ptr,
void *dest_ptr);
static void _desktop_parser_destroy_string(void *dest_ptr);
static void _desktop_parser_destroy_strings(void *dest_ptr);
/** Descriptor for a key. */
struct key_descriptor {
/** The key's name. */
const char *key;
/** Length of the key's name. */
size_t len;
/**
* Offset of the value field, relative to the userdata.
*/
size_t ofs;
/**
* Offset of the locale priority indicator. Iff priority_ofs != ofs, the
* field is treated as localized.
*/
size_t priority_ofs;
/** Translator function. */
bool (*translate)(const char *value_ptr, void *dest_ptr);
/** Dtor. */
void (*destroy)(void *dest_ptr);
};
/* == Data ================================================================= */
/** Regular expression describing a (possibly localized) key. */
static const char *key_regex_str =
"^"
"([A-Za-z0-9-]+)"
"(\\[[a-z]{2,3}[a-zA-Z0-9_@\\.-]*\\])?"
"$";
/** Actual transition table for extracting one string from "string(s)". */
static const struct _dfa_transition _dfa_transition_table[3][4] = {
// Normal state.
{
{ _DFA_STATE_ESCAPED, NULL },
{ _DFA_STATE_HALT, _dfa_append_nul },
{ _DFA_STATE_HALT, _dfa_append_char },
{ _DFA_STATE_NORMAL, _dfa_append_char },
},
// Escaped state.
{
{ _DFA_STATE_NORMAL, _dfa_append_char },
{ _DFA_STATE_NORMAL, _dfa_append_char },
{ _DFA_STATE_HALT, _dfa_append_bslash_nul },
{ _DFA_STATE_NORMAL, _dfa_append_escaped },
},
// Halt. Nothing to do.
{
{ _DFA_STATE_HALT, NULL },
{ _DFA_STATE_HALT, NULL },
{ _DFA_STATE_HALT, NULL },
{ _DFA_STATE_HALT, NULL }
}
};
/** Group name for the application details in a `.desktop` file. */
static const char *desktop_entry_group_name = "Desktop Entry";
/** Supported keys. */
struct key_descriptor keys[] = {
{
.key = "Type",
.len = strlen("Type"),
.ofs = offsetof(struct desktop_entry, type),
.priority_ofs = offsetof(struct desktop_entry, type),
.destroy = NULL,
.translate = _desktop_parser_translate_type,
},
{
.key = "Hidden",
.len = strlen("Hidden"),
.ofs = offsetof(struct desktop_entry, hidden),
.priority_ofs = offsetof(struct desktop_entry, hidden),
.destroy = NULL,
.translate = _desktop_parser_translate_boolean,
},
{
.key = "NoDisplay",
.len = strlen("NoDisplay"),
.ofs = offsetof(struct desktop_entry, no_display),
.priority_ofs = offsetof(struct desktop_entry, no_display),
.destroy = NULL,
.translate = _desktop_parser_translate_boolean,
},
{
.key = "Terminal",
.len = strlen("Terminal"),
.ofs = offsetof(struct desktop_entry, terminal),
.priority_ofs = offsetof(struct desktop_entry, terminal),
.destroy = NULL,
.translate = _desktop_parser_translate_boolean,
},
{
.key = "Name",
.len = strlen("Name"),
.ofs = offsetof(struct desktop_entry, name_ptr),
.translate = _desktop_parser_translate_string,
.destroy = _desktop_parser_destroy_string,
.priority_ofs = offsetof(struct desktop_entry, name_priority),
},
{
.key = "Exec",
.len = strlen("Exec"),
.ofs = offsetof(struct desktop_entry, exec_ptr),
.priority_ofs = offsetof(struct desktop_entry, exec_ptr),
.destroy = _desktop_parser_destroy_string,
.translate = _desktop_parser_translate_exec
},
{
.key = "TryExec",
.len = strlen("TryExec"),
.ofs = offsetof(struct desktop_entry, try_exec_ptr),
.priority_ofs = offsetof(struct desktop_entry, try_exec_ptr),
.destroy = _desktop_parser_destroy_string,
.translate = _desktop_parser_translate_string
},
{
.key = "Path",
.len = strlen("Path"),
.ofs = offsetof(struct desktop_entry, path_ptr),
.priority_ofs = offsetof(struct desktop_entry, path_ptr),
.destroy = _desktop_parser_destroy_string,
.translate = _desktop_parser_translate_string
},
{
.key = "Categories",
.len = strlen("Categories"),
.ofs = offsetof(struct desktop_entry, category_ptrs),
.priority_ofs = offsetof(struct desktop_entry, category_ptrs),
.destroy = _desktop_parser_destroy_strings,
.translate = _desktop_parser_translate_strings
},
{ .key = NULL }
};
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
struct desktop_parser *desktop_parser_create(const char *locale_ptr)
{
struct desktop_parser *parser = calloc(1, sizeof(struct desktop_parser));
if (NULL == parser) return NULL;
if (0 != regcomp(&parser->key_regex, key_regex_str, REG_EXTENDED)) {
desktop_parser_destroy(parser);
return NULL;
}
if (NULL != locale_ptr && 0 != strcmp("C", locale_ptr)) {
// Splits the locale, see setlocale(3). The locale name is of the form
// language[_territory][.codeset][@modifier].
char *language_ptr = strdup(locale_ptr);
if (NULL == language_ptr) {
desktop_parser_destroy(parser);
return NULL;
}
language_ptr = strtok(language_ptr, "@");
char *modifier_ptr = strtok(NULL, "@");
language_ptr = strtok(language_ptr, ".");
strtok(NULL, "."); // Code set. Not used for desktop file key.
language_ptr = strtok(language_ptr, "_");
char *territory_ptr = strtok(NULL, "_");
// Matching order. See speficication in "Localized values for keys".
parser->localization_key[3] = _create_locale_key(
language_ptr, territory_ptr, modifier_ptr);
parser->localization_key[2] = _create_locale_key(
language_ptr, territory_ptr, NULL);
parser->localization_key[1] = _create_locale_key(
language_ptr, NULL, modifier_ptr);
parser->localization_key[0] = _create_locale_key(
language_ptr, NULL, NULL);
for (int i = 0; i < 4; ++i) {
char *c = parser->localization_key[i];
parser->localization_key_len[i] = c ? strlen(c) : 0;
}
free(language_ptr);
}
return parser;
}
/* ------------------------------------------------------------------------- */
void desktop_parser_destroy(struct desktop_parser *parser)
{
for (int i = 0; i < 4; ++i) {
if (NULL != parser->localization_key[i]) {
free(parser->localization_key[i]);
}
parser->localization_key[i] = NULL;
parser->localization_key_len[i] = 0;
}
regfree(&parser->key_regex);
free(parser);
}
/* ------------------------------------------------------------------------- */
int desktop_parser_file_to_entry(
const struct desktop_parser *parser,
const char *fname_ptr,
struct desktop_entry *entry_ptr)
{
struct _desktop_parser_handler_arg arg = {
.parser = parser, .entry_ptr = entry_ptr
};
return ini_parse(fname_ptr, _desktop_parser_handler, &arg);
}
/* ------------------------------------------------------------------------- */
int desktop_parser_string_to_entry(
const struct desktop_parser *parser,
const char *string_ptr,
struct desktop_entry *entry_ptr)
{
struct _desktop_parser_handler_arg arg = {
.parser = parser, .entry_ptr = entry_ptr
};
return ini_parse_string(string_ptr, _desktop_parser_handler, &arg);
}
/* ------------------------------------------------------------------------- */
void desktop_parser_entry_release(struct desktop_entry *entry_ptr)
{
for (const struct key_descriptor *key_ptr = &keys[0];
NULL != key_ptr->key;
++key_ptr) {
if (key_ptr->destroy) {
key_ptr->destroy((char*)entry_ptr + key_ptr->ofs);
}
}
*entry_ptr = (struct desktop_entry){};
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/**
* Callback handler for the inih parse methods. Parses "Desktop Entry".
*
* @param userdata_ptr
* @param section_ptr
* @param name_ptr
* @param value_ptr
*
* @return 0 on error, and 1 if the name/value was parsed (or skipped for
* legitimate reason).
*/
int _desktop_parser_handler(
void *userdata_ptr,
const char *section_ptr,
const char *name_ptr,
const char *value_ptr)
{
struct _desktop_parser_handler_arg *arg = userdata_ptr;
const struct desktop_parser *parser = arg->parser;
struct desktop_entry *entry_ptr = arg->entry_ptr;
// Skip groups other than the main entry. Remove type?
if (0 != strcmp(section_ptr, desktop_entry_group_name)) return 1;
// Verify that the key is valid, with optional locale index.
regmatch_t m[3];
if (0 != regexec(&parser->key_regex, name_ptr, 3, &m[0], 0)) return 0;
// If there is a LOCALE key, attempt to match it with the configured locale
// and priority. 0 indicates no LOCALE key, higher value is better match.
int priority = 0 < m[2].rm_so;
for (; 0 < priority && priority <= 4; ++priority) {
size_t len = parser->localization_key_len[priority - 1];
const char *lk = parser->localization_key[priority - 1];
if (0 < m[2].rm_eo - m[2].rm_so &&
len + 2 == (size_t)(m[2].rm_eo - m[2].rm_so) &&
0 == memcmp(lk, name_ptr + m[2].rm_so + 1, len)) break;
}
// 5 means we didn't have a match: Skip this key/value pair.
if (5 <= priority) return 1;
// Look for a matching key descriptor, then translate.
for (const struct key_descriptor *kd = &keys[0]; kd->key; ++kd) {
if (0 >= m[1].rm_eo - m[1].rm_so ||
kd->len != (size_t)(m[1].rm_eo - m[1].rm_so) ||
0 != memcmp(kd->key, name_ptr + m[1].rm_so, kd->len)) continue;
// Descriptor doesn't expect a LOCALE, but the key had one: Fail.
if (kd->priority_ofs == kd->ofs && 0 < priority) return 0;
// Don't overwrite higher priority or earlier values for same priority.
if (kd->priority_ofs != kd->ofs) {
int8_t stored_priority = ((int8_t*)entry_ptr)[kd->priority_ofs];
if (stored_priority & (1 << priority)) return 0;
((int8_t*)entry_ptr)[kd->priority_ofs] |= 1 << priority;
if (stored_priority > (1 << priority)) return 1; // Higher prio.
}
if (!kd->translate(value_ptr, (char*)entry_ptr+kd->ofs)) return 0;
return 1;
}
// Unknown key. Skip.
return 1;
}
/* ------------------------------------------------------------------------- */
/**
* Creates a lookup key for localization.
*
* @param l Language ("lang"). If NULL, no string is created.
* @param t Territory (Freedesktop speficication: "COUNTRY"), may be NULL.
* @param m Modifier ("MODIFIER"), may be NULL.
*
* @return Pointer to an allocated string, holding "lang[_COUNTRY][@MODIFIER]",
* or NULL on error, or NULL if `l` was NULL.
*/
char *_create_locale_key(const char *l, const char *t, const char *m)
{
if (!l) return NULL;
size_t len = strlen(l)+1 + (t ? strlen(t)+1 : 0) + (m ? strlen(m)+1 : 0);
char *key_ptr = malloc(len);
if (NULL == key_ptr) return NULL;
snprintf(key_ptr, len, "%s%s%s%s%s", l,
(t ? "_" : ""), (t ? t : ""),
(m ? "@" : ""), (m ? m : ""));
return key_ptr;
}
/* ------------------------------------------------------------------------- */
/**
* Translates the "Type" key into an enum deskop_entry_type.
*
* @param value_ptr
* @param dest_ptr
*
* @return true on success.
*/
bool _desktop_parser_translate_type(
const char *value_ptr,
void *dest_ptr)
{
enum desktop_entry_type *entry_type_ptr = dest_ptr;
if (0 == strcmp("Application", value_ptr)) {
*entry_type_ptr = DESKTOP_ENTRY_TYPE_APPLICATION;
return true;
} else if (0 == strcmp("Link", value_ptr)) {
*entry_type_ptr = DESKTOP_ENTRY_TYPE_LINK;
return true;
} else if (0 == strcmp("Directory", value_ptr)) {
*entry_type_ptr = DESKTOP_ENTRY_TYPE_DIRECTORY;
return true;
}
*entry_type_ptr = DESKTOP_ENTRY_TYPE_UNKNOWN;
return false;
}
/* ------------------------------------------------------------------------- */
/**
* Translates a boolean-typed value into a bool at *dest_ptr.
*
* Accepts also "True" and "False", since there are `.desktop` files with mis-
* spelling. The specification restricts it to "true" and "false", though.
* https://specifications.freedesktop.org/desktop-entry/latest/value-types.htm.
*
* @param value_ptr Must either be "false" or "true".
* @param dest_ptr Pointer to a bool.
*
* @return true on success.
*/
bool _desktop_parser_translate_boolean(
const char *value_ptr,
void *dest_ptr)
{
bool *bool_ptr = dest_ptr;
if (0 == strcmp("true", value_ptr) ||
0 == strcmp("True", value_ptr)) {
*bool_ptr = true;
return true;
} else if (0 == strcmp("false", value_ptr) ||
0 == strcmp("False", value_ptr)) {
*bool_ptr = false;
return true;
}
*bool_ptr = false;
return false;
}
/* ------------------------------------------------------------------------- */
/**
* Translates a string, while un-escaping supported escape codes (s, n, t, r).
*
* @param value_ptr
* @param dest_ptr
*
* @return true on success.
*/
bool _desktop_parser_translate_string(
const char *value_ptr,
void *dest_ptr)
{
char **str_ptr_ptr = dest_ptr;
if (NULL != *str_ptr_ptr) {
free(*str_ptr_ptr);
}
char *d = malloc(strlen(value_ptr) + 1);
if (NULL == d) return false;
*str_ptr_ptr = d;
for (const char *s = value_ptr; *s != '\0'; ++s) {
if (*s == '\\') {
switch (*++s) {
case 's': *d++ = ' '; break;
case 'n': *d++ = '\n'; break;
case 't': *d++ = '\t'; break;
case 'r': *d++ = '\r'; break;
case '\\': *d++ = '\\'; break;
default: /* Escaped code not specified. Skip. */ break;
}
} else {
*d++ = *s;
}
}
*d = '\0';
return true;
}
/* ------------------------------------------------------------------------- */
/**
* Translates multiple strings, separated by semicolon; as for "Categories".
*
* The plural version of "string", as defined at
* https://specifications.freedesktop.org/desktop-entry/latest/value-types.html.
*
* @param value_ptr
* @param dest_ptr
*
* @return true on success.
*/
bool _desktop_parser_translate_strings(
const char *value_ptr,
void *dest_ptr)
{
char **ptrs = calloc(1, sizeof(char*));
if (NULL == ptrs) return false;
size_t cat_idx = 0;
do {
char *token_ptr = _dfa_next_strings_element(&value_ptr);
if ((NULL != token_ptr) && (0 < strlen(token_ptr))) {
char **new_ptrs = realloc(ptrs, (cat_idx + 2) * sizeof(char*));
if (NULL == new_ptrs) {
_desktop_parser_destroy_strings(&ptrs);
free(token_ptr);
return false;
}
ptrs = new_ptrs;
ptrs[cat_idx++] = token_ptr;
ptrs[cat_idx] = NULL;
token_ptr = NULL;
}
if (NULL != token_ptr) free(token_ptr);
while (';' == *value_ptr) ++value_ptr;
} while (*value_ptr);
*(char***)dest_ptr = ptrs;
return true;
}
/* ------------------------------------------------------------------------- */
/**
* Translates an exec key value, and un-escapes the specific escape codes.
*
* https://specifications.freedesktop.org/desktop-entry/latest/exec-variables.html
*
* TODO(kaeser@gubbe.ch): This is... lossy. When un-escaping the arguments, the
* result should be stored as separate strings, such as usable for execve(2).
*
* @param value_ptr
* @param dest_ptr
*
* @return true on success
*/
bool _desktop_parser_translate_exec(
const char *value_ptr,
void *dest_ptr)
{
char **str_ptr_ptr = dest_ptr;
if (NULL != *str_ptr_ptr) {
free(*str_ptr_ptr);
}
char *d = calloc(1, (strlen(value_ptr) + 1));
if (NULL == d) return false;
*str_ptr_ptr = d;
bool quoted_arg = false;
for (const char *s = value_ptr; *s != '\0'; ++s) {
if (quoted_arg) {
if (*s == '"') {
quoted_arg = false;
} else if (*s == '\\') {
switch (*++s) {
case '"': *d++ = '"'; break;
case '`': *d++ = '`'; break;
case '$': *d++ = '$'; break;
case '\\': *d++ = '\\'; break;
default: /* Invalid escape code. */ return false;
}
} else {
*d++ = *s;
}
} else {
if (*s == '"') {
quoted_arg = true;
} else if (*s == '%') {
if (!strchr("fFuUdDnNickvm", *(s + 1))) return false;
++s; // For now: Skip all (valid) field codes.
} else {
*d++ = *s;
}
}
}
*d = '\0';
return !quoted_arg; // All quotes must be closed.
}
/* ------------------------------------------------------------------------- */
/** Frees the memory associated with the string. */
void _desktop_parser_destroy_string(void *dest_ptr)
{
char **str_ptr_ptr = dest_ptr;
if (NULL == *str_ptr_ptr) return;
free(*str_ptr_ptr);
*str_ptr_ptr = NULL;
}
/* ------------------------------------------------------------------------- */
/** Frees the memory associated with the NULL-terminated string array. */
void _desktop_parser_destroy_strings(void *dest_ptr)
{
char **str_ptrs = *(char***)dest_ptr;
if (NULL == str_ptrs) return;
for (char **s = str_ptrs; NULL != *s; ++s) free(*s);
free(str_ptrs);
*(char***)dest_ptr = NULL;
}
/* ------------------------------------------------------------------------- */
/** Returns the next element of a "string(s)" string. */
char *_dfa_next_strings_element(const char **word_ptr)
{
char *copied_ptr = strdup(*word_ptr);
if (NULL == copied_ptr) return NULL;
char *dest_ptr = copied_ptr;
enum _dfa_state state = _DFA_STATE_NORMAL;
while (true) {
enum _dfa_class class;
switch (**word_ptr) {
case '\\': class = _DFA_CLASS_BACKSLASH; break;
case ';': class = _DFA_CLASS_SEMICOLON; break;
case '\0': class = _DFA_CLASS_NUL; break;
default: class = _DFA_CLASS_REGULAR; break;
}
const struct _dfa_transition *t = &_dfa_transition_table[state][class];
if (NULL != t->process) dest_ptr = t->process(dest_ptr, **word_ptr);
state = t->next_state;
if (_DFA_STATE_HALT == state) break;
++*word_ptr;
}
char *result_ptr = strdup(copied_ptr);
free(copied_ptr);
return result_ptr;
}
/* ------------------------------------------------------------------------- */
/** Appends the character to the output. */
char *_dfa_append_char(char *dest_ptr, const char c)
{
*dest_ptr = c;
return ++dest_ptr;
}
/* ------------------------------------------------------------------------- */
/** Appends the corresponnding escaped code to the output. */
char *_dfa_append_escaped(char *dest_ptr, const char c)
{
switch (c) {
case 'n': *dest_ptr++ = '\n'; break;
case 's': *dest_ptr++ = ' '; break;
case 't': *dest_ptr++ = '\t'; break;
case 'r': *dest_ptr++ = '\r'; break;
case '\\': *dest_ptr++ = '\\'; break;
default: *dest_ptr++ = c;
}
return dest_ptr;
}
/* ------------------------------------------------------------------------- */
/** Appends a NUL to the output. */
char *_dfa_append_nul(char *dest_ptr, __UNUSED__ const char c)
{
*dest_ptr = '\0';
return ++dest_ptr;
}
/* ------------------------------------------------------------------------- */
/** Appends a backlsash followed by NUL to the output. */
char *_dfa_append_bslash_nul(char *dest_ptr, __UNUSED__ const char c)
{
*dest_ptr++ = '\\';
*dest_ptr = '\0';
return ++dest_ptr;
}
/* == Unit tests =========================================================== */
static void _desktop_parser_test_ini_string(bs_test_t *test_ptr);
static void _desktop_parser_test_ini_file(bs_test_t *test_ptr);
static void _desktop_parser_test_locale_string(bs_test_t *test_ptr);
static void _desktop_parser_test_translate(bs_test_t *test_ptr);
static void _desktop_parser_test_translate_strings(bs_test_t *test_ptr);
/** Test cases for action items. */
static const bs_test_case_t _desktop_parser_test_cases[] = {
{ true, "ini_string", _desktop_parser_test_ini_string },
{ true, "ini_file", _desktop_parser_test_ini_file },
{ true, "locale_string", _desktop_parser_test_locale_string },
{ true, "translate", _desktop_parser_test_translate },
{ true, "translate_strings", _desktop_parser_test_translate_strings },
BS_TEST_CASE_SENTINEL()
};
const bs_test_set_t desktop_parser_test_set = BS_TEST_SET(
true, "desktop-parser", _desktop_parser_test_cases);
/* ------------------------------------------------------------------------- */
/** Tests parsing INI content, but from a string. */
void _desktop_parser_test_ini_string(bs_test_t *test_ptr)
{
struct desktop_parser *p = desktop_parser_create("en_US.UTF-8@euro");
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, p);
struct desktop_entry e = {};
static const char *i = "\
[Desktop Entry]\n\
Exec=TheBinary\n\
Categories=System;Compositor\n\
Name[en]=Name1\n\
Name[en_US@euro]=Name2\n\
Name[en_US]=Name3\n\
Name[de]=DerName";
BS_TEST_VERIFY_EQ(test_ptr, 0, desktop_parser_string_to_entry(p, i, &e));
BS_TEST_VERIFY_STREQ(test_ptr, "TheBinary", e.exec_ptr);
BS_TEST_VERIFY_STREQ(test_ptr, "Name2", e.name_ptr);
BS_TEST_VERIFY_NEQ(test_ptr, 0, e.name_priority & (1 << 4));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, e.category_ptrs);
BS_TEST_VERIFY_STREQ(test_ptr, "System", e.category_ptrs[0]);
BS_TEST_VERIFY_STREQ(test_ptr, "Compositor", e.category_ptrs[1]);
BS_TEST_VERIFY_EQ(test_ptr, NULL, e.category_ptrs[2]);
desktop_parser_entry_release(&e);
desktop_parser_destroy(p);
}
/* ------------------------------------------------------------------------- */
/** Tests parsing a sample .desktop file. */
void _desktop_parser_test_ini_file(bs_test_t *test_ptr)
{
struct desktop_parser *p = desktop_parser_create("en_US.UTF-8@euro");
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, p);
struct desktop_entry e = {};
const char *f = bs_test_data_path(test_ptr, "wlmaker.desktop");
BS_TEST_VERIFY_EQ_OR_RETURN(
test_ptr, 0, desktop_parser_file_to_entry(p, f, &e));
BS_TEST_VERIFY_EQ(test_ptr, DESKTOP_ENTRY_TYPE_APPLICATION, e.type);
BS_TEST_VERIFY_STREQ(test_ptr, "WaylandMaker", e.name_ptr);
BS_TEST_VERIFY_STREQ(test_ptr, "/usr/local/bin/wlmaker", e.exec_ptr);
BS_TEST_VERIFY_STREQ(test_ptr, "./wlmaker", e.try_exec_ptr);
BS_TEST_VERIFY_STREQ(test_ptr, "/usr/local", e.path_ptr);
BS_TEST_VERIFY_FALSE(test_ptr, e.hidden);
BS_TEST_VERIFY_FALSE(test_ptr, e.no_display);
BS_TEST_VERIFY_TRUE(test_ptr, e.terminal);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, e.category_ptrs);
BS_TEST_VERIFY_STREQ(test_ptr, "System", e.category_ptrs[0]);
BS_TEST_VERIFY_STREQ(test_ptr, "Compositor", e.category_ptrs[1]);
BS_TEST_VERIFY_EQ(test_ptr, NULL, e.category_ptrs[2]);
desktop_parser_entry_release(&e);
desktop_parser_destroy(p);
}
/* ------------------------------------------------------------------------- */
/** Tests parsing localized strings. */
void _desktop_parser_test_locale_string(bs_test_t *test_ptr)
{
// For convenience.
const char *gn = desktop_entry_group_name;
int (*h)(void *, const char *, const char *, const char *) =
_desktop_parser_handler;
struct desktop_entry e = {};
struct _desktop_parser_handler_arg ha = {
.parser = desktop_parser_create("en_US.UTF-8@euro"),
.entry_ptr = &e
};
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ha.parser);
// 'Exec' is not a localestring. Fail.
BS_TEST_VERIFY_EQ(test_ptr, 0, h(&ha, gn, "Exec[en]", "x"));
// Name is a localestring. Exercise increasing priority.
BS_TEST_VERIFY_NEQ(test_ptr, 0, h(&ha, gn, "Name", "n0"));
BS_TEST_VERIFY_STREQ(test_ptr, "n0", e.name_ptr);
// Cannot set twice.
BS_TEST_VERIFY_EQ(test_ptr, 0, h(&ha, gn, "Name", "n10"));
BS_TEST_VERIFY_NEQ(test_ptr, 0, h(&ha, gn, "Name[en]", "n1"));
BS_TEST_VERIFY_STREQ(test_ptr, "n1", e.name_ptr);
BS_TEST_VERIFY_NEQ(test_ptr, 0, e.name_priority & (1 << 1));
BS_TEST_VERIFY_EQ(test_ptr, 0, h(&ha, gn, "Name", "n0"));
e.name_priority = 1 << 1;
BS_TEST_VERIFY_NEQ(test_ptr, 0, h(&ha, gn, "Name", "n0"));
BS_TEST_VERIFY_STREQ(test_ptr, "n1", e.name_ptr);
BS_TEST_VERIFY_NEQ(test_ptr, 0, h(&ha, gn, "Name[en@euro]", "n2"));
BS_TEST_VERIFY_STREQ(test_ptr, "n2", e.name_ptr);
BS_TEST_VERIFY_NEQ(test_ptr, 0, e.name_priority & (1 << 2));
BS_TEST_VERIFY_EQ(test_ptr, 0, h(&ha, gn, "Name[en]", "n1"));
e.name_priority = 1 << 2;
BS_TEST_VERIFY_NEQ(test_ptr, 0, h(&ha, gn, "Name[en]", "n1"));
BS_TEST_VERIFY_STREQ(test_ptr, "n2", e.name_ptr);
BS_TEST_VERIFY_NEQ(test_ptr, 0, h(&ha, gn, "Name[en_US]", "n3"));
BS_TEST_VERIFY_STREQ(test_ptr, "n3", e.name_ptr);
BS_TEST_VERIFY_NEQ(test_ptr, 0, e.name_priority & (1 << 3));
BS_TEST_VERIFY_EQ(test_ptr, 0, h(&ha, gn, "Name[en@euro]", "n2"));
BS_TEST_VERIFY_STREQ(test_ptr, "n3", e.name_ptr);
BS_TEST_VERIFY_NEQ(test_ptr, 0, h(&ha, gn, "Name[en_US@euro]", "n4"));
BS_TEST_VERIFY_STREQ(test_ptr, "n4", e.name_ptr);
BS_TEST_VERIFY_NEQ(test_ptr, 0, e.name_priority & (1 << 4));
BS_TEST_VERIFY_EQ(test_ptr, 0, h(&ha, gn, "Name[en_US@euro]", "n4"));
BS_TEST_VERIFY_EQ(test_ptr, 0, h(&ha, gn, "Name[en_US]", "n3"));
BS_TEST_VERIFY_STREQ(test_ptr, "n4", e.name_ptr);
desktop_parser_destroy((struct desktop_parser*)ha.parser);
desktop_parser_entry_release(&e);
}
/* ------------------------------------------------------------------------- */
/** Tests translators: Escaped string. */
void _desktop_parser_test_translate(bs_test_t *test_ptr)
{
struct desktop_parser *p = desktop_parser_create("en_US.UTF-8@euro");
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, p);
struct desktop_entry e = {};
const char *i = "[Desktop Entry]\nName=A\\sB\\nC\\tD\\rE\\\\F\\xG";
BS_TEST_VERIFY_EQ(test_ptr, 0, desktop_parser_string_to_entry(p, i, &e));
BS_TEST_VERIFY_STREQ(test_ptr, "A B\nC\tD\rE\\FG", e.name_ptr);
i = "[Desktop Entry]\nExec=a %f %U \"a \\` \\\" \\$ \\\\ \"";
BS_TEST_VERIFY_EQ(test_ptr, 0, desktop_parser_string_to_entry(p, i, &e));
BS_TEST_VERIFY_STREQ(test_ptr, "a a ` \" $ \\ ", e.exec_ptr);
i = "[Desktop Entry]\nTerminal=true";
BS_TEST_VERIFY_EQ(test_ptr, 0, desktop_parser_string_to_entry(p, i, &e));
BS_TEST_VERIFY_TRUE(test_ptr, e.terminal);
i = "[Desktop Entry]\nTerminal=True";
BS_TEST_VERIFY_EQ(test_ptr, 0, desktop_parser_string_to_entry(p, i, &e));
BS_TEST_VERIFY_TRUE(test_ptr, e.terminal);
desktop_parser_entry_release(&e);
desktop_parser_destroy(p);
}
/* ------------------------------------------------------------------------- */
/** Tests translation of multi-strings. */
void _desktop_parser_test_translate_strings(bs_test_t *test_ptr)
{
// First: Some test cases for the escaper + tokenizer.
struct _test_data {
const char *input_ptr;
const char *expected_ptr ;
const char next_input;
} test_data[] = {
{ "token", "token", '\0' },
{ "token;", "token", ';' },
{ "to;ken;", "to", ';' },
{ "to\\;ken", "to;ken", '\0' },
{ "token\\", "token\\", '\0' },
{ "E\\nS\\sC\\tA\\rP\\\\E", "E\nS C\tA\rP\\E", '\0' },
{ NULL, NULL, '\0' }
};
for (const struct _test_data *t = test_data; t->input_ptr; t++) {
const char *arg = t->input_ptr;
char *result_ptr = _dfa_next_strings_element(&arg);
BS_TEST_VERIFY_STREQ(test_ptr, t->expected_ptr, result_ptr);
BS_TEST_VERIFY_EQ(test_ptr, t->next_input, *arg);
free(result_ptr);
}
// Then: Test the actual translator, ensuring it returns the array.
char **spp = NULL;
const char *s = "simple";
BS_TEST_VERIFY_TRUE(test_ptr, _desktop_parser_translate_strings(s, &spp));
BS_TEST_VERIFY_STREQ(test_ptr, "simple", spp[0]);
BS_TEST_VERIFY_EQ(test_ptr, NULL, spp[1]);
_desktop_parser_destroy_strings(&spp);
s = "semi;colon;;";
BS_TEST_VERIFY_TRUE(test_ptr, _desktop_parser_translate_strings(s, &spp));
BS_TEST_VERIFY_STREQ(test_ptr, "semi", spp[0]);
BS_TEST_VERIFY_STREQ(test_ptr, "colon", spp[1]);
BS_TEST_VERIFY_EQ(test_ptr, NULL, spp[2]);
_desktop_parser_destroy_strings(&spp);
s = "one;two;th\\;ree;;;four";
BS_TEST_VERIFY_TRUE(test_ptr, _desktop_parser_translate_strings(s, &spp));
BS_TEST_VERIFY_STREQ(test_ptr, "one", spp[0]);
BS_TEST_VERIFY_STREQ(test_ptr, "two", spp[1]);
BS_TEST_VERIFY_STREQ(test_ptr, "th;ree", spp[2]);
BS_TEST_VERIFY_STREQ(test_ptr, "four", spp[3]);
BS_TEST_VERIFY_EQ(test_ptr, NULL, spp[4]);
_desktop_parser_destroy_strings(&spp);
}
/* == End of parse.c ======================================================= */
wlmaker-0.8.1/tool/desktop-parser/desktop-parser.h 0000644 0001751 0001751 00000011074 15222270710 021625 0 ustar runner runner /* ========================================================================= */
/**
* @file desktop-parser.h
*
* Simple parser for FreeDesktop `.desktop` files, to provide application-
* specific information within a compositor and for constructing application
* menus. Depends on `libinih-dev`.
*
* Reference:
* * http://specifications.freedesktop.org/desktop-entry/1.5/
* * https://github.com/benhoyt/inih
*
* Currently built to support the necessary keys for building the root menu
* for Wayland Maker. Specifically, that includes:
* * [*] Type
* * [*] NoDisplay
* * [*] Hidden
* * [*] Terminal
* * [*] Exec
* * [*] Name
* * [*] Categories
* * [*] TryExec
* * [*] Path
*
* Further improvements:
* * Handle the %f, %u, ... specifiers.
* * Add support for "numeric" type. Though, it's currently unused for .desktop.
* * Use the "Terminal" flag and construct a command that executes in terminal.
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright (c) 2025 Google LLC and Philipp Kaeser
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __WLMAKER_DESKTOP_PARSER_H__
#define __WLMAKER_DESKTOP_PARSER_H__
#include
#include
#include
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
struct desktop_parser;
/** Permissible values for `Type=...`. */
enum desktop_entry_type {
DESKTOP_ENTRY_TYPE_UNKNOWN = 0,
DESKTOP_ENTRY_TYPE_APPLICATION = 1,
DESKTOP_ENTRY_TYPE_LINK = 2,
DESKTOP_ENTRY_TYPE_DIRECTORY = 3,
};
/** Holds information for one desktop entry. */
struct desktop_entry {
/** Type of the desktop entry. */
enum desktop_entry_type type;
/** Whether this desktop entry had been deleted (to be ignored). */
bool hidden;
/** Whether to exclude this entry from the menus. */
bool no_display;
/** Whether the program runs in a terminal window. */
bool terminal;
/** Helper for localized "Name". */
int8_t name_priority;
/** Localized specific name of the application. */
char *name_ptr;
/** Program to execute, possibly with arguments. */
char *exec_ptr;
/** Path to executable, used to determine if the program is installed. */
char *try_exec_ptr;
/** The working directory to run the program in. */
char *path_ptr;
/** An array of strings, each indicating a category. NULL-terminated. */
char **category_ptrs;
};
/**
* Creates a desktop parser, using the provided locale.
*
* @param locale_ptr Locale set for `LC_MESSAGES`. See setlocale(3).
*
* @return Pointer to the desktop parser, or NULL on error. Must be destroyed
* by calling @ref desktop_parser_destroy.
*/
struct desktop_parser *desktop_parser_create(const char *locale_ptr);
/**
* Destroys the desktop parser.
*
* @param parser
*/
void desktop_parser_destroy(struct desktop_parser *parser);
/**
* Parses a file into the provided entry.
*
* @param parser
* @param fname_ptr
* @param entry_ptr
*
* @return 0 on success, or the line number where the parser failed.
*/
int desktop_parser_file_to_entry(
const struct desktop_parser *parser,
const char *fname_ptr,
struct desktop_entry *entry_ptr);
/**
* Parses an in-memory string into the provided entry.
*
* @param parser
* @param string_ptr
* @param entry_ptr
*
* @return 0 on success, or the line number where the parser failed.
*/
int desktop_parser_string_to_entry(
const struct desktop_parser *parser,
const char *string_ptr,
struct desktop_entry *entry_ptr);
/**
* Releases the resources associated to the entry.
*
* @param entry_ptr
*/
void desktop_parser_entry_release(struct desktop_entry *entry_ptr);
/** Unit test set. */
extern const bs_test_set_t desktop_parser_test_set;
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif // __WLMAKER_DESKTOP_PARSER_H__
/* == End of desktop-parser.h ============================================== */
wlmaker-0.8.1/tool/desktop-parser/CMakeLists.txt 0000644 0001751 0001751 00000002133 15222270710 021245 0 ustar runner runner # Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cmake_minimum_required(VERSION 3.13)
add_library(desktop-parser STATIC desktop-parser.c)
target_link_libraries(
desktop-parser
PRIVATE
libbase
inih)
target_include_directories(
desktop-parser
PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/..")
set_target_properties(
desktop-parser PROPERTIES
VERSION 1.0
PUBLIC_HEADER desktop-parser.h)
if(iwyu_path_and_options)
set_target_properties(
desktop-parser PROPERTIES
C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}")
endif()
wlmaker-0.8.1/tool/CMakeLists.txt 0000644 0001751 0001751 00000003101 15222270710 016276 0 ustar runner runner # Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cmake_minimum_required(VERSION 3.13)
add_library(libwlmtool STATIC item.c menu.c)
target_include_directories(
libwlmtool
PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}"
PRIVATE
"${LIBXDGBASEDIR_INCLUDE_DIRS}")
target_link_libraries(
libwlmtool
PRIVATE
libbase
libbase_plist
desktop-parser
PkgConfig::LIBXDGBASEDIR)
add_executable(
wlmtool
wlmtool.c)
target_link_libraries(
wlmtool
libwlmtool
libbase)
target_compile_definitions(
wlmtool PRIVATE
"WLMAKER_VERSION_MAJOR=\"${WLMAKER_VERSION_MAJOR}\""
"WLMAKER_VERSION_MINOR=\"${WLMAKER_VERSION_MINOR}\""
"WLMAKER_VERSION_FULL=\"${WLMAKER_VERSION_FULL}\"")
install(
TARGETS wlmtool
DESTINATION "${CMAKE_INSTALL_BINDIR}")
if(iwyu_path_and_options)
set_target_properties(
wlmtool PROPERTIES
C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}")
set_target_properties(
libwlmtool PROPERTIES
C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}")
endif()
add_subdirectory(desktop-parser)
wlmaker-0.8.1/src/ 0000755 0001751 0001751 00000000000 15222270710 013355 5 ustar runner runner wlmaker-0.8.1/src/wlmbacktrace.c 0000644 0001751 0001751 00000007712 15222270710 016167 0 ustar runner runner /* ========================================================================= */
/**
* @file backtrace.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright (c) 2025 by Philipp Kaeser
*/
#include "wlmbacktrace.h"
#include
#include
#include
#include
#include
#if defined(WLMAKER_HAVE_LIBBACKTRACE)
#include
#endif // defined(WLMAKER_HAVE_LIBBACKTRACE)
/* == Declarations ========================================================= */
#if defined(WLMAKER_HAVE_LIBBACKTRACE)
/** State for libbacktrace. */
static struct backtrace_state *_wlmaker_bt_state_ptr;
static void _backtrace_error_callback(
__UNUSED__ void *data_ptr,
const char *msg_ptr,
int errnum);
static int _backtrace_full_callback(
__UNUSED__ void *data,
uintptr_t pc,
const char *filename_ptr,
int line_num,
const char *function_ptr);
static void _signal_backtrace(int signum);
#endif // defined(WLMAKER_HAVE_LIBBACKTRACE)
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
bool wlmaker_backtrace_setup(const char *filename_ptr)
{
#if defined(WLMAKER_HAVE_LIBBACKTRACE)
_wlmaker_bt_state_ptr = backtrace_create_state(
filename_ptr, 0, _backtrace_error_callback, NULL);
if (NULL == _wlmaker_bt_state_ptr) {
bs_log(BS_ERROR, "Failed backtrace_create_state()");
return false;
}
signal(SIGABRT, _signal_backtrace);
signal(SIGBUS, _signal_backtrace);
signal(SIGFPE, _signal_backtrace);
signal(SIGILL, _signal_backtrace);
signal(SIGSEGV, _signal_backtrace);
#else
bs_log(BS_DEBUG, "No libbacktrace, ignoring setup for %s", filename_ptr);
#endif // defined(WLMAKER_HAVE_LIBBACKTRACE)
return true;
}
/* == Local (static) methods =============================================== */
#if defined(WLMAKER_HAVE_LIBBACKTRACE)
/* ------------------------------------------------------------------------- */
/** Error callback for libbacktrace calls. */
void _backtrace_error_callback(
__UNUSED__ void *data_ptr,
const char *msg_ptr,
int errnum)
{
bs_log_severity_t severity = BS_ERROR;
if (0 != errnum && -1 != errnum) severity |= BS_ERRNO;
bs_log(severity, "Backtrace error: %s", msg_ptr);
}
/* ------------------------------------------------------------------------- */
/** Full callback for printing a libbacktrace frame. */
int _backtrace_full_callback(
__UNUSED__ void *data,
uintptr_t program_counter,
const char *filename_ptr,
int line_num,
const char *function_ptr)
{
bs_log(BS_ERROR, "%"PRIxPTR" in %s () at %s:%d",
program_counter,
function_ptr ? function_ptr : "(unknown)",
filename_ptr ? filename_ptr : "(unknown)",
line_num);
return 0;
}
/* ------------------------------------------------------------------------- */
/** Signal handler: Prints a backtrace. */
void _signal_backtrace(int signum)
{
bs_log(BS_ERROR, "Caught signal %d", signum);
backtrace_full(
_wlmaker_bt_state_ptr, 0,
_backtrace_full_callback, _backtrace_error_callback, NULL);
signal(SIGABRT, SIG_DFL);
abort();
}
#endif // defined(WLMAKER_HAVE_LIBBACKTRACE)
/* == End of backtrace.c =================================================== */
wlmaker-0.8.1/src/server.h 0000644 0001751 0001751 00000021002 15222270710 015027 0 ustar runner runner /* ========================================================================= */
/**
* @file server.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __WLMAKER_SERVER_H__
#define __WLMAKER_SERVER_H__
#include
#include
#include
#include
#include
#define WLR_USE_UNSTABLE
#include
#include
#include
#include
#include
#undef WLR_USE_UNSTABLE
#include "input/manager.h"
/** A handle for a wlmaker server. */
typedef struct _wlmaker_server_t wlmaker_server_t;
#include "backend/backend.h"
#include "config.h"
#include "corner.h" // IWYU pragma: keep
#include "cursor.h" // IWYU pragma: keep
#include "files.h"
#include "icon_manager.h" // IWYU pragma: keep
#include "input_observation.h"
#include "idle.h" // IWYU pragma: keep
#include "layer_shell.h" // IWYU pragma: keep
#include "lock_mgr.h" // IWYU pragma: keep
#include "root_menu.h" // IWYU pragma: keep
#include "subprocess_monitor.h" // IWYU pragma: keep
#include "toolkit/toolkit.h"
#include "xdg_decoration.h" // IWYU pragma: keep
#include "xdg_shell.h" // IWYU pragma: keep
#include "xwl.h" // IWYU pragma: keep
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/** Options for the Wayland server. */
typedef struct {
/** Whether to start XWayland. */
bool start_xwayland;
/** Desired output width, for windowed mode. 0 for no preference. */
uint32_t width;
/** Desired output height, for windowed mode. 0 for no preference. */
uint32_t height;
/** Whether to include 'Logo' to modifiers. */
bool bind_with_logo;
} wlmaker_server_options_t;
/** State of the Wayland server. */
struct _wlmaker_server_t {
/** Files module handle. */
wlmaker_files_t *files_ptr;
/** Configuration dictionnary. */
bspl_dict_t *config_dict_ptr;
/** Copy of the options. */
const wlmaker_server_options_t *options_ptr;
/** Wayland display. */
struct wl_display *wl_display_ptr;
/** Name of the socket for clients to connect. */
const char *wl_socket_name_ptr;
/** Session lock manager. */
wlmaker_lock_mgr_t *lock_mgr_ptr;
/** Idle monitor. */
wlmaker_idle_monitor_t *idle_monitor_ptr;
/** WLR viewporter. */
struct wlr_viewporter *wlr_viewporter_ptr;
/** Fractional scale manager. */
struct wlr_fractional_scale_manager_v1 *wlr_fractional_scale_manager_ptr;
/** wlroots seat. */
struct wlr_seat *wlr_seat_ptr;
/** The scene graph API. */
struct wlr_scene *wlr_scene_ptr;
/** wlroots output layout. */
struct wlr_output_layout *wlr_output_layout_ptr;
// Clipboard and selection support.
/** Provides the wl_data_device protocol for clipboard (Ctrl+C/V). */
struct wlr_data_device_manager *wlr_data_device_manager_ptr;
/** Listener to approve clipboard selection requests. */
struct wl_listener request_set_selection_listener;
/** Provides the primary selection protocol for middle-click paste. */
struct wlr_primary_selection_v1_device_manager
*wlr_primary_selection_v1_device_manager_ptr;
/** Listener to approve primary selection requests. */
struct wl_listener request_set_primary_selection_listener;
/** Enables clipboard managers and tools (wl-copy, wl-paste). */
struct wlr_data_control_manager_v1 *wlr_data_control_manager_v1_ptr;
/** The XDG Shell handler. */
wlmaker_xdg_shell_t *xdg_shell_ptr;
/** The XDG decoration manager. */
wlmaker_xdg_decoration_manager_t *xdg_decoration_manager_ptr;
/** Layer shell handler. */
wlmaker_layer_shell_t *layer_shell_ptr;
/** Backend handler. */
wlmbe_backend_t *backend_ptr;
/** Input device manager. */
wlmim_t *input_manager_ptr;
/** Any activity from the input manager. */
struct wl_listener input_activity_listener;
/** Hack: Deactivate task list, when losing 'ALT' modifier. */
struct wl_listener deactivate_task_list_listener;
/** Icon manager. */
wlmaker_icon_manager_t *icon_manager_ptr;
/** Input observation. */
wlmaker_input_observation_manager_t *input_observation_manager_ptr;
/**
* XWayland interface. Will be set only if compiled with XWayland, through
* WLMAKER_HAVE_XWAYLAND defined.
* And through setting @ref wlmaker_server_options_t::start_xwayland.
*/
wlmaker_xwl_t *xwl_ptr;
/** The root element. */
wlmtk_root_t *root_ptr;
/** Whether the task list is currently shown. */
bool task_list_enabled;
/** Signal: When the task list is enabled. (to be shown) */
struct wl_signal task_list_enabled_event;
/** Signal: When the task list is disabled. (to be hidden) */
struct wl_signal task_list_disabled_event;
/** Called when the theme has changed. */
struct wl_signal theme_changed_event;
/** Clients for this server. */
bs_dllist_t clients;
/** Subprocess monitoring. */
wlmaker_subprocess_monitor_t *monitor_ptr;
/** Montor & handler of 'hot corners'. */
wlmaker_corner_t *corner_ptr;
// TODO(kaeser@gubbe.ch): Move these events into a 'registry' struct, so
// it can be more easily shared throughout the code.
/** Signal: Triggered whenever a window is created. */
struct wl_signal window_created_event;
/** Signal: Triggered whenever a window is destroyed. */
struct wl_signal window_destroyed_event;
/** Temporary: Points to the @ref wlmtk_dock_t of the clip. */
wlmtk_dock_t *clip_dock_ptr;
/** Root menu, when active. NULL when not invoked. */
wlmaker_root_menu_t *root_menu_ptr;
/** Parsed contents of the root menu definition, from plist. */
bspl_array_t *root_menu_array_ptr;
/** Listener for `unclaimed_button_event` signal raised by `wlmtk_root`. */
struct wl_listener unclaimed_button_event_listener;
/** From @ref wlmaker_background::dlnode. */
bs_dllist_t backgrounds;
/** The current configuration style. */
wlmaker_config_style_t *style_ptr;
};
/**
* Creates the server and initializes all needed sub-modules.
*
* @param config_dict_ptr Configuration, as dictionary object. The server
* will keep a reference on it until destroyed.
* @param files_ptr Files handle. Must outlive the server.
* @param style_ptr Style. Must outlive the server.
* @param options_ptr Options for the server. The server expects the
* pointed area to outlive the server.
*
* @return The server handle or NULL on failure. The handle must be freed by
* calling wlmaker_server_destroy().
*/
wlmaker_server_t *wlmaker_server_create(
bspl_dict_t *config_dict_ptr,
wlmaker_files_t *files_ptr,
wlmaker_config_style_t *style_ptr,
const wlmaker_server_options_t *options_ptr);
/**
* Destroys the server handle, as created by wlmaker_server_create().
*
* @param server_ptr
*/
void wlmaker_server_destroy(wlmaker_server_t *server_ptr);
/**
* Activates the task list.
*
* @param server_ptr
*/
void wlmaker_server_activate_task_list(wlmaker_server_t *server_ptr);
/**
* Looks up which output serves the current cursor coordinates and returns that.
*
* @param server_ptr
*
* @return Pointer to the output at the seat's cursor position.
*/
struct wlr_output *wlmaker_server_get_output_at_cursor(
wlmaker_server_t *server_ptr);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __WLMAKER_SERVER_H__ */
/* == End of server.h ================================================== */
wlmaker-0.8.1/src/action_item.h 0000644 0001751 0001751 00000006534 15222270710 016031 0 ustar runner runner /* ========================================================================= */
/**
* @file action_item.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __WLMAKER_ACTION_ITEM_H__
#define __WLMAKER_ACTION_ITEM_H__
#include
#include
#include
/** Forward declaration: An action-triggering menu item. */
typedef struct _wlmaker_action_item_t wlmaker_action_item_t;
#include "action.h"
#include "server.h"
#include "toolkit/toolkit.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/** Descriptor for creating a menu item triggering an action. */
typedef struct {
/** Text for the menu item. */
const char *text_ptr;
/** The action to trigger. */
wlmaker_action_t action;
/** Extra argument for @ref wlmaker_action_execute. */
char *action_arg_ptr;
/**
* Where to store the @ref wlmaker_action_item_t, relative to the
* `dest_ptr` argument of @ref wlmaker_action_item_create_from_desc.
*/
size_t destination_ofs;
} wlmaker_action_item_desc_t;
/**
* Creates a menu item that triggers a @ref wlmaker_action_t.
*
* @param text_ptr
* @param style_ref_ptr
* @param action
* @param action_arg_ptr Extra argument. Will be duplicated.
* @param server_ptr
*
* @return Pointer to the menu item's handle or NULL on error.
*/
wlmaker_action_item_t *wlmaker_action_item_create(
const char *text_ptr,
wlmtk_menu_style_ref_t *style_ref_ptr,
wlmaker_action_t action,
const char *action_arg_ptr,
wlmaker_server_t *server_ptr);
/**
* Creates a menu item triggering an action item from a descriptor.
*
* @param desc_ptr
* @param dest_ptr
* @param style_ref_ptr
* @param server_ptr
*
* @return Pointer to the item's handle or NULL on error.
*/
wlmaker_action_item_t *wlmaker_action_item_create_from_desc(
const wlmaker_action_item_desc_t *desc_ptr,
void *dest_ptr,
wlmtk_menu_style_ref_t *style_ref_ptr,
wlmaker_server_t *server_ptr);
/** @returns pointer to the superclass @ref wlmtk_menu_item_t. */
wlmtk_menu_item_t *wlmaker_action_item_menu_item(
wlmaker_action_item_t *action_item_ptr);
/**
* Binds an action to a menu item.
*
* @param menu_item_ptr
* @param action
* @param action_arg_ptr
* @param server_ptr
*
* @return true on success.
*/
bool wlmaker_menu_item_bind_action(
wlmtk_menu_item_t* menu_item_ptr,
wlmaker_action_t action,
const char *action_arg_ptr,
wlmaker_server_t *server_ptr);
/** Unit test set. */
extern const bs_test_set_t wlmaker_action_item_test_set;
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __WLMAKER_ACTION_ITEM_H__ */
/* == End of action_item.h ================================================= */
wlmaker-0.8.1/src/layer_shell.c 0000644 0001751 0001751 00000011356 15222270710 016032 0 ustar runner runner /* ========================================================================= */
/**
* @file layer_shell.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "layer_shell.h"
#include
#include
#include
#define WLR_USE_UNSTABLE
#include
#undef WLR_USE_UNSTABLE
#include "layer_panel.h"
#include "toolkit/toolkit.h"
#include "server.h"
/* == Declarations ========================================================= */
/** State of Layer Shell handler. */
struct _wlmaker_layer_shell_t {
/** wlroots Layer Shell v1 handler. */
struct wlr_layer_shell_v1 *wlr_layer_shell_v1_ptr;
/** Back-link to the server. */
wlmaker_server_t *server_ptr;
/** Listener for the `new_surface` signal raised by `wlr_layer_shell_v1`. */
struct wl_listener new_surface_listener;
/** Listener for the `destroy` signal raised by `wlr_layer_shell_v1`. */
struct wl_listener destroy_listener;
};
static void handle_destroy(
struct wl_listener *listener_ptr,
void *data_ptr);
static void handle_new_surface(
struct wl_listener *listener_ptr,
void *data_ptr);
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
wlmaker_layer_shell_t *wlmaker_layer_shell_create(wlmaker_server_t *server_ptr)
{
wlmaker_layer_shell_t *layer_shell_ptr = logged_calloc(
1, sizeof(wlmaker_layer_shell_t));
if (NULL == layer_shell_ptr) return NULL;
layer_shell_ptr->server_ptr = server_ptr;
layer_shell_ptr->wlr_layer_shell_v1_ptr = wlr_layer_shell_v1_create(
server_ptr->wl_display_ptr, 4 /* version */);
if (NULL == layer_shell_ptr->wlr_layer_shell_v1_ptr) {
bs_log(BS_ERROR, "Failed wlr_layer_shell_v1_create()");
return NULL;
}
wlmtk_util_connect_listener_signal(
&layer_shell_ptr->wlr_layer_shell_v1_ptr->events.new_surface,
&layer_shell_ptr->new_surface_listener,
handle_new_surface);
wlmtk_util_connect_listener_signal(
&layer_shell_ptr->wlr_layer_shell_v1_ptr->events.destroy,
&layer_shell_ptr->destroy_listener,
handle_destroy);
return layer_shell_ptr;
}
/* ------------------------------------------------------------------------- */
void wlmaker_layer_shell_destroy(wlmaker_layer_shell_t *layer_shell_ptr)
{
wlmtk_util_disconnect_listener(&layer_shell_ptr->destroy_listener);
wlmtk_util_disconnect_listener(&layer_shell_ptr->new_surface_listener);
free(layer_shell_ptr);
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/**
* Event handler for the `destroy` signal raised by `wlr_layer_shell_v1`.
*
* @param listener_ptr
* @param data_ptr
*/
void handle_destroy(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_layer_shell_t *layer_shell_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_layer_shell_t, destroy_listener);
wlmaker_layer_shell_destroy(layer_shell_ptr);
}
/* ------------------------------------------------------------------------- */
/**
* Event handler for the `new_surface` signal raised by `wlr_layer_shell_v1`.
*
* @param listener_ptr
* @param data_ptr
*/
void handle_new_surface(
struct wl_listener *listener_ptr,
void *data_ptr)
{
wlmaker_layer_shell_t *layer_shell_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_layer_shell_t, new_surface_listener);
struct wlr_layer_surface_v1 *wlr_layer_surface_v1_ptr = data_ptr;
if (NULL == wlr_layer_surface_v1_ptr->output) {
wlr_layer_surface_v1_ptr->output = wlmaker_server_get_output_at_cursor(
layer_shell_ptr->server_ptr);
}
wlmaker_layer_panel_t *layer_panel_ptr =
wlmaker_layer_panel_create(
wlr_layer_surface_v1_ptr,
layer_shell_ptr->server_ptr);
if (NULL == layer_panel_ptr) {
wlr_layer_surface_v1_destroy(wlr_layer_surface_v1_ptr);
}
}
/* == End of layer_shell.c ================================================= */
wlmaker-0.8.1/src/corner.h 0000644 0001751 0001751 00000004054 15222270710 015021 0 ustar runner runner /* ========================================================================= */
/**
* @file corner.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __CORNER_H__
#define __CORNER_H__
/** Forward declaration: State of hot corner monitor. */
typedef struct _wlmaker_corner_t wlmaker_corner_t;
#include
#include
#include "task_list.h"
struct wl_event_loop;
struct wl_signal;
struct wlr_cursor;
struct wlr_output_layout;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Creates the hot-corner handler.
*
* @param hot_corner_config_dict_ptr
* @param wl_event_loop_ptr
* @param wlr_output_layout_ptr
* @param wlr_cursor_ptr
* @param cursor_position_updated_ptr
* @param server_ptr
*
* @return Pointer to the hot-corner monitor.
*/
wlmaker_corner_t *wlmaker_corner_create(
bspl_dict_t *hot_corner_config_dict_ptr,
struct wl_event_loop *wl_event_loop_ptr,
struct wlr_output_layout *wlr_output_layout_ptr,
struct wlr_cursor *wlr_cursor_ptr,
struct wl_signal *cursor_position_updated_ptr,
wlmaker_server_t *server_ptr);
/**
* Destroys the hot-corner handler.
*
* @param corner_ptr
*/
void wlmaker_corner_destroy(wlmaker_corner_t *corner_ptr);
/** Unit test set. */
extern const bs_test_set_t wlmaker_corner_test_set;
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __CORNER_H__ */
/* == End of corner.h ====================================================== */
wlmaker-0.8.1/src/icon_manager.h 0000644 0001751 0001751 00000003436 15222270710 016156 0 ustar runner runner /* ========================================================================= */
/**
* @file icon_manager.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ICON_MANAGER_H__
#define __ICON_MANAGER_H__
struct wl_display;
/** Forward declaration: Icon Manager handle. */
typedef struct _wlmaker_icon_manager_t wlmaker_icon_manager_t;
/** Forward declaration: Toplevel icon handle. */
typedef struct _wlmaker_toplevel_icon_t wlmaker_toplevel_icon_t;
#include "task_list.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Creates an icon manager.
*
* @param wl_display_ptr
* @param server_ptr
*
* @return The handle of the icon manager or NULL on error. Must be destroyed
* by calling @ref wlmaker_icon_manager_destroy.
*/
wlmaker_icon_manager_t *wlmaker_icon_manager_create(
struct wl_display *wl_display_ptr,
wlmaker_server_t *server_ptr);
/**
* Destroys the Icon Manager.
*
* @param icon_manager_ptr
*/
void wlmaker_icon_manager_destroy(
wlmaker_icon_manager_t *icon_manager_ptr);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __ICON_MANAGER_H__ */
/* == End of icon_manager.h ================================================ */
wlmaker-0.8.1/src/xdg_decoration.c 0000644 0001751 0001751 00000054712 15222270710 016523 0 ustar runner runner /* ========================================================================= */
/**
* @file xdg_decoration.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "xdg_decoration.h"
#include
#include
#include
#include
#include
#include
#include
#define WLR_USE_UNSTABLE
#include
#include
#undef WLR_USE_UNSTABLE
#include "config.h"
#include "toolkit/toolkit.h"
#include "xdg_shell.h"
#include "xdg_toplevel.h"
/* == Declarations ========================================================= */
/** State of the XDG decoration manager. */
struct _wlmaker_xdg_decoration_manager_t {
/** The wlroots XDG decoration manager. */
struct wlr_xdg_decoration_manager_v1* wlr_xdg_decoration_manager_v1_ptr;
/** Injectable, for tests: wlr_xdg_toplevel_decoration_v1_set_mode(). */
uint32_t (*set_mode)(
struct wlr_xdg_toplevel_decoration_v1 *decoration,
enum wlr_xdg_toplevel_decoration_v1_mode mode);
/** Operation mode for the decoration manager. */
wlmaker_config_decoration_t mode;
/** Listener for `new_toplevel_decoration`. */
struct wl_listener new_toplevel_decoration_listener;
/** Listener for `destroy` of `wlr_xdg_decoration_manager_v1`. */
struct wl_listener destroy_listener;
};
/** A decoration handle. */
typedef struct {
/** Points to the wlroots `wlr_xdg_toplevel_decoration_v1`. */
struct wlr_xdg_toplevel_decoration_v1 *wlr_xdg_toplevel_decoration_v1_ptr;
/** Back-link to the decoration manager. */
wlmaker_xdg_decoration_manager_t *decoration_manager_ptr;
/** Listener for `request_mode` of `wlr_xdg_toplevel_decoration_v1`. */
struct wl_listener request_mode_listener;
/** Listener for `destroy` of `wlr_xdg_toplevel_decoration_v1.` */
struct wl_listener destroy_listener;
/** Listener for `commit` of `wlr_surface::events`. */
struct wl_listener surface_commit_listener;
/** Listener for `destroy` of `wlr_surface::events`. */
struct wl_listener surface_destroy_listener;
} wlmaker_xdg_decoration_t;
static void handle_new_toplevel_decoration(
struct wl_listener *listener_ptr,
void *data_ptr);
static void handle_destroy(
struct wl_listener *listener_ptr,
void *data_ptr);
static wlmaker_xdg_decoration_t *wlmaker_xdg_decoration_create(
wlmaker_xdg_decoration_manager_t *decoration_manager_ptr,
struct wlr_xdg_toplevel_decoration_v1 *wlr_xdg_toplevel_decoration_v1_ptr);
static void wlmaker_xdg_decoration_destroy(
wlmaker_xdg_decoration_t *decoration_ptr);
static void handle_decoration_request_mode(
struct wl_listener *listener_ptr,
void *data_ptr);
static void handle_decoration_destroy(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _xdg_decoration_handle_surface_commit(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _xdg_decoration_handle_surface_destroy(
struct wl_listener *listener_ptr,
void *data_ptr);
/* == Data ================================================================= */
/** Plist descriptor of decoration mode. @see wlmaker_config_decoration_t. */
static const bspl_enum_desc_t _wlmaker_config_decoration_desc[] = {
BSPL_ENUM("SuggestClient", WLMAKER_CONFIG_DECORATION_SUGGEST_CLIENT),
BSPL_ENUM("SuggestServer", WLMAKER_CONFIG_DECORATION_SUGGEST_SERVER),
BSPL_ENUM("EnforceClient", WLMAKER_CONFIG_DECORATION_ENFORCE_CLIENT),
BSPL_ENUM("EnforceServer", WLMAKER_CONFIG_DECORATION_ENFORCE_SERVER),
BSPL_ENUM_SENTINEL()
};
/** Plist descriptor of the 'Decoration' dict contents. */
static const bspl_desc_t _wlmaker_xdg_decoration_config_desc[] = {
BSPL_DESC_ENUM("Mode", true, wlmaker_xdg_decoration_manager_t, mode, mode,
WLMAKER_CONFIG_DECORATION_SUGGEST_SERVER,
_wlmaker_config_decoration_desc),
BSPL_DESC_SENTINEL()
};
/** Name of the top-level dict holding the decoration manager's config. */
static const char *_wlmaker_xdg_decoration_dict_name = "Decoration";
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
wlmaker_xdg_decoration_manager_t *wlmaker_xdg_decoration_manager_create(
struct wl_display *wl_display_ptr,
bspl_dict_t *config_dict_ptr)
{
wlmaker_xdg_decoration_manager_t *decoration_manager_ptr = logged_calloc(
1, sizeof(wlmaker_xdg_decoration_manager_t));
if (NULL == decoration_manager_ptr) return NULL;
decoration_manager_ptr->set_mode = wlr_xdg_toplevel_decoration_v1_set_mode;
decoration_manager_ptr->wlr_xdg_decoration_manager_v1_ptr =
wlr_xdg_decoration_manager_v1_create(wl_display_ptr);
if (NULL == decoration_manager_ptr->wlr_xdg_decoration_manager_v1_ptr) {
wlmaker_xdg_decoration_manager_destroy(decoration_manager_ptr);
return NULL;
}
bspl_dict_t *decoration_dict_ptr = bspl_dict_ref(
bspl_dict_get_dict(config_dict_ptr,
_wlmaker_xdg_decoration_dict_name));
if (NULL == decoration_dict_ptr) {
bs_log(BS_ERROR, "No '%s' dict.", _wlmaker_xdg_decoration_dict_name);
wlmaker_xdg_decoration_manager_destroy(decoration_manager_ptr);
return NULL;
}
bool decoded = bspl_decode_dict(
decoration_dict_ptr,
_wlmaker_xdg_decoration_config_desc,
decoration_manager_ptr);
bspl_dict_unref(decoration_dict_ptr);
if (!decoded) {
bs_log(BS_ERROR, "Failed to decode '%s' dict",
_wlmaker_xdg_decoration_dict_name);
wlmaker_xdg_decoration_manager_destroy(decoration_manager_ptr);
return NULL;
}
wlmtk_util_connect_listener_signal(
&decoration_manager_ptr->wlr_xdg_decoration_manager_v1_ptr->
events.new_toplevel_decoration,
&decoration_manager_ptr->new_toplevel_decoration_listener,
handle_new_toplevel_decoration);
wlmtk_util_connect_listener_signal(
&decoration_manager_ptr->wlr_xdg_decoration_manager_v1_ptr->
events.destroy,
&decoration_manager_ptr->destroy_listener,
handle_destroy);
return decoration_manager_ptr;
}
/* ------------------------------------------------------------------------- */
void wlmaker_xdg_decoration_manager_destroy(
wlmaker_xdg_decoration_manager_t *decoration_manager_ptr)
{
wlmtk_util_disconnect_listener(&decoration_manager_ptr->new_toplevel_decoration_listener);
wlmtk_util_disconnect_listener(&decoration_manager_ptr->destroy_listener);
free(decoration_manager_ptr);
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/**
* Handler for the `new_toplevel_decoration` signal of
* `wlr_xdg_decoration_manager_v1`.
*
* @param listener_ptr
* @param data_ptr Points to `wlr_xdg_toplevel_decoration_v1`.
*/
void handle_new_toplevel_decoration(
struct wl_listener *listener_ptr,
void *data_ptr)
{
wlmaker_xdg_decoration_manager_t *decoration_manager_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_xdg_decoration_manager_t,
new_toplevel_decoration_listener);
struct wlr_xdg_toplevel_decoration_v1
*wlr_xdg_toplevel_decoration_v1_ptr = data_ptr;
wlmaker_xdg_decoration_t *decoration_ptr = wlmaker_xdg_decoration_create(
decoration_manager_ptr,
wlr_xdg_toplevel_decoration_v1_ptr);
if (NULL == decoration_ptr) return;
handle_decoration_request_mode(
&decoration_ptr->request_mode_listener, NULL);
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `destroy` signal of `wlr_xdg_decoration_manager_v1`.
*
* @param listener_ptr
* @param data_ptr
*/
static void handle_destroy(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_xdg_decoration_manager_t *decoration_manager_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_xdg_decoration_manager_t, destroy_listener);
free(decoration_manager_ptr);
}
/* ------------------------------------------------------------------------- */
/**
* Creates a decoration handle.
*
* @param decoration_manager_ptr
* @param wlr_xdg_toplevel_decoration_v1_ptr
*
* @returns The decoration handle or NULL on error.
*/
wlmaker_xdg_decoration_t *wlmaker_xdg_decoration_create(
wlmaker_xdg_decoration_manager_t *decoration_manager_ptr,
struct wlr_xdg_toplevel_decoration_v1 *wlr_xdg_toplevel_decoration_v1_ptr)
{
wlmaker_xdg_decoration_t *decoration_ptr = logged_calloc(
1, sizeof(wlmaker_xdg_decoration_t));
if (NULL == decoration_ptr) return NULL;
decoration_ptr->decoration_manager_ptr = decoration_manager_ptr;
decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr =
wlr_xdg_toplevel_decoration_v1_ptr;
wlmtk_util_connect_listener_signal(
&decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->events.destroy,
&decoration_ptr->destroy_listener,
handle_decoration_destroy);
wlmtk_util_connect_listener_signal(
&decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->events.request_mode,
&decoration_ptr->request_mode_listener,
handle_decoration_request_mode);
struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr =
decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->toplevel;
wlmtk_util_connect_listener_signal(
&wlr_xdg_toplevel_ptr->base->surface->events.commit,
&decoration_ptr->surface_commit_listener,
_xdg_decoration_handle_surface_commit);
wlmtk_util_connect_listener_signal(
&wlr_xdg_toplevel_ptr->base->surface->events.destroy,
&decoration_ptr->surface_destroy_listener,
_xdg_decoration_handle_surface_destroy);
return decoration_ptr;
}
/* ------------------------------------------------------------------------- */
/**
* Destroys the decoration handle.
*
* @param decoration_ptr
*/
void wlmaker_xdg_decoration_destroy(wlmaker_xdg_decoration_t *decoration_ptr)
{
_xdg_decoration_handle_surface_destroy(
&decoration_ptr->surface_destroy_listener, NULL);
wlmtk_util_disconnect_listener(&decoration_ptr->destroy_listener);
wlmtk_util_disconnect_listener(&decoration_ptr->request_mode_listener);
free(decoration_ptr);
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `request_mode` signal of `wlr_xdg_toplevel_decoration_v1`.
*
* @param listener_ptr
* @param data_ptr
*/
void handle_decoration_request_mode(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_xdg_decoration_t *decoration_ptr = wl_container_of(
listener_ptr, decoration_ptr, request_mode_listener);
struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr =
decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->toplevel;
enum wlr_xdg_toplevel_decoration_v1_mode mode =
decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->requested_mode;
switch (decoration_ptr->decoration_manager_ptr->mode) {
case WLMAKER_CONFIG_DECORATION_SUGGEST_CLIENT:
if (WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE == mode) {
mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
}
break;
case WLMAKER_CONFIG_DECORATION_SUGGEST_SERVER:
if (WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE == mode) {
mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
}
break;
case WLMAKER_CONFIG_DECORATION_ENFORCE_CLIENT:
mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
break;
case WLMAKER_CONFIG_DECORATION_ENFORCE_SERVER:
mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
break;
default:
bs_log(BS_FATAL, "Unhandled config_decoration value %d",
decoration_ptr->decoration_manager_ptr->mode);
BS_ABORT();
}
if (wlr_xdg_toplevel_ptr->base->initialized) {
decoration_ptr->decoration_manager_ptr->set_mode(
decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr, mode);
}
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr =
wlr_xdg_toplevel_ptr->base->data;
if (NULL == wlmaker_xdg_toplevel_ptr) {
bs_log(BS_WARNING,
"Decoration request for XDG toplevel %p w/o handle?",
wlr_xdg_toplevel_ptr);
return;
}
bs_log(BS_INFO, "XDG decoration request_mode for XDG surface %p, "
"XDG toplevel handle %p: Current %d, pending %d, scheduled %d, "
"requested %d. Set: %d",
wlr_xdg_toplevel_ptr->base->surface,
wlmaker_xdg_toplevel_ptr,
decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->current.mode,
decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->pending.mode,
decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->scheduled_mode,
decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->requested_mode,
mode);
wlmaker_xdg_toplevel_set_server_side_decorated(
wlmaker_xdg_toplevel_ptr,
mode != WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `destroy` signal of `wlr_xdg_toplevel_decoration_v1`.
*
* @param listener_ptr
* @param data_ptr
*/
void handle_decoration_destroy(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_xdg_decoration_t *decoration_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_xdg_decoration_t, destroy_listener);
wlmaker_xdg_decoration_destroy(decoration_ptr);
}
/* ------------------------------------------------------------------------- */
/**
* Handles surface commit: If initialized, set_mode and unsubscribe.
*
* @param listener_ptr
* @param data_ptr
*/
void _xdg_decoration_handle_surface_commit(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_xdg_decoration_t *decoration_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_xdg_decoration_t, surface_commit_listener);
struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr =
decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->toplevel;
if (!wlr_xdg_toplevel_ptr->base->initialized) return;
// Initialized! Unsubscribe from surface, and trigger a request_mode.
_xdg_decoration_handle_surface_destroy(
&decoration_ptr->surface_destroy_listener, NULL);
handle_decoration_request_mode(
&decoration_ptr->request_mode_listener, NULL);
}
/* ------------------------------------------------------------------------- */
/**
* Handles surface destroy: Unsubscribe surface listeners.
*
* @param listener_ptr
* @param data_ptr
*/
void _xdg_decoration_handle_surface_destroy(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_xdg_decoration_t *decoration_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_xdg_decoration_t, surface_destroy_listener);
wlmtk_util_disconnect_listener(&decoration_ptr->surface_commit_listener);
wlmtk_util_disconnect_listener(&decoration_ptr->surface_destroy_listener);
}
/* == Unit tests =========================================================== */
static void test_manager(bs_test_t *test_ptr);
static void test_decoration_initialized(bs_test_t *test_ptr);
static void test_decoration_uninitialized(bs_test_t *test_ptr);
/** Unit test cases. */
static const bs_test_case_t wlmaker_xdg_decoration_test_cases[] = {
{ true, "manager", test_manager },
{ true, "decoration_initialized", test_decoration_initialized },
{ true, "decoration_uninitialized", test_decoration_uninitialized },
BS_TEST_CASE_SENTINEL()
};
const bs_test_set_t wlmaker_xdg_decoration_test_set = BS_TEST_SET(
true, "xdg_decoration", wlmaker_xdg_decoration_test_cases);
/** Argument to injected set_mode. */
struct _xdg_decoration_test_arg {
/** The decoration handle. */
struct wlr_xdg_toplevel_decoration_v1 decoration;
/** Counter for calls to wlmaker_xdg_decoration_manager_t::set_mode. */
int set_mode_calls;
/** Last `mode` arg to wlmaker_xdg_decoration_manager_t::set_mode. */
enum wlr_xdg_toplevel_decoration_v1_mode set_mode_arg;
};
/** Injected method, for wlr_xdg_toplevel_decoration_v1_set_mode(). */
static uint32_t _xdg_decoration_fake_set_mode(
__UNUSED__ struct wlr_xdg_toplevel_decoration_v1 *decoration_ptr,
enum wlr_xdg_toplevel_decoration_v1_mode mode)
{
struct _xdg_decoration_test_arg *arg_ptr = BS_CONTAINER_OF(
decoration_ptr, struct _xdg_decoration_test_arg, decoration);
++arg_ptr->set_mode_calls;
arg_ptr->set_mode_arg = mode;
return 0;
}
/* ------------------------------------------------------------------------- */
/** Setup and teardown of XDG decoration manager. */
void test_manager(bs_test_t *test_ptr)
{
static const char *c = "{ Decoration = { Mode = SuggestClient }}";
struct wl_display *wl_display_ptr = wl_display_create();
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wl_display_ptr);
bspl_object_t *o = bspl_create_object_from_plist_string(c);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o);
wlmaker_xdg_decoration_manager_t *d =
wlmaker_xdg_decoration_manager_create(
wl_display_ptr, bspl_dict_from_object(o));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, d);
wlmaker_xdg_decoration_manager_destroy(d);
bspl_object_unref(o);
wl_display_destroy(wl_display_ptr);
}
/* ------------------------------------------------------------------------- */
/** Test decoration for an initialized surface. */
void test_decoration_initialized(bs_test_t *test_ptr)
{
wlmaker_xdg_decoration_manager_t m = {
.mode = WLMAKER_CONFIG_DECORATION_SUGGEST_CLIENT,
.set_mode = _xdg_decoration_fake_set_mode
};
struct wlr_surface ws = {};
wl_signal_init(&ws.events.commit);
wl_signal_init(&ws.events.destroy);
struct wlr_xdg_surface s = { .initialized = true, .surface = &ws };
struct wlr_xdg_toplevel tl = { .base = &s };
struct _xdg_decoration_test_arg t = { .decoration = { .toplevel = &tl } };
wl_signal_init(&t.decoration.events.destroy);
wl_signal_init(&t.decoration.events.request_mode);
// New decoration: Set_mode right away.
handle_new_toplevel_decoration(
&m.new_toplevel_decoration_listener,
&t.decoration);
BS_TEST_VERIFY_EQ(test_ptr, 1, t.set_mode_calls);
BS_TEST_VERIFY_EQ(
test_ptr,
WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE,
t.set_mode_arg);
// Upon request_mode: Respond with set_mode.
wl_signal_emit(&t.decoration.events.request_mode, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 2, t.set_mode_calls);
BS_TEST_VERIFY_EQ(
test_ptr,
WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE,
t.set_mode_arg);
// Client-side mode is kept.
t.decoration.requested_mode =
WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
wl_signal_emit(&t.decoration.events.request_mode, NULL);
BS_TEST_VERIFY_EQ(
test_ptr,
WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE,
t.set_mode_arg);
// Server-side mode is kept, too.
t.decoration.requested_mode =
WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
wl_signal_emit(&t.decoration.events.request_mode, NULL);
BS_TEST_VERIFY_EQ(
test_ptr,
WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE,
t.set_mode_arg);
wl_signal_emit(&t.decoration.events.destroy, NULL);
}
/* ------------------------------------------------------------------------- */
/** Test decoration for an uninitialized surface. */
void test_decoration_uninitialized(bs_test_t *test_ptr)
{
wlmaker_xdg_decoration_manager_t m = {
.mode = WLMAKER_CONFIG_DECORATION_SUGGEST_CLIENT,
.set_mode = _xdg_decoration_fake_set_mode
};
struct wlr_surface ws = {};
wl_signal_init(&ws.events.commit);
wl_signal_init(&ws.events.destroy);
struct wlr_xdg_surface s = { .initialized = false, .surface = &ws };
struct wlr_xdg_toplevel tl = { .base = &s };
struct _xdg_decoration_test_arg t = { .decoration = { .toplevel = &tl } };
wl_signal_init(&t.decoration.events.destroy);
wl_signal_init(&t.decoration.events.request_mode);
t.decoration.requested_mode =
WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
// New decoration: Do not set_mode right away.
handle_new_toplevel_decoration(
&m.new_toplevel_decoration_listener,
&t.decoration);
BS_TEST_VERIFY_EQ(test_ptr, 0, t.set_mode_calls);
// A surface commit, but still not initialized: Keep.
wl_signal_emit(&ws.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 0, t.set_mode_calls);
// Set to initialized. A surface commit triggers set_mode.
s.initialized = true;
wl_signal_emit(&ws.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 1, t.set_mode_calls);
BS_TEST_VERIFY_EQ(
test_ptr,
WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE,
t.set_mode_arg);
wl_signal_emit(&t.decoration.events.destroy, NULL);
// Reset surface. Not initialized. A request_mode won't set_mode.
t.set_mode_calls = 0;
s.initialized = false;
handle_new_toplevel_decoration(
&m.new_toplevel_decoration_listener,
&t.decoration);
wl_signal_emit(&t.decoration.events.request_mode, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 0, t.set_mode_calls);
// A surface commit, but still not initialized: Keep.
wl_signal_emit(&ws.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 0, t.set_mode_calls);
// Set to initialized. A surface commit triggers set_mode.
s.initialized = true;
wl_signal_emit(&ws.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 1, t.set_mode_calls);
BS_TEST_VERIFY_EQ(
test_ptr,
WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE,
t.set_mode_arg);
wl_signal_emit(&t.decoration.events.destroy, NULL);
}
/* == End of xdg_decoration.c ============================================== */
wlmaker-0.8.1/src/wlmaker.c 0000644 0001751 0001751 00000041055 15222270710 015170 0 ustar runner runner /* ========================================================================= */
/**
* @file wlmaker.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// setenv() is a POSIX extension.
#define _POSIX_C_SOURCE 200112L
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/// Use non-stable features of wlroots.
#define WLR_USE_UNSTABLE
#include
#undef WLR_USE_UNSTABLE
#include "../share/theme.h" // IWYU pragma: keep
#include "action.h"
#include "backend/backend.h"
#include "background.h"
#include "wlmbacktrace.h"
#include "clip.h"
#include "config.h"
#include "dock.h"
#include "files.h"
#include "root_menu.h"
#include "server.h"
#include "task_list.h"
#include "toolkit/toolkit.h"
/** Will hold the value of --config_file. */
static char *wlmaker_arg_config_file_ptr = NULL;
/** Will hold the value of --state_file. */
static char *wlmaker_arg_state_file_ptr = NULL;
/** Will hold the value of --theme_file. */
static char *wlmaker_arg_theme_file_ptr = NULL;
/** Will hold the value of --root_menu_file. */
static char *wlmaker_arg_root_menu_file_ptr = NULL;
#if !defined(WLMAKER_VERSION_MAJOR) || !defined(WLMAKER_VERSION_MINOR) || !defined(WLMAKER_VERSION_FULL)
#eror "WLMAKER_VERSION_... not defined!"
#else
static const char *wlmaker_version_major = WLMAKER_VERSION_MAJOR;
static const char *wlmaker_version_minor = WLMAKER_VERSION_MINOR;
static const char *wlmaker_version_full = WLMAKER_VERSION_FULL;
#endif
/** Startup options for the server. */
static wlmaker_server_options_t wlmaker_server_options = {
.start_xwayland = false,
.width = 0,
.height = 0,
.bind_with_logo = false,
};
/** Log levels. */
static const bs_arg_enum_table_t wlmaker_log_levels[] = {
{ .name_ptr = "DEBUG", BS_DEBUG },
{ .name_ptr = "INFO", BS_INFO },
{ .name_ptr = "WARNING", BS_WARNING },
{ .name_ptr = "ERROR", BS_ERROR },
{ .name_ptr = NULL },
};
/** Definition of commandline arguments. */
static const bs_arg_t wlmaker_args[] = {
#if defined(WLMAKER_HAVE_XWAYLAND)
BS_ARG_BOOL(
"start_xwayland",
"Optional: Whether to start XWayland. Disabled by default.",
false,
&wlmaker_server_options.start_xwayland),
#endif // defined(WLMAKER_HAVE_XWAYLAND)
BS_ARG_STRING(
"config_file",
"Optional: Path to a configuration file. If not provided, wlmaker "
"will scan default paths for a configuration file, or fall back to "
"a built-in configuration.",
NULL,
&wlmaker_arg_config_file_ptr),
BS_ARG_STRING(
"state_file",
"Optional: Path to a state file, with state of workspaces, dock and "
"clips configured. If not provided, wlmaker will scan default paths "
"for a state file, or fall back to a built-in default.",
NULL,
&wlmaker_arg_state_file_ptr),
BS_ARG_STRING(
"theme_file",
"Optional: Path to a \"theme\" file, configuring the visual style for "
"elements. If not provided, wlmaker will use a built-in default theme.",
NULL,
&wlmaker_arg_theme_file_ptr),
BS_ARG_STRING(
"root_menu_file",
"Optional: Path to a file describing the root menu. If not provided, "
"wlmaker will use a built-in definition for the root menu.",
NULL,
&wlmaker_arg_root_menu_file_ptr),
BS_ARG_ENUM(
"log_level",
"Log level to apply. One of DEBUG, INFO, WARNING, ERROR.",
"INFO",
&wlmaker_log_levels[0],
(int*)&bs_log_severity),
BS_ARG_BOOL(
"bind_with_logo",
"Optional: Whether to add 'Logo' as modifier to each key binding. "
"Useful to test key bindings in wlmaker when running as a Wayland "
"or X11 client. Disabled by default.",
false,
&wlmaker_server_options.bind_with_logo),
BS_ARG_UINT32(
"height",
"Desired output height. Applies when running in windowed mode, and "
"only if --width is set, too. Set to 0 for using the output's "
"preferred dimensions.",
0, 0, UINT32_MAX,
&wlmaker_server_options.height),
BS_ARG_UINT32(
"width",
"Desired output width. Applies when running in windowed mode, and "
"only if --height is set, too. Set to 0 for using the output's "
"preferred dimensions.",
0, 0, UINT32_MAX,
&wlmaker_server_options.width),
BS_ARG_SENTINEL()
};
/** References auto-started subprocesses. */
static bs_ptr_stack_t wlmaker_subprocess_stack;
/** Compiled regular expression for extracting file & line no. from wlr_log. */
static regex_t wlmaker_wlr_log_regex;
/** Regular expression string for extracting file & line no. from wlr_log. */
static const char *wlmaker_wlr_log_regex_string =
"^\\[([^\\:]+)\\:([0-9]+)\\]\\ ";
/** Contents of the workspace style. */
typedef struct {
/** Workspace name. */
char name[32];
/** Background color. */
uint32_t color;
} wlmaker_workspace_style_t;
/** Style descriptor for the "Workspace" dict of wlmaker-state.plist. */
static const bspl_desc_t wlmaker_workspace_style_desc[] = {
BSPL_DESC_CHARBUF(
"Name", true, wlmaker_workspace_style_t, name, name, 32, NULL),
BSPL_DESC_ARGB32(
"Color", false, wlmaker_workspace_style_t, color, color, 0),
BSPL_DESC_SENTINEL()
};
/* ------------------------------------------------------------------------- */
/**
* Wraps the wlr_log calls on bs_log.
*
* @param importance
* @param fmt
* @param args
*/
static void wlr_to_bs_log(
enum wlr_log_importance importance,
const char *fmt,
va_list args)
{
bs_log_severity_t severity = BS_DEBUG;
switch (importance) {
case WLR_SILENT: // Fall-through to DEBUG severity.
case WLR_DEBUG: severity = BS_DEBUG; break;
case WLR_INFO: severity = BS_INFO; break;
case WLR_ERROR: severity = BS_ERROR; break;
default: severity = BS_INFO; break;
}
if (!bs_will_log(severity)) return;
// Log to buffer. Ignores overflows.
char buf[BS_LOG_MAX_BUF_SIZE];
vsnprintf(buf, sizeof(buf), fmt, args);
regmatch_t matches[4];
if (0 != regexec(&wlmaker_wlr_log_regex, buf, 4, &matches[0], 0) ||
matches[0].rm_so != 0 ||
!(matches[0].rm_eo >= 6) || // Minimum "[x:1] ".
matches[1].rm_so != 1 ||
!(matches[2].rm_so > 2) ||
matches[3].rm_so != -1) {
bs_log(severity, "%s (wlr_log unexpected format!)", buf);
return;
}
buf[matches[1].rm_eo] = '\0';
buf[matches[2].rm_eo] = '\0';
uint64_t line_no = 0;
bs_strconvert_uint64(&buf[matches[2].rm_so], &line_no, 10);
line_no = BS_MIN((uint64_t)INT_MAX, line_no);
bs_log_write(severity, &buf[matches[1].rm_so], (int)line_no, "%s",
&buf[matches[0].rm_eo]);
}
/* ------------------------------------------------------------------------- */
/** Launches a sub-process, and keeps it on the subprocess stack. */
bool start_subprocess(const char *cmdline_ptr)
{
bs_subprocess_t *sp_ptr = bs_subprocess_create_cmdline(cmdline_ptr);
if (NULL == sp_ptr) {
bs_log(BS_ERROR, "Failed bs_subprocess_create_cmdline(\"%s\")",
cmdline_ptr);
return false;
}
if (!bs_subprocess_start(sp_ptr)) {
bs_log(BS_ERROR, "Failed bs_subprocess_start for \"%s\"",
cmdline_ptr);
return false;
}
// Push to stack. Ignore errors: We'll keep it running untracked.
bs_ptr_stack_push(&wlmaker_subprocess_stack, sp_ptr);
return true;
}
/* ------------------------------------------------------------------------- */
/** Creates workspaces as configured in the state dict. */
bool create_workspaces(
bspl_dict_t *state_dict_ptr,
wlmaker_server_t *server_ptr)
{
bspl_array_t *array_ptr = bspl_dict_get_array(
state_dict_ptr, "Workspaces");
if (NULL == array_ptr) return false;
bool rv = true;
for (size_t i = 0; i < bspl_array_size(array_ptr); ++i) {
bspl_dict_t *dict_ptr = bspl_dict_from_object(
bspl_array_at(array_ptr, i));
if (NULL == dict_ptr) {
bs_log(BS_ERROR, "Array element in \"Workspaces\" is not a dict");
rv = false;
break;
}
wlmaker_workspace_style_t s;
if (!bspl_decode_dict(dict_ptr, wlmaker_workspace_style_desc, &s)) {
bs_log(BS_ERROR,
"Failed to decode dict element %zu in \"Workspace\"",
i);
rv = false;
break;
}
wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_create(
server_ptr->wlr_output_layout_ptr,
s.name,
&server_ptr->style_ptr->tile);
if (NULL == workspace_ptr) {
bs_log(BS_ERROR, "Failed wlmtk_workspace_create(\"%s\")", s.name);
rv = false;
break;
}
if (s.color == 0) {
s.color = server_ptr->style_ptr->background_color;
}
bs_dllist_node_t *bg_dlnode_ptr = wlmaker_background_create(
workspace_ptr,
server_ptr->wlr_output_layout_ptr,
s.color);
if (NULL == bg_dlnode_ptr) {
bs_log(BS_ERROR, "Failed wlmaker_background()");
rv = false;
break;
}
bs_dllist_push_back(&server_ptr->backgrounds, bg_dlnode_ptr);
wlmtk_root_add_workspace(server_ptr->root_ptr, workspace_ptr);
}
return rv;
}
/* == Main program ========================================================= */
/** The main program. */
int main(__UNUSED__ int argc, __UNUSED__ const char **argv)
{
wlmaker_dock_t *dock_ptr = NULL;
wlmaker_clip_t *clip_ptr = NULL;
wlmaker_task_list_t *task_list_ptr = NULL;
int rv = EXIT_SUCCESS;
if (!wlmaker_backtrace_setup(argv[0])) return EXIT_FAILURE;
rv = regcomp(
&wlmaker_wlr_log_regex,
wlmaker_wlr_log_regex_string,
REG_EXTENDED);
if (0 != rv) {
char err_buf[512];
regerror(rv, &wlmaker_wlr_log_regex, err_buf, sizeof(err_buf));
bs_log(BS_ERROR, "Failed compiling regular expression: %s", err_buf);
return EXIT_FAILURE;
}
wlr_log_init(WLR_DEBUG, wlr_to_bs_log);
bs_log_severity = BS_INFO; // Will be overwritten in bs_arg_parse().
BS_ASSERT(bs_ptr_stack_init(&wlmaker_subprocess_stack));
if (!bs_arg_parse(wlmaker_args, BS_ARG_MODE_EXTRA_ARGS, &argc, argv)) {
fprintf(stderr, "Failed to parse commandline arguments.\n");
bs_arg_print_usage(stderr, wlmaker_args);
return EXIT_FAILURE;
}
for (int i = 1; i < argc; ++i) {
if (0 == strcmp(argv[i], "--help")) {
bs_arg_print_usage(stdout, wlmaker_args);
return EXIT_SUCCESS;
} else if (0 == strcmp(argv[i], "--version")) {
fprintf(stdout, "wlmaker version %s.%s (%s)\n",
wlmaker_version_major,
wlmaker_version_minor,
wlmaker_version_full);
return EXIT_SUCCESS;
} else {
bs_log(BS_ERROR, "Unhandled extra argument \"%s\"", argv[i]);
return EXIT_FAILURE;
}
}
bs_log(BS_INFO, "Starting wlmaker %s.%s (%s)",
wlmaker_version_major,
wlmaker_version_minor,
wlmaker_version_full);
wlmaker_files_t *files_ptr = wlmaker_files_create("wlmaker");
if (NULL == files_ptr) {
bs_log(BS_ERROR, "Failed wlmaker_files_create(\"wlmaker\")");
return EXIT_FAILURE;
}
bspl_dict_t *config_dict_ptr = wlmaker_config_load(
files_ptr, wlmaker_arg_config_file_ptr);
if (NULL != wlmaker_arg_config_file_ptr) free(wlmaker_arg_config_file_ptr);
if (NULL == config_dict_ptr) {
fprintf(stderr, "Failed to load & initialize configuration.\n");
return EXIT_FAILURE;
}
bspl_dict_t *state_dict_ptr = wlmaker_state_load(
files_ptr, wlmaker_arg_state_file_ptr);
if (NULL != wlmaker_arg_state_file_ptr) free(wlmaker_arg_state_file_ptr);
if (NULL == state_dict_ptr) {
fprintf(stderr, "Failed to load & initialize state.\n");
return EXIT_FAILURE;
}
const char *theme_file_ptr = wlmaker_arg_theme_file_ptr;
if (NULL == theme_file_ptr) {
theme_file_ptr = bspl_dict_get_string_value(
config_dict_ptr, "ThemeFile");
}
wlmaker_config_style_t style = {};
if (!wlmaker_theme_load(files_ptr, theme_file_ptr, &style)) {
fprintf(stderr, "Failed to load & initialize theme.\n");
return EXIT_FAILURE;
}
wlmaker_server_t *server_ptr = wlmaker_server_create(
config_dict_ptr, files_ptr, &style, &wlmaker_server_options);
if (NULL == server_ptr) return EXIT_FAILURE;
// TODO(kaeser@gubbe.ch): Uh, that's ugly...
server_ptr->root_menu_ptr = wlmaker_root_menu_create(
server_ptr,
wlmaker_arg_root_menu_file_ptr,
wlmtk_window_style_to_ref(server_ptr->style_ptr->window_style_ptr),
wlmtk_menu_style_to_ref(server_ptr->style_ptr->menu_style_ptr));
if (NULL == server_ptr->root_menu_ptr) {
return EXIT_FAILURE;
}
wlmtk_menu_set_open(
wlmaker_root_menu_menu(server_ptr->root_menu_ptr),
false);
wlmaker_action_handle_t *action_handle_ptr = wlmaker_action_bind_keys(
server_ptr,
bspl_dict_get_dict(config_dict_ptr, wlmaker_action_config_dict_key),
wlmaker_server_options.bind_with_logo);
if (NULL == action_handle_ptr) {
bs_log(BS_ERROR, "Failed to bind keys.");
return EXIT_FAILURE;
}
if (!create_workspaces(state_dict_ptr, server_ptr)) {
return EXIT_FAILURE;
}
rv = EXIT_SUCCESS;
if (wlr_backend_start(wlmbe_backend_wlr(server_ptr->backend_ptr))) {
if (0 >= wlmbe_num_outputs(server_ptr->wlr_output_layout_ptr)) {
bs_log(BS_ERROR, "No outputs available!");
return EXIT_FAILURE;
}
bs_log(BS_INFO, "Starting Wayland compositor for server %p at %s ...",
server_ptr, server_ptr->wl_socket_name_ptr);
setenv("WAYLAND_DISPLAY", server_ptr->wl_socket_name_ptr, true);
bspl_array_t *autostarted_ptr = bspl_dict_get_array(
config_dict_ptr, "Autostart");
if (NULL != autostarted_ptr) {
for (size_t i = 0; i < bspl_array_size(autostarted_ptr); ++i) {
const char *cmd_ptr = bspl_array_string_value_at(
autostarted_ptr, i);
if (!start_subprocess(cmd_ptr)) return EXIT_FAILURE;
}
}
dock_ptr = wlmaker_dock_create(
server_ptr, state_dict_ptr, &style);
clip_ptr = wlmaker_clip_create(
server_ptr, state_dict_ptr, &style);
task_list_ptr = wlmaker_task_list_create(
server_ptr, &style.task_list);
if (NULL == dock_ptr || NULL == clip_ptr || NULL == task_list_ptr) {
bs_log(BS_ERROR, "Failed to create dock, clip or task list.");
} else {
wl_display_run(server_ptr->wl_display_ptr);
}
} else {
bs_log(BS_ERROR, "Failed wlmaker_server_start()");
rv = EXIT_FAILURE;
}
if (NULL != task_list_ptr) wlmaker_task_list_destroy(task_list_ptr);
if (NULL != clip_ptr) wlmaker_clip_destroy(clip_ptr);
if (NULL != dock_ptr) wlmaker_dock_destroy(dock_ptr);
wlmaker_action_unbind_keys(action_handle_ptr);
bspl_array_unref(server_ptr->root_menu_array_ptr);
wlmaker_server_destroy(server_ptr);
bs_subprocess_t *sp_ptr;
while (NULL != (sp_ptr = bs_ptr_stack_pop(&wlmaker_subprocess_stack))) {
bs_subprocess_destroy(sp_ptr);
}
bs_ptr_stack_fini(&wlmaker_subprocess_stack);
bspl_decoded_destroy(wlmaker_config_style_desc, &style);
bspl_dict_unref(config_dict_ptr);
bspl_dict_unref(state_dict_ptr);
regfree(&wlmaker_wlr_log_regex);
return rv;
}
/* == End of wlmaker.c ===================================================== */
wlmaker-0.8.1/src/idle.c 0000644 0001751 0001751 00000035710 15222270710 014444 0 ustar runner runner /* ========================================================================= */
/**
* @file idle.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "idle.h"
#include
#include
#include
#include
#include
#include
#include
#define WLR_USE_UNSTABLE
#include
#undef WLR_USE_UNSTABLE
#include "subprocess_monitor.h"
#include "toolkit/toolkit.h"
#include "server.h"
/* == Declarations ========================================================= */
/** Forward declaration: Handle of an idle inhibitor. */
typedef struct _wlmaker_idle_inhibitor_t wlmaker_idle_inhibitor_t;
/** State of the idle monitor. */
struct _wlmaker_idle_monitor_t {
/** Back-link to the server. */
wlmaker_server_t *server_ptr;
/** Dictionnary holding the 'ScreenLock' configuration. */
bspl_dict_t *lock_config_dict_ptr;
/** Reference to the event loop. */
struct wl_event_loop *wl_event_loop_ptr;
/** The timer's event source. */
struct wl_event_source *timer_event_source_ptr;
/** Whether the timer expired. Reset in @ref wlmaker_idle_monitor_reset. */
bool timer_expired;
/** Listener for `new_inhibitor` of wlr_idle_inhibit_manager_v1`. */
struct wl_listener new_inhibitor_listener;
/** Lists registered inhibitors: @ref wlmaker_idle_inhibitor_t::dlnode. */
bs_dllist_t idle_inhibitors;
/**
* Counter for inhibits. Timer-triggered locks are taking effect only
* when inhibits == 0.
*/
int inhibits;
/** Listener for @ref wlmtk_root_events_t::unlock_event. */
struct wl_listener unlock_listener;
/** The wlroots idle inhibit manager. */
struct wlr_idle_inhibit_manager_v1 *wlr_idle_inhibit_manager_v1_ptr;
/** Whether the idle monitor is locked. Prevents timer registry. */
bool locked;
};
/** State of an idle inhibitor. */
struct _wlmaker_idle_inhibitor_t {
/** Back-link to the idle monitor. */
wlmaker_idle_monitor_t *idle_monitor_ptr;
/** The idle inhibitor tied to this inhibitor. */
struct wlr_idle_inhibitor_v1 *wlr_idle_inhibitor_v1_ptr;
/** List node, part of @ref wlmaker_idle_monitor_t::idle_inhibitors. */
bs_dllist_node_t dlnode;
/** Listener for the `destroy` signal of `wlr_idle_inhibitor_v1`. */
struct wl_listener destroy_listener;
};
static void _wlmaker_idle_monitor_consider_locking(
wlmaker_idle_monitor_t *idle_monitor_ptr);
static int _wlmaker_idle_monitor_timer(void *data_ptr);
static int _wlmaker_idle_msec(wlmaker_idle_monitor_t *idle_monitor_ptr);
static bool _wlmaker_idle_monitor_add_inhibitor(
wlmaker_idle_monitor_t *idle_monitor_ptr,
struct wlr_idle_inhibitor_v1 *wlr_idle_inhibitor_v1_ptr);
static void _wlmaker_idle_monitor_handle_destroy_inhibitor(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_idle_monitor_handle_new_inhibitor(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_idle_monitor_handle_unlock(
struct wl_listener *listener_ptr,
void *data_ptr);
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
wlmaker_idle_monitor_t *wlmaker_idle_monitor_create(
wlmaker_server_t *server_ptr)
{
wlmaker_idle_monitor_t *monitor_ptr = logged_calloc(
1, sizeof(wlmaker_idle_monitor_t));
if (NULL == monitor_ptr) return NULL;
monitor_ptr->server_ptr = server_ptr;
monitor_ptr->wl_event_loop_ptr = wl_display_get_event_loop(
server_ptr->wl_display_ptr);
monitor_ptr->lock_config_dict_ptr = bspl_dict_ref(
bspl_dict_get_dict(server_ptr->config_dict_ptr, "ScreenLock"));
if (NULL == monitor_ptr->lock_config_dict_ptr) {
bs_log(BS_ERROR, "No 'ScreenLock' dict found in config.");
wlmaker_idle_monitor_destroy(monitor_ptr);
return NULL;
}
monitor_ptr->wlr_idle_inhibit_manager_v1_ptr =
wlr_idle_inhibit_v1_create(server_ptr->wl_display_ptr);
if (NULL == monitor_ptr->wlr_idle_inhibit_manager_v1_ptr) {
bs_log(BS_ERROR, "Failed wlr_idle_inhibit_v1_create(%p)",
server_ptr->wl_display_ptr);
wlmaker_idle_monitor_destroy(monitor_ptr);
return NULL;
}
wlmtk_util_connect_listener_signal(
&monitor_ptr->wlr_idle_inhibit_manager_v1_ptr->events.new_inhibitor,
&monitor_ptr->new_inhibitor_listener,
_wlmaker_idle_monitor_handle_new_inhibitor);
monitor_ptr->timer_event_source_ptr = wl_event_loop_add_timer(
monitor_ptr->wl_event_loop_ptr,
_wlmaker_idle_monitor_timer,
monitor_ptr);
if (NULL == monitor_ptr->timer_event_source_ptr) {
bs_log(BS_ERROR, "Failed wl_event_loop_add_timer(%p, %p, %p)",
monitor_ptr->wl_event_loop_ptr,
_wlmaker_idle_monitor_timer,
monitor_ptr);
wlmaker_idle_monitor_destroy(monitor_ptr);
return NULL;
}
if (0 != wl_event_source_timer_update(
monitor_ptr->timer_event_source_ptr,
_wlmaker_idle_msec(monitor_ptr))) {
bs_log(BS_ERROR, "Failed wl_event_source_timer_update(%p, 1000)",
monitor_ptr->timer_event_source_ptr);
wlmaker_idle_monitor_destroy(monitor_ptr);
return NULL;
}
return monitor_ptr;
}
/* ------------------------------------------------------------------------- */
void wlmaker_idle_monitor_destroy(wlmaker_idle_monitor_t *idle_monitor_ptr)
{
if (NULL != idle_monitor_ptr->unlock_listener.link.prev) {
wlmtk_util_disconnect_listener(&idle_monitor_ptr->unlock_listener);
}
if (NULL != idle_monitor_ptr->timer_event_source_ptr) {
wl_event_source_remove(idle_monitor_ptr->timer_event_source_ptr);
idle_monitor_ptr->timer_event_source_ptr = NULL;
}
if (NULL != idle_monitor_ptr->lock_config_dict_ptr) {
bspl_dict_unref(idle_monitor_ptr->lock_config_dict_ptr);
idle_monitor_ptr->lock_config_dict_ptr = NULL;
}
// Note: The idle inhibit manager does not have a dtor.
free(idle_monitor_ptr);
}
/* ------------------------------------------------------------------------- */
void wlmaker_idle_monitor_reset(wlmaker_idle_monitor_t *idle_monitor_ptr)
{
if (idle_monitor_ptr->locked) return;
int rv = wl_event_source_timer_update(
idle_monitor_ptr->timer_event_source_ptr,
_wlmaker_idle_msec(idle_monitor_ptr));
BS_ASSERT(0 == rv);
idle_monitor_ptr->timer_expired = false;
}
/* ------------------------------------------------------------------------- */
bool wlmaker_idle_monitor_lock(wlmaker_idle_monitor_t *idle_monitor_ptr)
{
const char *cmd_ptr = bspl_dict_get_string_value(
idle_monitor_ptr->lock_config_dict_ptr, "Command");
if (NULL == cmd_ptr) {
bs_log(BS_WARNING, "No 'Command' configured in 'ScreenLock' config.");
return false;
}
bs_subprocess_t *subprocess_ptr = bs_subprocess_create_cmdline(cmd_ptr);
if (NULL == subprocess_ptr) {
bs_log(BS_WARNING, "Failed bs_subprocess_create_cmdline(\"%s\")",
cmd_ptr);
return false;
}
if (!bs_subprocess_start(subprocess_ptr)) {
bs_log(BS_WARNING, "Failed bs_subprocess_start(%p) for \"%s\".",
subprocess_ptr, cmd_ptr);
bs_subprocess_destroy(subprocess_ptr);
return false;
}
wlmaker_subprocess_handle_t *handle_ptr =
wlmaker_subprocess_monitor_entrust(
idle_monitor_ptr->server_ptr->monitor_ptr,
subprocess_ptr,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL);
if (NULL != handle_ptr) {
wlmaker_subprocess_monitor_cede(
idle_monitor_ptr->server_ptr->monitor_ptr, handle_ptr);
}
return true;
}
/* ------------------------------------------------------------------------- */
void wlmaker_idle_monitor_inhibit(wlmaker_idle_monitor_t *idle_monitor_ptr)
{
++idle_monitor_ptr->inhibits;
}
/* ------------------------------------------------------------------------- */
void wlmaker_idle_monitor_uninhibit(wlmaker_idle_monitor_t *idle_monitor_ptr)
{
BS_ASSERT(0 < idle_monitor_ptr->inhibits);
--idle_monitor_ptr->inhibits;
_wlmaker_idle_monitor_consider_locking(idle_monitor_ptr);
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/** Executes a lock, if not inhibited & timer has indeed expired. */
void _wlmaker_idle_monitor_consider_locking(
wlmaker_idle_monitor_t *idle_monitor_ptr)
{
// No locking if there's inhibitors or no expired timer.
if (0 < idle_monitor_ptr->inhibits ||
!idle_monitor_ptr->timer_expired) return;
// Lock. If there's a problem there => don't register for unlock.
if (!wlmaker_idle_monitor_lock(idle_monitor_ptr)) return;
wlmtk_root_t *root_ptr = idle_monitor_ptr->server_ptr->root_ptr;
idle_monitor_ptr->locked = true;
wlmtk_util_connect_listener_signal(
&wlmtk_root_events(root_ptr)->unlock_event,
&idle_monitor_ptr->unlock_listener,
_wlmaker_idle_monitor_handle_unlock);
}
/* ------------------------------------------------------------------------- */
/**
* Timer function for the wayland event loop.
*
* @param data_ptr Untyped pointer to @ref wlmaker_idle_monitor_t.
*
* @return Whether the event source is registered for re-check with
* wl_event_source_check(): 0 for all done, 1 for needing a re-check. If
* not registered, the return value is ignored and should be zero.
*/
int _wlmaker_idle_monitor_timer(void *data_ptr)
{
wlmaker_idle_monitor_t *idle_monitor_ptr = data_ptr;
idle_monitor_ptr->timer_expired = true;
_wlmaker_idle_monitor_consider_locking(idle_monitor_ptr);
return 0;
}
/* ------------------------------------------------------------------------- */
/**
* Returns the idle timeout time in milliseconds.
*
* @param idle_monitor_ptr
*
* @return The idle timeout, read from the config dictionnary. If no or a
* negative value was configured, 0 is returned, indicating the timer
* to NOT be armed.
*/
int _wlmaker_idle_msec(wlmaker_idle_monitor_t *idle_monitor_ptr)
{
const char *idle_seconds_ptr = bspl_dict_get_string_value(
idle_monitor_ptr->lock_config_dict_ptr, "IdleSeconds");
if (NULL == idle_seconds_ptr) return 0;
uint64_t seconds;
if (!bs_strconvert_uint64(idle_seconds_ptr, &seconds, 10) ||
seconds >= (INT32_MAX / 1000)) {
bs_log(BS_WARNING, "Bad value for 'IdleSeconds': %s",
idle_seconds_ptr);
return 0;
}
if (0 >= seconds) return 0;
return 1000 * seconds;
}
/* ------------------------------------------------------------------------- */
/**
* Creates and adds a new inhibitor to the monitor.
*
* @param idle_monitor_ptr
* @param wlr_idle_inhibitor_v1_ptr
*
* @return true on success.
*/
bool _wlmaker_idle_monitor_add_inhibitor(
wlmaker_idle_monitor_t *idle_monitor_ptr,
struct wlr_idle_inhibitor_v1 *wlr_idle_inhibitor_v1_ptr)
{
wlmaker_idle_inhibitor_t *idle_inhibitor_ptr = logged_calloc(
1, sizeof(wlmaker_idle_inhibitor_t));
if (NULL == idle_inhibitor_ptr) return false;
idle_inhibitor_ptr->idle_monitor_ptr = idle_monitor_ptr;
idle_inhibitor_ptr->wlr_idle_inhibitor_v1_ptr = wlr_idle_inhibitor_v1_ptr;
wlmtk_util_connect_listener_signal(
&wlr_idle_inhibitor_v1_ptr->events.destroy,
&idle_inhibitor_ptr->destroy_listener,
_wlmaker_idle_monitor_handle_destroy_inhibitor);
bs_dllist_push_back(&idle_monitor_ptr->idle_inhibitors,
&idle_inhibitor_ptr->dlnode);
wlmaker_idle_monitor_inhibit(idle_monitor_ptr);
return true;
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `destroy` signal of the inhibitor. Destroys it.
*
* @param listener_ptr
* @param data_ptr Unused.
*/
void _wlmaker_idle_monitor_handle_destroy_inhibitor(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_idle_inhibitor_t *idle_inhibitor_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_idle_inhibitor_t, destroy_listener);
wlmaker_idle_monitor_uninhibit(idle_inhibitor_ptr->idle_monitor_ptr);
bs_dllist_remove(
&idle_inhibitor_ptr->idle_monitor_ptr->idle_inhibitors,
&idle_inhibitor_ptr->dlnode);
wlmtk_util_disconnect_listener(&idle_inhibitor_ptr->destroy_listener);
free(idle_inhibitor_ptr);
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `new_inhibitor` signal of the inhibit manager: Registers
* the inhibitor.
*
* @param listener_ptr
* @param data_ptr Pointer to struct wlr_idle_inhibitor_v1.
*/
static void _wlmaker_idle_monitor_handle_new_inhibitor(
struct wl_listener *listener_ptr,
void *data_ptr)
{
wlmaker_idle_monitor_t *idle_monitor_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_idle_monitor_t, new_inhibitor_listener);
struct wlr_idle_inhibitor_v1 *wlr_idle_inhibitor_v1_ptr = data_ptr;
if (!_wlmaker_idle_monitor_add_inhibitor(
idle_monitor_ptr,
wlr_idle_inhibitor_v1_ptr)) {
wl_resource_post_error(
wlr_idle_inhibitor_v1_ptr->resource,
WL_DISPLAY_ERROR_NO_MEMORY,
"Failed _wlmaker_idle_monitor_add_inhibitor(%p, %p)",
idle_monitor_ptr,
wlr_idle_inhibitor_v1_ptr);
return;
}
}
/* ------------------------------------------------------------------------- */
/**
* Handler for @ref wlmtk_root_events_t::unlock_event. Re-arms the timer.
*
* @param listener_ptr
* @param data_ptr unused.
*/
void _wlmaker_idle_monitor_handle_unlock(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_idle_monitor_t *idle_monitor_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_idle_monitor_t, unlock_listener);
wlmtk_util_disconnect_listener(&idle_monitor_ptr->unlock_listener);
idle_monitor_ptr->locked = false;
wlmaker_idle_monitor_reset(idle_monitor_ptr);
}
/* == End of idle.c ======================================================== */
wlmaker-0.8.1/src/backtrace.c 0000644 0001751 0001751 00000007670 15222270710 015452 0 ustar runner runner /* ========================================================================= */
/**
* @file wlmbacktrace.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright (c) 2025 by Philipp Kaeser
*/
#include "wlmbacktrace.h"
#include
#include
#include
#include
#if defined(WLMAKER_HAVE_LIBBACKTRACE)
#include
#endif // defined(WLMAKER_HAVE_LIBBACKTRACE)
/* == Declarations ========================================================= */
#if defined(WLMAKER_HAVE_LIBBACKTRACE)
/** State for libbacktrace. */
static struct backtrace_state *_wlmaker_bt_state_ptr;
static void _backtrace_error_callback(
__UNUSED__ void *data_ptr,
const char *msg_ptr,
int errnum);
static int _backtrace_full_callback(
__UNUSED__ void *data,
uintptr_t pc,
const char *filename_ptr,
int line_num,
const char *function_ptr);
static void _signal_backtrace(int signum);
#endif // defined(WLMAKER_HAVE_LIBBACKTRACE)
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
bool wlmaker_backtrace_setup(const char *filename_ptr)
{
#if defined(WLMAKER_HAVE_LIBBACKTRACE)
_wlmaker_bt_state_ptr = backtrace_create_state(
filename_ptr, 0, _backtrace_error_callback, NULL);
if (NULL == _wlmaker_bt_state_ptr) {
bs_log(BS_ERROR, "Failed backtrace_create_state()");
return false;
}
signal(SIGABRT, _signal_backtrace);
signal(SIGBUS, _signal_backtrace);
signal(SIGFPE, _signal_backtrace);
signal(SIGILL, _signal_backtrace);
signal(SIGSEGV, _signal_backtrace);
#else
bs_log(BS_DEBUG, "No libbacktrace, ignoring setup for %s", filename_ptr);
#endif // defined(WLMAKER_HAVE_LIBBACKTRACE)
return true;
}
/* == Local (static) methods =============================================== */
#if defined(WLMAKER_HAVE_LIBBACKTRACE)
/* ------------------------------------------------------------------------- */
/** Error callback for libbacktrace calls. */
void _backtrace_error_callback(
__UNUSED__ void *data_ptr,
const char *msg_ptr,
int errnum)
{
bs_log_severity_t severity = BS_ERROR;
if (0 != errnum && -1 != errnum) severity |= BS_ERRNO;
bs_log(severity, "Backtrace error: %s", msg_ptr);
}
/* ------------------------------------------------------------------------- */
/** Full callback for printing a libbacktrace frame. */
int _backtrace_full_callback(
__UNUSED__ void *data,
uintptr_t program_counter,
const char *filename_ptr,
int line_num,
const char *function_ptr)
{
bs_log(BS_ERROR, "%"PRIxPTR" in %s () at %s:%d",
program_counter,
function_ptr ? function_ptr : "(unknown)",
filename_ptr ? filename_ptr : "(unknown)",
line_num);
return 0;
}
/* ------------------------------------------------------------------------- */
/** Signal handler: Prints a backtrace. */
void _signal_backtrace(int signum)
{
bs_log(BS_ERROR, "Caught signal %d", signum);
backtrace_full(
_wlmaker_bt_state_ptr, 0,
_backtrace_full_callback, _backtrace_error_callback, NULL);
signal(SIGABRT, SIG_DFL);
abort();
}
#endif // defined(WLMAKER_HAVE_LIBBACKTRACE)
/* == End of wlmbacktrace.c ================================================ */
wlmaker-0.8.1/src/files.h 0000644 0001751 0001751 00000006033 15222270710 014632 0 ustar runner runner /* ========================================================================= */
/**
* @file files.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __WLMAKER_FILES_H__
#define __WLMAKER_FILES_H__
#include
/** Handle for files module. */
typedef struct _wlmaker_files_t wlmaker_files_t;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Create the files module.
*
* @param dirname_ptr Name of a subdirectory, that will be used as a
* component for each of the XDG paths.
*
* @return Pointer to the module handle. Must be freed by calling
* @ref wlmaker_files_destroy.
*/
wlmaker_files_t *wlmaker_files_create(const char *dirname_ptr);
/**
* Destroys the file module.
*
* @param files_ptr
*/
void wlmaker_files_destroy(wlmaker_files_t *files_ptr);
/**
* Returns a full path name for a config file.
*
* This expands into ${XDG_CONFIG_HOME}/`*dirname_ptr`/`*fname_ptr`.
*
* @param files_ptr
* @param fname_ptr
*
* @return Full path name, or NULL on error.
*/
char *wlmaker_files_xdg_config_fname(
wlmaker_files_t *files_ptr,
const char *fname_ptr);
/**
* Finds a file (or directory, ...) from XDG configuration directories.
*
* Joins @ref wlmaker_files_t::dirname_ptr with `fname_ptr`, and then looks for
* that entity in `${XDG_CONFIG_HOME}` and `${XDG_CONFIG_DIRS}`. Returns true
* if the file has `mode_type`, eg. `S_IFREG` or `S_ISDIR`.
*
* @return Full path name, or NULL on error. The caller must call free() to
* release the associated memory.
*/
char *wlmaker_files_xdg_config_find(
wlmaker_files_t *files_ptr,
const char *fname_ptr,
int mode_type);
/**
* Finds a file (or directory, ...) from XDG data directories.
*
* Joins @ref wlmaker_files_t::dirname_ptr with `fname_ptr`, and then looks for
* that entity in `${XDG_DATA_HOME}` and `${XDG_DATA_DIRS}`. Returns true
* if the file has `mode_type`, eg. `S_IFREG` or `S_ISDIR`.
*
* @return Full path name, or NULL on error. The caller must call free() to
* release the associated memory.
*/
char *wlmaker_files_xdg_data_find(
wlmaker_files_t *files_ptr,
const char *fname_ptr,
int mode_type);
/** Unit test set for @ref wlmaker_files_t. */
extern const bs_test_set_t wlmaker_files_test_set;
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __WLMAKER_FILES_H__ */
/* == End of files.h ======================================================= */
wlmaker-0.8.1/src/task_list.c 0000644 0001751 0001751 00000045172 15222270710 015527 0 ustar runner runner /* ========================================================================= */
/**
* @file task_list.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// for PATH_MAX.
#define _POSIX_C_SOURCE 1
#include "task_list.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/// Include unstable interfaces of wlroots.
#define WLR_USE_UNSTABLE
#include
#include
#undef WLR_USE_UNSTABLE
#include "config.h"
#include "server.h"
#include "toolkit/toolkit.h"
/* == Declarations ========================================================= */
/** State of the task list. */
struct _wlmaker_task_list_t {
/** Derived from a toolkit panel. */
wlmtk_panel_t super_panel;
/** Buffer that shows the tasklist's content. */
wlmtk_buffer_t buffer;
/** Backlink to the server. */
wlmaker_server_t *server_ptr;
/** Listener for the `task_list_enabled` signal by `wlmaker_server_t`. */
struct wl_listener task_list_enabled_listener;
/** Listener for the `task_list_disabled` signal by `wlmaker_server_t`. */
struct wl_listener task_list_disabled_listener;
/** Listener for @ref wlmtk_root_events_t::window_mapped. */
struct wl_listener window_mapped_listener;
/** Listener for @ref wlmtk_root_events_t::window_unmapped. */
struct wl_listener window_unmapped_listener;
/** Listener for @ref wlmaker_server_t::theme_changed_event. */
struct wl_listener theme_changed_listener;
/** Whether the task list is currently enabled (mapped). */
bool enabled;
/** Visual style. */
struct wlmaker_task_list_style style;
};
static bool _wlmaker_task_list_refresh(
wlmaker_task_list_t *task_list_ptr,
const struct wlmaker_task_list_style *style_ptr);
static struct wlr_buffer *create_wlr_buffer(
wlmtk_workspace_t *workspace_ptr,
const struct wlmaker_task_list_style *style_ptr);
static void _wlmaker_task_list_draw_into_cairo(
cairo_t *cairo_ptr,
const struct wlmaker_task_list_style *style_ptr,
wlmtk_workspace_t *workspace_ptr);
static void _wlmaker_task_list_draw_window_into_cairo(
cairo_t *cairo_ptr,
const wlmtk_style_font_t *font_style_ptr,
uint32_t color,
wlmtk_window_t *window_ptr,
bool active,
int pos_y);
static const char *_wlmaker_task_list_window_name(
wlmtk_window_t *window_ptr);
static uint32_t _wlmaker_task_list_request_size(
wlmtk_panel_t *panel_ptr,
int width,
int height);
static void _wlmaker_task_list_handle_task_list_enabled(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_task_list_handle_task_list_disabled(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_task_list_handle_theme_changed(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_task_list_handle_window_mapped(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_task_list_handle_window_unmapped(
struct wl_listener *listener_ptr,
void *data_ptr);
/* == Data ================================================================= */
const bspl_desc_t wlmaker_task_list_style_desc[] = {
BSPL_DESC_CUSTOM(
"Fill", true, struct wlmaker_task_list_style, fill, fill,
wlmtk_style_decode_fill, NULL, NULL, NULL),
BSPL_DESC_DICT(
"Font", true, struct wlmaker_task_list_style, font, font,
wlmtk_style_font_desc),
BSPL_DESC_ARGB32(
"TextColor", true, struct wlmaker_task_list_style,
text_color, text_color, 0),
BSPL_DESC_SENTINEL()
};
/** Task list positioning: Fixed dimensions, at center of layer. */
static const wlmtk_panel_positioning_t _wlmaker_task_list_positioning = {
.desired_width = 400,
.desired_height = 200,
.anchor = WLR_EDGE_BOTTOM | WLR_EDGE_TOP | WLR_EDGE_LEFT | WLR_EDGE_RIGHT
};
/** Virtual method table for the task list. */
static const wlmtk_panel_vmt_t _wlmaker_task_list_vmt = {
.request_size = _wlmaker_task_list_request_size
};
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
wlmaker_task_list_t *wlmaker_task_list_create(
wlmaker_server_t *server_ptr,
const struct wlmaker_task_list_style *style_ptr)
{
wlmaker_task_list_t *task_list_ptr = logged_calloc(
1, sizeof(wlmaker_task_list_t));
task_list_ptr->server_ptr = server_ptr;
task_list_ptr->style = *style_ptr;
if (!wlmtk_panel_init(&task_list_ptr->super_panel,
&_wlmaker_task_list_positioning)) {
wlmaker_task_list_destroy(task_list_ptr);
return NULL;
}
wlmtk_panel_extend(&task_list_ptr->super_panel,
&_wlmaker_task_list_vmt);
wlmtk_element_set_visible(
wlmtk_panel_element(&task_list_ptr->super_panel), true);
if (!wlmtk_buffer_init(&task_list_ptr->buffer)) {
wlmaker_task_list_destroy(task_list_ptr);
return NULL;
}
wlmtk_element_set_visible(
wlmtk_buffer_element(&task_list_ptr->buffer), true);
wlmtk_container_add_element(
&task_list_ptr->super_panel.super_container,
wlmtk_buffer_element(&task_list_ptr->buffer));
wlmtk_util_connect_listener_signal(
&server_ptr->task_list_enabled_event,
&task_list_ptr->task_list_enabled_listener,
_wlmaker_task_list_handle_task_list_enabled);
wlmtk_util_connect_listener_signal(
&server_ptr->task_list_disabled_event,
&task_list_ptr->task_list_disabled_listener,
_wlmaker_task_list_handle_task_list_disabled);
wlmtk_util_connect_listener_signal(
&server_ptr->theme_changed_event,
&task_list_ptr->theme_changed_listener,
_wlmaker_task_list_handle_theme_changed);
wlmtk_util_connect_listener_signal(
&wlmtk_root_events(server_ptr->root_ptr)->window_mapped,
&task_list_ptr->window_mapped_listener,
_wlmaker_task_list_handle_window_mapped);
wlmtk_util_connect_listener_signal(
&wlmtk_root_events(server_ptr->root_ptr)->window_unmapped,
&task_list_ptr->window_unmapped_listener,
_wlmaker_task_list_handle_window_unmapped);
return task_list_ptr;
}
/* ------------------------------------------------------------------------- */
void wlmaker_task_list_destroy(wlmaker_task_list_t *task_list_ptr)
{
wlmtk_util_disconnect_listener(&task_list_ptr->window_unmapped_listener);
wlmtk_util_disconnect_listener(&task_list_ptr->window_mapped_listener);
wlmtk_util_disconnect_listener(&task_list_ptr->theme_changed_listener);
wlmtk_util_disconnect_listener(&task_list_ptr->task_list_disabled_listener);
wlmtk_util_disconnect_listener(&task_list_ptr->task_list_enabled_listener);
if (wlmtk_buffer_element(&task_list_ptr->buffer)->parent_container_ptr) {
wlmtk_container_remove_element(
&task_list_ptr->super_panel.super_container,
wlmtk_buffer_element(&task_list_ptr->buffer));
}
wlmtk_buffer_fini(&task_list_ptr->buffer);
wlmtk_panel_fini(&task_list_ptr->super_panel);
free(task_list_ptr);
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/**
* Refreshes the task list. Should be done whenever a list is mapped/unmapped.
*
* @param task_list_ptr
* @param style_ptr
*
* @return true on success.
*/
bool _wlmaker_task_list_refresh(
wlmaker_task_list_t *task_list_ptr,
const struct wlmaker_task_list_style *style_ptr)
{
wlmtk_workspace_t *workspace_ptr =
wlmtk_root_get_current_workspace(task_list_ptr->server_ptr->root_ptr);
struct wlr_buffer *wlr_buffer_ptr = create_wlr_buffer(
workspace_ptr, style_ptr);
if (NULL == wlr_buffer_ptr) return false;
wlmtk_buffer_set(&task_list_ptr->buffer, wlr_buffer_ptr);
wlr_buffer_drop(wlr_buffer_ptr);
return true;
}
/* ------------------------------------------------------------------------- */
/**
* Creates a `struct wlr_buffer` with windows of `workspace_ptr` drawn into.
*
* @param workspace_ptr
* @param style_ptr
*
* @return A pointer to the `struct wlr_buffer` with the list of windows
* (tasks), or NULL on error.
*/
struct wlr_buffer *create_wlr_buffer(
wlmtk_workspace_t *workspace_ptr,
const struct wlmaker_task_list_style *style_ptr)
{
struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer(
_wlmaker_task_list_positioning.desired_width,
_wlmaker_task_list_positioning.desired_height);
if (NULL == wlr_buffer_ptr) return NULL;
cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr);
if (NULL == cairo_ptr) {
wlr_buffer_drop(wlr_buffer_ptr);
return NULL;
}
_wlmaker_task_list_draw_into_cairo(cairo_ptr, style_ptr, workspace_ptr);
cairo_destroy(cairo_ptr);
return wlr_buffer_ptr;
}
/* ------------------------------------------------------------------------- */
/**
* Draws all tasks of `workspace_ptr` into `cairo_ptr`.
*
* @param cairo_ptr
* @param style_ptr
* @param workspace_ptr
*/
void _wlmaker_task_list_draw_into_cairo(
cairo_t *cairo_ptr,
const struct wlmaker_task_list_style *style_ptr,
wlmtk_workspace_t *workspace_ptr)
{
wlmaker_primitives_cairo_fill(cairo_ptr, &style_ptr->fill);
// Not tied to a workspace? We're done, all set.
if (NULL == workspace_ptr) return;
const bs_dllist_t *windows_ptr = wlmtk_workspace_get_windows_dllist(
workspace_ptr);
// No windows at all? Done here.
if (bs_dllist_empty(windows_ptr)) return;
// Find node of the active window, for centering the task list.
bs_dllist_node_t *centered_dlnode_ptr = windows_ptr->head_ptr;
bs_dllist_node_t *active_dlnode_ptr = windows_ptr->head_ptr;
while (NULL != active_dlnode_ptr &&
wlmtk_workspace_get_activated_window(workspace_ptr) !=
wlmtk_window_from_dlnode(active_dlnode_ptr)) {
active_dlnode_ptr = active_dlnode_ptr->next_ptr;
}
if (NULL != active_dlnode_ptr) centered_dlnode_ptr = active_dlnode_ptr;
int pos_y = _wlmaker_task_list_positioning.desired_height / 2 + 10;
_wlmaker_task_list_draw_window_into_cairo(
cairo_ptr,
&style_ptr->font,
style_ptr->text_color,
wlmtk_window_from_dlnode(centered_dlnode_ptr),
centered_dlnode_ptr == active_dlnode_ptr,
pos_y);
bs_dllist_node_t *dlnode_ptr = centered_dlnode_ptr->prev_ptr;
for (int further_windows = 1;
NULL != dlnode_ptr && further_windows <= 3;
dlnode_ptr = dlnode_ptr->prev_ptr, ++further_windows) {
_wlmaker_task_list_draw_window_into_cairo(
cairo_ptr,
&style_ptr->font,
style_ptr->text_color,
wlmtk_window_from_dlnode(dlnode_ptr),
false,
pos_y - further_windows * 26);
}
dlnode_ptr = centered_dlnode_ptr->next_ptr;
for (int further_windows = 1;
NULL != dlnode_ptr && further_windows <= 3;
dlnode_ptr = dlnode_ptr->next_ptr, ++further_windows) {
_wlmaker_task_list_draw_window_into_cairo(
cairo_ptr,
&style_ptr->font,
style_ptr->text_color,
wlmtk_window_from_dlnode(dlnode_ptr),
false,
pos_y + further_windows * 26);
}
}
/* ------------------------------------------------------------------------- */
/**
* Draws one window (task) into `cairo_ptr`.
*
* @param cairo_ptr
* @param font_style_ptr
* @param color
* @param window_ptr
* @param active Whether this window is currently active.
* @param pos_y Y position within the `cairo_ptr`.
*/
void _wlmaker_task_list_draw_window_into_cairo(
cairo_t *cairo_ptr,
const wlmtk_style_font_t *font_style_ptr,
uint32_t color,
wlmtk_window_t *window_ptr,
bool active,
int pos_y)
{
cairo_set_source_argb8888(cairo_ptr, color);
cairo_set_font_size(cairo_ptr, font_style_ptr->size);
cairo_select_font_face(
cairo_ptr,
font_style_ptr->face,
CAIRO_FONT_SLANT_NORMAL,
active ? CAIRO_FONT_WEIGHT_BOLD : CAIRO_FONT_WEIGHT_NORMAL);
cairo_move_to(cairo_ptr, 10, pos_y);
cairo_show_text(cairo_ptr, _wlmaker_task_list_window_name(window_ptr));
}
/* ------------------------------------------------------------------------- */
/**
* Constructs a comprehensive name for the window.
*
* @param window_ptr
*
* @return Pointer to the constructed name. This is a static buffer that does
* not require to be free'd, but will be re-used upon next call to
* window_name.
*/
const char *_wlmaker_task_list_window_name(wlmtk_window_t *window_ptr)
{
static char name[256];
size_t pos = 0;
const char *title_ptr = wlmtk_window_get_title(window_ptr);
if (NULL != title_ptr) {
pos = bs_strappendf(name, sizeof(name), pos, "%s", title_ptr);
}
const wlmtk_util_client_t *client_ptr = wlmtk_window_get_client_ptr(
window_ptr);
if (NULL != client_ptr && 0 != client_ptr->pid) {
if (0 < pos) pos = bs_strappendf(name, sizeof(name), pos, " ");
pos = bs_strappendf(name, sizeof(name), pos, "[%"PRIdMAX,
(intmax_t)client_ptr->pid);
char fname[PATH_MAX], cmdline[PATH_MAX];
snprintf(fname, sizeof(fname), "/proc/%"PRIdMAX"/cmdline",
(intmax_t)client_ptr->pid);
ssize_t read_bytes = bs_file_read_buffer(
fname, cmdline, sizeof(cmdline));
if (0 < read_bytes) {
pos = bs_strappendf(name, sizeof(name), pos, ": %s", cmdline);
}
pos = bs_strappendf(name, sizeof(name), pos, "]");
}
if (0 < pos) pos = bs_strappendf(name, sizeof(name), pos, " ");
pos = bs_strappendf(name, sizeof(name), pos, "(%p)", window_ptr);
return &name[0];
}
/* ------------------------------------------------------------------------- */
/**
* Implements @ref wlmtk_panel_vmt_t::request_size.
*
* @param panel_ptr
* @param width
* @param height
*
* @return 0 always.
*/
uint32_t _wlmaker_task_list_request_size(
wlmtk_panel_t *panel_ptr,
__UNUSED__ int width,
__UNUSED__ int height)
{
wlmtk_panel_commit(panel_ptr, 0, &_wlmaker_task_list_positioning);
return 0;
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `task_list_enabled_listener`.
*
* Enables the task listener: Creates the task list for the currently-active
* workspace and enables the task list on that workspace.
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmaker_task_list_handle_task_list_enabled(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_task_list_t *task_list_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_task_list_t, task_list_enabled_listener);
_wlmaker_task_list_refresh(task_list_ptr, &task_list_ptr->style);
if (task_list_ptr->enabled) {
BS_ASSERT(NULL != wlmtk_panel_get_layer(&task_list_ptr->super_panel));
return;
}
wlmtk_workspace_t *workspace_ptr =
wlmtk_root_get_current_workspace(task_list_ptr->server_ptr->root_ptr);
wlmtk_layer_t *layer_ptr = wlmtk_workspace_get_layer(
workspace_ptr, WLMTK_WORKSPACE_LAYER_OVERLAY);
wlmtk_layer_add_panel(
layer_ptr,
&task_list_ptr->super_panel,
wlmaker_server_get_output_at_cursor(task_list_ptr->server_ptr));
task_list_ptr->enabled = true;
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `task_list_disabled_listener`: Hides the list.
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmaker_task_list_handle_task_list_disabled(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_task_list_t *task_list_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_task_list_t, task_list_disabled_listener);
BS_ASSERT(NULL != wlmtk_panel_get_layer(&task_list_ptr->super_panel));
wlmtk_layer_remove_panel(
wlmtk_panel_get_layer(&task_list_ptr->super_panel),
&task_list_ptr->super_panel);
task_list_ptr->enabled = false;
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `theme_changed_listener`: Updates the style
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmaker_task_list_handle_theme_changed(
struct wl_listener *listener_ptr,
void *data_ptr)
{
wlmaker_task_list_t *task_list_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_task_list_t, theme_changed_listener);
wlmaker_config_style_t *style_ptr = data_ptr;
if (_wlmaker_task_list_refresh(task_list_ptr, &style_ptr->task_list)) {
task_list_ptr->style = style_ptr->task_list;
}
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `window_mapped_listener`: Refreshes the list (if enabled).
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmaker_task_list_handle_window_mapped(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_task_list_t *task_list_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_task_list_t, window_mapped_listener);
if (task_list_ptr->enabled) {
_wlmaker_task_list_refresh(task_list_ptr, &task_list_ptr->style);
}
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `window_unmapped_listener`: Refreshes the list (if enabled).
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmaker_task_list_handle_window_unmapped(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_task_list_t *task_list_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_task_list_t, window_unmapped_listener);
if (task_list_ptr->enabled) {
_wlmaker_task_list_refresh(task_list_ptr, &task_list_ptr->style);
}
}
/* == End of task_list.c =================================================== */
wlmaker-0.8.1/src/wlmbacktrace.h 0000644 0001751 0001751 00000002745 15222270710 016175 0 ustar runner runner /* ========================================================================= */
/**
* @file wlmbacktrace.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __WLMBACKTRACE_H__
#define __WLMBACKTRACE_H__
#include
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Sets up signal handlers to catch issues and log a backtrace.
*
* @param filename_ptr path name of the executable file; if it is NULL
* the library will try system-specific path names.
* If not NULL, FILENAME must point to a permanent
* buffer.
*
* @return true on success.
*/
bool wlmaker_backtrace_setup(const char *filename_ptr);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __WLMBACKTRACE_H__ */
/* == End of wlmbacktrace.h ================================================ */
wlmaker-0.8.1/src/xdg_toplevel.c 0000644 0001751 0001751 00000161060 15222270710 016221 0 ustar runner runner /* ========================================================================= */
/**
* @file xdg_toplevel.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "xdg_toplevel.h"
#include
#include
#include
#include
#include
#include
#include
#define WLR_USE_UNSTABLE
#include
#include
#include
#include
#include
#include
#undef WLR_USE_UNSTABLE
#include "config.h"
#include "input/cursor.h"
#include "input/manager.h"
#include "server.h"
#include "tl_menu.h"
#include "toolkit/toolkit.h"
#include "xdg_popup.h"
/* == Declarations ========================================================= */
/** State of an XDG toplevel in wlmaker. */
struct wlmaker_xdg_toplevel {
/** Holds surface as content, will be the window's content. */
wlmtk_base_t base;
/** Back-link to server. */
wlmaker_server_t *server_ptr;
/** The corresponding wlroots XDG toplevel. */
struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr;
/** The toplevel's toolkit surface. */
wlmtk_surface_t *surface_ptr;
/** The toplevel's window, when mapped. */
wlmtk_window_t *window_ptr;
/** The toplevel's window menu. */
wlmaker_tl_menu_t *tl_menu_ptr;
/** Listener for the `destroy` signal of the `wlr_xdg_toplevel::events`. */
struct wl_listener destroy_listener;
/** Listener for `request_maximize` of `wlr_xdg_toplevel::events`. */
struct wl_listener request_maximize_listener;
/** Listener for `request_fullscreen` of `wlr_xdg_toplevel::events`. */
struct wl_listener request_fullscreen_listener;
/** Listener for `request_minimize` of `wlr_xdg_toplevel::events`. */
struct wl_listener request_minimize_listener;
/** Listener for `request_move` signal of `wlr_xdg_toplevel::events`. */
struct wl_listener request_move_listener;
/** Listener for `request_resize` signal of `wlr_xdg_toplevel::events`. */
struct wl_listener request_resize_listener;
/** Listener for `show_window_menu` of `wlr_xdg_toplevel::events`. */
struct wl_listener request_show_window_menu_listener;
/** Listener for `set_parent` of `wlr_xdg_toplevel::events`. */
struct wl_listener set_parent_listener;
/** Listener for `set_title` of the `wlr_xdg_toplevel::events`. */
struct wl_listener set_title_listener;
/** Listener for `set_app_id` of `wlr_xdg_toplevel::events`. */
struct wl_listener set_app_id_listener;
/** Listener for the `new_popup` signal of the `wlr_xdg_surface`. */
struct wl_listener new_popup_listener;
/** Listener for the `map` signal of the `wlr_surface`. */
struct wl_listener surface_map_listener;
/** Listener for the `unmap` signal of the `wlr_surface`. */
struct wl_listener surface_unmap_listener;
/** Listener for the `commit` signal of the `wlr_surface`. */
struct wl_listener surface_commit_listener;
/** Listener for @ref wlmtk_window_events_t::request_close. */
struct wl_listener window_request_close_listener;
/** Listener for @ref wlmtk_window_events_t::set_activated. */
struct wl_listener window_set_activated_listener;
/** Listener for @ref wlmtk_window_events_t::request_size. */
struct wl_listener window_request_size_listener;
/** Listener for @ref wlmtk_window_events_t::request_fullscreen. */
struct wl_listener window_request_fullscreen_listener;
/** Listener for @ref wlmtk_window_events_t::request_maximized. */
struct wl_listener window_request_maximized_listener;
/** Injected method for wlr_xdg_toplevel_set_maximized(). */
uint32_t (*_set_maximized)(struct wlr_xdg_toplevel *, bool);
/** Injected method for wlr_xdg_toplevel_set_fullscreen(). */
uint32_t (*_set_fullscreen)(struct wlr_xdg_toplevel *, bool);
/** Injected method for wlr_xdg_toplevel_set_size(). */
uint32_t (*_set_size)(struct wlr_xdg_toplevel *, int32_t, int32_t);
/** Injected method for wlr_xdg_toplevel_set_activated(). */
uint32_t (*_set_activated)(struct wlr_xdg_toplevel *, bool);
/** Injected method for wlr_surface_get_extents(). */
void (*_get_extents)(struct wlr_surface *, struct wlr_box *);
/** Serial of the most recent commit() call. */
uint32_t committed_serial;
/** Serial of the most recent set_size() call. */
uint32_t set_size_serial;
/** Whether this toplevel is configured to be server-side decorated. */
bool server_side_decorated;
/** Properties that are pending to be configured for the toplevel. */
struct {
enum {
/** Maximization. */
WXT_PROP_MAXIMIZED = 1 << 0,
/** Fullscreen. */
WXT_PROP_FULLSCREEN = 1 << 1,
/** Size. */
WXT_PROP_SIZE = 1 << 2,
/** Activation. */
WXT_PROP_ACTIVATED = 1 << 3
} properties;
/** Maximization status. */
bool maximized;
/** Fullscreen status. */
bool fullscreen;
/** Width and height. */
int32_t width, height;
/** Activated. */
bool activated;
} pending;
/**
* Whether the surface had been mapped. Actual map to the workspace may be
* pending, if size was not yet non-zero.
*/
bool mapped;
};
struct wlmaker_xdg_toplevel *_wlmaker_xdg_toplevel_create_injected(
struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr,
wlmaker_server_t *server_ptr,
uint32_t (*_set_maximized)(struct wlr_xdg_toplevel *, bool),
uint32_t (*_set_fullscreen)(struct wlr_xdg_toplevel *, bool),
uint32_t (*_set_size)(struct wlr_xdg_toplevel *, int32_t, int32_t),
uint32_t (*_set_activated)(struct wlr_xdg_toplevel *, bool),
void (*_get_extents)(struct wlr_surface *, struct wlr_box *));
static void _wlmaker_xdg_toplevel_flush_properties(
struct wlmaker_xdg_toplevel *wxt_ptr);
static void _wlmaker_xdg_toplevel_try_map(
struct wlmaker_xdg_toplevel *wxt_ptr);
static void _wlmaker_xdg_toplevel_handle_destroy(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_request_maximize(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_request_fullscreen(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_request_minimize(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_request_move(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_request_resize(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_request_show_window_menu(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_set_parent(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_set_title(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_set_app_id(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_new_popup(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_surface_map(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_surface_unmap(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_surface_commit(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_window_request_close(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_window_set_activated(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_window_request_size(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_window_request_fullscreen(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_xdg_toplevel_handle_window_request_maximized(
struct wl_listener *listener_ptr,
void *data_ptr);
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_create(
struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr,
wlmaker_server_t *server_ptr)
{
return _wlmaker_xdg_toplevel_create_injected(
wlr_xdg_toplevel_ptr,
server_ptr,
wlr_xdg_toplevel_set_maximized,
wlr_xdg_toplevel_set_fullscreen,
wlr_xdg_toplevel_set_size,
wlr_xdg_toplevel_set_activated,
#if WLR_VERSION_NUM >= (19 << 8)
wlr_surface_get_extents
#else
wlr_surface_get_extends
#endif
);
}
/* ------------------------------------------------------------------------- */
void wlmaker_xdg_toplevel_destroy(struct wlmaker_xdg_toplevel *wxt_ptr)
{
bs_log(BS_INFO, "Destroying XDG toplevel %p", wxt_ptr);
wxt_ptr->wlr_xdg_toplevel_ptr->base->data = NULL;
wlmtk_util_disconnect_listener(&wxt_ptr->window_request_fullscreen_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->window_request_size_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->window_set_activated_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->window_request_close_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->surface_commit_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->surface_unmap_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->surface_map_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->new_popup_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->set_app_id_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->set_title_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->set_parent_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->request_show_window_menu_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->request_resize_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->request_move_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->request_fullscreen_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->request_maximize_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->request_minimize_listener);
wlmtk_util_disconnect_listener(&wxt_ptr->destroy_listener);
if (NULL != wxt_ptr->tl_menu_ptr) {
wlmaker_tl_menu_destroy(wxt_ptr->tl_menu_ptr);
wxt_ptr->tl_menu_ptr = NULL;
}
if (NULL != wxt_ptr->window_ptr) {
wl_signal_emit(
&wxt_ptr->server_ptr->window_destroyed_event,
wxt_ptr->window_ptr);
wlmtk_window_destroy(wxt_ptr->window_ptr);
wxt_ptr->window_ptr = NULL;
}
wlmtk_base_fini(&wxt_ptr->base);
free(wxt_ptr);
}
/* ------------------------------------------------------------------------- */
void wlmaker_xdg_toplevel_set_server_side_decorated(
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr,
bool server_side_decorated)
{
wlmaker_xdg_toplevel_ptr->server_side_decorated = server_side_decorated;
wlmtk_window_set_server_side_decorated(
wlmaker_xdg_toplevel_ptr->window_ptr,
wlmaker_xdg_toplevel_ptr->server_side_decorated);
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/** Creates the toplevel, permitting injected calls to set properties. */
struct wlmaker_xdg_toplevel *_wlmaker_xdg_toplevel_create_injected(
struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr,
wlmaker_server_t *server_ptr,
uint32_t (*_set_maximized)(struct wlr_xdg_toplevel *, bool),
uint32_t (*_set_fullscreen)(struct wlr_xdg_toplevel *, bool),
uint32_t (*_set_size)(struct wlr_xdg_toplevel *, int32_t, int32_t),
uint32_t (*_set_activated)(struct wlr_xdg_toplevel *, bool),
void (*_get_extents)(struct wlr_surface *, struct wlr_box *))
{
// Guard clause: Must have a base. */
if (NULL == wlr_xdg_toplevel_ptr->base) {
bs_log(BS_ERROR, "Missing base for wlr_xdg_toplevel at %p",
wlr_xdg_toplevel_ptr);
return NULL;
}
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = logged_calloc(
1, sizeof(struct wlmaker_xdg_toplevel));
if (NULL == wlmaker_xdg_toplevel_ptr) return NULL;
wlmaker_xdg_toplevel_ptr->wlr_xdg_toplevel_ptr = wlr_xdg_toplevel_ptr;
wlmaker_xdg_toplevel_ptr->server_ptr = server_ptr;
wlmaker_xdg_toplevel_ptr->_set_maximized = _set_maximized;
wlmaker_xdg_toplevel_ptr->_set_fullscreen = _set_fullscreen;
wlmaker_xdg_toplevel_ptr->_set_size = _set_size;
wlmaker_xdg_toplevel_ptr->_set_activated = _set_activated;
wlmaker_xdg_toplevel_ptr->_get_extents = _get_extents;
if (!wlmtk_base_init(&wlmaker_xdg_toplevel_ptr->base, NULL)) goto error;
wlmaker_xdg_toplevel_ptr->surface_ptr = wlmtk_xdg_surface_create(
wlr_xdg_toplevel_ptr->base,
server_ptr->wlr_seat_ptr);
if (NULL == wlmaker_xdg_toplevel_ptr->surface_ptr) goto error;
wlmtk_base_set_content_element(
&wlmaker_xdg_toplevel_ptr->base,
wlmtk_surface_element(wlmaker_xdg_toplevel_ptr->surface_ptr));
wlmaker_xdg_toplevel_ptr->window_ptr = wlmtk_window_create(
wlmtk_base_element(&wlmaker_xdg_toplevel_ptr->base),
wlmtk_window_style_to_ref(
wlmaker_xdg_toplevel_ptr->server_ptr->style_ptr->window_style_ptr),
wlmtk_menu_style_to_ref(
wlmaker_xdg_toplevel_ptr->server_ptr->style_ptr->menu_style_ptr));
if (NULL == wlmaker_xdg_toplevel_ptr->window_ptr) goto error;
if (NULL != server_ptr->input_manager_ptr) {
wlmtk_window_set_options(
wlmaker_xdg_toplevel_ptr->window_ptr,
wlmim_cursor_options(
server_ptr->input_manager_ptr)->move_window_modifier);
}
wlmtk_window_set_properties(
wlmaker_xdg_toplevel_ptr->window_ptr,
WLMTK_WINDOW_PROPERTY_RESIZABLE |
WLMTK_WINDOW_PROPERTY_ICONIFIABLE |
WLMTK_WINDOW_PROPERTY_CLOSABLE);
wlmtk_util_client_t client = {};
if (NULL != wlr_xdg_toplevel_ptr->resource) {
wl_client_get_credentials(
wlr_xdg_toplevel_ptr->resource->client,
&client.pid, &client.uid, &client.gid);
}
wlmtk_window_set_client(wlmaker_xdg_toplevel_ptr->window_ptr, &client);
wlmaker_xdg_toplevel_ptr->tl_menu_ptr = wlmaker_tl_menu_create(
wlmaker_xdg_toplevel_ptr->window_ptr, server_ptr);
if (NULL == wlmaker_xdg_toplevel_ptr->tl_menu_ptr) goto error;
wlmtk_util_connect_listener_signal(
&wlr_xdg_toplevel_ptr->events.destroy,
&wlmaker_xdg_toplevel_ptr->destroy_listener,
_wlmaker_xdg_toplevel_handle_destroy);
wlmtk_util_connect_listener_signal(
&wlr_xdg_toplevel_ptr->events.request_maximize,
&wlmaker_xdg_toplevel_ptr->request_maximize_listener,
_wlmaker_xdg_toplevel_handle_request_maximize);
wlmtk_util_connect_listener_signal(
&wlr_xdg_toplevel_ptr->events.request_fullscreen,
&wlmaker_xdg_toplevel_ptr->request_fullscreen_listener,
_wlmaker_xdg_toplevel_handle_request_fullscreen);
wlmtk_util_connect_listener_signal(
&wlr_xdg_toplevel_ptr->events.request_minimize,
&wlmaker_xdg_toplevel_ptr->request_minimize_listener,
_wlmaker_xdg_toplevel_handle_request_minimize);
wlmtk_util_connect_listener_signal(
&wlr_xdg_toplevel_ptr->events.request_move,
&wlmaker_xdg_toplevel_ptr->request_move_listener,
_wlmaker_xdg_toplevel_handle_request_move);
wlmtk_util_connect_listener_signal(
&wlr_xdg_toplevel_ptr->events.request_resize,
&wlmaker_xdg_toplevel_ptr->request_resize_listener,
_wlmaker_xdg_toplevel_handle_request_resize);
wlmtk_util_connect_listener_signal(
&wlr_xdg_toplevel_ptr->events.request_show_window_menu,
&wlmaker_xdg_toplevel_ptr->request_show_window_menu_listener,
_wlmaker_xdg_toplevel_handle_request_show_window_menu);
wlmtk_util_connect_listener_signal(
&wlr_xdg_toplevel_ptr->events.set_parent,
&wlmaker_xdg_toplevel_ptr->set_parent_listener,
_wlmaker_xdg_toplevel_handle_set_parent);
wlmtk_util_connect_listener_signal(
&wlr_xdg_toplevel_ptr->events.set_title,
&wlmaker_xdg_toplevel_ptr->set_title_listener,
_wlmaker_xdg_toplevel_handle_set_title);
wlmtk_util_connect_listener_signal(
&wlr_xdg_toplevel_ptr->events.set_app_id,
&wlmaker_xdg_toplevel_ptr->set_app_id_listener,
_wlmaker_xdg_toplevel_handle_set_app_id);
wlmtk_util_connect_listener_signal(
&wlr_xdg_toplevel_ptr->base->events.new_popup,
&wlmaker_xdg_toplevel_ptr->new_popup_listener,
_wlmaker_xdg_toplevel_handle_new_popup);
wlmtk_surface_connect_map_listener_signal(
wlmaker_xdg_toplevel_ptr->surface_ptr,
&wlmaker_xdg_toplevel_ptr->surface_map_listener,
_wlmaker_xdg_toplevel_handle_surface_map);
wlmtk_surface_connect_unmap_listener_signal(
wlmaker_xdg_toplevel_ptr->surface_ptr,
&wlmaker_xdg_toplevel_ptr->surface_unmap_listener,
_wlmaker_xdg_toplevel_handle_surface_unmap);
wlmtk_util_connect_listener_signal(
&wlmaker_xdg_toplevel_ptr->wlr_xdg_toplevel_ptr->base->surface->events.commit,
&wlmaker_xdg_toplevel_ptr->surface_commit_listener,
_wlmaker_xdg_toplevel_handle_surface_commit);
wlmtk_util_connect_listener_signal(
&wlmtk_window_events(wlmaker_xdg_toplevel_ptr->window_ptr)->request_close,
&wlmaker_xdg_toplevel_ptr->window_request_close_listener,
_wlmaker_xdg_toplevel_handle_window_request_close);
wlmtk_util_connect_listener_signal(
&wlmtk_window_events(wlmaker_xdg_toplevel_ptr->window_ptr)->set_activated,
&wlmaker_xdg_toplevel_ptr->window_set_activated_listener,
_wlmaker_xdg_toplevel_handle_window_set_activated);
wlmtk_util_connect_listener_signal(
&wlmtk_window_events(wlmaker_xdg_toplevel_ptr->window_ptr)->request_size,
&wlmaker_xdg_toplevel_ptr->window_request_size_listener,
_wlmaker_xdg_toplevel_handle_window_request_size);
wlmtk_util_connect_listener_signal(
&wlmtk_window_events(wlmaker_xdg_toplevel_ptr->window_ptr)->request_fullscreen,
&wlmaker_xdg_toplevel_ptr->window_request_fullscreen_listener,
_wlmaker_xdg_toplevel_handle_window_request_fullscreen);
wlmtk_util_connect_listener_signal(
&wlmtk_window_events(wlmaker_xdg_toplevel_ptr->window_ptr)->request_maximized,
&wlmaker_xdg_toplevel_ptr->window_request_maximized_listener,
_wlmaker_xdg_toplevel_handle_window_request_maximized);
wlmaker_xdg_toplevel_ptr->wlr_xdg_toplevel_ptr->base->data =
wlmaker_xdg_toplevel_ptr;
wl_signal_emit(
&server_ptr->window_created_event,
wlmaker_xdg_toplevel_ptr->window_ptr);
bs_log(BS_INFO, "Created XDG toplevel %p", wlmaker_xdg_toplevel_ptr);
return wlmaker_xdg_toplevel_ptr;
error:
wlmaker_xdg_toplevel_destroy(wlmaker_xdg_toplevel_ptr);
return NULL;
}
/* ------------------------------------------------------------------------- */
/**
* Sends pending properties as configure() to the client, after 1st commit.
*
* If the first commit hasn't happened yet, the properties will not be sent.
* A later call to @ref _wlmaker_xdg_toplevel_flush_properties will then flush
* them. See @ref wlmaker_xdg_toplevel::pending.
*
* @param wxt_ptr
*/
void _wlmaker_xdg_toplevel_flush_properties(
struct wlmaker_xdg_toplevel *wxt_ptr)
{
if (!wxt_ptr->wlr_xdg_toplevel_ptr->base->initialized) return;
if (wxt_ptr->pending.properties & WXT_PROP_MAXIMIZED) {
wxt_ptr->_set_maximized(
wxt_ptr->wlr_xdg_toplevel_ptr,
wxt_ptr->pending.maximized);
}
if (wxt_ptr->pending.properties & WXT_PROP_FULLSCREEN) {
wxt_ptr->_set_fullscreen(
wxt_ptr->wlr_xdg_toplevel_ptr,
wxt_ptr->pending.fullscreen);
}
if (wxt_ptr->pending.properties & WXT_PROP_SIZE) {
wxt_ptr->set_size_serial = wxt_ptr->_set_size(
wxt_ptr->wlr_xdg_toplevel_ptr,
wxt_ptr->pending.width,
wxt_ptr->pending.height);
}
if (wxt_ptr->pending.properties & WXT_PROP_ACTIVATED){
wxt_ptr->_set_activated(
wxt_ptr->wlr_xdg_toplevel_ptr,
wxt_ptr->pending.activated);
}
wxt_ptr->pending.properties = 0;
}
/* ------------------------------------------------------------------------- */
/**
* Maps the window to the workspace, if the surface is ready.
*
* A surface is ready if it had received the map signal (and no unmap), and
* if it has non-zero width and height.
*
* @param wxt_ptr
*/
void _wlmaker_xdg_toplevel_try_map(struct wlmaker_xdg_toplevel *wxt_ptr)
{
if (!wxt_ptr->mapped) return;
struct wlr_box committed_size = wlmtk_window_get_size(wxt_ptr->window_ptr);
if (0 == committed_size.width || 0 == committed_size.height) return;
if (NULL != wlmtk_window_get_workspace(wxt_ptr->window_ptr)) return;
wlmtk_workspace_t *workspace_ptr = wlmtk_root_get_current_workspace(
wxt_ptr->server_ptr->root_ptr);
wlmtk_workspace_map_window(workspace_ptr, wxt_ptr->window_ptr);
}
/* ------------------------------------------------------------------------- */
/** The XDG toplevel is destroyed: Destry the wlmaker toplevel, too. */
void _wlmaker_xdg_toplevel_handle_destroy(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF(
listener_ptr, struct wlmaker_xdg_toplevel, destroy_listener);
wlmaker_xdg_toplevel_destroy(wlmaker_xdg_toplevel_ptr);
}
/* ------------------------------------------------------------------------- */
/** The XDG toplevel requests to be maximized. */
void _wlmaker_xdg_toplevel_handle_request_maximize(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF(
listener_ptr, struct wlmaker_xdg_toplevel, request_maximize_listener);
wlmtk_window_request_maximized(
wxt_ptr->window_ptr,
wxt_ptr->wlr_xdg_toplevel_ptr->requested.maximized);
}
/* ------------------------------------------------------------------------- */
/** The XDG toplevel (client) requests to be put in fullscreen. */
void _wlmaker_xdg_toplevel_handle_request_fullscreen(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF(
listener_ptr,
struct wlmaker_xdg_toplevel,
request_fullscreen_listener);
// If the request is to enable fullscreen: Set the requested output.
// Otherwise clear it, to prevent lingering references.
wlmtk_window_set_preferred_wlr_output(
wxt_ptr->window_ptr,
wxt_ptr->wlr_xdg_toplevel_ptr->requested.fullscreen ?
wxt_ptr->wlr_xdg_toplevel_ptr->requested.fullscreen_output :
NULL);
wlmtk_window_request_fullscreen(
wxt_ptr->window_ptr,
wxt_ptr->wlr_xdg_toplevel_ptr->requested.fullscreen);
}
/* ------------------------------------------------------------------------- */
/** The XDG toplevel requests to be minimized. */
void _wlmaker_xdg_toplevel_handle_request_minimize(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF(
listener_ptr, struct wlmaker_xdg_toplevel, request_minimize_listener);
bs_log(BS_ERROR, "TODO: Request minimize %p", wlmaker_xdg_toplevel_ptr);
}
/* ------------------------------------------------------------------------- */
/** The XDG toplevel requests to be moved. */
void _wlmaker_xdg_toplevel_handle_request_move(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF(
listener_ptr, struct wlmaker_xdg_toplevel, request_move_listener);
wlmtk_workspace_t *workspace_ptr = wlmtk_window_get_workspace(
wlmaker_xdg_toplevel_ptr->window_ptr);
if (NULL == workspace_ptr) return;
wlmtk_workspace_begin_window_move(
workspace_ptr,
wlmaker_xdg_toplevel_ptr->window_ptr);
}
/* ------------------------------------------------------------------------- */
/** The XDG toplevel requests to be resized. */
void _wlmaker_xdg_toplevel_handle_request_resize(
struct wl_listener *listener_ptr,
void *data_ptr)
{
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF(
listener_ptr, struct wlmaker_xdg_toplevel, request_resize_listener);
struct wlr_xdg_toplevel_resize_event *resize_event_ptr = data_ptr;
wlmtk_workspace_t *workspace_ptr = wlmtk_window_get_workspace(
wlmaker_xdg_toplevel_ptr->window_ptr);
if (NULL == workspace_ptr) return;
wlmtk_workspace_begin_window_resize(
workspace_ptr,
wlmaker_xdg_toplevel_ptr->window_ptr,
resize_event_ptr->edges);
}
/* ------------------------------------------------------------------------- */
/** The XDG toplevel requests to show the window menu. */
void _wlmaker_xdg_toplevel_handle_request_show_window_menu(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF(
listener_ptr,
struct wlmaker_xdg_toplevel,
request_show_window_menu_listener);
wlmtk_window_menu_set_enabled(
wlmaker_xdg_toplevel_ptr->window_ptr,
!wlmtk_menu_is_open(wlmtk_window_menu(wlmaker_xdg_toplevel_ptr->window_ptr)));
}
/* ------------------------------------------------------------------------- */
/** The XDG toplevel sets the parent. */
void _wlmaker_xdg_toplevel_handle_set_parent(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF(
listener_ptr, struct wlmaker_xdg_toplevel, set_parent_listener);
bs_log(BS_ERROR, "TODO: set_parent %p", wlmaker_xdg_toplevel_ptr);
}
/* ------------------------------------------------------------------------- */
/** The XDG toplevel sets the title. */
void _wlmaker_xdg_toplevel_handle_set_title(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF(
listener_ptr, struct wlmaker_xdg_toplevel, set_title_listener);
wlmtk_window_set_title(
wlmaker_xdg_toplevel_ptr->window_ptr,
wlmaker_xdg_toplevel_ptr->wlr_xdg_toplevel_ptr->title);
}
/* ------------------------------------------------------------------------- */
/** The XDG toplevel sets the app ID. */
void _wlmaker_xdg_toplevel_handle_set_app_id(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF(
listener_ptr, struct wlmaker_xdg_toplevel, set_app_id_listener);
bs_log(BS_ERROR, "TODO: set_app_id %p", wlmaker_xdg_toplevel_ptr);
}
/* ------------------------------------------------------------------------- */
/** The XDG toplevel adds a new popup. */
void _wlmaker_xdg_toplevel_handle_new_popup(
struct wl_listener *listener_ptr,
void *data_ptr)
{
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF(
listener_ptr, struct wlmaker_xdg_toplevel, new_popup_listener);
struct wlr_xdg_popup *wlr_xdg_popup_ptr = data_ptr;
wlmaker_xdg_popup_t *xdg_popup_ptr = wlmaker_xdg_popup_create(
wlr_xdg_popup_ptr,
wlmaker_xdg_toplevel_ptr->server_ptr->wlr_seat_ptr);
if (NULL == xdg_popup_ptr) {
wl_resource_post_error(
wlr_xdg_popup_ptr->resource,
WL_DISPLAY_ERROR_NO_MEMORY,
"Failed wlmtk_xdg_popup2_create");
return;
}
wlmtk_base_push_element(
&wlmaker_xdg_toplevel_ptr->base,
wlmaker_xdg_popup_element(xdg_popup_ptr));
}
/* ------------------------------------------------------------------------- */
/** Handles the XDG's surface map event. */
void _wlmaker_xdg_toplevel_handle_surface_map(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF(
listener_ptr, struct wlmaker_xdg_toplevel, surface_map_listener);
wxt_ptr->mapped = true;
_wlmaker_xdg_toplevel_try_map(wxt_ptr);
}
/* ------------------------------------------------------------------------- */
/** Handles when the XDG surface is to be unmapped. */
void _wlmaker_xdg_toplevel_handle_surface_unmap(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF(
listener_ptr, struct wlmaker_xdg_toplevel, surface_unmap_listener);
wxt_ptr->mapped = false;
if (NULL != wlmtk_window_get_workspace(wxt_ptr->window_ptr)) {
wlmtk_workspace_unmap_window(
wlmtk_window_get_workspace(wxt_ptr->window_ptr),
wxt_ptr->window_ptr);
}
}
/* ------------------------------------------------------------------------- */
/** Handles XDG surface commits. */
void _wlmaker_xdg_toplevel_handle_surface_commit(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF(
listener_ptr, struct wlmaker_xdg_toplevel, surface_commit_listener);
struct wlr_xdg_surface *wlr_xdg_surface_ptr =
wxt_ptr->wlr_xdg_toplevel_ptr->base;
struct wlr_box geo = wxt_ptr->wlr_xdg_toplevel_ptr->base->current.geometry;
if (0 >= geo.width && 0 >= geo.height) {
wxt_ptr->_get_extents(
wxt_ptr->wlr_xdg_toplevel_ptr->base->surface,
&geo);
}
wlmtk_window_commit_size(
wxt_ptr->window_ptr, geo.width - geo.x, geo.height - geo.y);
if (0 != wxt_ptr->pending.properties) {
_wlmaker_xdg_toplevel_flush_properties(wxt_ptr);
} else if (wlr_xdg_surface_ptr->initial_commit) {
wlr_xdg_surface_schedule_configure(wlr_xdg_surface_ptr);
}
wxt_ptr->committed_serial =
wxt_ptr->wlr_xdg_toplevel_ptr->base->current.configure_serial;
_wlmaker_xdg_toplevel_try_map(wxt_ptr);
if (wxt_ptr->wlr_xdg_toplevel_ptr->current.fullscreen !=
wlmtk_window_is_fullscreen(wxt_ptr->window_ptr)) {
wlmtk_window_commit_fullscreen(
wxt_ptr->window_ptr,
wxt_ptr->wlr_xdg_toplevel_ptr->current.fullscreen);
} else if (wxt_ptr->wlr_xdg_toplevel_ptr->current.maximized !=
wlmtk_window_is_maximized(wxt_ptr->window_ptr)) {
wlmtk_window_commit_maximized(
wxt_ptr->window_ptr,
wxt_ptr->wlr_xdg_toplevel_ptr->current.maximized);
}
}
/* ------------------------------------------------------------------------- */
/** Handles the window's request_close event: Forwards to the toplevel. */
void _wlmaker_xdg_toplevel_handle_window_request_close(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF(
listener_ptr,
struct wlmaker_xdg_toplevel,
window_request_close_listener);
wlr_xdg_toplevel_send_close(
wlmaker_xdg_toplevel_ptr->wlr_xdg_toplevel_ptr);
}
/* ------------------------------------------------------------------------- */
/** Handles the window's activation event: Forwards to the toplevel. */
void _wlmaker_xdg_toplevel_handle_window_set_activated(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF(
listener_ptr,
struct wlmaker_xdg_toplevel,
window_set_activated_listener);
wlmaker_xdg_toplevel_ptr->pending.properties |= WXT_PROP_ACTIVATED;
wlmaker_xdg_toplevel_ptr->pending.activated =
wlmtk_window_is_activated(wlmaker_xdg_toplevel_ptr->window_ptr);
_wlmaker_xdg_toplevel_flush_properties(wlmaker_xdg_toplevel_ptr);
if (NULL != wlmaker_xdg_toplevel_ptr->surface_ptr) {
wlmtk_surface_set_activated(
wlmaker_xdg_toplevel_ptr->surface_ptr,
wlmtk_window_is_activated(wlmaker_xdg_toplevel_ptr->window_ptr));
}
}
/* ------------------------------------------------------------------------- */
/** Handles the window's request for size. */
void _wlmaker_xdg_toplevel_handle_window_request_size(
struct wl_listener *listener_ptr,
void *data_ptr)
{
struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF(
listener_ptr,
struct wlmaker_xdg_toplevel,
window_request_size_listener);
const struct wlr_box *box_ptr = data_ptr;
// Ignore the request, if commits have not caught up yet.
if (wxt_ptr->set_size_serial > wxt_ptr->committed_serial) return;
wxt_ptr->pending.width = box_ptr->width;
wxt_ptr->pending.height = box_ptr->height;
wxt_ptr->pending.properties |= WXT_PROP_SIZE;
_wlmaker_xdg_toplevel_flush_properties(wxt_ptr);
}
/* ------------------------------------------------------------------------- */
/** Handles the window's request for going to fullscreen. */
void _wlmaker_xdg_toplevel_handle_window_request_fullscreen(
struct wl_listener *listener_ptr,
void *data_ptr)
{
struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF(
listener_ptr,
struct wlmaker_xdg_toplevel,
window_request_fullscreen_listener);
bool *fullscreen_ptr = data_ptr;
wxt_ptr->pending.properties |= WXT_PROP_FULLSCREEN;
wxt_ptr->pending.fullscreen = *fullscreen_ptr;
if (wxt_ptr->pending.fullscreen) wxt_ptr->pending.maximized = false;
_wlmaker_xdg_toplevel_flush_properties(wxt_ptr);
}
/* ------------------------------------------------------------------------- */
/** Handles the window's request for going to maximized. */
void _wlmaker_xdg_toplevel_handle_window_request_maximized(
struct wl_listener *listener_ptr,
void *data_ptr)
{
struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF(
listener_ptr,
struct wlmaker_xdg_toplevel,
window_request_maximized_listener);
bool *maximized_ptr = data_ptr;
wxt_ptr->pending.properties |= WXT_PROP_MAXIMIZED;
wxt_ptr->pending.maximized = *maximized_ptr;
_wlmaker_xdg_toplevel_flush_properties(wxt_ptr);
}
/** == Unit test helpers =================================================== */
/** Data needed for using an output layout for tests. */
struct _wlmaker_test_layout {
#ifndef DOXYGEN_SHOULD_SKIP_THIS
struct wl_display *wl_display_ptr;
struct wlr_output_layout *wlr_output_layout_ptr;
struct wlr_output wlr_output;
#endif
};
static bool _wlmaker_test_layout_init(struct _wlmaker_test_layout *tl_ptr);
static void _wlmaker_test_layout_fini(struct _wlmaker_test_layout *tl_ptr);
/** Initializes @ref _wlmaker_test_layout */
bool _wlmaker_test_layout_init(struct _wlmaker_test_layout *tl_ptr)
{
*tl_ptr = (struct _wlmaker_test_layout){};
tl_ptr->wl_display_ptr = wl_display_create();
if (NULL == tl_ptr->wl_display_ptr) return false;
tl_ptr->wlr_output_layout_ptr = wlr_output_layout_create(
tl_ptr->wl_display_ptr);
if (NULL == tl_ptr->wl_display_ptr) {
_wlmaker_test_layout_fini(tl_ptr);
return false;
}
tl_ptr->wlr_output = (struct wlr_output){
.name = "Output", .width = 1024, .height = 768, .scale = 1
};
wlmtk_test_wlr_output_init(&tl_ptr->wlr_output);
return true;
}
/** Un-initializes @ref _wlmaker_test_layout */
void _wlmaker_test_layout_fini(struct _wlmaker_test_layout *tl_ptr)
{
if (NULL != tl_ptr->wl_display_ptr) {
wl_display_destroy(tl_ptr->wl_display_ptr);
tl_ptr->wl_display_ptr = NULL;
}
}
/** Data for testing an XDG toplevel. */
struct _xdg_toplevel_test_data {
#ifndef DOXYGEN_SHOULD_SKIP_THIS
struct wlr_surface wlr_surface;
struct wlr_xdg_surface wlr_xdg_surface;
struct wlr_xdg_toplevel wlr_xdg_toplevel;
struct _wlmaker_test_layout test_layout;
wlmaker_config_style_t style;
wlmaker_server_t server;
int32_t set_size_width;
int32_t set_size_height;
int set_maximized_calls;
int set_fullscreen_calls;
int set_size_calls;
int set_activated_calls;
wlmtk_workspace_t *workspace_ptr;
#endif
};
/** A fake for wlr_xdg_toplevel_set_maximized(). Records the call. */
uint32_t _wlmaker_xdg_toplevel_fake_set_maximized(
struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr,
__UNUSED__ bool maximized)
{
struct _xdg_toplevel_test_data *td_ptr = BS_CONTAINER_OF(
wlr_xdg_toplevel_ptr, struct _xdg_toplevel_test_data, wlr_xdg_toplevel);
td_ptr->set_maximized_calls++;
return 0;
}
/** A fake for wlr_xdg_toplevel_set_fullscreen(). Records the call. */
uint32_t _wlmaker_xdg_toplevel_fake_set_fullscreen(
struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr,
__UNUSED__ bool fullscreen)
{
struct _xdg_toplevel_test_data *td_ptr = BS_CONTAINER_OF(
wlr_xdg_toplevel_ptr, struct _xdg_toplevel_test_data, wlr_xdg_toplevel);
td_ptr->set_fullscreen_calls++;
return 0;
}
/** A fake for wlr_xdg_toplevel_set_size(). Records the call. */
uint32_t _wlmaker_xdg_toplevel_fake_set_size(
struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr,
int32_t width,
int32_t height)
{
struct _xdg_toplevel_test_data *td_ptr = BS_CONTAINER_OF(
wlr_xdg_toplevel_ptr, struct _xdg_toplevel_test_data, wlr_xdg_toplevel);
td_ptr->set_size_calls++;
td_ptr->set_size_width = width;
td_ptr->set_size_height = height;
return 0;
}
/** A fake for wlr_xdg_toplevel_set_activated(). Records the call. */
uint32_t _wlmaker_xdg_toplevel_fake_set_activated(
struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr,
__UNUSED__ bool activated)
{
struct _xdg_toplevel_test_data *td_ptr = BS_CONTAINER_OF(
wlr_xdg_toplevel_ptr, struct _xdg_toplevel_test_data, wlr_xdg_toplevel);
td_ptr->set_activated_calls++;
return 0;
}
/** A fake for wlr_surface_get_extents(). Sets box to empty. */
void _wlmaker_xdg_toplevel_fake_get_extents_empty(
__UNUSED__ struct wlr_surface *wlr_surface_ptr,
struct wlr_box *box_ptr)
{
*box_ptr = (struct wlr_box){};
}
/** A fake for wlr_surface_get_extents(). Sets box to fixed size. */
void _wlmaker_xdg_toplevel_fake_get_extents_64x32(
__UNUSED__ struct wlr_surface *wlr_surface_ptr,
struct wlr_box *box_ptr)
{
*box_ptr = (struct wlr_box){ .width = 64, .height = 32 };
}
/* == Unit tests =========================================================== */
static void *_wlmaker_xdg_toplevel_test_setup(void);
static void _wlmaker_xdg_toplevel_test_teardown(void *test_context_ptr);
static void _wlmaker_xdg_toplevel_test_maximize(bs_test_t *test_ptr);
static void _wlmaker_xdg_toplevel_test_fullscreen(bs_test_t *test_ptr);
static void _wlmaker_xdg_toplevel_test_size(bs_test_t *test_ptr);
static void _wlmaker_xdg_toplevel_test_activated(bs_test_t *test_ptr);
static void _wlmaker_xdg_toplevel_test_map(bs_test_t *test_ptr);
static void _wlmaker_xdg_toplevel_test_map_nogeo(bs_test_t *test_ptr);
/** Unit test cases. */
const bs_test_case_t _test_cases[] = {
{ true, "maximize", _wlmaker_xdg_toplevel_test_maximize },
{ true, "fullscreen", _wlmaker_xdg_toplevel_test_fullscreen },
{ true, "size", _wlmaker_xdg_toplevel_test_size },
{ true, "activated", _wlmaker_xdg_toplevel_test_activated },
{ true, "map", _wlmaker_xdg_toplevel_test_map },
{ true, "map_nogeo", _wlmaker_xdg_toplevel_test_map_nogeo },
BS_TEST_CASE_SENTINEL()
};
const bs_test_set_t wlmaker_xdg_toplevel_test_set = BS_TEST_SET_CONTEXT(
true,
"xdg_toplevel",
_test_cases,
_wlmaker_xdg_toplevel_test_setup,
_wlmaker_xdg_toplevel_test_teardown);
/* ------------------------------------------------------------------------- */
/** Creates a @ref _xdg_toplevel_test_data in the test context. */
void *_wlmaker_xdg_toplevel_test_setup(void)
{
struct _xdg_toplevel_test_data *td_ptr = logged_calloc(
1, sizeof(struct _xdg_toplevel_test_data));
if (NULL == td_ptr) return NULL;
td_ptr->wlr_surface.data = td_ptr;
td_ptr->wlr_xdg_surface.surface = &td_ptr->wlr_surface;
td_ptr->wlr_xdg_toplevel.base = &td_ptr->wlr_xdg_surface;
td_ptr->style = (wlmaker_config_style_t){
.window_style_ptr = wlmtk_window_style_create(),
.menu_style_ptr = wlmtk_menu_style_create(),
};
if (NULL == td_ptr->style.window_style_ptr ||
NULL == td_ptr->style.menu_style_ptr) goto error;
if (!_wlmaker_test_layout_init(&td_ptr->test_layout)) goto error;
wlr_output_layout_add_auto(
td_ptr->test_layout.wlr_output_layout_ptr,
&td_ptr->test_layout.wlr_output);
td_ptr->server.wlr_output_layout_ptr =
td_ptr->test_layout.wlr_output_layout_ptr;
td_ptr->server.style_ptr = &td_ptr->style;
td_ptr->server.wlr_scene_ptr = wlr_scene_create();
if (NULL == td_ptr->server.wlr_scene_ptr) goto error;
td_ptr->server.root_ptr = wlmtk_root_create(
td_ptr->server.wlr_scene_ptr,
td_ptr->test_layout.wlr_output_layout_ptr);
if (NULL == td_ptr->server.root_ptr) goto error;
wl_signal_init(&td_ptr->server.window_created_event);
wl_signal_init(&td_ptr->server.window_destroyed_event);
wl_signal_init(&td_ptr->wlr_surface.events.commit);
wl_signal_init(&td_ptr->wlr_surface.events.map);
wl_signal_init(&td_ptr->wlr_surface.events.unmap);
wl_signal_init(&td_ptr->wlr_xdg_surface.events.destroy);
wl_signal_init(&td_ptr->wlr_xdg_surface.events.ping_timeout);
wl_signal_init(&td_ptr->wlr_xdg_surface.events.new_popup);
wl_signal_init(&td_ptr->wlr_xdg_surface.events.configure);
wl_signal_init(&td_ptr->wlr_xdg_surface.events.ack_configure);
wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.destroy);
wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.request_maximize);
wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.request_fullscreen);
wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.request_minimize);
wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.request_move);
wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.request_resize);
wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.request_show_window_menu);
wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.set_parent);
wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.set_title);
wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.set_app_id);
static const struct wlmtk_tile_style ts = { .size = 64 };
td_ptr->workspace_ptr = wlmtk_workspace_create(
td_ptr->test_layout.wlr_output_layout_ptr, "test", &ts);
if (NULL == td_ptr->workspace_ptr) goto error;
wlmtk_root_add_workspace(td_ptr->server.root_ptr, td_ptr->workspace_ptr);
return td_ptr;
error:
_wlmaker_xdg_toplevel_test_teardown(td_ptr);
return NULL;
}
/* ------------------------------------------------------------------------- */
/** Tear down XDG toplevel data. */
void _wlmaker_xdg_toplevel_test_teardown(void *test_context_ptr)
{
struct _xdg_toplevel_test_data *td_ptr = test_context_ptr;
if (NULL != td_ptr->server.root_ptr) {
wlmtk_root_destroy(td_ptr->server.root_ptr);
td_ptr->server.root_ptr = NULL;
}
wlr_scene_node_destroy(&td_ptr->server.wlr_scene_ptr->tree.node);
_wlmaker_test_layout_fini(&td_ptr->test_layout);
if (NULL != td_ptr->style.window_style_ptr) {
wlmtk_window_style_ref_release(
wlmtk_window_style_to_ref(td_ptr->style.window_style_ptr));
}
if (NULL != td_ptr->style.menu_style_ptr) {
wlmtk_menu_style_ref_release(
wlmtk_menu_style_to_ref(td_ptr->style.menu_style_ptr));
}
free(td_ptr);
}
/* ------------------------------------------------------------------------- */
/** Tests maximize requests, from client & window. */
void _wlmaker_xdg_toplevel_test_maximize(bs_test_t *test_ptr)
{
struct _xdg_toplevel_test_data *td_ptr =
BS_ASSERT_NOTNULL(bs_test_context(test_ptr));
wlmtk_util_test_listener_t created, destroyed;
wlmtk_util_connect_test_listener(
&td_ptr->server.window_created_event, &created);
wlmtk_util_connect_test_listener(
&td_ptr->server.window_destroyed_event, &destroyed);
struct wlmaker_xdg_toplevel *wxt_ptr =
_wlmaker_xdg_toplevel_create_injected(
&td_ptr->wlr_xdg_toplevel,
&td_ptr->server,
_wlmaker_xdg_toplevel_fake_set_maximized,
_wlmaker_xdg_toplevel_fake_set_fullscreen,
_wlmaker_xdg_toplevel_fake_set_size,
_wlmaker_xdg_toplevel_fake_set_activated,
_wlmaker_xdg_toplevel_fake_get_extents_empty);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wxt_ptr);
BS_TEST_VERIFY_EQ(test_ptr, 1, created.calls);
// No initial commit yet. No `set_maximized`.
wl_signal_emit(&td_ptr->wlr_xdg_toplevel.events.request_maximize, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 0, td_ptr->set_maximized_calls);
// Now we have pending properties. Commit must trigger a `configure`.
td_ptr->wlr_xdg_surface.initialized = true; // Ready for configure().
td_ptr->wlr_xdg_surface.current.geometry.width = 200;
td_ptr->wlr_xdg_surface.current.geometry.height = 100;
wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_maximized_calls);
// When initialized a `request_maximize` configures right away.
td_ptr->set_maximized_calls = 0;
wl_signal_emit(&td_ptr->wlr_xdg_toplevel.events.request_maximize, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_maximized_calls);
// Issued from the window, ie. from the compositor.
bool m = true;
td_ptr->wlr_xdg_surface.initialized = false; // Ready for configure().
td_ptr->set_maximized_calls = 0;
wl_signal_emit(
&wlmtk_window_events(wxt_ptr->window_ptr)->request_maximized, &m);
BS_TEST_VERIFY_EQ(test_ptr, 0, td_ptr->set_maximized_calls);
// Pending properties. Next commit must trigger.
td_ptr->wlr_xdg_surface.initialized = true; // Ready for configure().
wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_maximized_calls);
td_ptr->set_maximized_calls = 0;
wl_signal_emit(
&wlmtk_window_events(wxt_ptr->window_ptr)->request_maximized, &m);
BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_maximized_calls);
wlmaker_xdg_toplevel_destroy(wxt_ptr);
BS_TEST_VERIFY_EQ(test_ptr, 1, destroyed.calls);
}
/* ------------------------------------------------------------------------- */
/** Tests fullscreen requests, from client & window. */
void _wlmaker_xdg_toplevel_test_fullscreen(bs_test_t *test_ptr)
{
struct _xdg_toplevel_test_data *td_ptr =
BS_ASSERT_NOTNULL(bs_test_context(test_ptr));
struct wlmaker_xdg_toplevel *wxt_ptr =
_wlmaker_xdg_toplevel_create_injected(
&td_ptr->wlr_xdg_toplevel,
&td_ptr->server,
_wlmaker_xdg_toplevel_fake_set_maximized,
_wlmaker_xdg_toplevel_fake_set_fullscreen,
_wlmaker_xdg_toplevel_fake_set_size,
_wlmaker_xdg_toplevel_fake_set_activated,
_wlmaker_xdg_toplevel_fake_get_extents_empty);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wxt_ptr);
// Issued from the window, ie. from the compositor.
bool f = true;
wl_signal_emit(
&wlmtk_window_events(wxt_ptr->window_ptr)->request_fullscreen, &f);
BS_TEST_VERIFY_EQ(test_ptr, 0, td_ptr->set_fullscreen_calls);
// Pending properties. Next commit must trigger.
td_ptr->wlr_xdg_surface.initialized = true; // Ready for configure().
td_ptr->wlr_xdg_surface.current.geometry.width = 200;
td_ptr->wlr_xdg_surface.current.geometry.height = 100;
wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_fullscreen_calls);
// Issue from the client. No commit yet. Must not configure().
td_ptr->wlr_xdg_surface.initialized = false; // Not ready for configure().
td_ptr->set_fullscreen_calls = 0;
wl_signal_emit(&td_ptr->wlr_xdg_toplevel.events.request_fullscreen, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 0, td_ptr->set_fullscreen_calls);
// Following commit must trigger a `configure`.
td_ptr->wlr_xdg_surface.initialized = true; // Ready for configure().
wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_fullscreen_calls);
wlmaker_xdg_toplevel_destroy(wxt_ptr);
}
/* ------------------------------------------------------------------------- */
/** Tests set_size requests. */
void _wlmaker_xdg_toplevel_test_size(bs_test_t *test_ptr)
{
struct _xdg_toplevel_test_data *td_ptr =
BS_ASSERT_NOTNULL(bs_test_context(test_ptr));
struct wlmaker_xdg_toplevel *wxt_ptr =
_wlmaker_xdg_toplevel_create_injected(
&td_ptr->wlr_xdg_toplevel,
&td_ptr->server,
_wlmaker_xdg_toplevel_fake_set_maximized,
_wlmaker_xdg_toplevel_fake_set_fullscreen,
_wlmaker_xdg_toplevel_fake_set_size,
_wlmaker_xdg_toplevel_fake_set_activated,
_wlmaker_xdg_toplevel_fake_get_extents_empty);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wxt_ptr);
// Issued from the window, ie. from the compositor.
struct wlr_box b = { .width = 10, .height = 20 };
wl_signal_emit(
&wlmtk_window_events(wxt_ptr->window_ptr)->request_size, &b);
BS_TEST_VERIFY_EQ(test_ptr, 0, td_ptr->set_size_calls);
// Pending properties. Next commit must trigger.
td_ptr->wlr_xdg_surface.initialized = true; // Ready for configure().
td_ptr->wlr_xdg_surface.current.geometry.width = 200;
td_ptr->wlr_xdg_surface.current.geometry.height = 100;
wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_size_calls);
BS_TEST_VERIFY_EQ(test_ptr, 10, td_ptr->set_size_width);
BS_TEST_VERIFY_EQ(test_ptr, 20, td_ptr->set_size_height);
wlmaker_xdg_toplevel_destroy(wxt_ptr);
}
/* ------------------------------------------------------------------------- */
/** Tests set_activated requests. */
void _wlmaker_xdg_toplevel_test_activated(bs_test_t *test_ptr)
{
struct _xdg_toplevel_test_data *td_ptr =
BS_ASSERT_NOTNULL(bs_test_context(test_ptr));
struct wlmaker_xdg_toplevel *wxt_ptr =
_wlmaker_xdg_toplevel_create_injected(
&td_ptr->wlr_xdg_toplevel,
&td_ptr->server,
_wlmaker_xdg_toplevel_fake_set_maximized,
_wlmaker_xdg_toplevel_fake_set_fullscreen,
_wlmaker_xdg_toplevel_fake_set_size,
_wlmaker_xdg_toplevel_fake_set_activated,
_wlmaker_xdg_toplevel_fake_get_extents_empty);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wxt_ptr);
// Issued from the window, ie. from the compositor.
bool a = true;
wl_signal_emit(
&wlmtk_window_events(wxt_ptr->window_ptr)->set_activated, &a);
BS_TEST_VERIFY_EQ(test_ptr, 0, td_ptr->set_activated_calls);
// Pending properties. Next commit must trigger.
td_ptr->wlr_xdg_surface.initialized = true; // Ready for configure().
td_ptr->wlr_xdg_surface.current.geometry.width = 200;
td_ptr->wlr_xdg_surface.current.geometry.height = 100;
wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_activated_calls);
wlmaker_xdg_toplevel_destroy(wxt_ptr);
}
/* ------------------------------------------------------------------------- */
/** Tests surface map calls. */
void _wlmaker_xdg_toplevel_test_map(bs_test_t *test_ptr)
{
struct _xdg_toplevel_test_data *td_ptr =
BS_ASSERT_NOTNULL(bs_test_context(test_ptr));
struct wlmaker_xdg_toplevel *wxt_ptr =
_wlmaker_xdg_toplevel_create_injected(
&td_ptr->wlr_xdg_toplevel,
&td_ptr->server,
_wlmaker_xdg_toplevel_fake_set_maximized,
_wlmaker_xdg_toplevel_fake_set_fullscreen,
_wlmaker_xdg_toplevel_fake_set_size,
_wlmaker_xdg_toplevel_fake_set_activated,
_wlmaker_xdg_toplevel_fake_get_extents_empty);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wxt_ptr);
wlmtk_window_t *w = wxt_ptr->window_ptr; // for convenience.
// Override the surface element. We cannot actually map it.
wlmtk_base_set_content_element(&wxt_ptr->base, NULL);
wxt_ptr->surface_ptr = NULL;
// Situation 1: Initial map call, but surface has size (0x0) yet.
// => will not be mapped yet, but only after a commit with non-zero size.
wl_signal_emit(&td_ptr->wlr_surface.events.map, NULL);
BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_workspace(w));
wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_workspace(w));
td_ptr->wlr_xdg_surface.current.geometry.width = 200;
td_ptr->wlr_xdg_surface.current.geometry.height = 100;
wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL);
BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlmtk_window_get_workspace(w));
WLMTK_TEST_VERIFY_WLRBOX_EQ(
test_ptr, 0, 0, 200, 100, wlmtk_window_get_size(w));
wl_signal_emit(&td_ptr->wlr_surface.events.unmap, NULL);
BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_workspace(w));
// Situation 2: Size is set. Must get mapped upon surface 'map'.
wl_signal_emit(&td_ptr->wlr_surface.events.map, NULL);
BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlmtk_window_get_workspace(w));
wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL);
BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlmtk_window_get_workspace(w));
wl_signal_emit(&td_ptr->wlr_surface.events.unmap, NULL);
BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_workspace(w));
wlmaker_xdg_toplevel_destroy(wxt_ptr);
}
/* ------------------------------------------------------------------------- */
/** Tests surface map calls if no geometry is given. */
void _wlmaker_xdg_toplevel_test_map_nogeo(bs_test_t *test_ptr)
{
struct _xdg_toplevel_test_data *td_ptr =
BS_ASSERT_NOTNULL(bs_test_context(test_ptr));
struct wlmaker_xdg_toplevel *wxt_ptr =
_wlmaker_xdg_toplevel_create_injected(
&td_ptr->wlr_xdg_toplevel,
&td_ptr->server,
_wlmaker_xdg_toplevel_fake_set_maximized,
_wlmaker_xdg_toplevel_fake_set_fullscreen,
_wlmaker_xdg_toplevel_fake_set_size,
_wlmaker_xdg_toplevel_fake_set_activated,
_wlmaker_xdg_toplevel_fake_get_extents_64x32);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wxt_ptr);
wlmtk_window_t *w = wxt_ptr->window_ptr; // for convenience.
// Override the surface element. We cannot actually map it.
wlmtk_base_set_content_element(&wxt_ptr->base, NULL);
wxt_ptr->surface_ptr = NULL;
// Situation 1: Initial map call. Surface has geometry 0,0, but
// the extents are 64x32. Will map, with that size.
wl_signal_emit(&td_ptr->wlr_surface.events.map, NULL);
BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_workspace(w));
wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL);
BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlmtk_window_get_workspace(w));
WLMTK_TEST_VERIFY_WLRBOX_EQ(
test_ptr, 0, 0, 64, 32, wlmtk_window_get_size(w));
wl_signal_emit(&td_ptr->wlr_surface.events.unmap, NULL);
BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_workspace(w));
// Situation 2: Size is set. Must get mapped upon surface 'map'.
wl_signal_emit(&td_ptr->wlr_surface.events.map, NULL);
BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlmtk_window_get_workspace(w));
wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL);
BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlmtk_window_get_workspace(w));
wl_signal_emit(&td_ptr->wlr_surface.events.unmap, NULL);
BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_workspace(w));
wlmaker_xdg_toplevel_destroy(wxt_ptr);
}
/* == End of xdg_toplevel.c ================================================ */
wlmaker-0.8.1/src/xdg_decoration.h 0000644 0001751 0001751 00000003540 15222270710 016521 0 ustar runner runner /* ========================================================================= */
/**
* @file xdg_decoration.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __XDG_DECORATION_H__
#define __XDG_DECORATION_H__
#include
#include
struct wl_display;
/** The decoration manager handle. */
typedef struct _wlmaker_xdg_decoration_manager_t wlmaker_xdg_decoration_manager_t;
#include "server.h" // IWYU pragma: keep
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Creates a new XDG decoration manager.
*
* @param wl_display_ptr
* @param config_dict_ptr
*
* @return A decoration manager handle or NULL on error.
*/
wlmaker_xdg_decoration_manager_t *wlmaker_xdg_decoration_manager_create(
struct wl_display *wl_display_ptr,
bspl_dict_t *config_dict_ptr);
/**
* Destroys the XDG decoration manager.
*
* @param decoration_manager_ptr
*/
void wlmaker_xdg_decoration_manager_destroy(
wlmaker_xdg_decoration_manager_t *decoration_manager_ptr);
/** Unit test set. */
extern const bs_test_set_t wlmaker_xdg_decoration_test_set;
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __XDG_DECORATION_H__ */
/* == End of xdg_decoration.h ============================================== */
wlmaker-0.8.1/src/input_observation.c 0000644 0001751 0001751 00000036511 15222270710 017301 0 ustar runner runner /* ========================================================================= */
/**
* @file input_observation.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "input_observation.h"
#include
#include
#include
#include
#include
#define WLR_USE_UNSTABLE
#include
#include
#include
#include
#include
#undef WLR_USE_UNSTABLE
#include "server.h"
#include "toolkit/toolkit.h"
#include "ext-input-observation-v1-server-protocol.h"
struct wl_client;
struct wl_resource;
/* == Declarations ========================================================= */
/** State of the input observation manager. */
struct _wlmaker_input_observation_manager_t {
/** The global holding the input observation's interface. */
struct wl_global *wl_global_ptr;
/** Link to the wlroots' implementation of wl_seat. */
struct wlr_seat *wlr_seat_ptr;
/** Link to the wlroots' cursor implementation. */
struct wlr_cursor *wlr_cursor_ptr;
};
/** State of a position observer. */
struct _wlmaker_input_position_observer_t {
/** The corresponding resource. */
struct wl_resource *wl_resource_ptr;
/** The pointer it was set up for. */
struct wl_resource *pointer_wl_resource_ptr;
/** The surface it observes the position for. */
struct wlr_surface *wlr_surface_ptr;
/** Link to the wlroots' cursor implementation. */
struct wlr_cursor *wlr_cursor_ptr;
/** Listener for `destroy` event of the `wlr_surface_ptr`. */
struct wl_listener surface_destroy_listener;
/** Listener for `frame` event of struct wlr_cursor. */
struct wl_listener cursor_frame_listener;
};
static wlmaker_input_observation_manager_t *input_observation_manager_from_resource(
struct wl_resource *wl_resource_ptr);
static void bind_input_observation(
struct wl_client *wl_client_ptr,
void *data_ptr,
uint32_t version,
uint32_t id);
static void handle_resource_destroy(
struct wl_client *wl_client_ptr,
struct wl_resource *wl_resource_ptr);
static void input_observation_manager_handle_create_pointer_observer(
struct wl_client *client,
struct wl_resource *resource,
uint32_t id,
struct wl_resource *pointer_wl_resource_ptr,
struct wl_resource *surface);
static wlmaker_input_position_observer_t *wlmaker_input_position_observer_from_resource(
struct wl_resource *wl_resource_ptr);
static wlmaker_input_position_observer_t *wlmaker_input_position_observer_create(
struct wl_client *wl_client_ptr,
wlmaker_input_observation_manager_t *manager_ptr,
uint32_t id,
int version,
struct wl_resource *pointer_wl_resource_ptr,
struct wlr_surface *wlr_surface_ptr);
static void wlmaker_input_position_observer_resource_destroy(
struct wl_resource *wl_resource_ptr);
static void wlmaker_input_position_observer_destroy(
wlmaker_input_position_observer_t *position_observer_ptr);
static void _wlmaker_input_position_observer_handle_surface_destroy(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_input_position_observer_handle_cursor_frame(
struct wl_listener *listener_ptr,
void *data_ptr);
/* ========================================================================= */
/** Implementation of the position observation manager. */
static const struct ext_input_observation_manager_v1_interface
input_observation_manager_v1_implementation = {
.destroy = handle_resource_destroy,
.create_pointer_observer = input_observation_manager_handle_create_pointer_observer,
};
/** Implementation of the position (position) observer. */
static const struct ext_input_position_observer_v1_interface
input_position_observer_v1_implementation = {
.destroy = handle_resource_destroy,
};
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
wlmaker_input_observation_manager_t *wlmaker_input_observation_manager_create(
struct wl_display *wl_display_ptr,
struct wlr_seat *wlr_seat_ptr,
struct wlr_cursor *wlr_cursor_ptr)
{
wlmaker_input_observation_manager_t *manager_ptr = logged_calloc(
1, sizeof(wlmaker_input_observation_manager_t));
if (NULL == manager_ptr) return NULL;
manager_ptr->wlr_seat_ptr = wlr_seat_ptr;
manager_ptr->wlr_cursor_ptr = wlr_cursor_ptr;
manager_ptr->wl_global_ptr = wl_global_create(
wl_display_ptr,
&ext_input_observation_manager_v1_interface,
1,
manager_ptr,
bind_input_observation);
if (NULL == manager_ptr->wl_global_ptr) {
bs_log(BS_ERROR, "Failed wl_global_create");
wlmaker_input_observation_manager_destroy(manager_ptr);
return NULL;
}
return manager_ptr;
}
/* ------------------------------------------------------------------------- */
void wlmaker_input_observation_manager_destroy(
wlmaker_input_observation_manager_t *manager_ptr)
{
if (NULL != manager_ptr->wl_global_ptr) {
wl_global_destroy(manager_ptr->wl_global_ptr);
manager_ptr->wl_global_ptr = NULL;
}
free(manager_ptr);
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/**
* Returns the toplevel position observer from the resource, with type check.
*
* @param wl_resource_ptr
*
* @return Position to the @ref wlmaker_input_observation_manager_t.
*/
wlmaker_input_observation_manager_t *input_observation_manager_from_resource(
struct wl_resource *wl_resource_ptr)
{
BS_ASSERT(wl_resource_instance_of(
wl_resource_ptr,
&ext_input_observation_manager_v1_interface,
&input_observation_manager_v1_implementation));
return wl_resource_get_user_data(wl_resource_ptr);
}
/* ------------------------------------------------------------------------- */
/**
* Binds the position observation manager for the client.
*
* @param wl_client_ptr
* @param data_ptr
* @param version
* @param id
*/
void bind_input_observation(
struct wl_client *wl_client_ptr,
void *data_ptr,
uint32_t version,
uint32_t id)
{
struct wl_resource *wl_resource_ptr = wl_resource_create(
wl_client_ptr,
&ext_input_observation_manager_v1_interface,
version,
id);
if (NULL == wl_resource_ptr) {
wl_client_post_no_memory(wl_client_ptr);
return;
}
wlmaker_input_observation_manager_t *manager_ptr = data_ptr;
wl_resource_set_implementation(
wl_resource_ptr,
&input_observation_manager_v1_implementation, // implementation.
manager_ptr, // data
NULL); // dtor. We don't have an explicit one.
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the 'destroy' method: Destroys the resource.
*
* @param wl_client_ptr
* @param wl_resource_ptr
*/
void handle_resource_destroy(
__UNUSED__ struct wl_client *wl_client_ptr,
struct wl_resource *wl_resource_ptr)
{
wl_resource_destroy(wl_resource_ptr);
}
/* ------------------------------------------------------------------------- */
/**
* Creates an observer, associated with the surface.
*
* Requires that @ref wlmaker_input_observation_manager_t::wlr_seat_ptr is set
* and has the `WL_SEAT_CAPABILITY_POINTER` capability.
*
* @param wl_client_ptr
* @param wl_resource_ptr
* @param id
* @param pointer_wl_resource_ptr Resource handle of the pointer.
* @param surface_wl_resource_ptr Resource handle of the surface.
*/
void input_observation_manager_handle_create_pointer_observer(
struct wl_client *wl_client_ptr,
struct wl_resource *wl_resource_ptr,
uint32_t id,
struct wl_resource *pointer_wl_resource_ptr,
struct wl_resource *surface_wl_resource_ptr)
{
wlmaker_input_observation_manager_t *manager_ptr =
input_observation_manager_from_resource(wl_resource_ptr);
// Guard clause: We require the position capability to be (or have been)
// present for the seat.
if (!(manager_ptr->wlr_seat_ptr->accumulated_capabilities &
WL_SEAT_CAPABILITY_POINTER)) {
wl_resource_post_error(
wl_resource_ptr,
WL_DISPLAY_ERROR_INVALID_METHOD,
"Missing pointer capability on seat");
return;
}
struct wlr_surface *wlr_surface_ptr =
wlr_surface_from_resource(surface_wl_resource_ptr);
wlmaker_input_position_observer_t *position_observer_ptr =
wlmaker_input_position_observer_create(
wl_client_ptr,
manager_ptr,
id,
wl_resource_get_version(wl_resource_ptr),
pointer_wl_resource_ptr,
wlr_surface_ptr);
if (NULL == position_observer_ptr) {
wl_client_post_no_memory(wl_client_ptr);
return;
}
}
/* ------------------------------------------------------------------------- */
/** Ctor for the position observer. */
wlmaker_input_position_observer_t *wlmaker_input_position_observer_create(
struct wl_client *wl_client_ptr,
wlmaker_input_observation_manager_t *manager_ptr,
uint32_t id,
int version,
struct wl_resource *pointer_wl_resource_ptr,
struct wlr_surface *wlr_surface_ptr)
{
wlmaker_input_position_observer_t *position_observer_ptr = logged_calloc(
1, sizeof(wlmaker_input_position_observer_t));
if (NULL == position_observer_ptr) return NULL;
position_observer_ptr->pointer_wl_resource_ptr = pointer_wl_resource_ptr;
position_observer_ptr->wlr_surface_ptr = wlr_surface_ptr;
position_observer_ptr->wlr_cursor_ptr = manager_ptr->wlr_cursor_ptr;
position_observer_ptr->wl_resource_ptr = wl_resource_create(
wl_client_ptr,
&ext_input_position_observer_v1_interface,
version,
id);
if (NULL == position_observer_ptr->wl_resource_ptr) {
bs_log(BS_ERROR, "Failed wl_resource_create(%p, %p, %d, %"PRIu32")",
wl_client_ptr,
&ext_input_position_observer_v1_interface,
version, id);
wlmaker_input_position_observer_destroy(position_observer_ptr);
return NULL;
}
wl_resource_set_implementation(
position_observer_ptr->wl_resource_ptr,
&input_position_observer_v1_implementation,
position_observer_ptr,
wlmaker_input_position_observer_resource_destroy);
wlmtk_util_connect_listener_signal(
&position_observer_ptr->wlr_surface_ptr->events.destroy,
&position_observer_ptr->surface_destroy_listener,
_wlmaker_input_position_observer_handle_surface_destroy);
wlmtk_util_connect_listener_signal(
&position_observer_ptr->wlr_cursor_ptr->events.frame,
&position_observer_ptr->cursor_frame_listener,
_wlmaker_input_position_observer_handle_cursor_frame);
return position_observer_ptr;
}
/* ------------------------------------------------------------------------- */
/** Dtor, from the resource. */
void wlmaker_input_position_observer_resource_destroy(
struct wl_resource *wl_resource_ptr)
{
wlmaker_input_position_observer_t *position_observer_ptr =
wlmaker_input_position_observer_from_resource(wl_resource_ptr);
wlmaker_input_position_observer_destroy(position_observer_ptr);
}
/* ------------------------------------------------------------------------- */
/** Dtor. */
void wlmaker_input_position_observer_destroy(
wlmaker_input_position_observer_t *position_observer_ptr)
{
wlmtk_util_disconnect_listener(&position_observer_ptr->cursor_frame_listener);
wlmtk_util_disconnect_listener(&position_observer_ptr->surface_destroy_listener);
free(position_observer_ptr);
}
/* ------------------------------------------------------------------------- */
/** Type-safe conversion from resource to position observer. */
wlmaker_input_position_observer_t *wlmaker_input_position_observer_from_resource(
struct wl_resource *wl_resource_ptr)
{
BS_ASSERT(wl_resource_instance_of(
wl_resource_ptr,
&ext_input_position_observer_v1_interface,
&input_position_observer_v1_implementation));
return wl_resource_get_user_data(wl_resource_ptr);
}
/* ------------------------------------------------------------------------- */
/** Handles surface destruction: Destroy the position observer. */
static void _wlmaker_input_position_observer_handle_surface_destroy(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_input_position_observer_t *position_observer_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_input_position_observer_t, surface_destroy_listener);
wl_resource_destroy(position_observer_ptr->wl_resource_ptr);
}
/* ------------------------------------------------------------------------- */
/** Handles cursor frame events: Sends the current pointer position. */
void _wlmaker_input_position_observer_handle_cursor_frame(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_input_position_observer_t *position_observer_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_input_position_observer_t, cursor_frame_listener);
// Guard clause: No wlmtk surface or no scene means it's not fully mapped.
wlmtk_surface_t *surface_ptr = position_observer_ptr->wlr_surface_ptr->data;
if (NULL == surface_ptr ||
NULL == wlmtk_surface_element(surface_ptr)->wlr_scene_node_ptr) return;
// TODO(kaeser@gubbe.ch): For a lower-level implementation, we should
// only send if the pointer is from the given seat.
// See wlr_seat_client_from_pointer_resource().
// Get coordinates. Returns false if not all parents rae enabled.
int node_x, node_y;
if (!wlr_scene_node_coords(
wlmtk_surface_element(surface_ptr)->wlr_scene_node_ptr,
&node_x, &node_y)) {
return;
}
// Then, compute the cursor position, relative to the surface dimenions.
// Note: This assumes the surface remains aligned to X/Y axes.
int width, height;
wlmtk_surface_get_size(surface_ptr, &width, &height);
// Translate to 24:8 fixpoint and bound to range.
double x = 256.0 * (double)(
position_observer_ptr->wlr_cursor_ptr->x - node_x) / width;
double y = 256.0 * (double)(
position_observer_ptr->wlr_cursor_ptr->y - node_y) / height;
ext_input_position_observer_v1_send_position(
position_observer_ptr->wl_resource_ptr,
position_observer_ptr->wlr_surface_ptr->resource,
0,
BS_MAX(INT32_MIN, BS_MIN(INT32_MAX, x)),
BS_MAX(INT32_MIN, BS_MIN(INT32_MAX, y)));
}
/* == End of input_observation.c =========================================== */
wlmaker-0.8.1/src/clip.h 0000644 0001751 0001751 00000003671 15222270710 014464 0 ustar runner runner /* ========================================================================= */
/**
* @file clip.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Creates the wlmaker clip. A view, with server-bound surfaces, that act
* as a workspace-local dock and a workspace pager.
*
* Corresponding Window Maker documentation:
* http://www.windowmaker.org/docs/guidedtour/clip.html
*/
#ifndef __CLIP_H__
#define __CLIP_H__
#include
#include
/** Forward definition: Clip handle. */
typedef struct _wlmaker_clip_t wlmaker_clip_t;
#include "config.h"
#include "server.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Creates the Clip. Needs the server to be up with workspaces running.
*
* @param server_ptr
* @param state_dict_ptr
* @param style_ptr
*
* @return Pointer to the Clip handle, or NULL on error.
*/
wlmaker_clip_t *wlmaker_clip_create(
wlmaker_server_t *server_ptr,
bspl_dict_t *state_dict_ptr,
const wlmaker_config_style_t *style_ptr);
/**
* Destroys the Clip.
*
* @param clip_ptr
*/
void wlmaker_clip_destroy(wlmaker_clip_t *clip_ptr);
/** Unit test set. */
extern const bs_test_set_t wlmaker_clip_test_set;
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __CLIP_H__ */
/* == End of clip.h ======================================================== */
wlmaker-0.8.1/src/xdg_shell.h 0000644 0001751 0001751 00000004552 15222270710 015505 0 ustar runner runner /* ========================================================================= */
/**
* @file xdg_shell.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __XDG_SHELL_H__
#define __XDG_SHELL_H__
#include
#define WLR_USE_UNSTABLE
#include
#undef WLR_USE_UNSTABLE
/** Handle for XDG Shell server handler. */
typedef struct _wlmaker_xdg_shell_t wlmaker_xdg_shell_t;
#include "task_list.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/** Handle for XDG Shell server handler. */
struct _wlmaker_xdg_shell_t {
/** Back-link to the server this XDG Shell belongs to. */
wlmaker_server_t *server_ptr;
/** XDG Shell handler. */
struct wlr_xdg_shell *wlr_xdg_shell_ptr;
/** Listener for the `new_surface` signal raised by `wlr_xdg_shell`. */
struct wl_listener new_surface_listener;
/** Listener for the `new_toplevel` signal raised by `wlr_xdg_shell`. */
struct wl_listener new_toplevel_listener;
/** Listener for the `new_popup` signal raised by `wlr_xdg_shell`. */
struct wl_listener new_popup_listener;
/** Listener for the `destroy` signal raised by `wlr_xdg_shell`. */
struct wl_listener destroy_listener;
};
/**
* Creates an XDG shell server handler.
*
* @param server_ptr
*
* @return The XDG Shell server handler or NULL on error.
*/
wlmaker_xdg_shell_t *wlmaker_xdg_shell_create(wlmaker_server_t *server_ptr);
/**
* Destroys the XDG Shell server handler.
*
* @param xdg_shell_ptr
*/
void wlmaker_xdg_shell_destroy(wlmaker_xdg_shell_t *xdg_shell_ptr);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __XDG_SHELL_H__ */
/* == End of xdg_shell.h =================================================== */
wlmaker-0.8.1/src/background.h 0000644 0001751 0001751 00000003572 15222270710 015654 0 ustar runner runner /* ========================================================================= */
/**
* @file background.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __BACKGROUND_H__
#define __BACKGROUND_H__
#include
#include
#include "toolkit/toolkit.h"
struct wlr_output_layout;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Creates a background, derived from a @ref wlmtk_panel_t.
*
* @param workspace_ptr
* @param wlr_output_layout_ptr
* @param color
*
* @return A list node acting as handle for the background, or NULL on error.
*/
bs_dllist_node_t *wlmaker_background_create(
wlmtk_workspace_t *workspace_ptr,
struct wlr_output_layout *wlr_output_layout_ptr,
uint32_t color);
/**
* Destroys the background.
*
* @param dlnode_ptr
* @param ud_ptr
*/
void wlmaker_background_dlnode_destroy(
bs_dllist_node_t *dlnode_ptr,
void *ud_ptr);
/**
* Sets the color for this workspace's background on each output.
*
* @param dlnode_ptr
* @param ud_ptr
*/
void wlmaker_background_dlnode_set_color(
bs_dllist_node_t *dlnode_ptr,
void *ud_ptr);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __BACKGROUND_H__ */
/* == End of background.h ================================================== */
wlmaker-0.8.1/src/xwl_surface.h 0000644 0001751 0001751 00000003675 15222270710 016063 0 ustar runner runner /* ========================================================================= */
/**
* @file xwl_surface.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __XWL_SURFACE_H__
#define __XWL_SURFACE_H__
#if defined(WLMAKER_HAVE_XWAYLAND)
#include
#include "task_list.h"
#include "xwl.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/** Forward declaration. */
struct wlr_xwayland_surface;
/** XWayland window (surface) state. */
typedef struct _wlmaker_xwl_surface_t wlmaker_xwl_surface_t;
/**
* Creates an XWayland window. Technically, window surface.
*
* @param wlr_xwayland_surface_ptr
* @param xwl_ptr
* @param server_ptr
*
* @return Pointer to a @ref wlmaker_xwl_surface_t.
*/
wlmaker_xwl_surface_t *wlmaker_xwl_surface_create(
struct wlr_xwayland_surface *wlr_xwayland_surface_ptr,
wlmaker_xwl_t *xwl_ptr,
wlmaker_server_t *server_ptr);
/**
* Destroys the XWayland window (surface).
*
* @param xwl_surface_ptr
*/
void wlmaker_xwl_surface_destroy(wlmaker_xwl_surface_t *xwl_surface_ptr);
/** Unit test set for XWL surface. */
extern const bs_test_set_t wlmaker_xwl_surface_test_set;
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif // defined(WLMAKER_HAVE_XWAYLAND)
#endif /* __XWL_SURFACE_H__ */
/* == End of xwl_surface.h ================================================= */
wlmaker-0.8.1/src/layer_shell.h 0000644 0001751 0001751 00000003003 15222270710 016025 0 ustar runner runner /* ========================================================================= */
/**
* @file layer_shell.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __LAYER_SHELL_H__
#define __LAYER_SHELL_H__
/** Handle for the layer shell. */
typedef struct _wlmaker_layer_shell_t wlmaker_layer_shell_t;
#include "task_list.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Creates a layer shell handler.
*
* @param server_ptr
*
* @return A handle to the layer shell handler, or NULL on error.
*/
wlmaker_layer_shell_t *wlmaker_layer_shell_create(
wlmaker_server_t *server_ptr);
/**
* Destroys the layer shell handler.
*
* @param layer_shell_ptr
*/
void wlmaker_layer_shell_destroy(wlmaker_layer_shell_t *layer_shell_ptr);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __LAYER_SHELL_H__ */
/* == End of layer_shell.h ================================================= */
wlmaker-0.8.1/src/root_menu.c 0000644 0001751 0001751 00000105662 15222270710 015542 0 ustar runner runner /* ========================================================================= */
/**
* @file root_menu.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "root_menu.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define WLR_USE_UNSTABLE
#include
#include
#include
#undef WLR_USE_UNSTABLE
#include "../etc/root_menu.h"
#include "action.h"
#include "action_item.h"
#include "config.h"
#include "subprocess_monitor.h"
#include "server.h"
/* == Declarations ========================================================= */
/** State of the root menu. */
struct _wlmaker_root_menu_t {
/** Window. */
wlmtk_window_t *window_ptr;
/** The root menu base instance. */
wlmtk_menu_t *menu_ptr;
/** Listener for @ref wlmtk_menu_events_t::open_changed. */
struct wl_listener menu_open_changed_listener;
/** Listener for @ref wlmtk_menu_events_t::request_close. */
struct wl_listener menu_request_close_listener;
/** Listener for @ref wlmtk_window_events_t::request_close. */
struct wl_listener window_request_close_listener;
/** Listener for @ref wlmtk_window_events_t::set_activated. */
struct wl_listener window_set_activated_listener;
/** Back-link to the server. */
wlmaker_server_t *server_ptr;
};
/** State of a menu generator, while waiting for subprocess to complete. */
typedef struct {
/** Subprocess handle. */
wlmaker_subprocess_handle_t *subprocess_handle_ptr;
/** Back-link to the server. */
wlmaker_server_t *server_ptr;
/** The menu this generator is going to populate. */
wlmtk_menu_t *menu_ptr;
/** Menu style reference. */
wlmtk_menu_style_ref_t *style_ref_ptr;
/** Dynamic buffer to hold stdout while the process is running. */
bs_dynbuf_t *stdout_dynbuf_ptr;
/** Listener for @ref wlmtk_menu_events_t::destroy. */
struct wl_listener menu_destroy_listener;
} wlmaker_root_menu_generator_t;
static void _wlmaker_root_menu_handle_window_request_close(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_root_menu_handle_window_set_activated(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_root_menu_handle_menu_open_changed(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_root_menu_handle_request_close(
struct wl_listener *listener_ptr,
void *data_ptr);
static bool _wlmaker_root_menu_init_menu_from_array(
wlmtk_menu_t *menu_ptr,
bspl_array_t *array_ptr,
wlmtk_menu_style_ref_t *menu_style_ref_ptr,
wlmaker_server_t *server_ptr);
static bool _wlmaker_root_menu_populate_menu_items_from_array(
wlmtk_menu_t *menu_ptr,
bspl_array_t *array_ptr,
wlmtk_menu_style_ref_t *menu_style_ref_ptr,
wlmaker_server_t *server_ptr);
static bool _wlmaker_root_menu_populate_menu_items_from_file(
wlmtk_menu_t *menu_ptr,
const char *filename_ptr,
wlmtk_menu_style_ref_t *menu_style_ref_ptr,
wlmaker_server_t *server_ptr);
static bool _wlmaker_root_menu_populate_menu_items_from_generator(
wlmtk_menu_t *menu_ptr,
const char *command_ptr,
wlmtk_menu_style_ref_t *menu_style_ref_ptr,
wlmaker_server_t *server_ptr);
static void _wlmaker_root_menu_generator_destroy(
wlmaker_root_menu_generator_t *gen_menu_ptr);
static void _wlmaker_root_menu_generator_handle_menu_destroy(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_root_menu_generator_handle_terminated(
void *userdata_ptr,
wlmaker_subprocess_handle_t *subprocess_handle_ptr,
int state,
int code);
static wlmtk_menu_item_t *_wlmaker_root_menu_create_item_from_array(
bspl_array_t *item_array_ptr,
wlmtk_menu_style_ref_t *menu_style_ref_ptr,
wlmaker_server_t *server_ptr);
static wlmtk_menu_item_t *_wlmaker_root_menu_create_disabled_item(
wlmtk_menu_style_ref_t *style_ref_ptr,
const char *fmt_ptr,
...) __ARG_PRINTF__(2, 3);
/* == Data ================================================================= */
/** Indicates to load the file specified in following argument. */
static const char *_wlmaker_root_menu_statement_include = "IncludePlistMenu";
/**
* Indicates to generate the menu using a shell command specified in the
* following argument.
*/
static const char *_wlmaker_root_menu_statement_generate = "GeneratePlistMenu";
/**
* Unit test injector: struct wl_display that will be terminated when
* subprocess terminates. Must be NULL when not for in unit tests.
*/
static struct wl_display *_wlmaker_root_menu_test_wl_display_ptr = NULL;
/** Number of generators, used to terminate display in unit tests. */
static size_t _wlmaker_root_menu_generators = 0;
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
wlmaker_root_menu_t *wlmaker_root_menu_create(
wlmaker_server_t *server_ptr,
const char *arg_root_menu_file_ptr,
wlmtk_window_style_ref_t *window_style_ref_ptr,
wlmtk_menu_style_ref_t *menu_style_ref_ptr)
{
bspl_array_t *root_menu_array_ptr = bspl_array_from_object(
wlmaker_config_object_load(
server_ptr->files_ptr,
"root_menu",
arg_root_menu_file_ptr,
"RootMenu.plist",
embedded_binary_root_menu_data,
embedded_binary_root_menu_size));
if (NULL == root_menu_array_ptr) return NULL;
if (bspl_array_size(root_menu_array_ptr) <= 1) {
bs_log(BS_ERROR, "Needs > 1 array element for menu definition.");
return NULL;
}
if (BSPL_STRING != bspl_object_type(
bspl_array_at(root_menu_array_ptr, 0))) {
bs_log(BS_ERROR, "Array element [0] must be a string.");
return NULL;
}
wlmaker_root_menu_t *root_menu_ptr = logged_calloc(
1, sizeof(wlmaker_root_menu_t));
if (NULL == root_menu_ptr) return NULL;
root_menu_ptr->server_ptr = server_ptr;
root_menu_ptr->server_ptr->root_menu_ptr = root_menu_ptr;
root_menu_ptr->menu_ptr = wlmtk_menu_create(menu_style_ref_ptr);
if (NULL == root_menu_ptr->menu_ptr) {
wlmaker_root_menu_destroy(root_menu_ptr);
bspl_array_unref(root_menu_array_ptr);
return NULL;
}
if (!_wlmaker_root_menu_init_menu_from_array(
root_menu_ptr->menu_ptr,
root_menu_array_ptr,
menu_style_ref_ptr,
server_ptr)) {
bspl_array_unref(root_menu_array_ptr);
return NULL;
}
wlmtk_util_connect_listener_signal(
&wlmtk_menu_events(root_menu_ptr->menu_ptr)->open_changed,
&root_menu_ptr->menu_open_changed_listener,
_wlmaker_root_menu_handle_menu_open_changed);
wlmtk_util_connect_listener_signal(
&wlmtk_menu_events(root_menu_ptr->menu_ptr)->request_close,
&root_menu_ptr->menu_request_close_listener,
_wlmaker_root_menu_handle_request_close);
root_menu_ptr->window_ptr = wlmtk_window_create(
wlmtk_menu_element(root_menu_ptr->menu_ptr),
window_style_ref_ptr,
menu_style_ref_ptr);
if (NULL == root_menu_ptr->window_ptr) {
wlmaker_root_menu_destroy(root_menu_ptr);
bspl_array_unref(root_menu_array_ptr);
return NULL;
}
wlmtk_util_connect_listener_signal(
&wlmtk_window_events(root_menu_ptr->window_ptr)->request_close,
&root_menu_ptr->window_request_close_listener,
_wlmaker_root_menu_handle_window_request_close);
wlmtk_util_connect_listener_signal(
&wlmtk_window_events(root_menu_ptr->window_ptr)->set_activated,
&root_menu_ptr->window_set_activated_listener,
_wlmaker_root_menu_handle_window_set_activated);
wlmtk_window_set_title(
root_menu_ptr->window_ptr,
bspl_array_string_value_at(root_menu_array_ptr, 0));
wlmtk_window_set_server_side_decorated(root_menu_ptr->window_ptr, true);
bspl_array_unref(root_menu_array_ptr);
return root_menu_ptr;
}
/* ------------------------------------------------------------------------- */
void wlmaker_root_menu_destroy(wlmaker_root_menu_t *root_menu_ptr)
{
if (NULL != root_menu_ptr->server_ptr) {
BS_ASSERT(root_menu_ptr->server_ptr->root_menu_ptr == root_menu_ptr);
root_menu_ptr->server_ptr->root_menu_ptr = NULL;
root_menu_ptr->server_ptr = NULL;;
}
if (NULL != root_menu_ptr->window_ptr) {
// Unmap, in case it's not unmapped yet.
wlmtk_workspace_t *workspace_ptr = wlmtk_window_get_workspace(
root_menu_ptr->window_ptr);
if (NULL != workspace_ptr) {
wlmtk_workspace_unmap_window(workspace_ptr,
root_menu_ptr->window_ptr);
}
wlmtk_util_disconnect_listener(
&root_menu_ptr->window_request_close_listener);
wlmtk_util_disconnect_listener(
&root_menu_ptr->window_set_activated_listener);
wlmtk_window_destroy(root_menu_ptr->window_ptr);
root_menu_ptr->window_ptr = NULL;
}
if (NULL != root_menu_ptr->menu_ptr) {
wlmtk_util_disconnect_listener(
&root_menu_ptr->menu_request_close_listener);
wlmtk_util_disconnect_listener(
&root_menu_ptr->menu_open_changed_listener);
wlmtk_menu_destroy(root_menu_ptr->menu_ptr);
root_menu_ptr->menu_ptr = NULL;
}
free(root_menu_ptr);
}
/* ------------------------------------------------------------------------- */
wlmtk_window_t *wlmaker_root_menu_window(wlmaker_root_menu_t *root_menu_ptr)
{
return root_menu_ptr->window_ptr;
}
/* ------------------------------------------------------------------------- */
wlmtk_menu_t *wlmaker_root_menu_menu(wlmaker_root_menu_t *root_menu_ptr)
{
return root_menu_ptr->menu_ptr;
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/** Handles when window close button is pressed: Hides the menu. */
void _wlmaker_root_menu_handle_window_request_close(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_root_menu_t *root_menu_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_root_menu_t, window_request_close_listener);
wlmtk_menu_set_open(root_menu_ptr->menu_ptr, false);
}
/* ------------------------------------------------------------------------- */
/** Handles when the menu is activated: Get keyboard focus. */
void _wlmaker_root_menu_handle_window_set_activated(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_root_menu_t *root_menu_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_root_menu_t, window_set_activated_listener);
wlmtk_element_t *e = wlmtk_menu_element(root_menu_ptr->menu_ptr);
if (NULL != e->parent_container_ptr) {
wlmtk_container_set_keyboard_focus_element(
e->parent_container_ptr,
e,
wlmtk_window_is_activated(root_menu_ptr->window_ptr));
}
}
/* ------------------------------------------------------------------------- */
/** Handles @ref wlmtk_menu_events_t::open_changed. Unmaps window on close. */
void _wlmaker_root_menu_handle_menu_open_changed(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_root_menu_t *root_menu_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_root_menu_t, menu_open_changed_listener);
if (!wlmtk_menu_is_open(root_menu_ptr->menu_ptr) &&
NULL != wlmtk_window_get_workspace(root_menu_ptr->window_ptr)) {
wlmtk_workspace_unmap_window(
wlmtk_window_get_workspace(root_menu_ptr->window_ptr),
root_menu_ptr->window_ptr);
} else {
uint32_t properties = 0;
if (WLMTK_MENU_MODE_RIGHTCLICK ==
wlmtk_menu_get_mode(root_menu_ptr->menu_ptr)) {
properties |= WLMTK_WINDOW_PROPERTY_RIGHTCLICK;
wlmtk_container_pointer_grab(
wlmtk_menu_element(
root_menu_ptr->menu_ptr)->parent_container_ptr,
wlmtk_menu_element(root_menu_ptr->menu_ptr));
} else {
properties |= WLMTK_WINDOW_PROPERTY_CLOSABLE;
}
wlmtk_window_set_properties(root_menu_ptr->window_ptr, properties);
}
}
/* ------------------------------------------------------------------------- */
/** Listens to @ref wlmtk_menu_events_t::request_close. Closes the menu. */
void _wlmaker_root_menu_handle_request_close(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_root_menu_t *root_menu_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_root_menu_t, menu_request_close_listener);
wlmtk_menu_set_open(root_menu_ptr->menu_ptr, false);
}
/* ------------------------------------------------------------------------- */
/**
* Initializes the menu from the menu configuration array.
*
* The menu configuration is a Plist array. The first item is the menu's title,
* while the second item defines the nature of the menu configuration:
*
* It can define a set of menu items, in form of Plist arrays:
* @verbinclude tests/data/menu.plist
*
* Or, it is a definition to include a Plist menu:
* @verbinclude tests/data/menu-include.plist
*
* Or, it is a definition to generate a Plist menu:
* @verbinclude tests/data/menu-generate.plist
*
* @param menu_ptr
* @param array_ptr
* @param menu_style_ref_ptr
* @param server_ptr
*
* @return true on success.
*/
bool _wlmaker_root_menu_init_menu_from_array(
wlmtk_menu_t *menu_ptr,
bspl_array_t *array_ptr,
wlmtk_menu_style_ref_t *menu_style_ref_ptr,
wlmaker_server_t *server_ptr)
{
// (1) object must be array, and have >= 2 elements: title and content.
if (2 > bspl_array_size(array_ptr)) {
bs_log(BS_ERROR, "Plist menu definition array size must be >= 2.");
return false;
}
bspl_object_t *content_object_ptr = bspl_array_at(array_ptr, 1);
switch (bspl_object_type(content_object_ptr)) {
case BSPL_ARRAY:
// Indicates the first element is an item with a submenu, and there
// are optionally further elements. Populate the parent menu from
// that.
return _wlmaker_root_menu_populate_menu_items_from_array(
menu_ptr,
array_ptr,
menu_style_ref_ptr,
server_ptr);
case BSPL_STRING:
if (3 > bspl_array_size(array_ptr)) {
bs_log(BS_ERROR, "Must have 3 objects on \"%s\"",
bspl_array_string_value_at(array_ptr, 1));
return false;
}
if (0 == strcmp(
bspl_string_value_from_object(content_object_ptr),
_wlmaker_root_menu_statement_include)) {
return _wlmaker_root_menu_populate_menu_items_from_file(
menu_ptr,
bspl_array_string_value_at(array_ptr, 2),
menu_style_ref_ptr,
server_ptr);
} else if (0 == strcmp(
bspl_string_value_from_object(content_object_ptr), _wlmaker_root_menu_statement_generate)) {
return _wlmaker_root_menu_populate_menu_items_from_generator(
menu_ptr,
bspl_array_string_value_at(array_ptr, 2),
menu_style_ref_ptr,
server_ptr);
}
bs_log(BS_ERROR, "Unknown menu definition \"%s\"",
bspl_string_value_from_object(content_object_ptr));
return false;
default:
break;
}
bs_log(BS_ERROR, "Unhandled object type to populate menu.");
return false;
}
/* ------------------------------------------------------------------------- */
/**
* Populates the menu's items from the Plist array. This handles the case of
* a menu configuration that specifies the menu items as a Plist array.
*
* The first item of `array_ptr` is the menu's title, and each further item
* is expected to be another Plist array, defining a menu item.
*
* @param menu_ptr
* @param array_ptr
* @param menu_style_ref_ptr
* @param server_ptr
*
* @return true on success
*/
bool _wlmaker_root_menu_populate_menu_items_from_array(
wlmtk_menu_t *menu_ptr,
bspl_array_t *array_ptr,
wlmtk_menu_style_ref_t *menu_style_ref_ptr,
wlmaker_server_t *server_ptr)
{
if (bspl_array_size(array_ptr) <= 1) {
bs_log(BS_ERROR, "Needs > 1 array element for menu definition.");
return false;
}
const char *name_ptr = bspl_array_string_value_at(array_ptr, 0);
if (NULL == name_ptr) {
bs_log(BS_ERROR, "Array element [0] must be a string.");
return false;
}
for (size_t i = 1; i < bspl_array_size(array_ptr); ++i) {
bspl_array_t *item_array_ptr = bspl_array_from_object(
bspl_array_at(array_ptr, i));
if (NULL == item_array_ptr) {
bs_log(BS_ERROR, "Menu %s: Element [%zu] must be an array",
name_ptr, i);
return false;
}
if (NULL == bspl_array_string_value_at(item_array_ptr, 0)) {
bs_log(BS_ERROR,
"Menu %s: First element of item [%zu] must be a string",
name_ptr, i);
return false;
}
wlmtk_menu_item_t *menu_item_ptr =
_wlmaker_root_menu_create_item_from_array(
item_array_ptr,
menu_style_ref_ptr,
server_ptr);
if (NULL == menu_item_ptr) return false;
wlmtk_menu_add_item(menu_ptr, menu_item_ptr);
}
return true;
}
/* ------------------------------------------------------------------------- */
/**
* Loads a Plist array from file and populates the menu's items from it.
*
* @param menu_ptr
* @param filename_ptr
* @param menu_style_ref_ptr
* @param server_ptr
*
* @return true on success
*/
bool _wlmaker_root_menu_populate_menu_items_from_file(
wlmtk_menu_t *menu_ptr,
const char *filename_ptr,
wlmtk_menu_style_ref_t *menu_style_ref_ptr,
wlmaker_server_t *server_ptr)
{
char *path_ptr = bs_file_resolve_path(filename_ptr, NULL);
if (NULL == path_ptr) {
bs_log(BS_ERROR, "Failed bs_file_resolve_path(\"%s\", NULL)",
filename_ptr);
return false;
}
bspl_object_t *object_ptr = bspl_create_object_from_plist_file(path_ptr);
free(path_ptr);
if (NULL == object_ptr || BSPL_ARRAY != bspl_object_type(object_ptr)) {
bs_log(BS_ERROR, "Failed to load Plist ARRAY from \"%s\"",
filename_ptr);
if (NULL != object_ptr) bspl_object_unref(object_ptr);
return false;
}
bool rv = _wlmaker_root_menu_populate_menu_items_from_array(
menu_ptr,
bspl_array_from_object(object_ptr),
menu_style_ref_ptr,
server_ptr);
if (!rv) {
bs_log(BS_ERROR, "Failed to generate menu from Plist file \"%s\"",
filename_ptr);
}
bspl_object_unref(object_ptr);
return rv;
}
/* ------------------------------------------------------------------------- */
/**
* Launches a subprocess, to populate the menu's items.
*
* Uses a @ref wlmaker_root_menu_generator_t to track state of the subprocess
* and to tie it with the menu's lifecycle.
*
* @param menu_ptr
* @param command_ptr
* @param menu_style_ref_ptr
* @param server_ptr
*
* @return true on success
*/
bool _wlmaker_root_menu_populate_menu_items_from_generator(
wlmtk_menu_t *menu_ptr,
const char *command_ptr,
wlmtk_menu_style_ref_t *menu_style_ref_ptr,
wlmaker_server_t *server_ptr)
{
bs_subprocess_t *subprocess_ptr = NULL;
wlmaker_root_menu_generator_t *generator_ptr = logged_calloc(
1, sizeof(wlmaker_root_menu_generator_t));
if (NULL == generator_ptr) return false;
generator_ptr->server_ptr = server_ptr;
generator_ptr->menu_ptr = menu_ptr;
generator_ptr->style_ref_ptr = menu_style_ref_ptr;
generator_ptr->stdout_dynbuf_ptr = bs_dynbuf_create(1024, INT32_MAX);
if (NULL == generator_ptr->stdout_dynbuf_ptr) goto error;
wlmtk_util_connect_listener_signal(
&wlmtk_menu_events(menu_ptr)->destroy,
&generator_ptr->menu_destroy_listener,
_wlmaker_root_menu_generator_handle_menu_destroy);
const char *args[] = { "/bin/sh", "-c", command_ptr, NULL };
subprocess_ptr = bs_subprocess_create(args[0], args, NULL);
if (NULL == subprocess_ptr) goto error;
if (!bs_subprocess_start(subprocess_ptr)) goto error;
bs_log(BS_INFO,
"Created subprocess %p [%"PRIdMAX"] for \"/bin/sh\" \"-c\" \"%s\"",
subprocess_ptr,
(intmax_t)bs_subprocess_pid(subprocess_ptr),
command_ptr);
_wlmaker_root_menu_generators++;
generator_ptr->subprocess_handle_ptr = wlmaker_subprocess_monitor_entrust(
server_ptr->monitor_ptr,
subprocess_ptr,
_wlmaker_root_menu_generator_handle_terminated,
generator_ptr,
NULL,
NULL,
NULL,
NULL,
generator_ptr->stdout_dynbuf_ptr);
if (NULL == generator_ptr->subprocess_handle_ptr) goto error;
return true;
error:
if (NULL != subprocess_ptr) {
bs_subprocess_destroy(subprocess_ptr);
}
_wlmaker_root_menu_generator_destroy(generator_ptr);
return false;
}
/* ------------------------------------------------------------------------- */
/** Dtor for the menu generator. */
void _wlmaker_root_menu_generator_destroy(
wlmaker_root_menu_generator_t *generator_ptr)
{
if (NULL != generator_ptr->subprocess_handle_ptr) {
wlmaker_subprocess_monitor_cede(
generator_ptr->server_ptr->monitor_ptr,
generator_ptr->subprocess_handle_ptr);
generator_ptr->subprocess_handle_ptr = NULL;
}
if (NULL != generator_ptr->stdout_dynbuf_ptr) {
bs_dynbuf_destroy(generator_ptr->stdout_dynbuf_ptr);
generator_ptr->stdout_dynbuf_ptr = NULL;
}
wlmtk_util_disconnect_listener(&generator_ptr->menu_destroy_listener);
free(generator_ptr);
}
/* ------------------------------------------------------------------------- */
/** Handles @ref wlmtk_menu_events_t::destroy. Calls dtor. */
void _wlmaker_root_menu_generator_handle_menu_destroy(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_root_menu_generator_t *generator_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_root_menu_generator_t, menu_destroy_listener);
generator_ptr->menu_ptr = NULL;
_wlmaker_root_menu_generator_destroy(generator_ptr);
}
/* ------------------------------------------------------------------------- */
/** Handler for when the subprocess is terminated. */
void _wlmaker_root_menu_generator_handle_terminated(
void *userdata_ptr,
wlmaker_subprocess_handle_t *subprocess_handle_ptr,
int state,
int code)
{
wlmaker_root_menu_generator_t *generator_ptr = userdata_ptr;
wlmtk_menu_item_t *menu_item_ptr = NULL;
if (0 != state) {
if (INT_MIN != state) {
menu_item_ptr = _wlmaker_root_menu_create_disabled_item(
generator_ptr->style_ref_ptr,
"Failed, exit code %d", state);
bs_log(BS_ERROR, "Subprocess %p failed, exit code %d",
subprocess_handle_ptr, state);
} else {
menu_item_ptr = _wlmaker_root_menu_create_disabled_item(
generator_ptr->style_ref_ptr,
"Failed, signal %d", code);
bs_log(BS_ERROR, "Subprocess %p failed, signal %d",
subprocess_handle_ptr, code);
}
} else {
bs_log(BS_INFO, "Subprocess %p terminated", subprocess_handle_ptr);
bspl_object_t *object_ptr = bspl_create_object_from_dynbuf(
generator_ptr->stdout_dynbuf_ptr);
if (NULL == object_ptr ||
BSPL_ARRAY != bspl_object_type(object_ptr)) {
menu_item_ptr = _wlmaker_root_menu_create_disabled_item(
generator_ptr->style_ref_ptr,
"Failed to parse Plist ARRAY from \"%.*s\"",
(int)(generator_ptr->stdout_dynbuf_ptr->length),
(char*)(generator_ptr->stdout_dynbuf_ptr->data_ptr));
bs_log(BS_ERROR,
"Failed to parse Plist ARRAY from \"%.*s\"",
(int)(generator_ptr->stdout_dynbuf_ptr->length),
(char*)(generator_ptr->stdout_dynbuf_ptr->data_ptr));
} else {
if (!_wlmaker_root_menu_populate_menu_items_from_array(
generator_ptr->menu_ptr,
bspl_array_from_object(object_ptr),
generator_ptr->style_ref_ptr,
generator_ptr->server_ptr)) {
menu_item_ptr = _wlmaker_root_menu_create_disabled_item(
generator_ptr->style_ref_ptr,
"Failed to populate menu from Plist ARRAY \"%.*s\"",
(int)(generator_ptr->stdout_dynbuf_ptr->length),
(char*)(generator_ptr->stdout_dynbuf_ptr->data_ptr));
bs_log(BS_ERROR,
"Failed to populate menu from Plist ARRAY \"%.*s\"",
(int)(generator_ptr->stdout_dynbuf_ptr->length),
(char*)(generator_ptr->stdout_dynbuf_ptr->data_ptr));
}
}
if (NULL != object_ptr) {
bspl_object_unref(object_ptr);
object_ptr = NULL;
}
}
if (NULL!= menu_item_ptr) {
wlmtk_menu_add_item(generator_ptr->menu_ptr, menu_item_ptr);
}
generator_ptr->subprocess_handle_ptr = NULL;
_wlmaker_root_menu_generators--;
if (NULL != _wlmaker_root_menu_test_wl_display_ptr &&
0 >= _wlmaker_root_menu_generators) {
wl_display_terminate(_wlmaker_root_menu_test_wl_display_ptr);
_wlmaker_root_menu_test_wl_display_ptr = NULL;
}
}
/* ------------------------------------------------------------------------- */
/**
* Creates a menu item from the Plist array.
*
* The Plist array either defines a menu action item, where the array elements
* are `(Title, ActionName, OptionalActionArg)`. Or, it defines a submenu, as
* specified in @ref _wlmaker_root_menu_init_menu_from_array.
* Or, it has only a title, then it will be shown as disabled menu item.
*
* For the list of permitted `ActionName` values, see @ref wlmaker_action_desc.
* @param item_array_ptr
* @param menu_style_ref_ptr
* @param server_ptr
*
* @return The menu item, or NULL on error.
*/
wlmtk_menu_item_t *_wlmaker_root_menu_create_item_from_array(
bspl_array_t *item_array_ptr,
wlmtk_menu_style_ref_t *menu_style_ref_ptr,
wlmaker_server_t *server_ptr)
{
wlmtk_menu_item_t *menu_item_ptr = wlmtk_menu_item_create(
menu_style_ref_ptr);
if (NULL == menu_item_ptr) return NULL;
if (!wlmtk_menu_item_set_text(
menu_item_ptr,
bspl_array_string_value_at(item_array_ptr, 0))) goto error;
if (1 >= bspl_array_size(item_array_ptr)) {
wlmtk_menu_item_set_enabled(menu_item_ptr, false);
} else {
// If second element is a string that translates to an action: Bind it.
int action;
if (bspl_enum_name_to_value(
wlmaker_action_desc,
bspl_array_string_value_at(item_array_ptr, 1),
&action)) {
wlmaker_menu_item_bind_action(
menu_item_ptr,
action,
bspl_array_string_value_at(item_array_ptr, 2),
server_ptr);
return menu_item_ptr;
}
wlmtk_menu_t *submenu_ptr = wlmtk_menu_create(menu_style_ref_ptr);
if (NULL == submenu_ptr) goto error;
wlmtk_menu_item_set_submenu(menu_item_ptr, submenu_ptr);
if (!_wlmaker_root_menu_init_menu_from_array(
submenu_ptr,
item_array_ptr,
menu_style_ref_ptr,
server_ptr)) {
goto error;
}
}
return menu_item_ptr;
error:
wlmtk_menu_item_destroy(menu_item_ptr);
return NULL;
}
/* ------------------------------------------------------------------------- */
/**
* Creates a disabled menu item as a means to display generator state.
*
* @param style_ref_ptr
* @param fmt_ptr
*
* @return the disabled menu item, or NULL on error.
*/
wlmtk_menu_item_t *_wlmaker_root_menu_create_disabled_item(
wlmtk_menu_style_ref_t *style_ref_ptr,
const char *fmt_ptr,
...)
{
va_list ap;
char buf[1024];
va_start(ap, fmt_ptr);
vsnprintf(buf, sizeof(buf), fmt_ptr, ap);
va_end(ap);
wlmtk_menu_item_t *menu_item_ptr = wlmtk_menu_item_create(style_ref_ptr);
if (NULL == menu_item_ptr) {
bs_log(BS_ERROR, "Failed wlmtk_menu_item_create(%p) for \"%s\"",
style_ref_ptr, buf);
return NULL;
}
wlmtk_menu_item_set_text(menu_item_ptr, buf);
wlmtk_menu_item_set_enabled(menu_item_ptr, false);
return menu_item_ptr;
}
/* == Unit tests =========================================================== */
static void test_default_menu(bs_test_t *test_ptr);
static void test_generated_menu(bs_test_t *test_ptr);
/** Unit test cases. */
static const bs_test_case_t wlmaker_root_menu_test_cases[] = {
{ true, "default_menu", test_default_menu },
{ true, "generated_menu", test_generated_menu },
BS_TEST_CASE_SENTINEL()
};
const bs_test_set_t wlmaker_root_menu_test_set = BS_TEST_SET(
true, "root_menu", wlmaker_root_menu_test_cases);
/* ------------------------------------------------------------------------- */
/** Verifies that the compiled-in configuration translates into a menu. */
void test_default_menu(bs_test_t *test_ptr)
{
wlmaker_server_t server = {};
wlmtk_window_style_ref_t *wsr = wlmtk_window_style_to_ref(wlmtk_window_style_create());
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wsr);
wlmtk_menu_style_ref_t *msr = wlmtk_menu_style_to_ref(wlmtk_menu_style_create());
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, msr);
wlmaker_root_menu_t *root_menu_ptr = wlmaker_root_menu_create(
&server, NULL, wsr, msr);
BS_TEST_VERIFY_NEQ(test_ptr, NULL, root_menu_ptr);
wlmaker_root_menu_destroy(root_menu_ptr);
wlmtk_window_style_ref_release(wsr);
wlmtk_menu_style_ref_release(msr);
}
/* ------------------------------------------------------------------------- */
/** Verifies that an example menu with generator is translated. */
void test_generated_menu(bs_test_t *test_ptr)
{
wlmtk_window_style_ref_t *wsr = wlmtk_window_style_to_ref(wlmtk_window_style_create());
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wsr);
wlmtk_menu_style_ref_t *msr = wlmtk_menu_style_to_ref(wlmtk_menu_style_create());
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, msr);
wlmaker_server_t server = {
.wl_display_ptr = wl_display_create(),
.wlr_scene_ptr = wlr_scene_create()
};
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.wlr_scene_ptr);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.wl_display_ptr);
wl_signal_init(&server.window_created_event);
wl_signal_init(&server.window_destroyed_event);
server.wlr_output_layout_ptr = wlr_output_layout_create(
server.wl_display_ptr);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.wlr_output_layout_ptr);
server.root_ptr = wlmtk_root_create(
server.wlr_scene_ptr,
server.wlr_output_layout_ptr);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.root_ptr);
server.monitor_ptr = wlmaker_subprocess_monitor_create(
&server);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.monitor_ptr);
#ifndef WLMAKER_SOURCE_DIR
#error "Missing definition of WLMAKER_SOURCE_DIR!"
#endif
BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 0, chdir(WLMAKER_SOURCE_DIR));
wlmaker_root_menu_t *root_menu_ptr;
// Exercise & verify including a submenu from a file.
root_menu_ptr = wlmaker_root_menu_create(
&server,
bs_test_data_path(test_ptr, "menu-include.plist"),
wsr,
msr);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, root_menu_ptr);
wlmtk_menu_t *menu_ptr = wlmaker_root_menu_menu(root_menu_ptr);
BS_TEST_VERIFY_NEQ(test_ptr, 0, wlmtk_menu_items_size(menu_ptr));
wlmaker_root_menu_destroy(root_menu_ptr);
// Exercise & verify generating a submenu from a shell command.
root_menu_ptr = wlmaker_root_menu_create(
&server,
bs_test_data_path(test_ptr, "menu-generate.plist"),
wsr,
msr);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, root_menu_ptr);
menu_ptr = wlmaker_root_menu_menu(root_menu_ptr);
_wlmaker_root_menu_test_wl_display_ptr = server.wl_display_ptr;
wl_display_run(server.wl_display_ptr);
BS_TEST_VERIFY_NEQ(test_ptr, 0, wlmtk_menu_items_size(menu_ptr));
wlmaker_root_menu_destroy(root_menu_ptr);
// Exercise & verify that a menu can be generated from output of the
// `wlmtool` command.
// Note: RootMenuDebian.plist is generated, it in WLMAKER_BINARY_DIR.
#ifndef WLMAKER_BINARY_DIR
#error "Missing definition of WLMAKER_BINARY_DIR!"
#endif
root_menu_ptr = wlmaker_root_menu_create(
&server,
WLMAKER_BINARY_DIR "/etc/RootMenuDebian.plist",
wsr,
msr);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, root_menu_ptr);
menu_ptr = wlmaker_root_menu_menu(root_menu_ptr);
_wlmaker_root_menu_test_wl_display_ptr = server.wl_display_ptr;
wl_display_run(server.wl_display_ptr);
BS_TEST_VERIFY_NEQ(test_ptr, 0, wlmtk_menu_items_size(menu_ptr));
wlmtk_menu_item_t *item_ptr = wlmtk_menu_item_at(menu_ptr, 0);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, item_ptr);
menu_ptr = wlmtk_menu_item_get_submenu(item_ptr);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, menu_ptr);
BS_TEST_VERIFY_NEQ(test_ptr, 0, wlmtk_menu_items_size(menu_ptr));
item_ptr = wlmtk_menu_item_at(menu_ptr, 0);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, item_ptr);
BS_TEST_VERIFY_EQ(
test_ptr,
WLMTK_MENU_ITEM_ENABLED,
wlmtk_menu_item_get_state(item_ptr));
wlmaker_root_menu_destroy(root_menu_ptr);
wlmaker_subprocess_monitor_destroy(server.monitor_ptr);
wlmtk_root_destroy(server.root_ptr);
wl_display_destroy(server.wl_display_ptr);
wlr_scene_node_destroy(&server.wlr_scene_ptr->tree.node);
wlmtk_window_style_ref_release(wsr);
wlmtk_menu_style_ref_release(msr);
}
/* == End of root_menu.c =================================================== */
wlmaker-0.8.1/src/lock_mgr.c 0000644 0001751 0001751 00000127456 15222270710 015335 0 ustar runner runner /* ========================================================================= */
/**
* @file lock_mgr.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "lock_mgr.h"
#include
#include
#include
#include
#include
#include
#include
#define WLR_USE_UNSTABLE
#include
#include
#include
#include
#include
#undef WLR_USE_UNSTABLE
#include "toolkit/toolkit.h"
#include "server.h"
/* == Declarations ========================================================= */
/** Type of wlr_session_lock_surface_v1_configure(). */
typedef uint32_t (*_wlmaker_lock_surface_configure_t)(
struct wlr_session_lock_surface_v1 *lock_surface,
uint32_t width, uint32_t height);
/** Type of wlr_session_lock_v1_send_locked(). */
typedef void (*_wlmaker_lock_send_locked_t)(
struct wlr_session_lock_v1 *lock);
/** State of the session lock manager. */
struct _wlmaker_lock_mgr_t {
/** The wlroots session lock manager. */
struct wlr_session_lock_manager_v1 *wlr_session_lock_manager_v1_ptr;
/** Reference to the wlmaker server. */
wlmaker_server_t *server_ptr;
/** Listener for the `new_lock` signal of `wlr_session_lock_manager_v1`. */
struct wl_listener new_lock_listener;
/** Listener for the `destroy` signal of `wlr_session_lock_manager_v1`. */
struct wl_listener destroy_listener;
};
/** Forward declaration: Lock. */
typedef struct _wlmaker_lock_t wlmaker_lock_t;
/** Forward declaration: Output. */
typedef struct _wlmaker_lock_output_t wlmaker_lock_output_t;
/** State of the session lock. */
struct _wlmaker_lock_t {
/** The wlroots session lock. */
struct wlr_session_lock_v1 *wlr_session_lock_v1_ptr;
/** Seat for the session. */
struct wlr_seat *wlr_seat_ptr;
/** The root this lock is applied for. */
wlmtk_root_t *root_ptr;
/** The output layout. */
struct wlr_output_layout *wlr_output_layout_ptr;
/** Injected method: Configure the lock surface. */
_wlmaker_lock_surface_configure_t injected_surface_configure;
/** Injected method: Confirm session lock. */
_wlmaker_lock_send_locked_t injected_send_locked;
/** Container holding the lock surfaces. */
wlmtk_container_t container;
/** Listener for the `new_surface` signal of `wlr_session_lock_v1`. */
struct wl_listener new_surface_listener;
/** Listener for the `unlock` signal of `wlr_session_lock_v1`. */
struct wl_listener unlock_listener;
/** Listener for the `destroy` signal of `wlr_session_lock_v1`. */
struct wl_listener destroy_listener;
/** Tracks all the outputs. */
wlmtk_output_tracker_t *output_tracker_ptr;
/** Outputs with surface. Via @ref wlmaker_lock_output_t::dlnode. */
bs_dllist_t outputs;
};
/** An active output, that should then also get locked. */
struct _wlmaker_lock_output_t {
/** Element of @ref wlmaker_lock_t::outputs. */
bs_dllist_node_t dlnode;
/** The wlroots session lock surface. */
struct wlr_session_lock_surface_v1 *wlr_session_lock_surface_v1_ptr;
/** Toolkit surface for the associated wl_surface. */
wlmtk_surface_t *wlmtk_surface_ptr;
/** Back-link to the lock. */
wlmaker_lock_t *lock_ptr;
/** Whether this lock surface got committed, ie. is ready to lock. */
bool committed;
/** Serial returned by `wlr_session_lock_surface_v1_configure`. */
uint32_t configure_serial;
/** Listener for the `destroy` signal of `wlr_session_lock_surface_v1`. */
struct wl_listener destroy_listener;
/** Listener for `commit` signal of `wlr_session_lock_surface_v1::surface`. */
struct wl_listener surface_commit_listener;
};
static void _wlmaker_lock_mgr_handle_new_lock(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_lock_mgr_handle_destroy(
struct wl_listener *listener_ptr,
void *data_ptr);
static wlmaker_lock_t *_wlmaker_lock_create(
struct wlr_session_lock_v1 *wlr_session_lock_v1_ptr,
struct wlr_output_layout *wlr_output_layout_ptr,
struct wlr_seat *wlr_seat_ptr,
wlmtk_root_t *root_ptr,
_wlmaker_lock_surface_configure_t injected_surface_configure,
_wlmaker_lock_send_locked_t injected_send_locked);
static void _wlmaker_lock_destroy(wlmaker_lock_t *lock_ptr);
static wlmtk_element_t *_wlmaker_lock_element(wlmaker_lock_t *lock_ptr);
static void _wlmaker_lock_handle_new_surface(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_lock_handle_unlock(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_lock_handle_destroy(
struct wl_listener *listener_ptr,
void *data_ptr);
static void *_wlmaker_lock_output_create(
struct wlr_output *wlr_output_ptr,
void *ud_ptr);
static void _wlmaker_lock_output_update(
struct wlr_output *wlr_output_ptr,
void *ud_ptr,
void *output_ptr);
static void _wlmaker_lock_output_destroy(
struct wlr_output *wlr_output_ptr,
void *ud_ptr,
void *output_ptr);
static bool _wlmaker_lock_output_create_surface(
wlmaker_lock_output_t *lock_output_ptr,
struct wlr_session_lock_surface_v1 *wlr_session_lock_surface_v1_ptr,
wlmaker_lock_t *lock_ptr);
static void _wlmaker_lock_output_destroy_surface(
wlmaker_lock_output_t *lock_output_ptr);
static void _wlmaker_lock_output_handle_surface_destroy(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_lock_output_handle_surface_commit(
struct wl_listener *listener_ptr,
void *data_ptr);
static bool _wlmaker_lock_output_surface_is_committed(
bs_dllist_node_t *dlnode_ptr,
void *ud_ptr);
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
wlmaker_lock_mgr_t *wlmaker_lock_mgr_create(
wlmaker_server_t *server_ptr)
{
wlmaker_lock_mgr_t *lock_mgr_ptr = logged_calloc(
1, sizeof(wlmaker_lock_mgr_t));
if (NULL == lock_mgr_ptr) return NULL;
lock_mgr_ptr->server_ptr = server_ptr;
lock_mgr_ptr->wlr_session_lock_manager_v1_ptr =
wlr_session_lock_manager_v1_create(server_ptr->wl_display_ptr);
if (NULL == lock_mgr_ptr->wlr_session_lock_manager_v1_ptr) {
bs_log(BS_ERROR, "Failed wlr_session_lock_manager_v1_create(%p)",
server_ptr->wl_display_ptr);
wlmaker_lock_mgr_destroy(lock_mgr_ptr);
return NULL;
}
wlmtk_util_connect_listener_signal(
&lock_mgr_ptr->wlr_session_lock_manager_v1_ptr->events.new_lock,
&lock_mgr_ptr->new_lock_listener,
_wlmaker_lock_mgr_handle_new_lock);
wlmtk_util_connect_listener_signal(
&lock_mgr_ptr->wlr_session_lock_manager_v1_ptr->events.destroy,
&lock_mgr_ptr->destroy_listener,
_wlmaker_lock_mgr_handle_destroy);
return lock_mgr_ptr;
}
/* ------------------------------------------------------------------------- */
void wlmaker_lock_mgr_destroy(wlmaker_lock_mgr_t *lock_mgr_ptr)
{
wlmtk_util_disconnect_listener(&lock_mgr_ptr->destroy_listener);
wlmtk_util_disconnect_listener(&lock_mgr_ptr->new_lock_listener);
// Note: No destroy method for wlr_session_lock_manager_v1_ptr.
free(lock_mgr_ptr);
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/**
* Handler for the `new_lock` signal of `wlr_session_lock_manager_v1`: creates
* the corresponding lock.
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmaker_lock_mgr_handle_new_lock(
struct wl_listener *listener_ptr,
void *data_ptr)
{
wlmaker_lock_mgr_t *lock_mgr_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_lock_mgr_t, new_lock_listener);
struct wlr_session_lock_v1 *wlr_session_lock_v1_ptr = data_ptr;
wlmaker_lock_t *lock_ptr = _wlmaker_lock_create(
wlr_session_lock_v1_ptr,
lock_mgr_ptr->server_ptr->wlr_output_layout_ptr,
lock_mgr_ptr->server_ptr->wlr_seat_ptr,
lock_mgr_ptr->server_ptr->root_ptr,
wlr_session_lock_surface_v1_configure,
wlr_session_lock_v1_send_locked);
if (NULL == lock_ptr) {
wl_resource_post_error(
wlr_session_lock_v1_ptr->resource,
WL_DISPLAY_ERROR_NO_MEMORY,
"Failed wlmt_lock_create(%p, %p)",
wlr_session_lock_v1_ptr,
lock_mgr_ptr->server_ptr->root_ptr);
bs_log(BS_WARNING, "Failed wlmt_lock_create(%p, %p)",
wlr_session_lock_v1_ptr,
lock_mgr_ptr->server_ptr->root_ptr);
return;
}
bs_log(BS_INFO, "Lock manager %p: New lock %p", lock_mgr_ptr, lock_ptr);
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `destroy` signal of `wlr_session_lock_manager_v1`: Cleans
* up associated resources.
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmaker_lock_mgr_handle_destroy(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_lock_mgr_t *lock_mgr_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_lock_mgr_t, destroy_listener);
wlmaker_lock_mgr_destroy(lock_mgr_ptr);
}
/* == lock methods ========================================================= */
/* ------------------------------------------------------------------------- */
/**
* Creates a session lock handle.
*
* @param wlr_session_lock_v1_ptr
* @param wlr_output_layout_ptr
* @param wlr_seat_ptr
* @param root_ptr
* @param injected_surface_configure
* @param injected_send_locked
*
* @return The lock handle or NULL on error.
*/
wlmaker_lock_t *_wlmaker_lock_create(
struct wlr_session_lock_v1 *wlr_session_lock_v1_ptr,
struct wlr_output_layout *wlr_output_layout_ptr,
struct wlr_seat *wlr_seat_ptr,
wlmtk_root_t *root_ptr,
_wlmaker_lock_surface_configure_t injected_surface_configure,
_wlmaker_lock_send_locked_t injected_send_locked)
{
wlmaker_lock_t *lock_ptr = logged_calloc(1, sizeof(wlmaker_lock_t));
if (NULL == lock_ptr) return NULL;
lock_ptr->wlr_session_lock_v1_ptr = wlr_session_lock_v1_ptr;
lock_ptr->wlr_output_layout_ptr = wlr_output_layout_ptr;
lock_ptr->wlr_seat_ptr = wlr_seat_ptr;
lock_ptr->root_ptr = root_ptr;
lock_ptr->injected_surface_configure = injected_surface_configure;
lock_ptr->injected_send_locked = injected_send_locked;
lock_ptr->output_tracker_ptr = wlmtk_output_tracker_create(
wlr_output_layout_ptr,
lock_ptr,
_wlmaker_lock_output_create,
_wlmaker_lock_output_update,
_wlmaker_lock_output_destroy);
if (!wlmtk_container_init(&lock_ptr->container)) {
_wlmaker_lock_destroy(lock_ptr);
return NULL;
}
wlmtk_util_connect_listener_signal(
&lock_ptr->wlr_session_lock_v1_ptr->events.new_surface,
&lock_ptr->new_surface_listener,
_wlmaker_lock_handle_new_surface);
wlmtk_util_connect_listener_signal(
&lock_ptr->wlr_session_lock_v1_ptr->events.unlock,
&lock_ptr->unlock_listener,
_wlmaker_lock_handle_unlock);
wlmtk_util_connect_listener_signal(
&lock_ptr->wlr_session_lock_v1_ptr->events.destroy,
&lock_ptr->destroy_listener,
_wlmaker_lock_handle_destroy);
return lock_ptr;
}
/* ------------------------------------------------------------------------- */
/**
* Destroys the session lock handle.
*
* @param lock_ptr
*/
void _wlmaker_lock_destroy(wlmaker_lock_t *lock_ptr)
{
if (NULL != lock_ptr->output_tracker_ptr) {
wlmtk_output_tracker_destroy(lock_ptr->output_tracker_ptr);
lock_ptr->output_tracker_ptr = NULL;
}
wlmtk_util_disconnect_listener(&lock_ptr->destroy_listener);
wlmtk_util_disconnect_listener(&lock_ptr->unlock_listener);
wlmtk_util_disconnect_listener(&lock_ptr->new_surface_listener);
wlmtk_root_lock_unreference(lock_ptr->root_ptr,
_wlmaker_lock_element(lock_ptr));
wlmtk_container_fini(&lock_ptr->container);
free(lock_ptr);
}
/* ------------------------------------------------------------------------- */
/**
* @returns Pointer to @ref wlmtk_element_t of @ref wlmaker_lock_t::container.
* */
wlmtk_element_t *_wlmaker_lock_element(wlmaker_lock_t *lock_ptr)
{
return &lock_ptr->container.super_element;
}
/* ------------------------------------------------------------------------- */
/** Locks the session, if all output surfaces are ready & not locked yet. */
void _wlmaker_lock_if_ready(wlmaker_lock_t *lock_ptr)
{
if (bs_dllist_empty(&lock_ptr->outputs)) return;
if (!bs_dllist_all(&lock_ptr->outputs,
_wlmaker_lock_output_surface_is_committed,
NULL)) return;
if (wlmtk_root_locked(lock_ptr->root_ptr)) return;
if (!wlmtk_root_lock(lock_ptr->root_ptr, _wlmaker_lock_element(lock_ptr))) {
if (NULL != lock_ptr->wlr_session_lock_v1_ptr->resource) {
wl_resource_post_error(
lock_ptr->wlr_session_lock_v1_ptr->resource,
WL_DISPLAY_ERROR_INVALID_METHOD,
"Failed wlmtk_root_lock(%p, %p): Already locked?",
lock_ptr->root_ptr, _wlmaker_lock_element(lock_ptr));
}
return;
}
wlmtk_element_set_visible(&lock_ptr->container.super_element, true);
// Grant keyboard focus to the first-found surface that's committed.
bs_dllist_node_t *dlnode_ptr = bs_dllist_find(
&lock_ptr->outputs,
_wlmaker_lock_output_surface_is_committed,
NULL);
if (NULL == dlnode_ptr) return;
wlmaker_lock_output_t *lock_output_ptr = BS_CONTAINER_OF(
dlnode_ptr, wlmaker_lock_output_t, dlnode);
wlmtk_surface_set_activated(lock_output_ptr->wlmtk_surface_ptr, true);
// Root is locked. Send confirmation to the client.
lock_ptr->injected_send_locked(lock_ptr->wlr_session_lock_v1_ptr);
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `new_surface` signal of `wlr_session_lock_v1`: Creates the
* associated surface and enables it on the screenlock container.
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmaker_lock_handle_new_surface(
struct wl_listener *listener_ptr,
void *data_ptr)
{
wlmaker_lock_t *lock_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_lock_t, new_surface_listener);
struct wlr_session_lock_surface_v1 *wlr_session_lock_surface_v1_ptr =
data_ptr;
// Guard clause: We expect the output to be set.
if (NULL == wlr_session_lock_surface_v1_ptr->output) {
bs_log(BS_ERROR, "Session lock surface %p does not have an output!",
wlr_session_lock_surface_v1_ptr);
if (NULL != wlr_session_lock_surface_v1_ptr->resource) {
wl_resource_post_error(
wlr_session_lock_surface_v1_ptr->resource,
WL_DISPLAY_ERROR_INVALID_METHOD,
"Session lock surface does not have an output!");
}
return;
}
// Additionally, we expect the output to be part of the output layout.
wlmaker_lock_output_t *lock_output_ptr = wlmtk_output_tracker_get_output(
lock_ptr->output_tracker_ptr,
wlr_session_lock_surface_v1_ptr->output);
if (NULL == lock_output_ptr) {
bs_log(BS_ERROR, "Session lock surface %p refers to invalid output %p",
wlr_session_lock_surface_v1_ptr,
wlr_session_lock_surface_v1_ptr->output);
if (NULL != wlr_session_lock_surface_v1_ptr->resource) {
wl_resource_post_error(
wlr_session_lock_surface_v1_ptr->resource,
WL_DISPLAY_ERROR_INVALID_METHOD,
"Session lock surface refers to invalid output!");
}
return;
}
if (!_wlmaker_lock_output_create_surface(
lock_output_ptr,
wlr_session_lock_surface_v1_ptr,
lock_ptr)) {
wl_resource_post_error(
wlr_session_lock_surface_v1_ptr->resource,
WL_DISPLAY_ERROR_NO_MEMORY,
"Failed _wlmaker_lock_output_create_surface(%p, %p, %p)",
lock_output_ptr,
wlr_session_lock_surface_v1_ptr->surface,
lock_ptr);
return;
}
bs_log(BS_INFO, "Lock %p, output %p: New lock surface %p",
lock_ptr, lock_output_ptr, wlr_session_lock_surface_v1_ptr);
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `unlock` signal of `wlr_session_lock_v1`: Marks the session
* as unlocked.
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmaker_lock_handle_unlock(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
__UNUSED__ wlmaker_lock_t *lock_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_lock_t, unlock_listener);
wlmtk_element_set_visible(&lock_ptr->container.super_element, false);
wlmtk_root_unlock(lock_ptr->root_ptr, _wlmaker_lock_element(lock_ptr));
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `destroy` signal of `wlr_session_lock_v1`: Destroy the lock.
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmaker_lock_handle_destroy(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_lock_t *lock_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_lock_t, destroy_listener);
_wlmaker_lock_destroy(lock_ptr);
}
/* == Lock output methods ================================================== */
/* ------------------------------------------------------------------------- */
/** Ctor for the lock output. */
void *_wlmaker_lock_output_create(
__UNUSED__ struct wlr_output *wlr_output_ptr,
void *ud_ptr)
{
wlmaker_lock_t *lock_ptr = ud_ptr;
wlmaker_lock_output_t *lock_output_ptr = logged_calloc(
1, sizeof(wlmaker_lock_output_t));
if (NULL == lock_output_ptr) return NULL;
lock_output_ptr->lock_ptr = lock_ptr;
bs_dllist_push_back(&lock_ptr->outputs, &lock_output_ptr->dlnode);
return lock_output_ptr;
}
/* ------------------------------------------------------------------------- */
/** Layout update: Dimensions of the surface might have changed. Update. */
void _wlmaker_lock_output_update(
__UNUSED__ struct wlr_output *wlr_output_ptr,
void *ud_ptr,
void *output_ptr)
{
wlmaker_lock_t *lock_ptr = ud_ptr;
wlmaker_lock_output_t *lock_output_ptr = output_ptr;
// The output dimensions may have changed. Send a configure().
lock_ptr->injected_surface_configure(
lock_output_ptr->wlr_session_lock_surface_v1_ptr,
lock_output_ptr->wlr_session_lock_surface_v1_ptr->output->width,
lock_output_ptr->wlr_session_lock_surface_v1_ptr->output->height);
struct wlr_box box;
wlr_output_layout_get_box(
lock_ptr->wlr_output_layout_ptr,
lock_output_ptr->wlr_session_lock_surface_v1_ptr->output,
&box);
wlmtk_element_set_position(
wlmtk_surface_element(lock_output_ptr->wlmtk_surface_ptr),
box.x, box.y);
}
/* ------------------------------------------------------------------------- */
/** Dtor for the lock output. */
void _wlmaker_lock_output_destroy(
__UNUSED__ struct wlr_output *wlr_output_ptr,
void *ud_ptr,
void *output_ptr)
{
wlmaker_lock_t *lock_ptr = ud_ptr;
wlmaker_lock_output_t *lock_output_ptr = output_ptr;
_wlmaker_lock_output_destroy_surface(lock_output_ptr);
bs_dllist_remove(
&lock_output_ptr->lock_ptr->outputs,
&lock_output_ptr->dlnode);
free(lock_output_ptr);
// Activating the first-found surface ensures there's still one that
// is activated.
bs_dllist_node_t *dlnode_ptr = bs_dllist_find(
&lock_ptr->outputs,
_wlmaker_lock_output_surface_is_committed,
NULL);
if (NULL == dlnode_ptr) return;
lock_output_ptr = BS_CONTAINER_OF(
dlnode_ptr, wlmaker_lock_output_t, dlnode);
wlmtk_surface_set_activated(lock_output_ptr->wlmtk_surface_ptr, true);
}
/* ------------------------------------------------------------------------- */
/**
* Creates a lock surface.
*
* @param lock_output_ptr
* @param wlr_session_lock_surface_v1_ptr
* @param lock_ptr
*
* @return The lock surface or NULL on error.
*/
bool _wlmaker_lock_output_create_surface(
wlmaker_lock_output_t *lock_output_ptr,
struct wlr_session_lock_surface_v1 *wlr_session_lock_surface_v1_ptr,
wlmaker_lock_t *lock_ptr)
{
if (NULL != lock_output_ptr->wlr_session_lock_surface_v1_ptr) {
bs_log(BS_ERROR, "Lock %p, output %p already has surface %p (vs %p)",
lock_ptr,
lock_output_ptr,
lock_output_ptr->wlr_session_lock_surface_v1_ptr,
wlr_session_lock_surface_v1_ptr);
return false;
}
lock_output_ptr->wlr_session_lock_surface_v1_ptr =
wlr_session_lock_surface_v1_ptr;
lock_output_ptr->wlmtk_surface_ptr = wlmtk_surface_create(
wlr_session_lock_surface_v1_ptr->surface,
lock_ptr->wlr_seat_ptr);
if (NULL == lock_output_ptr->wlmtk_surface_ptr) {
bs_log(BS_ERROR, "Failed wlmtk_surface_create(%p)",
wlr_session_lock_surface_v1_ptr->surface);
_wlmaker_lock_output_destroy_surface(lock_output_ptr);
return false;
}
wlmtk_util_connect_listener_signal(
&wlr_session_lock_surface_v1_ptr->events.destroy,
&lock_output_ptr->destroy_listener,
_wlmaker_lock_output_handle_surface_destroy);
wlmtk_util_connect_listener_signal(
&wlr_session_lock_surface_v1_ptr->surface->events.commit,
&lock_output_ptr->surface_commit_listener,
_wlmaker_lock_output_handle_surface_commit);
// We need computed & scaled output resolution for setting the lock
// surface's dimensions.
int w, h;
wlr_output_effective_resolution(
wlr_session_lock_surface_v1_ptr->output, &w, &h);
lock_output_ptr->configure_serial = lock_ptr->injected_surface_configure(
wlr_session_lock_surface_v1_ptr, w, h);
struct wlr_box box;
wlr_output_layout_get_box(
lock_ptr->wlr_output_layout_ptr,
wlr_session_lock_surface_v1_ptr->output,
&box);
wlmtk_element_set_position(
wlmtk_surface_element(lock_output_ptr->wlmtk_surface_ptr),
box.x, box.y);
wlmtk_container_add_element(
&lock_ptr->container,
wlmtk_surface_element(lock_output_ptr->wlmtk_surface_ptr));
wlmtk_element_set_visible(
wlmtk_surface_element(lock_output_ptr->wlmtk_surface_ptr), true);
return true;
}
/* ------------------------------------------------------------------------- */
/**
* Destroys the lock surface.
*
* @param lock_output_ptr
*/
void _wlmaker_lock_output_destroy_surface(
wlmaker_lock_output_t *lock_output_ptr)
{
bs_log(BS_INFO, "Lock %p, output %p: Destroying lock surface %p",
lock_output_ptr->lock_ptr,
lock_output_ptr,
lock_output_ptr->wlr_session_lock_surface_v1_ptr);
if (NULL != lock_output_ptr->wlmtk_surface_ptr) {
wlmtk_container_remove_element(
&lock_output_ptr->lock_ptr->container,
wlmtk_surface_element(lock_output_ptr->wlmtk_surface_ptr));
wlmtk_util_disconnect_listener(&lock_output_ptr->surface_commit_listener);
wlmtk_util_disconnect_listener(&lock_output_ptr->destroy_listener);
wlmtk_surface_destroy(lock_output_ptr->wlmtk_surface_ptr);
lock_output_ptr->wlmtk_surface_ptr = NULL;
}
lock_output_ptr->committed = false;
lock_output_ptr->wlr_session_lock_surface_v1_ptr = NULL;
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `destroy` signal of `wlr_session_lock_surface_v1`: Destroy
* the surface.
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmaker_lock_output_handle_surface_destroy(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_lock_output_t *lock_output_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_lock_output_t, destroy_listener);
_wlmaker_lock_output_destroy_surface(lock_output_ptr);
}
/* ------------------------------------------------------------------------- */
/**
* Handler for the `commit` signal of `wlr_session_lock_surface_v1::surface`.
*
* Checks whether the serial is at-or-above the 'configure' serial, and
* reports the surface and output as locked. Once all surfaces are locked,
* a 'send_locked' event will be sent.
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmaker_lock_output_handle_surface_commit(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_lock_output_t *lock_output_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_lock_output_t, surface_commit_listener);
struct wlr_session_lock_surface_v1 *wlr_session_lock_surface_v1_ptr =
lock_output_ptr->wlr_session_lock_surface_v1_ptr;
// Do not accept locking for commits before the requested configuration.
if (wlr_session_lock_surface_v1_ptr->current.configure_serial >=
lock_output_ptr->configure_serial) {
lock_output_ptr->committed = true;
_wlmaker_lock_if_ready(lock_output_ptr->lock_ptr);
}
}
/* ------------------------------------------------------------------------- */
/** Iterator for @ref wlmaker_lock_t::outputs. Is the output committed? */
bool _wlmaker_lock_output_surface_is_committed(
bs_dllist_node_t *dlnode_ptr,
__UNUSED__ void *ud_ptr)
{
wlmaker_lock_output_t *lock_output_ptr = BS_CONTAINER_OF(
dlnode_ptr, wlmaker_lock_output_t, dlnode);
return (NULL != lock_output_ptr->wlr_session_lock_surface_v1_ptr &&
lock_output_ptr->committed);
}
/* == Unit tests =========================================================== */
static void test_lock_unlock(bs_test_t *test_ptr);
static void test_lock_crash(bs_test_t *test_ptr);
static void test_lock_multi_output(bs_test_t *test_ptr);
static uint32_t _mock_wlr_session_lock_surface_v1_configure(
struct wlr_session_lock_surface_v1 *lock_surface,
uint32_t width, uint32_t height);
static void _mock_wlr_session_lock_v1_send_locked(
struct wlr_session_lock_v1 *lock);
static void _init_test_session_lock(
struct wlr_session_lock_v1 *wlr_session_lock_v1_ptr);
static void _init_test_surface(struct wlr_surface *wlr_surface_ptr);
/** Unit test cases. */
const bs_test_case_t wlmaker_lock_mgr_test_cases[] = {
{ true, "lock_unlock", test_lock_unlock },
{ true, "lock_crash", test_lock_crash },
{ true, "lock_multi_output", test_lock_multi_output },
BS_TEST_CASE_SENTINEL()
};
const bs_test_set_t wlmaker_lock_mgr_test_set = BS_TEST_SET(
true, "lock_mgr", wlmaker_lock_mgr_test_cases);
/** Return value for @ref _mock_wlr_session_lock_surface_v1_configure. */
static uint32_t _mock_configure_serial;
/** Arg of last call to @ref _mock_wlr_session_lock_surface_v1_configure. */
static uint32_t _mock_configure_width;
/** Arg of last call to @ref _mock_wlr_session_lock_surface_v1_configure. */
static uint32_t _mock_configure_height;
/** Arg of last call to @ref _mock_wlr_session_lock_surface_v1_configure. */
static struct wlr_session_lock_surface_v1 *_mock_configure_lock_surface;
/** Arg of last call to @ref _mock_wlr_session_lock_v1_send_locked. */
static struct wlr_session_lock_v1 *_mock_send_locked_lock;
/** Mock for configure(). */
uint32_t _mock_wlr_session_lock_surface_v1_configure(
struct wlr_session_lock_surface_v1 *lock_surface,
uint32_t width, uint32_t height)
{
_mock_configure_lock_surface = lock_surface;
_mock_configure_width = width;
_mock_configure_height = height;
return _mock_configure_serial;
}
/** Mock for send_locked(). */
void _mock_wlr_session_lock_v1_send_locked(
struct wlr_session_lock_v1 *lock)
{
_mock_send_locked_lock = lock;
}
/** Initializes the minimum required attributes of the session lock. */
void _init_test_session_lock(
struct wlr_session_lock_v1 *wlr_session_lock_v1_ptr)
{
wl_signal_init(&wlr_session_lock_v1_ptr->events.new_surface);
wl_signal_init(&wlr_session_lock_v1_ptr->events.unlock);
wl_signal_init(&wlr_session_lock_v1_ptr->events.destroy);
}
/** Initializes the minimum required attributes of the wlr_surface. */
void _init_test_surface(struct wlr_surface *wlr_surface_ptr)
{
wl_list_init(&wlr_surface_ptr->current.subsurfaces_below);
wl_list_init(&wlr_surface_ptr->current.subsurfaces_above);
wl_signal_init(&wlr_surface_ptr->events.commit);
wl_signal_init(&wlr_surface_ptr->events.destroy);
wl_signal_init(&wlr_surface_ptr->events.map);
wl_signal_init(&wlr_surface_ptr->events.unmap);
}
/* ------------------------------------------------------------------------- */
/** Tests locking & unlocking, proper sequence, single output. */
void test_lock_unlock(bs_test_t *test_ptr)
{
wlmaker_server_t server = { .wl_display_ptr = wl_display_create() };
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.wl_display_ptr);
server.wlr_output_layout_ptr = wlr_output_layout_create(
server.wl_display_ptr);
struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 };
wlmtk_test_wlr_output_init(&output);
wlr_output_layout_add_auto(server.wlr_output_layout_ptr, &output);
server.root_ptr = wlmtk_root_create(NULL, server.wlr_output_layout_ptr);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.root_ptr);
struct wlmtk_tile_style tile_style = {};
wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_create(
server.wlr_output_layout_ptr, "name", &tile_style);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, workspace_ptr);
wlmtk_root_add_workspace(server.root_ptr, workspace_ptr);
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr));
BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr));
struct wlr_session_lock_v1 wlr_session_lock_v1 = {};
_init_test_session_lock(&wlr_session_lock_v1);
wlmaker_lock_t *lock_ptr = _wlmaker_lock_create(
&wlr_session_lock_v1,
server.wlr_output_layout_ptr,
NULL,
server.root_ptr,
_mock_wlr_session_lock_surface_v1_configure,
_mock_wlr_session_lock_v1_send_locked);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, lock_ptr);
struct wlr_surface wlr_surface = {};
_init_test_surface(&wlr_surface);
struct wlr_session_lock_surface_v1 lock_surface = {
.surface = &wlr_surface,
.output = &output,
};
wl_signal_init(&lock_surface.events.destroy);
// A new surface request will be greeted by a configure() event.
_mock_configure_serial = 42;
_mock_send_locked_lock = NULL;
wl_signal_emit(&wlr_session_lock_v1.events.new_surface, &lock_surface);
BS_TEST_VERIFY_EQ(test_ptr, &lock_surface, _mock_configure_lock_surface);
BS_TEST_VERIFY_EQ(test_ptr, 1024, _mock_configure_width);
BS_TEST_VERIFY_EQ(test_ptr, 768, _mock_configure_height);
// A commit, but with too-low serial. Will be ignored.
lock_surface.current.configure_serial = 41;
wl_signal_emit(&wlr_surface.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, NULL, _mock_send_locked_lock);
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr));
BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr));
// Another commit, with matching serial. Will mark as locked.
wlr_surface.current.width = 1024;
wlr_surface.current.height = 768;
lock_surface.current.configure_serial = 42;
wl_signal_emit(&wlr_surface.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, &wlr_session_lock_v1, _mock_send_locked_lock);
BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_workspace_enabled(workspace_ptr));
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_root_locked(server.root_ptr));
// Client unlocks.
wl_signal_emit(&wlr_session_lock_v1.events.unlock, NULL);
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr));
BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr));
_wlmaker_lock_destroy(lock_ptr);
wlmtk_root_remove_workspace(server.root_ptr, workspace_ptr);
wlmtk_workspace_destroy(workspace_ptr);
wlmtk_root_destroy(server.root_ptr);
wl_display_destroy(server.wl_display_ptr);
}
/* ------------------------------------------------------------------------- */
/** Tests locking, and then the session lock going away: Must remain locked. */
void test_lock_crash(bs_test_t *test_ptr)
{
wlmaker_server_t server = { .wl_display_ptr = wl_display_create() };
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.wl_display_ptr);
server.wlr_output_layout_ptr = wlr_output_layout_create(
server.wl_display_ptr);
struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 };
wlmtk_test_wlr_output_init(&output);
wlr_output_layout_add_auto(server.wlr_output_layout_ptr, &output);
server.root_ptr = wlmtk_root_create(NULL, server.wlr_output_layout_ptr);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.root_ptr);
struct wlmtk_tile_style tile_style = {};
wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_create(
server.wlr_output_layout_ptr, "name", &tile_style);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, workspace_ptr);
wlmtk_root_add_workspace(server.root_ptr, workspace_ptr);
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr));
BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr));
struct wlr_session_lock_v1 wlr_session_lock_v1 = {};
_init_test_session_lock(&wlr_session_lock_v1);
wlmaker_lock_t *lock_ptr = _wlmaker_lock_create(
&wlr_session_lock_v1,
server.wlr_output_layout_ptr,
NULL,
server.root_ptr,
_mock_wlr_session_lock_surface_v1_configure,
_mock_wlr_session_lock_v1_send_locked);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, lock_ptr);
struct wlr_surface wlr_surface = {};
_init_test_surface(&wlr_surface);
struct wlr_session_lock_surface_v1 lock_surface = {
.surface = &wlr_surface,
.output = &output,
};
wl_signal_init(&lock_surface.events.destroy);
// A new surface request will be greeted by a configure() event.
_mock_configure_serial = 42;
_mock_send_locked_lock = NULL;
wl_signal_emit(&wlr_session_lock_v1.events.new_surface, &lock_surface);
BS_TEST_VERIFY_EQ(test_ptr, &lock_surface, _mock_configure_lock_surface);
BS_TEST_VERIFY_EQ(test_ptr, 1024, _mock_configure_width);
BS_TEST_VERIFY_EQ(test_ptr, 768, _mock_configure_height);
// Commit with matching serial. Will mark as locked.
wlr_surface.current.width = 1024;
wlr_surface.current.height = 768;
lock_surface.current.configure_serial = 42;
wl_signal_emit(&wlr_surface.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, &wlr_session_lock_v1, _mock_send_locked_lock);
BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_workspace_enabled(workspace_ptr));
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_root_locked(server.root_ptr));
// No unlock. If the session lock is destroyed without: Lock remains.
_wlmaker_lock_destroy(lock_ptr);
BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_workspace_enabled(workspace_ptr));
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_root_locked(server.root_ptr));
wlmtk_root_remove_workspace(server.root_ptr, workspace_ptr);
wlmtk_workspace_destroy(workspace_ptr);
wlmtk_root_destroy(server.root_ptr);
wl_display_destroy(server.wl_display_ptr);
}
/* ------------------------------------------------------------------------- */
/** Tests locking with multiple outputs. Lock only when all outputs covered. */
void test_lock_multi_output(bs_test_t *test_ptr)
{
wlmaker_server_t server = { .wl_display_ptr = wl_display_create() };
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.wl_display_ptr);
server.wlr_output_layout_ptr = wlr_output_layout_create(
server.wl_display_ptr);
struct wlr_output o1 = { .width = 1024, .height = 768, .scale = 1 };
struct wlr_output o2 = { .width = 1024, .height = 768, .scale = 1 };
struct wlr_output o3 = { .width = 1024, .height = 768, .scale = 1 };
wlmtk_test_wlr_output_init(&o1);
wlmtk_test_wlr_output_init(&o2);
wlmtk_test_wlr_output_init(&o3);
wlr_output_layout_add_auto(server.wlr_output_layout_ptr, &o1);
// But not: o2.
wlr_output_layout_add_auto(server.wlr_output_layout_ptr, &o3);
server.root_ptr = wlmtk_root_create(NULL, server.wlr_output_layout_ptr);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.root_ptr);
struct wlmtk_tile_style tile_style = {};
wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_create(
server.wlr_output_layout_ptr, "name", &tile_style);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, workspace_ptr);
wlmtk_root_add_workspace(server.root_ptr, workspace_ptr);
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr));
BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr));
struct wlr_session_lock_v1 wlr_session_lock_v1 = {};
_init_test_session_lock(&wlr_session_lock_v1);
wlmaker_lock_t *lock_ptr = _wlmaker_lock_create(
&wlr_session_lock_v1,
server.wlr_output_layout_ptr,
NULL,
server.root_ptr,
_mock_wlr_session_lock_surface_v1_configure,
_mock_wlr_session_lock_v1_send_locked);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, lock_ptr);
struct wlr_surface wlr_surface1 = {};
_init_test_surface(&wlr_surface1);
struct wlr_session_lock_surface_v1 lock_surface1 = {
.surface = &wlr_surface1,
.output = &o1,
};
wl_signal_init(&lock_surface1.events.destroy);
struct wlr_surface wlr_surface2 = {};
_init_test_surface(&wlr_surface2);
struct wlr_session_lock_surface_v1 lock_surface2 = {
.surface = &wlr_surface2,
.output = &o2,
};
wl_signal_init(&lock_surface2.events.destroy);
struct wlr_surface wlr_surface3 = {};
_init_test_surface(&wlr_surface3);
struct wlr_session_lock_surface_v1 lock_surface3 = {
.surface = &wlr_surface3,
.output = &o3,
};
wl_signal_init(&lock_surface3.events.destroy);
// Surface 1. Create, configure, commit. No lock yet.
_mock_configure_serial = 42;
_mock_send_locked_lock = NULL;
wl_signal_emit(&wlr_session_lock_v1.events.new_surface, &lock_surface1);
BS_TEST_VERIFY_EQ(test_ptr, &lock_surface1, _mock_configure_lock_surface);
BS_TEST_VERIFY_EQ(test_ptr, 1024, _mock_configure_width);
BS_TEST_VERIFY_EQ(test_ptr, 768, _mock_configure_height);
wlr_surface1.current.width = 1024;
wlr_surface1.current.height = 768;
lock_surface1.current.configure_serial = 42;
wl_signal_emit(&wlr_surface1.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, NULL, _mock_send_locked_lock);
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr));
BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr));
wlmtk_surface_t *surface_ptr = wlr_surface1.data;
int x, y;
wlmtk_element_get_position(wlmtk_surface_element(surface_ptr), &x, &y);
BS_TEST_VERIFY_EQ(test_ptr, 0, x);
BS_TEST_VERIFY_EQ(test_ptr, 0, y);
// Surface 2. Create, configure, commit. Non-layout output -> ignored.
_mock_configure_serial = 42;
_mock_send_locked_lock = NULL;
wl_signal_emit(&wlr_session_lock_v1.events.new_surface, &lock_surface2);
// no 'configure'.
wlr_surface2.current.width = 1024;
wlr_surface2.current.height = 768;
lock_surface2.current.configure_serial = 42;
wl_signal_emit(&wlr_surface2.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, NULL, _mock_send_locked_lock);
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr));
BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr));
wl_signal_emit(&lock_surface2.events.destroy, NULL);
// Surface 3.
_mock_configure_serial = 42;
_mock_send_locked_lock = NULL;
wl_signal_emit(&wlr_session_lock_v1.events.new_surface, &lock_surface3);
BS_TEST_VERIFY_EQ(test_ptr, &lock_surface3, _mock_configure_lock_surface);
BS_TEST_VERIFY_EQ(test_ptr, 1024, _mock_configure_width);
BS_TEST_VERIFY_EQ(test_ptr, 768, _mock_configure_height);
wlr_surface3.current.width = 1024;
wlr_surface3.current.height = 768;
lock_surface3.current.configure_serial = 42;
wl_signal_emit(&wlr_surface3.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, &wlr_session_lock_v1, _mock_send_locked_lock);
BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_workspace_enabled(workspace_ptr));
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_root_locked(server.root_ptr));
surface_ptr = wlr_surface3.data;
wlmtk_element_get_position(wlmtk_surface_element(surface_ptr), &x, &y);
BS_TEST_VERIFY_EQ(test_ptr, 1024, x);
BS_TEST_VERIFY_EQ(test_ptr, 0, y);
// o3 changes size & position. Test configure(). Remains locked.
o3.width = 1920;
o3.height = 1080;
wlr_output_layout_add(server.wlr_output_layout_ptr, &o3, 1200, 200);
_mock_configure_serial = 43;
_mock_configure_lock_surface = NULL;
wl_signal_emit(&server.wlr_output_layout_ptr->events.change,
server.wlr_output_layout_ptr);
// Note: Issues two configure() events, the second one is for o3.
BS_TEST_VERIFY_EQ(test_ptr, &lock_surface3, _mock_configure_lock_surface);
BS_TEST_VERIFY_EQ(test_ptr, 1920, _mock_configure_width);
BS_TEST_VERIFY_EQ(test_ptr, 1080, _mock_configure_height);
wlr_surface3.current.width = 1024;
wlr_surface3.current.height = 768;
lock_surface3.current.configure_serial = 42;
_mock_send_locked_lock = NULL;
wl_signal_emit(&wlr_surface3.events.commit, NULL);
BS_TEST_VERIFY_EQ(test_ptr, NULL, _mock_send_locked_lock);
BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_workspace_enabled(workspace_ptr));
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_root_locked(server.root_ptr));
surface_ptr = wlr_surface1.data;
wlmtk_element_get_position(wlmtk_surface_element(surface_ptr), &x, &y);
BS_TEST_VERIFY_EQ(test_ptr, 3120, x);
BS_TEST_VERIFY_EQ(test_ptr, 200, y);
surface_ptr = wlr_surface3.data;
wlmtk_element_get_position(wlmtk_surface_element(surface_ptr), &x, &y);
BS_TEST_VERIFY_EQ(test_ptr, 1200, x);
BS_TEST_VERIFY_EQ(test_ptr, 200, y);
// Confirm: The earliest added surface is active.
surface_ptr = wlr_surface1.data;
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, surface_ptr);
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_surface_is_activated(surface_ptr));
wlr_output_layout_remove(server.wlr_output_layout_ptr, &o1);
_mock_configure_serial = 44;
_mock_configure_lock_surface = NULL;
wl_signal_emit(&server.wlr_output_layout_ptr->events.change,
server.wlr_output_layout_ptr);
// Now want surface1 active.
surface_ptr = wlr_surface3.data;
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, surface_ptr);
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_surface_is_activated(surface_ptr));
// Unlock correctly.
wl_signal_emit(&wlr_session_lock_v1.events.unlock, NULL);
BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr));
BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr));
_wlmaker_lock_destroy(lock_ptr);
wlmtk_root_remove_workspace(server.root_ptr, workspace_ptr);
wlmtk_workspace_destroy(workspace_ptr);
wlmtk_root_destroy(server.root_ptr);
wl_display_destroy(server.wl_display_ptr);
}
/* == End of lock_mgr.c ==================================================== */
wlmaker-0.8.1/src/root_menu.h 0000644 0001751 0001751 00000004144 15222270710 015540 0 ustar runner runner /* ========================================================================= */
/**
* @file root_menu.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __WLMAKER_ROOT_MENU_H__
#define __WLMAKER_ROOT_MENU_H__
#include
#include "toolkit/toolkit.h"
/** Forward declaration: State of root menu. */
typedef struct _wlmaker_root_menu_t wlmaker_root_menu_t;
#include "task_list.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Creates a root menu.
*
* @param server_ptr
* @param arg_root_menu_file_ptr
* @param menu_style_ref_ptr
* @param window_style_ref_ptr
*
* @return Handle of the root menu, or NULL on error.
*/
wlmaker_root_menu_t *wlmaker_root_menu_create(
wlmaker_server_t *server_ptr,
const char *arg_root_menu_file_ptr,
wlmtk_window_style_ref_t *window_style_ref_ptr,
wlmtk_menu_style_ref_t *menu_style_ref_ptr);
/**
* Destroys the root menu.
*
* @param root_menu_ptr
*/
void wlmaker_root_menu_destroy(wlmaker_root_menu_t *root_menu_ptr);
/** @return the window holding the root menu. */
wlmtk_window_t *wlmaker_root_menu_window(wlmaker_root_menu_t *root_menu_ptr);
/** @return Pointer to @ref wlmtk_menu_t of the root menu. */
wlmtk_menu_t *wlmaker_root_menu_menu(wlmaker_root_menu_t *root_menu_ptr);
/** Unit test set. */
extern const bs_test_set_t wlmaker_root_menu_test_set;
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __ROOT_MENU_H__ */
/* == End of root_menu.h =================================================== */
wlmaker-0.8.1/src/background.c 0000644 0001751 0001751 00000022433 15222270710 015644 0 ustar runner runner /* ========================================================================= */
/**
* @file background.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "background.h"
#include
#include
#include
#define WLR_USE_UNSTABLE
#include
#include
#undef WLR_USE_UNSTABLE
struct wlr_output;
/* == Declarations ========================================================= */
/** Background state. */
struct wlmaker_background {
/** List node, linked via @ref wlmaker_server_t::backgrounds. */
bs_dllist_node_t dlnode;
/** Links to layer. */
wlmtk_layer_t *layer_ptr;
/** color of the background. */
uint32_t color;
/** Tracks the outputs available. */
wlmtk_output_tracker_t *output_tracker_ptr;
};
/** Background panel: The workspace's backgrund for the output. */
typedef struct {
/** A layer background for one output is a panel. */
wlmtk_panel_t super_panel;
/** Initial implementation: The background is a uni-color rectangle. */
wlmtk_rectangle_t *rectangle_ptr;
} wlmaker_background_panel_t;
static uint32_t _wlmaker_background_panel_request_size(
wlmtk_panel_t *panel_ptr,
int width,
int height);
static void *_wlmaker_background_panel_create(
struct wlr_output *wlr_output_ptr,
void *ud_ptr);
static void _wlmaker_background_panel_destroy(
struct wlr_output *wlr_output_ptr,
void *ud_ptr,
void *output_ptr);
static void _wlmaker_background_panel_element_destroy(
wlmtk_element_t *element_ptr);
static void _wlmaker_backgrund_panel_set_color(
struct wlr_output *wlr_output_ptr,
void *ud_ptr,
void *output_ptr,
void *arg_ptr);
/* == Data ================================================================= */
/** The background panels' virtual method table. */
static const wlmtk_panel_vmt_t _wlmaker_background_panel_vmt = {
.request_size = _wlmaker_background_panel_request_size
};
/** The panel's superclass element virtual method table. */
static const wlmtk_element_vmt_t _wlmaker_background_panel_element_vmt = {
.destroy = _wlmaker_background_panel_element_destroy
};
/** Panel's position: Anchored to all 4 edges, and auto-sized. */
static const wlmtk_panel_positioning_t _wlmaker_background_panel_position = {
.desired_width = 0,
.desired_height = 0,
.anchor = WLR_EDGE_LEFT | WLR_EDGE_TOP | WLR_EDGE_RIGHT | WLR_EDGE_BOTTOM,
};
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
bs_dllist_node_t *wlmaker_background_create(
wlmtk_workspace_t *workspace_ptr,
struct wlr_output_layout *wlr_output_layout_ptr,
uint32_t color)
{
struct wlmaker_background *bg_ptr = logged_calloc(1, sizeof(*bg_ptr));
if (NULL == bg_ptr) return NULL;
bg_ptr->layer_ptr = wlmtk_workspace_get_layer(
workspace_ptr, WLMTK_WORKSPACE_LAYER_BACKGROUND),
bg_ptr->color = color;
bg_ptr->output_tracker_ptr = wlmtk_output_tracker_create(
wlr_output_layout_ptr,
bg_ptr,
_wlmaker_background_panel_create,
NULL,
_wlmaker_background_panel_destroy);
return &bg_ptr->dlnode;
}
/* ------------------------------------------------------------------------- */
void wlmaker_background_dlnode_destroy(
bs_dllist_node_t *dlnode_ptr,
__UNUSED__ void *ud_ptr)
{
struct wlmaker_background *bg_ptr = BS_CONTAINER_OF(
dlnode_ptr, struct wlmaker_background, dlnode);
if (NULL != bg_ptr->output_tracker_ptr) {
wlmtk_output_tracker_destroy(bg_ptr->output_tracker_ptr);
bg_ptr->output_tracker_ptr = NULL;
}
free(bg_ptr);
}
/* ------------------------------------------------------------------------- */
void wlmaker_background_dlnode_set_color(
bs_dllist_node_t *dlnode_ptr,
void *ud_ptr)
{
struct wlmaker_background *bg_ptr = BS_CONTAINER_OF(
dlnode_ptr, struct wlmaker_background, dlnode);
wlmtk_output_tracker_for_each(
bg_ptr->output_tracker_ptr,
_wlmaker_backgrund_panel_set_color,
ud_ptr);
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/** Implements @ref wlmtk_panel_vmt_t::request_size. Updates the panel size. */
uint32_t _wlmaker_background_panel_request_size(
wlmtk_panel_t *panel_ptr,
int width,
int height)
{
wlmaker_background_panel_t *background_ptr = BS_CONTAINER_OF(
panel_ptr, wlmaker_background_panel_t, super_panel);
wlmtk_rectangle_set_size(background_ptr->rectangle_ptr, width, height);
wlmtk_panel_commit(
&background_ptr->super_panel, 0,
&_wlmaker_background_panel_position);
return 0;
}
/* ------------------------------------------------------------------------- */
/** Ctor. */
void *_wlmaker_background_panel_create(
struct wlr_output *wlr_output_ptr,
void *ud_ptr)
{
struct wlmaker_background *background_ptr = ud_ptr;
wlmaker_background_panel_t *background_panel_ptr = logged_calloc(
1, sizeof(wlmaker_background_panel_t));
if (NULL == background_panel_ptr) return NULL;
if (!wlmtk_panel_init(&background_panel_ptr->super_panel,
&_wlmaker_background_panel_position)) {
_wlmaker_background_panel_destroy(
NULL, NULL, background_panel_ptr);
return NULL;
}
wlmtk_panel_extend(&background_panel_ptr->super_panel,
&_wlmaker_background_panel_vmt);
wlmtk_element_extend(
wlmtk_panel_element(&background_panel_ptr->super_panel),
&_wlmaker_background_panel_element_vmt);
background_panel_ptr->rectangle_ptr = wlmtk_rectangle_create(
0, 0, background_ptr->color);
if (NULL == background_panel_ptr->rectangle_ptr) {
_wlmaker_background_panel_destroy(
NULL, NULL, background_panel_ptr);
return NULL;
}
wlmtk_element_set_visible(
wlmtk_rectangle_element(background_panel_ptr->rectangle_ptr),
true);
wlmtk_container_add_element(
&background_panel_ptr->super_panel.super_container,
wlmtk_rectangle_element(background_panel_ptr->rectangle_ptr));
wlmtk_element_set_visible(
wlmtk_panel_element(&background_panel_ptr->super_panel),
true);
wlmtk_layer_add_panel(
background_ptr->layer_ptr,
&background_panel_ptr->super_panel,
wlr_output_ptr);
return background_panel_ptr;
}
/* ------------------------------------------------------------------------- */
/** Dtor. */
void _wlmaker_background_panel_destroy(
__UNUSED__ struct wlr_output *wlr_output_ptr,
__UNUSED__ void *ud_ptr,
void *output_ptr)
{
wlmaker_background_panel_t *background_panel_ptr = output_ptr;
_wlmaker_background_panel_element_destroy(
wlmtk_panel_element(&background_panel_ptr->super_panel));
free(background_panel_ptr);
}
/* ------------------------------------------------------------------------- */
/** Dtor for the panel's element. Leaves the output handle intact. */
void _wlmaker_background_panel_element_destroy(wlmtk_element_t *element_ptr)
{
wlmaker_background_panel_t *background_panel_ptr = BS_CONTAINER_OF(
element_ptr,
wlmaker_background_panel_t,
super_panel.super_container.super_element);
if (NULL != wlmtk_panel_get_layer(
&background_panel_ptr->super_panel)) {
wlmtk_layer_remove_panel(
wlmtk_panel_get_layer(&background_panel_ptr->super_panel),
&background_panel_ptr->super_panel);
}
if (NULL != background_panel_ptr->rectangle_ptr) {
wlmtk_container_remove_element(
&background_panel_ptr->super_panel.super_container,
wlmtk_rectangle_element(background_panel_ptr->rectangle_ptr));
wlmtk_rectangle_destroy(background_panel_ptr->rectangle_ptr);
background_panel_ptr->rectangle_ptr = NULL;
}
wlmtk_panel_fini(&background_panel_ptr->super_panel);
}
/* ------------------------------------------------------------------------- */
/** Sets the background color for the panel on this output. */
void _wlmaker_backgrund_panel_set_color(
__UNUSED__ struct wlr_output *wlr_output_ptr,
__UNUSED__ void *ud_ptr,
void *output_ptr,
void *arg_ptr)
{
wlmaker_background_panel_t *background_panel_ptr = output_ptr;
uint32_t *color_ptr = arg_ptr;
if (NULL != background_panel_ptr->rectangle_ptr) {
wlmtk_rectangle_set_color(
background_panel_ptr->rectangle_ptr,
*color_ptr);
}
}
/* == End of background.c ================================================== */
wlmaker-0.8.1/src/tl_menu.h 0000644 0001751 0001751 00000003131 15222270710 015167 0 ustar runner runner /* ========================================================================= */
/**
* @file tl_menu.h
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __WLMAKER_TL_MENU_H__
#define __WLMAKER_TL_MENU_H__
#include "toolkit/toolkit.h"
#include "task_list.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/** Forward declaration: State of a toplevel's menu. */
typedef struct _wlmaker_tl_menu_t wlmaker_tl_menu_t;
/**
* Creates a (window) menu for a toplevel (window).
*
* @param window_ptr
* @param server_ptr
*
* @return pointer to the toplevel's menu state or NULL on error.
*/
wlmaker_tl_menu_t *wlmaker_tl_menu_create(
wlmtk_window_t *window_ptr,
wlmaker_server_t *server_ptr);
/**
* Destroys the toplevel's menu.
*
* @param tl_menu_ptr
*/
void wlmaker_tl_menu_destroy(wlmaker_tl_menu_t *tl_menu_ptr);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* __TL_MENU_H__ */
/* == End of tl_menu.h ===================================================== */
wlmaker-0.8.1/src/xwl.c 0000644 0001751 0001751 00000030237 15222270710 014340 0 ustar runner runner /* ========================================================================= */
/**
* @file xwl.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @internal
* The current XWayland implementation is not very cleanly designed and should
* be considered *experimental*.
* TODO(kaeser@gubbe.ch): Re-design, once object model is updated.
*
* Known issues:
*
* * Scene graph API nodes for toplevel windows are created early. This leads
* to issues with ownership (cleanup?), stacking order, and when properties
* (position) are set. It'd be better to only create them when mapping a
* window (and destroying when unmapping).
*
* * Windows with parents are created as plain surfaces and don't clearly show
* their stacking order. Decorations may not get applied in all cases.
*
* * Stacking order is not tackled, eg. popups may appear below. Reproduce:
* Open `emacs`, click a menu, and hover over a menu item for the tooltip to
* appear. When moving across menus, the tooltip sometimes appears below the
* menu window.
*
* * Popups or dialogs may not be activated or focussed correctly. Reproduce:
* Open `emacs`, open the `File` menu, and `Visit New File...`. The dialogue
* does not accept mouse events. Moving the dialogue window moves the entire
* emacs window.
*
* * `modal` windows are not identified and treated as such.
*
* * Positioning of windows: Applications such as `gimp` are setting the main
* window's position based on the earlier application's status. We currently
* don't translate this to the toplevel window's position, but apply it to
* the surface within the tree => leading to a title bar that's oddly offset.
* Reproduce: Open a gimp menu, and view the tooltip being off.
*
* * The window types are not well understood. Eg. `gimp` menu tooltips are
* created as windows without parent. We can identify them as TOOLTIP windows
* that won't have a border; but we don't have a well-understood set of
* properties for the window types.
*/
#include "xwl.h"
#if defined(WLMAKER_HAVE_XWAYLAND)
#define WLR_USE_UNSTABLE
#include
#include
#undef WLR_USE_UNSTABLE
#endif // defined(WLMAKER_HAVE_XWAYLAND)
#include
#include
#if defined(WLMAKER_HAVE_XWAYLAND)
#include
#include
#include
#include
#include "backend/backend.h"
#include "server.h"
#include "toolkit/toolkit.h"
#include "xwl_surface.h"
#include "x11_cursor.xpm"
#endif // defined(WLMAKER_HAVE_XWAYLAND)
/* == Declarations ========================================================= */
/** XWayland interface state. */
struct _wlmaker_xwl_t {
/** Back-link to server. */
wlmaker_server_t *server_ptr;
#if defined(WLMAKER_HAVE_XWAYLAND)
/** XWayland server and XWM. */
struct wlr_xwayland *wlr_xwayland_ptr;
/** Listener for the `ready` signal raised by `wlr_xwayland`. */
struct wl_listener ready_listener;
/** Listener for the `new_surface` signal raised by `wlr_xwayland`. */
struct wl_listener new_surface_listener;
/** XCB atoms we consider relevant. */
xcb_atom_t xcb_atoms[XWL_MAX_ATOM_ID];
#endif // defined(WLMAKER_HAVE_XWAYLAND)
};
#if defined(WLMAKER_HAVE_XWAYLAND)
static void handle_ready(
struct wl_listener *listener_ptr,
void *data_ptr);
static void handle_new_surface(
struct wl_listener *listener_ptr,
void *data_ptr);
/* == Data ================================================================= */
/** Lookup map for some of XCB atom identifiers. */
static const char *xwl_atom_name_map[XWL_MAX_ATOM_ID] = {
[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",
};
/* == Exported methods ===================================================== */
#endif // defined(WLMAKER_HAVE_XWAYLAND)
/* ------------------------------------------------------------------------- */
wlmaker_xwl_t *wlmaker_xwl_create(wlmaker_server_t *server_ptr)
{
wlmaker_xwl_t *xwl_ptr = logged_calloc(1, sizeof(wlmaker_xwl_t));
if (NULL == xwl_ptr) return NULL;
xwl_ptr->server_ptr = server_ptr;
#if defined(WLMAKER_HAVE_XWAYLAND)
xwl_ptr->wlr_xwayland_ptr = wlr_xwayland_create(
server_ptr->wl_display_ptr,
wlmbe_backend_compositor(server_ptr->backend_ptr),
false);
if (NULL == xwl_ptr->wlr_xwayland_ptr) {
bs_log(BS_ERROR, "Failed wlr_xwayland_create(%p, %p, false).",
server_ptr->wl_display_ptr,
wlmbe_backend_compositor(server_ptr->backend_ptr));
wlmaker_xwl_destroy(xwl_ptr);
return NULL;
}
wlmtk_util_connect_listener_signal(
&xwl_ptr->wlr_xwayland_ptr->events.ready,
&xwl_ptr->ready_listener,
handle_ready);
wlmtk_util_connect_listener_signal(
&xwl_ptr->wlr_xwayland_ptr->events.new_surface,
&xwl_ptr->new_surface_listener,
handle_new_surface);
// TODO(kaeser@gubbe.ch): That's a bit ugly. We should only do a setenv
// as we create & fork the subprocesses. Needs infrastructure, though.
setenv("DISPLAY", xwl_ptr->wlr_xwayland_ptr->display_name, true);
#endif // defined(WLMAKER_HAVE_XWAYLAND)
return xwl_ptr;
}
/* ------------------------------------------------------------------------- */
void wlmaker_xwl_destroy(wlmaker_xwl_t *xwl_ptr)
{
#if defined(WLMAKER_HAVE_XWAYLAND)
wlmtk_util_disconnect_listener(&xwl_ptr->ready_listener);
wlmtk_util_disconnect_listener(&xwl_ptr->new_surface_listener);
if (NULL != xwl_ptr->wlr_xwayland_ptr) {
wlr_xwayland_destroy(xwl_ptr->wlr_xwayland_ptr);
xwl_ptr->wlr_xwayland_ptr = NULL;
}
#endif // defined(WLMAKER_HAVE_XWAYLAND)
free(xwl_ptr);
}
#if defined(WLMAKER_HAVE_XWAYLAND)
/* ------------------------------------------------------------------------- */
/**
* Returns whether the XWayland surface has any of the window types.
*
* @param xwl_ptr
* @param wlr_xwayland_surface_ptr
* @param atom_identifiers NULL-terminated set of window type we're looking
* for.
*
* @return Whether `atom_identifiers` is in any of the window types.
*/
bool xwl_is_window_type(
wlmaker_xwl_t *xwl_ptr,
struct wlr_xwayland_surface *wlr_xwayland_surface_ptr,
const xwl_atom_identifier_t *atom_identifiers)
{
for (; *atom_identifiers < XWL_MAX_ATOM_ID; ++atom_identifiers) {
for (size_t i = 0;
i < wlr_xwayland_surface_ptr->window_type_len;
++i) {
if (wlr_xwayland_surface_ptr->window_type[i] ==
xwl_ptr->xcb_atoms[*atom_identifiers]) {
return true;
}
}
}
return false;
}
/* ------------------------------------------------------------------------- */
const char *xwl_atom_name(
wlmaker_xwl_t *xwl_ptr,
xcb_atom_t atom)
{
for (size_t atom_idx = 0; atom_idx < XWL_MAX_ATOM_ID; ++atom_idx) {
if (xwl_ptr->xcb_atoms[atom_idx] == atom) {
return xwl_atom_name_map[atom_idx];
}
}
return NULL;
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/**
* Event handler for the `ready` signal raised by `wlr_xwayland`.
*
* @param listener_ptr
* @param data_ptr
*/
void handle_ready(struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_xwl_t *xwl_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_xwl_t, ready_listener);
xcb_connection_t *xcb_connection_ptr = xcb_connect(
xwl_ptr->wlr_xwayland_ptr->display_name, NULL);
int error = xcb_connection_has_error(xcb_connection_ptr);
if (0 != error) {
bs_log(BS_ERROR, "Failed xcb_connect(%s, NULL): %d",
xwl_ptr->wlr_xwayland_ptr->display_name, error);
return;
}
xcb_intern_atom_cookie_t atom_cookies[XWL_MAX_ATOM_ID];
for (size_t i = 0; i < XWL_MAX_ATOM_ID; ++i) {
const char *name_ptr = xwl_atom_name_map[i];
atom_cookies[i] = xcb_intern_atom(
xcb_connection_ptr, 0, strlen(name_ptr), name_ptr);
}
for (size_t i = 0; i < XWL_MAX_ATOM_ID; ++i) {
xcb_generic_error_t *error_ptr = NULL;
xcb_intern_atom_reply_t *atom_reply_ptr = xcb_intern_atom_reply(
xcb_connection_ptr, atom_cookies[i], &error_ptr);
if (NULL != atom_reply_ptr) {
if (NULL == error_ptr) {
xwl_ptr->xcb_atoms[i] = atom_reply_ptr->atom;
bs_log(BS_DEBUG, "XCB lookup on %s: atom %s = 0x%"PRIx32,
xwl_ptr->wlr_xwayland_ptr->display_name,
xwl_atom_name_map[i],
atom_reply_ptr->atom);
}
free(atom_reply_ptr);
}
if (NULL != error_ptr) {
bs_log(BS_ERROR, "Failed xcb_intern_atom_reply(%p, %s, %p): %d",
xcb_connection_ptr, xwl_atom_name_map[i],
&error_ptr, error_ptr->error_code);
free(error_ptr);
break;
}
}
xcb_disconnect(xcb_connection_ptr);
// Sets the default cursor to use for XWayland surfaces, unless overrideen.
#if WLR_VERSION_NUM >= (20 << 8)
bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_xpm_create_from_data(x11_cursor_xpm);
if (NULL == gfxbuf_ptr) return;
struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer(
gfxbuf_ptr->width, gfxbuf_ptr->height);
if (NULL != wlr_buffer_ptr) {
bs_gfxbuf_copy(bs_gfxbuf_from_wlr_buffer(wlr_buffer_ptr), gfxbuf_ptr);
wlr_xwayland_set_cursor(
xwl_ptr->wlr_xwayland_ptr,
wlr_buffer_ptr,
0, 0);
wlr_buffer_drop(wlr_buffer_ptr);
}
bs_gfxbuf_destroy(gfxbuf_ptr);
#else
bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_xpm_create_from_data(x11_cursor_xpm);
if (NULL != gfxbuf_ptr) {
wlr_xwayland_set_cursor(
xwl_ptr->wlr_xwayland_ptr,
(uint8_t*)gfxbuf_ptr->data_ptr,
gfxbuf_ptr->pixels_per_line * sizeof(uint32_t),
gfxbuf_ptr->width, gfxbuf_ptr->height,
0, 0);
bs_gfxbuf_destroy(gfxbuf_ptr);
}
#endif
}
/* ------------------------------------------------------------------------- */
/**
* Event handler for the `new_surface` signal raised by `wlr_xwayland`.
*
* @param listener_ptr
* @param data_ptr
*/
void handle_new_surface(struct wl_listener *listener_ptr,
void *data_ptr)
{
wlmaker_xwl_t *xwl_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_xwl_t, new_surface_listener);
struct wlr_xwayland_surface *wlr_xwayland_surface_ptr = data_ptr;
wlmaker_xwl_surface_t *xwl_surface_ptr = wlmaker_xwl_surface_create(
wlr_xwayland_surface_ptr,
xwl_ptr,
xwl_ptr->server_ptr);
if (NULL == xwl_surface_ptr) {
bs_log(BS_ERROR, "Failed wlmaker_xwl_surface_create(%p)",
wlr_xwayland_surface_ptr);
}
}
#endif // defined(WLMAKER_HAVE_XWAYLAND)
/* == End of xwl.c ========================================================= */
wlmaker-0.8.1/src/clip.c 0000644 0001751 0001751 00000110064 15222270710 014452 0 ustar runner runner /* ========================================================================= */
/**
* @file clip.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "clip.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define WLR_USE_UNSTABLE
#include
#include
#include
#undef WLR_USE_UNSTABLE
#include "backend/backend.h"
#include "backend/output_config.h"
#include "files.h"
#include "toolkit/toolkit.h"
/* == Declarations ========================================================= */
/** Clip handle. */
struct _wlmaker_clip_t {
/** The clip happens to be derived from a tile. */
wlmtk_tile_t super_tile;
/** Original virtual method table fo the superclass' element. */
wlmtk_element_vmt_t orig_super_element_vmt;
/** Backlink to the server. */
wlmaker_server_t *server_ptr;
/** The toolkit dock, holding the clip tile. */
wlmtk_dock_t *wlmtk_dock_ptr;
/** The tile's texture buffer without any buttons pressed */
struct wlr_buffer *tile_buffer_ptr;
/** The tile's texture buffer with the 'Next' buttons pressed. */
struct wlr_buffer *next_pressed_tile_buffer_ptr;
/** The tile's texture buffer with the 'Previous' buttons pressed. */
struct wlr_buffer *prev_pressed_tile_buffer_ptr;
/** Overlay buffer element: Contains the workspace's title and number. */
wlmtk_buffer_t overlay_buffer;
/** Path to the image file. */
char *image_path_ptr;
/** Clip image. */
wlmtk_image_t *image_ptr;
/** Description of the desired output, if any. */
wlmbe_output_description_t output_description;
/** Whether the pointer is currently inside the 'prev' button. */
bool pointer_inside_prev_button;
/** Whether the pointer is currently inside the 'next' button. */
bool pointer_inside_next_button;
/** Whether the 'prev' button had been pressed. */
bool prev_button_pressed;
/** Whether the 'next' button had been pressed. */
bool next_button_pressed;
/** Listener for @ref wlmtk_root_events_t::workspace_changed. */
struct wl_listener workspace_changed_listener;
/** Listener for wlr_output_layout::events.change. */
struct wl_listener output_layout_change_listener;
/** Listener for @ref wlmtk_element_events_t::pointer_motion. */
struct wl_listener pointer_motion_listener;
/** Listener for @ref wlmtk_element_events_t::pointer_leave. */
struct wl_listener pointer_leave_listener;
/** Listener for @ref wlmaker_server_t::theme_changed_event. */
struct wl_listener theme_changed_listener;
/** The clip's style. */
wlmaker_config_clip_style_t style;
};
static bool _wlmaker_clip_pointer_axis(
wlmtk_element_t *element_ptr,
struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr);
static bool _wlmaker_clip_pointer_button(
wlmtk_element_t *element_ptr,
const wlmtk_button_event_t *button_event_ptr);
static bool _wlmaker_clip_update_tiles(
wlmaker_clip_t *clip_ptr,
const struct wlmtk_tile_style *tile_style_ptr);
static void _wlmaker_clip_apply_button_state(wlmaker_clip_t *clip_ptr);
static bool _wlmaker_clip_update_overlay(
wlmaker_clip_t *clip_ptr,
const struct wlmtk_tile_style *tile_style_ptr,
const wlmaker_config_clip_style_t *clip_style_ptr);
static bool _wlmaker_clip_update_image(
wlmaker_clip_t *clip_ptr,
const struct wlmtk_tile_style *tile_style_ptr);
static struct wlr_buffer *_wlmaker_clip_create_tile(
const struct wlmtk_tile_style *tile_style_ptr,
bool prev_pressed,
bool next_pressed);
static void _wlmaker_clip_handle_workspace_changed(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_clip_handle_output_layout_change(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_clip_handle_pointer_motion(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_clip_handle_pointer_leave(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmaker_clip_handle_theme_changed(
struct wl_listener *listener_ptr,
void *data_ptr);
/* == Data ================================================================= */
/** The clip's extension to @ref wlmtk_element_t virtual method table. */
static const wlmtk_element_vmt_t _wlmaker_clip_element_vmt = {
.pointer_axis = _wlmaker_clip_pointer_axis,
.pointer_button = _wlmaker_clip_pointer_button,
};
/** TODO: Replace this. */
typedef struct {
/** Positioning data. */
wlmtk_dock_positioning_t positioning;
} parse_args;
/** Enum descriptor for `enum wlr_edges`. */
static const bspl_enum_desc_t _wlmaker_clip_edges[] = {
BSPL_ENUM("TOP", WLR_EDGE_TOP),
BSPL_ENUM("BOTTOM", WLR_EDGE_BOTTOM),
BSPL_ENUM("LEFT", WLR_EDGE_LEFT),
BSPL_ENUM("RIGHT", WLR_EDGE_RIGHT),
BSPL_ENUM_SENTINEL(),
};
/** Descriptor for the clip's plist. */
const bspl_desc_t _wlmaker_clip_desc[] = {
BSPL_DESC_ENUM("Edge", true, parse_args,
positioning.edge, positioning.edge,
WLR_EDGE_NONE, _wlmaker_clip_edges),
BSPL_DESC_ENUM("Anchor", true, parse_args,
positioning.anchor, positioning.anchor,
WLR_EDGE_NONE, _wlmaker_clip_edges),
BSPL_DESC_SENTINEL(),
};
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
wlmaker_clip_t *wlmaker_clip_create(
wlmaker_server_t *server_ptr,
bspl_dict_t *state_dict_ptr,
const wlmaker_config_style_t *style_ptr)
{
wlmaker_clip_t *clip_ptr = logged_calloc(1, sizeof(wlmaker_clip_t));
if (NULL == clip_ptr) return NULL;
clip_ptr->server_ptr = server_ptr;
clip_ptr->style = style_ptr->clip;
parse_args args = {};
bspl_dict_t *dict_ptr = bspl_dict_get_dict(state_dict_ptr, "Clip");
if (NULL == dict_ptr) {
bs_log(BS_ERROR, "No 'Clip' dict found in state.");
wlmaker_clip_destroy(clip_ptr);
return NULL;
}
bspl_decode_dict(dict_ptr, _wlmaker_clip_desc, &args);
bspl_dict_t *output_dict_ptr = bspl_dict_get_dict(dict_ptr, "Output");
if (NULL != output_dict_ptr) {
if (!wlmbe_output_description_init_from_plist(
&clip_ptr->output_description, output_dict_ptr)) {
wlmaker_clip_destroy(clip_ptr);
return NULL;
}
}
clip_ptr->wlmtk_dock_ptr = wlmtk_dock_create(
&args.positioning, &style_ptr->dock);
wlmtk_element_set_visible(
wlmtk_dock_element(clip_ptr->wlmtk_dock_ptr),
true);
if (!wlmtk_tile_init(
&clip_ptr->super_tile,
&style_ptr->tile)) {
wlmaker_clip_destroy(clip_ptr);
return NULL;
}
clip_ptr->orig_super_element_vmt = wlmtk_element_extend(
wlmtk_tile_element(&clip_ptr->super_tile),
&_wlmaker_clip_element_vmt);
wlmtk_util_connect_listener_signal(
&wlmtk_tile_element(&clip_ptr->super_tile)->events.pointer_motion,
&clip_ptr->pointer_motion_listener,
_wlmaker_clip_handle_pointer_motion);
wlmtk_util_connect_listener_signal(
&wlmtk_tile_element(&clip_ptr->super_tile)->events.pointer_leave,
&clip_ptr->pointer_leave_listener,
_wlmaker_clip_handle_pointer_leave);
if (!_wlmaker_clip_update_tiles(clip_ptr, &style_ptr->tile)) {
wlmaker_clip_destroy(clip_ptr);
return NULL;
}
wlmtk_element_set_visible(
wlmtk_tile_element(&clip_ptr->super_tile), true);
wlmtk_tile_set_background_buffer(
&clip_ptr->super_tile, clip_ptr->tile_buffer_ptr);
wlmtk_dock_add_tile(clip_ptr->wlmtk_dock_ptr, &clip_ptr->super_tile);
if (!wlmtk_buffer_init(&clip_ptr->overlay_buffer)) {
wlmaker_clip_destroy(clip_ptr);
return NULL;
}
wlmtk_element_set_visible(
wlmtk_buffer_element(&clip_ptr->overlay_buffer), true);
struct wlr_output *wlr_output_ptr = wlmbe_output_description_first_fnmatch(
&clip_ptr->output_description, server_ptr->wlr_output_layout_ptr);
if (NULL == wlr_output_ptr) {
wlr_output_ptr = wlmbe_primary_output(
server_ptr->wlr_output_layout_ptr);
}
wlmtk_workspace_t *workspace_ptr =
wlmtk_root_get_current_workspace(server_ptr->root_ptr);
wlmtk_layer_t *layer_ptr = wlmtk_workspace_get_layer(
workspace_ptr, WLMTK_WORKSPACE_LAYER_TOP);
if (!wlmtk_layer_add_panel(
layer_ptr,
wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr),
wlr_output_ptr)) {
wlmaker_clip_destroy(clip_ptr);
return NULL;
}
// Resolves to a full path, and verifies the icon file exists.
clip_ptr->image_path_ptr = wlmaker_files_xdg_data_find(
server_ptr->files_ptr, "icons/clip-56x56.png", S_IFREG);
if (NULL == clip_ptr->image_path_ptr) {
bs_log(
BS_WARNING,
"Failed to locate ${XDG_DATA_DIRS}/wlmaker/icons/clip-56x56.png");
#ifdef WLMAKER_SOURCE_DIR
clip_ptr->image_path_ptr = logged_strdup(
WLMAKER_SOURCE_DIR "/share/wlmaker/icons/clip-56x56.png");
#endif
}
if (NULL == clip_ptr->image_path_ptr) {
wlmaker_clip_destroy(clip_ptr);
return NULL;
}
if (!_wlmaker_clip_update_image(clip_ptr, &style_ptr->tile)) {
wlmaker_clip_destroy(clip_ptr);
return NULL;
}
if (!_wlmaker_clip_update_overlay(
clip_ptr, &clip_ptr->super_tile.style, &clip_ptr->style)) {
wlmaker_clip_destroy(clip_ptr);
return NULL;
};
wlmtk_util_connect_listener_signal(
&wlmtk_root_events(server_ptr->root_ptr)->workspace_changed,
&clip_ptr->workspace_changed_listener,
_wlmaker_clip_handle_workspace_changed);
wlmtk_util_connect_listener_signal(
&server_ptr->theme_changed_event,
&clip_ptr->theme_changed_listener,
_wlmaker_clip_handle_theme_changed);
// TODO(kaeser@gubbe.ch): This is a very hacky way of updating the output
// before the layer's handler removes all associated panels. Should be
// a native method of wlmtk_dock_t or wlmtk_panel_t.
clip_ptr->output_layout_change_listener.notify =
_wlmaker_clip_handle_output_layout_change;
wl_list_insert(
server_ptr->wlr_output_layout_ptr->events.change.listener_list.next,
&clip_ptr->output_layout_change_listener.link);
server_ptr->clip_dock_ptr = clip_ptr->wlmtk_dock_ptr;
bs_log(BS_INFO, "Created clip %p", clip_ptr);
return clip_ptr;
}
/* ------------------------------------------------------------------------- */
void wlmaker_clip_destroy(wlmaker_clip_t *clip_ptr)
{
if (NULL != clip_ptr->server_ptr) {
clip_ptr->server_ptr->clip_dock_ptr = NULL;
}
wlmtk_util_disconnect_listener(&clip_ptr->output_layout_change_listener);
wlmtk_util_disconnect_listener(&clip_ptr->workspace_changed_listener);
wlmtk_util_disconnect_listener(&clip_ptr->theme_changed_listener);
if (wlmtk_tile_element(&clip_ptr->super_tile)->parent_container_ptr) {
wlmtk_tile_set_content(&clip_ptr->super_tile, NULL);
wlmtk_tile_set_overlay(&clip_ptr->super_tile, NULL);
wlmtk_dock_remove_tile(
clip_ptr->wlmtk_dock_ptr,
&clip_ptr->super_tile);
}
wlmtk_util_disconnect_listener(&clip_ptr->pointer_leave_listener);
wlmtk_util_disconnect_listener(&clip_ptr->pointer_motion_listener);
wlmtk_tile_fini(&clip_ptr->super_tile);
wlmtk_buffer_fini(&clip_ptr->overlay_buffer);
if (NULL != clip_ptr->image_ptr) {
wlmtk_image_destroy(clip_ptr->image_ptr);
clip_ptr->image_ptr = NULL;
}
if (NULL != clip_ptr->image_path_ptr) {
free(clip_ptr->image_path_ptr);
clip_ptr->image_path_ptr = NULL;
}
if (NULL != clip_ptr->wlmtk_dock_ptr) {
if (NULL != wlmtk_panel_get_layer(
wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr))) {
wlmtk_layer_remove_panel(
wlmtk_panel_get_layer(wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr)),
wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr));
}
wlmtk_dock_destroy(clip_ptr->wlmtk_dock_ptr);
clip_ptr->wlmtk_dock_ptr = NULL;
}
if (NULL != clip_ptr->tile_buffer_ptr) {
wlr_buffer_drop(clip_ptr->tile_buffer_ptr);
clip_ptr->tile_buffer_ptr = NULL;
}
if (NULL != clip_ptr->prev_pressed_tile_buffer_ptr) {
wlr_buffer_drop(clip_ptr->prev_pressed_tile_buffer_ptr);
clip_ptr->prev_pressed_tile_buffer_ptr = NULL;
}
if (NULL != clip_ptr->next_pressed_tile_buffer_ptr) {
wlr_buffer_drop(clip_ptr->next_pressed_tile_buffer_ptr);
clip_ptr->next_pressed_tile_buffer_ptr = NULL;
}
wlmbe_output_description_fini(&clip_ptr->output_description);
free(clip_ptr);
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/**
* Implements @ref wlmtk_element_vmt_t::pointer_axis.
*
* Moves to the next or previous workspace, depending on the axis (scroll-
* wheel) direction.
*
* @param element_ptr
* @param wlr_pointer_axis_event_ptr
*
* @return true
*/
bool _wlmaker_clip_pointer_axis(
wlmtk_element_t *element_ptr,
struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr)
{
wlmaker_clip_t *clip_ptr = BS_CONTAINER_OF(
element_ptr, wlmaker_clip_t,
super_tile.super_container.super_element);
if (0 > wlr_pointer_axis_event_ptr->delta) {
// Scroll wheel "up" -> next.
wlmtk_root_switch_to_next_workspace(clip_ptr->server_ptr->root_ptr);
} else if (0 < wlr_pointer_axis_event_ptr->delta) {
// Scroll wheel "down" -> next.
wlmtk_root_switch_to_previous_workspace(clip_ptr->server_ptr->root_ptr);
}
return true;
}
/* ------------------------------------------------------------------------- */
/**
* Implements @ref wlmtk_element_vmt_t::pointer_button.
*
* Checks if the button press is on either 'next' or 'prev' button area,
* updates visualization if pressed, and switches workspace if needed.
*
* @param element_ptr
* @param button_event_ptr
*
* @return true.
*/
bool _wlmaker_clip_pointer_button(
wlmtk_element_t *element_ptr,
const wlmtk_button_event_t *button_event_ptr)
{
wlmaker_clip_t *clip_ptr = BS_CONTAINER_OF(
element_ptr, wlmaker_clip_t,
super_tile.super_container.super_element);
if (BTN_LEFT != button_event_ptr->button) return true;
switch (button_event_ptr->type) {
case WLMTK_BUTTON_DOWN:
// Pointer button tressed. Translate to button press if in area.
if (clip_ptr->pointer_inside_next_button ||
clip_ptr->pointer_inside_next_button) {
clip_ptr->next_button_pressed = true;
clip_ptr->prev_button_pressed = false;
} else if (clip_ptr->pointer_inside_prev_button ||
clip_ptr->pointer_inside_prev_button) {
clip_ptr->next_button_pressed = false;
clip_ptr->prev_button_pressed = true;
}
break;
case WLMTK_BUTTON_UP:
// Button is released (closed the click). If we're within the area of
// the pressed button: Trigger the action.
if ((clip_ptr->pointer_inside_next_button ||
clip_ptr->pointer_inside_next_button) &&
clip_ptr->next_button_pressed) {
clip_ptr->next_button_pressed = false;
wlmtk_root_switch_to_next_workspace(
clip_ptr->server_ptr->root_ptr);
} else if ((clip_ptr->pointer_inside_prev_button ||
clip_ptr->pointer_inside_prev_button) &&
clip_ptr->prev_button_pressed) {
clip_ptr->prev_button_pressed = false;
wlmtk_root_switch_to_previous_workspace(
clip_ptr->server_ptr->root_ptr);
}
break;
case WLMTK_BUTTON_CLICK:
default:
break;
}
_wlmaker_clip_apply_button_state(clip_ptr);
return true;
}
/* ------------------------------------------------------------------------- */
/** Updates the button textures, based on current state what's pressed. */
static void _wlmaker_clip_apply_button_state(wlmaker_clip_t *clip_ptr)
{
struct wlr_buffer *wlr_buffer_ptr = clip_ptr->tile_buffer_ptr;
if ((clip_ptr->pointer_inside_next_button ||
clip_ptr->pointer_inside_next_button)&&
clip_ptr->next_button_pressed) {
wlr_buffer_ptr = clip_ptr->next_pressed_tile_buffer_ptr;
} else if ((clip_ptr->pointer_inside_prev_button ||
clip_ptr->pointer_inside_prev_button) &&
clip_ptr->prev_button_pressed) {
wlr_buffer_ptr = clip_ptr->prev_pressed_tile_buffer_ptr;
}
wlmtk_tile_set_background_buffer(&clip_ptr->super_tile, wlr_buffer_ptr);
}
/* ------------------------------------------------------------------------- */
/** Updates the overlay buffer's content with workspace name and index. */
bool _wlmaker_clip_update_overlay(
wlmaker_clip_t *clip_ptr,
const struct wlmtk_tile_style *tile_style_ptr,
const wlmaker_config_clip_style_t *clip_style_ptr)
{
struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer(
tile_style_ptr->size, tile_style_ptr->size);
if (NULL == wlr_buffer_ptr) return false;
int index = 0;
const char *name_ptr = NULL;
wlmtk_workspace_get_details(
wlmtk_root_get_current_workspace(clip_ptr->server_ptr->root_ptr),
&name_ptr, &index);
cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr);
if (NULL == cairo_ptr) {
wlr_buffer_drop(wlr_buffer_ptr);
return false;
}
cairo_select_font_face(
cairo_ptr,
clip_style_ptr->font.face,
CAIRO_FONT_SLANT_NORMAL,
wlmtk_style_font_weight_cairo_from_wlmtk(clip_style_ptr->font.weight));
cairo_set_font_size(cairo_ptr, clip_style_ptr->font.size);
cairo_set_source_argb8888(cairo_ptr, clip_style_ptr->text_color);
cairo_move_to(
cairo_ptr,
clip_style_ptr->font.size * 4 / 12,
clip_style_ptr->font.size * 2 / 12 + clip_style_ptr->font.size);
cairo_show_text(cairo_ptr, name_ptr);
cairo_move_to(
cairo_ptr,
tile_style_ptr->size - clip_style_ptr->font.size * 14 / 12,
tile_style_ptr->size - clip_style_ptr->font.size * 8 / 12);
char buf[10];
snprintf(buf, sizeof(buf), "%d", index);
cairo_show_text(cairo_ptr, buf);
cairo_destroy(cairo_ptr);
wlmtk_buffer_set(&clip_ptr->overlay_buffer, wlr_buffer_ptr);
wlr_buffer_drop(wlr_buffer_ptr);
wlmtk_tile_set_overlay(
&clip_ptr->super_tile,
wlmtk_buffer_element(&clip_ptr->overlay_buffer));
return true;
}
/* ------------------------------------------------------------------------- */
/** Updates (reloads) the content image. */
bool _wlmaker_clip_update_image(
wlmaker_clip_t *clip_ptr,
const struct wlmtk_tile_style *tile_style_ptr)
{
wlmtk_image_t *i = wlmtk_image_create_scaled(
clip_ptr->image_path_ptr,
tile_style_ptr->content_size,
tile_style_ptr->content_size);
if (NULL == i) return false;
wlmtk_element_set_visible(wlmtk_image_element(i), true);
wlmtk_tile_set_content(&clip_ptr->super_tile, wlmtk_image_element(i));
if (NULL != clip_ptr->image_ptr) wlmtk_image_destroy(clip_ptr->image_ptr);
clip_ptr->image_ptr = i;
return true;
}
/* ------------------------------------------------------------------------- */
/**
* Creates (or updates) the tile buffers for the button states.
*
* @param clip_ptr
* @param tile_style_ptr
*
* @return true on success
*/
bool _wlmaker_clip_update_tiles(
wlmaker_clip_t *clip_ptr,
const struct wlmtk_tile_style *tile_style_ptr)
{
struct wlr_buffer *tile, *tile_prev, *tile_next;
tile = _wlmaker_clip_create_tile(tile_style_ptr, false, false);
tile_prev = _wlmaker_clip_create_tile(tile_style_ptr, true, false);
tile_next = _wlmaker_clip_create_tile(tile_style_ptr, false, true);
if (NULL == tile || NULL == tile_prev || NULL == tile_next) {
if (tile) wlr_buffer_drop(tile);
if (tile_prev) wlr_buffer_drop(tile_prev);
if (tile_next) wlr_buffer_drop(tile_next);
return false;
}
if (NULL != clip_ptr->tile_buffer_ptr) {
wlr_buffer_drop(clip_ptr->tile_buffer_ptr);
}
if (NULL != clip_ptr->prev_pressed_tile_buffer_ptr) {
wlr_buffer_drop(clip_ptr->prev_pressed_tile_buffer_ptr);
}
if (NULL != clip_ptr->next_pressed_tile_buffer_ptr) {
wlr_buffer_drop(clip_ptr->next_pressed_tile_buffer_ptr);
}
clip_ptr->tile_buffer_ptr = tile;
clip_ptr->prev_pressed_tile_buffer_ptr = tile_prev;
clip_ptr->next_pressed_tile_buffer_ptr = tile_next;
_wlmaker_clip_apply_button_state(clip_ptr);
return true;
}
/* ------------------------------------------------------------------------- */
/**
* Creates a wlr_buffer with texture suitable to show the 'next' and 'prev'
* buttons in each raised or pressed state.
*
* @param tile_style_ptr
* @param prev_pressed
* @param next_pressed
*
* @return A wlr buffer.
*/
struct wlr_buffer *_wlmaker_clip_create_tile(
const struct wlmtk_tile_style *tile_style_ptr,
bool prev_pressed,
bool next_pressed)
{
struct wlr_buffer* wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer(
tile_style_ptr->size, tile_style_ptr->size);
if (NULL == wlr_buffer_ptr) return NULL;
double tsize = tile_style_ptr->size;
double bsize = 22.0 / 64.0 * tile_style_ptr->size;
double margin = tile_style_ptr->bezel_width;
cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr);
if (NULL == cairo_ptr) {
wlr_buffer_drop(wlr_buffer_ptr);
return NULL;
}
wlmaker_primitives_cairo_fill(cairo_ptr, &tile_style_ptr->fill);
// Northern + Western sides. Drawn clock-wise.
wlmaker_primitives_set_bezel_color(cairo_ptr, true);
cairo_move_to(cairo_ptr, 0, 0);
cairo_line_to(cairo_ptr, tsize - bsize, 0);
cairo_line_to(cairo_ptr, tsize - bsize, margin);
cairo_line_to(cairo_ptr, margin, margin);
cairo_line_to(cairo_ptr, margin, tsize - bsize);
cairo_line_to(cairo_ptr, 0, tsize - bsize);
cairo_line_to(cairo_ptr, 0, 0);
cairo_fill(cairo_ptr);
// Southern + Eastern sides. Also drawn Also clock-wise.
wlmaker_primitives_set_bezel_color(cairo_ptr, false);
cairo_move_to(cairo_ptr, tsize, tsize);
cairo_line_to(cairo_ptr, bsize, tsize);
cairo_line_to(cairo_ptr, bsize, tsize - margin);
cairo_line_to(cairo_ptr, tsize - margin, tsize - margin);
cairo_line_to(cairo_ptr, tsize - margin, bsize);
cairo_line_to(cairo_ptr, tsize, bsize);
cairo_line_to(cairo_ptr, tsize, tsize);
cairo_fill(cairo_ptr);
// Diagonal at the north-eastern corner. Drawn clockwise.
wlmaker_primitives_set_bezel_color(cairo_ptr, true);
cairo_move_to(cairo_ptr, tsize - bsize, 0);
cairo_line_to(cairo_ptr, tsize, bsize);
cairo_line_to(cairo_ptr, tsize - margin, bsize);
cairo_line_to(cairo_ptr, tsize - bsize, margin);
cairo_line_to(cairo_ptr, tsize - bsize, 0);
cairo_fill(cairo_ptr);
// Diagonal at south-western corner. Drawn clockwise.
wlmaker_primitives_set_bezel_color(cairo_ptr, false);
cairo_move_to(cairo_ptr, 0, tsize - bsize);
cairo_line_to(cairo_ptr, margin, tsize - bsize);
cairo_line_to(cairo_ptr, bsize, tsize - margin);
cairo_line_to(cairo_ptr, bsize, tsize);
cairo_line_to(cairo_ptr, 0, tsize - bsize);
cairo_fill(cairo_ptr);
// The "Next" button, north-eastern corner.
// Northern edge, illuminated when raised
wlmaker_primitives_set_bezel_color(cairo_ptr, !next_pressed);
cairo_move_to(cairo_ptr, tsize - bsize, 0);
cairo_line_to(cairo_ptr, tsize, 0);
cairo_line_to(cairo_ptr, tsize - margin, margin);
cairo_line_to(cairo_ptr, tsize - bsize + 2 * margin, margin);
cairo_line_to(cairo_ptr, tsize - bsize , 0);
cairo_fill(cairo_ptr);
// Eastern edge, illuminated when pressed
wlmaker_primitives_set_bezel_color(cairo_ptr, next_pressed);
cairo_move_to(cairo_ptr, tsize, 0);
cairo_line_to(cairo_ptr, tsize, bsize);
cairo_line_to(cairo_ptr, tsize - margin, bsize - 2 * margin);
cairo_line_to(cairo_ptr, tsize - margin, margin);
cairo_line_to(cairo_ptr, tsize, 0);
cairo_fill(cairo_ptr);
// Diagonal, illuminated when pressed.
wlmaker_primitives_set_bezel_color(cairo_ptr, next_pressed);
cairo_move_to(cairo_ptr, tsize - bsize, 0);
cairo_line_to(cairo_ptr, tsize - bsize + 2 * margin, margin);
cairo_line_to(cairo_ptr, tsize - margin, bsize - 2 *margin);
cairo_line_to(cairo_ptr, tsize, bsize);
cairo_line_to(cairo_ptr, tsize - bsize, 0);
cairo_fill(cairo_ptr);
// The black triangle. Use relative sizes.
double tpad = bsize * 5.0 / 22.0;
double trsize = bsize * 7.0 / 22.0;
double tmargin = bsize * 1.0 / 22.0;
cairo_set_source_rgba(cairo_ptr, 0, 0, 0, 1.0);
cairo_move_to(cairo_ptr, tsize - tpad, tpad);
cairo_line_to(cairo_ptr, tsize - tpad, trsize + tpad);
cairo_line_to(cairo_ptr, tsize - tpad - trsize, tpad);
cairo_line_to(cairo_ptr, tsize - tpad, tpad);
cairo_fill(cairo_ptr);
// Northern edge of triangle, not illuminated.
wlmaker_primitives_set_bezel_color(cairo_ptr, false);
cairo_move_to(cairo_ptr, tsize - tpad, tpad);
cairo_line_to(cairo_ptr, tsize - tpad - trsize, tpad);
cairo_line_to(cairo_ptr, tsize - tpad - trsize - tmargin, tpad - tmargin);
cairo_line_to(cairo_ptr, tsize - tpad + tmargin, tpad - tmargin);
cairo_line_to(cairo_ptr, tsize - tpad, tpad);
cairo_fill(cairo_ptr);
// Eastern side of triangle, illuminated.
wlmaker_primitives_set_bezel_color(cairo_ptr, true);
cairo_move_to(cairo_ptr, tsize - tpad, tpad);
cairo_line_to(cairo_ptr, tsize - tpad + tmargin, tpad - tmargin);
cairo_line_to(cairo_ptr, tsize - tpad + tmargin, tpad + trsize + tmargin);
cairo_line_to(cairo_ptr, tsize - tpad, tpad + trsize);
cairo_line_to(cairo_ptr, tsize - tpad, tpad);
cairo_fill(cairo_ptr);
// The "Prev" button, south-western corner.
// Southern edge, illuminated when pressed.
wlmaker_primitives_set_bezel_color(cairo_ptr, prev_pressed);
cairo_move_to(cairo_ptr, 0, tsize);
cairo_line_to(cairo_ptr, margin, tsize - margin);
cairo_line_to(cairo_ptr, bsize - 2 * margin, tsize - margin);
cairo_line_to(cairo_ptr, bsize, tsize);
cairo_line_to(cairo_ptr, 0, tsize);
cairo_fill(cairo_ptr);
// Western edge, illuminated when raised.
wlmaker_primitives_set_bezel_color(cairo_ptr, !prev_pressed);
cairo_move_to(cairo_ptr, 0, tsize);
cairo_line_to(cairo_ptr, 0, tsize - bsize + 0);
cairo_line_to(cairo_ptr, margin, tsize - bsize + 2 * margin);
cairo_line_to(cairo_ptr, margin, tsize - margin);
cairo_line_to(cairo_ptr, 0, tsize);
cairo_fill(cairo_ptr);
// Diagonal, illuminated when raised.
wlmaker_primitives_set_bezel_color(cairo_ptr, !prev_pressed);
cairo_move_to(cairo_ptr, 0, tsize - bsize + 0);
cairo_line_to(cairo_ptr, bsize, tsize);
cairo_line_to(cairo_ptr, bsize - 2 * margin, tsize - margin);
cairo_line_to(cairo_ptr, margin, tsize - bsize + 2 * margin);
cairo_line_to(cairo_ptr, 0, tsize - bsize + 0);
cairo_fill(cairo_ptr);
// The black triangle. Use relative sizes.
cairo_set_source_rgba(cairo_ptr, 0, 0, 0, 1.0);
cairo_move_to(cairo_ptr, tpad, tsize - tpad);
cairo_line_to(cairo_ptr, tpad, tsize - trsize - tpad);
cairo_line_to(cairo_ptr, tpad + trsize, tsize - tpad);
cairo_line_to(cairo_ptr, tpad, tsize - tpad);
cairo_fill(cairo_ptr);
// Southern edge of triangle, illuminated.
wlmaker_primitives_set_bezel_color(cairo_ptr, true);
cairo_move_to(cairo_ptr, tpad, tsize - tpad);
cairo_line_to(cairo_ptr, tpad + trsize, tsize - tpad);
cairo_line_to(cairo_ptr, tpad + trsize + tmargin, tsize - tpad + tmargin);
cairo_line_to(cairo_ptr, tpad - tmargin, tsize - tpad + tmargin);
cairo_line_to(cairo_ptr, tpad, tsize - tpad);
cairo_fill(cairo_ptr);
// Eastern side of triangle, not illuminated.
wlmaker_primitives_set_bezel_color(cairo_ptr, false);
cairo_move_to(cairo_ptr, tpad, tsize - tpad);
cairo_line_to(cairo_ptr, tpad - tmargin, tsize - tpad + tmargin);
cairo_line_to(cairo_ptr, tpad - tmargin, tsize - tpad - trsize - tmargin);
cairo_line_to(cairo_ptr, tpad, tsize - tpad - trsize);
cairo_line_to(cairo_ptr, tpad, tsize - tpad);
cairo_fill(cairo_ptr);
cairo_destroy(cairo_ptr);
return wlr_buffer_ptr;
}
/* -------------------------------------------------------------------------- */
/**
* Handler for the `workspace_changed` signal of `wlmaker_server_t`.
*
* Will redraw the clip contents with the current workspace, and re-map the
* clip to the new workspace.
*
* @param listener_ptr
* @param data_ptr Points to the new `wlmtk_workspace_t`.
*/
void _wlmaker_clip_handle_workspace_changed(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_clip_t *clip_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_clip_t, workspace_changed_listener);
wlmtk_panel_t *panel_ptr = wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr);
wlmtk_layer_t *current_layer_ptr = wlmtk_panel_get_layer(panel_ptr);
wlmtk_workspace_t *workspace_ptr =
wlmtk_root_get_current_workspace(clip_ptr->server_ptr->root_ptr);
wlmtk_layer_t *new_layer_ptr = wlmtk_workspace_get_layer(
workspace_ptr, WLMTK_WORKSPACE_LAYER_TOP);
if (current_layer_ptr == new_layer_ptr) return;
if (NULL != current_layer_ptr) {
wlmtk_layer_remove_panel(current_layer_ptr, panel_ptr);
}
BS_ASSERT(wlmtk_layer_add_panel(
new_layer_ptr,
panel_ptr,
wlmbe_primary_output(
clip_ptr->server_ptr->wlr_output_layout_ptr)));
_wlmaker_clip_update_overlay(
clip_ptr, &clip_ptr->super_tile.style, &clip_ptr->style);
}
/* ------------------------------------------------------------------------- */
/** Handles when output layout changes; Re-computes the output to attach. */
void _wlmaker_clip_handle_output_layout_change(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_clip_t *clip_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_clip_t, output_layout_change_listener);
struct wlr_output *wlr_output_ptr = wlmbe_output_description_first_fnmatch(
&clip_ptr->output_description, clip_ptr->server_ptr->wlr_output_layout_ptr);
if (NULL == wlr_output_ptr) {
wlr_output_ptr = wlmbe_primary_output(
clip_ptr->server_ptr->wlr_output_layout_ptr);
}
wlmtk_layer_t *layer_ptr = wlmtk_panel_get_layer(
wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr));
wlmtk_layer_remove_panel(layer_ptr, wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr));
if (NULL != wlr_output_ptr) {
BS_ASSERT(wlmtk_layer_add_panel(
layer_ptr,
wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr),
wlr_output_ptr));
}
}
/* ------------------------------------------------------------------------- */
/** Handles @ref wlmtk_element_events_t::pointer_leave. Resets buttons. */
void _wlmaker_clip_handle_pointer_leave(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmaker_clip_t *clip_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_clip_t, pointer_leave_listener);
clip_ptr->pointer_inside_prev_button = false;
clip_ptr->pointer_inside_next_button = false;
_wlmaker_clip_apply_button_state(clip_ptr);
}
/* ------------------------------------------------------------------------- */
/** Handles @ref wlmtk_element_events_t::pointer_motion. */
void _wlmaker_clip_handle_pointer_motion(
struct wl_listener *listener_ptr,
void *data_ptr)
{
wlmaker_clip_t *clip_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_clip_t, pointer_motion_listener);
wlmtk_pointer_motion_event_t *motion_event_ptr = data_ptr;
clip_ptr->pointer_inside_prev_button = false;
clip_ptr->pointer_inside_next_button = false;
double tile_size = clip_ptr->super_tile.style.size;
double button_size = (22.0 / 64.0) * tile_size;
if (motion_event_ptr->x >= tile_size - button_size &&
motion_event_ptr->x < tile_size &&
motion_event_ptr->y >= 0 &&
motion_event_ptr->y < button_size) {
// Next button.
clip_ptr->pointer_inside_next_button = true;
} else if (motion_event_ptr->x >= 0 &&
motion_event_ptr->x < button_size &&
motion_event_ptr->y >= tile_size - button_size &&
motion_event_ptr->y < tile_size) {
// Prev button.
clip_ptr->pointer_inside_prev_button = true;
}
_wlmaker_clip_apply_button_state(clip_ptr);
}
/* ------------------------------------------------------------------------- */
/** Event listener: The theme changed. Applies the new style to the clip. */
void _wlmaker_clip_handle_theme_changed(
struct wl_listener *listener_ptr,
void *data_ptr)
{
wlmaker_clip_t *clip_ptr = BS_CONTAINER_OF(
listener_ptr, wlmaker_clip_t, theme_changed_listener);
wlmaker_config_style_t *style_ptr = data_ptr;
if (!_wlmaker_clip_update_tiles(clip_ptr, &style_ptr->tile)) return;
if (!_wlmaker_clip_update_overlay(
clip_ptr, &style_ptr->tile, &style_ptr->clip)) return;
if (!_wlmaker_clip_update_image(clip_ptr, &style_ptr->tile)) return;
clip_ptr->style = style_ptr->clip;
wlmtk_dock_set_style(
clip_ptr->wlmtk_dock_ptr,
&style_ptr->dock,
&style_ptr->tile);
// Need to apply the button state (again) after @ref wlmtk_dock_set_style,
// since the latter will apply the generic tile backend. Which the tile
// overwrites.
_wlmaker_clip_apply_button_state(clip_ptr);
}
/* == Unit tests =========================================================== */
static void test_draw_tile(bs_test_t *test_ptr);
/** Test cases. */
static const bs_test_case_t wlmaker_clip_test_cases[] = {
{ true, "draw_tile", test_draw_tile },
BS_TEST_CASE_SENTINEL()
};
const bs_test_set_t wlmaker_clip_test_set = BS_TEST_SET(
true, "clip", wlmaker_clip_test_cases);
/* ------------------------------------------------------------------------- */
/** Tests that the clip tile is drawn correctly. */
void test_draw_tile(bs_test_t *test_ptr)
{
static const struct wlmtk_tile_style style = {
.fill = {
.type = WLMTK_STYLE_COLOR_DGRADIENT,
.param = { .dgradient = { .from = 0xffa6a6b6, .to = 0xff515561 } }
},
.bezel_width = 2,
.size = 64
};
struct wlr_buffer* wlr_buffer_ptr;
bs_gfxbuf_t *gfxbuf_ptr;
wlr_buffer_ptr = _wlmaker_clip_create_tile(&style, false, false);
BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlr_buffer_ptr);
gfxbuf_ptr = bs_gfxbuf_from_wlr_buffer(wlr_buffer_ptr);
BS_TEST_VERIFY_GFXBUF_EQUALS_PNG(
test_ptr, gfxbuf_ptr, "clip_raised.png");
wlr_buffer_drop(wlr_buffer_ptr);
wlr_buffer_ptr = _wlmaker_clip_create_tile(&style, true, true);
BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlr_buffer_ptr);
gfxbuf_ptr = bs_gfxbuf_from_wlr_buffer(wlr_buffer_ptr);
BS_TEST_VERIFY_GFXBUF_EQUALS_PNG(
test_ptr, gfxbuf_ptr, "clip_pressed.png");
wlr_buffer_drop(wlr_buffer_ptr);
}
/* == End of clip.c ======================================================== */
wlmaker-0.8.1/src/backend/ 0000755 0001751 0001751 00000000000 15222270710 014744 5 ustar runner runner wlmaker-0.8.1/src/backend/output_manager.c 0000644 0001751 0001751 00000047446 15222270710 020161 0 ustar runner runner /* ========================================================================= */
/**
* @file output_manager.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "output_manager.h"
#include
#include
#include
#include
#include
#define WLR_USE_UNSTABLE
#include
#include
#include
#include
#include
#include
#undef WLR_USE_UNSTABLE
#include "output.h"
struct wl_list;
/* == Declarations ========================================================= */
/** Implementation of the wlr output manager. */
struct _wlmbe_output_manager_t {
/** Points to wlroots `struct wlr_output_manager_v1`. */
struct wlr_output_manager_v1 *wlr_output_manager_v1_ptr;
/** Listener for wlr_output_manager_v1::events::destroy. */
struct wl_listener wlr_om_destroy_listener;
/** Listener for wlr_output_manager_v1::events::apply. */
struct wl_listener apply_listener;
/** Listener for wlr_output_manager_v1::events::test. */
struct wl_listener test_listener;
/** Points to wlroots 'struct wlr_xdg_output_manager_v1`. */
struct wlr_xdg_output_manager_v1 *wlr_xdg_output_manager_v1_ptr;
/** Listener for wlr_xdg_output_manager_v1::events::destroy. */
struct wl_listener xdg_om_destroy_listener;
/** Listener for wlr_output_layout::events::destroy. */
struct wl_listener output_layout_destroy_listener;
/** Listener for wlr_output_layout::events::change. */
struct wl_listener output_layout_change_listener;
// Below: Not owned by @ref wlmbe_output_manager_t.
/** The scene. */
struct wlr_scene *wlr_scene_ptr;
/** Points to struct wlr_output_layout. */
struct wlr_output_layout *wlr_output_layout_ptr;
/** Points to struct wlr_backend. */
struct wlr_backend *wlr_backend_ptr;
};
/** Argument to @ref _wlmaker_output_manager_config_head_apply. */
typedef struct {
/** Points to struct wlr_output_layout. */
struct wlr_output_layout *wlr_output_layout_ptr;
/** Whether to test only, or to apply "really". */
bool really;
} _wlmaker_output_manager_config_head_apply_arg_t;
static bool _wlmbe_output_manager_update_output_configuration(
struct wl_list *link_ptr,
void *ud_ptr);
static bool _wlmaker_output_manager_config_head_scale(
struct wl_list *link_ptr,
void *ud_ptr);
static bool _wlmaker_output_manager_config_head_apply(
struct wl_list *link_ptr,
void *ud_ptr);
static bool _wlmbe_output_manager_apply(
wlmbe_output_manager_t *output_manager_ptr,
struct wlr_output_configuration_v1 *wlr_output_configuration_v1_ptr,
bool really);
static void _wlmbe_output_manager_handle_wlr_om_destroy(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmbe_output_manager_handle_apply(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmbe_output_manager_handle_test(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmbe_output_manager_handle_xdg_om_destroy(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmbe_output_manager_handle_output_layout_destroy(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmbe_output_manager_handle_output_layout_change(
struct wl_listener *listener_ptr,
void *data_ptr);
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
wlmbe_output_manager_t *wlmbe_output_manager_create(
struct wl_display *wl_display_ptr,
struct wlr_scene *wlr_scene_ptr,
struct wlr_output_layout *wlr_output_layout_ptr,
struct wlr_backend *wlr_backend_ptr)
{
wlmbe_output_manager_t *output_manager_ptr = logged_calloc(
1, sizeof(wlmbe_output_manager_t));
if (NULL == output_manager_ptr) return NULL;
output_manager_ptr->wlr_backend_ptr = wlr_backend_ptr;
output_manager_ptr->wlr_scene_ptr = wlr_scene_ptr;
output_manager_ptr->wlr_output_layout_ptr = wlr_output_layout_ptr;
output_manager_ptr->wlr_output_manager_v1_ptr =
wlr_output_manager_v1_create(wl_display_ptr);
if (NULL == output_manager_ptr->wlr_output_manager_v1_ptr) {
wlmbe_output_manager_destroy(output_manager_ptr);
return NULL;
}
wlmtk_util_connect_listener_signal(
&output_manager_ptr->wlr_output_manager_v1_ptr->events.destroy,
&output_manager_ptr->wlr_om_destroy_listener,
_wlmbe_output_manager_handle_wlr_om_destroy);
wlmtk_util_connect_listener_signal(
&output_manager_ptr->wlr_output_manager_v1_ptr->events.apply,
&output_manager_ptr->apply_listener,
_wlmbe_output_manager_handle_apply);
wlmtk_util_connect_listener_signal(
&output_manager_ptr->wlr_output_manager_v1_ptr->events.test,
&output_manager_ptr->test_listener,
_wlmbe_output_manager_handle_test);
wlmtk_util_connect_listener_signal(
&wlr_output_layout_ptr->events.destroy,
&output_manager_ptr->output_layout_destroy_listener,
_wlmbe_output_manager_handle_output_layout_destroy);
wlmtk_util_connect_listener_signal(
&wlr_output_layout_ptr->events.change,
&output_manager_ptr->output_layout_change_listener,
_wlmbe_output_manager_handle_output_layout_change);
output_manager_ptr->wlr_xdg_output_manager_v1_ptr =
wlr_xdg_output_manager_v1_create(
wl_display_ptr,
wlr_output_layout_ptr);
if (NULL == output_manager_ptr->wlr_xdg_output_manager_v1_ptr) {
wlmbe_output_manager_destroy(output_manager_ptr);
return NULL;
}
wlmtk_util_connect_listener_signal(
&output_manager_ptr->wlr_xdg_output_manager_v1_ptr->events.destroy,
&output_manager_ptr->xdg_om_destroy_listener,
_wlmbe_output_manager_handle_xdg_om_destroy);
// Initializes the output manager from current output layout.
_wlmbe_output_manager_handle_output_layout_change(
&output_manager_ptr->output_layout_change_listener,
wlr_output_layout_ptr);
return output_manager_ptr;
}
/* ------------------------------------------------------------------------- */
void wlmbe_output_manager_destroy(
wlmbe_output_manager_t *output_manager_ptr)
{
_wlmbe_output_manager_handle_output_layout_destroy(
&output_manager_ptr->output_layout_destroy_listener, NULL);
_wlmbe_output_manager_handle_wlr_om_destroy(
&output_manager_ptr->wlr_om_destroy_listener, NULL);
_wlmbe_output_manager_handle_xdg_om_destroy(
&output_manager_ptr->xdg_om_destroy_listener, NULL);
free(output_manager_ptr);
}
/* ------------------------------------------------------------------------- */
void wlmbe_output_manager_scale(
wlmbe_output_manager_t *output_manager_ptr,
double scale)
{
struct wlr_output_configuration_v1 *wlr_output_configuration_v1_ptr =
wlr_output_configuration_v1_create();
if (NULL == wlr_output_configuration_v1_ptr) {
bs_log(BS_ERROR, "Failed wlr_output_configuration_v1_create()");
return;
}
// First: retrieve each head's status, then scale them. Then, apply.
if (wlmtk_util_wl_list_for_each(
&output_manager_ptr->wlr_output_layout_ptr->outputs,
_wlmbe_output_manager_update_output_configuration,
wlr_output_configuration_v1_ptr)) {
wlmtk_util_wl_list_for_each(
&wlr_output_configuration_v1_ptr->heads,
_wlmaker_output_manager_config_head_scale,
&scale);
_wlmbe_output_manager_apply(
output_manager_ptr,
wlr_output_configuration_v1_ptr,
true);
}
wlr_output_configuration_v1_destroy(wlr_output_configuration_v1_ptr);
// Not to forget: Propagate to potential layout manager clients.
_wlmbe_output_manager_handle_output_layout_change(
&output_manager_ptr->output_layout_change_listener, NULL);
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/**
* Iterator for the `struct wlr_output_layout_output` referenced from
* `struct wlr_output_layout::outputs`.
*
* Adds the configuration head for the given output to the provided output
* configuration.
*
* @param link_ptr struct wlr_output_layout_output::link.
* @param ud_ptr The output configuration, a pointer to
* `struct wlr_output_configuration_v1`.
*
* @return true on success.
*/
bool _wlmbe_output_manager_update_output_configuration(
struct wl_list *link_ptr,
void *ud_ptr)
{
struct wlr_output_layout_output *wlr_output_layout_output_ptr =
BS_CONTAINER_OF(link_ptr, struct wlr_output_layout_output, link);
struct wlr_output_configuration_v1 *wlr_output_configuration_v1_ptr =
ud_ptr;
struct wlr_output_configuration_head_v1 *head_v1_ptr =
wlr_output_configuration_head_v1_create(
wlr_output_configuration_v1_ptr,
wlr_output_layout_output_ptr->output);
if (NULL == head_v1_ptr) {
bs_log(BS_ERROR,
"Failed wlr_output_configuration_head_v1_create(%p, %p)",
wlr_output_configuration_v1_ptr,
wlr_output_layout_output_ptr->output);
return false;
}
struct wlr_box box;
wlr_output_layout_get_box(
wlr_output_layout_output_ptr->layout,
wlr_output_layout_output_ptr->output,
&box);
head_v1_ptr->state.x = box.x;
head_v1_ptr->state.y = box.y;
return true;
}
/* ------------------------------------------------------------------------- */
/**
* Applies the scale factor at `ud_ptr` to the head.
*
* Callback for @ref wlmtk_util_wl_list_for_each.
*
* @param link_ptr
* @param ud_ptr
*
* @return true.
*/
bool _wlmaker_output_manager_config_head_scale(
struct wl_list *link_ptr,
void *ud_ptr)
{
struct wlr_output_configuration_head_v1 *head_v1_ptr = BS_CONTAINER_OF(
link_ptr, struct wlr_output_configuration_head_v1, link);
head_v1_ptr->state.scale =
BS_MAX(1.0, head_v1_ptr->state.scale * *(double*)ud_ptr);
return true;
}
/* ------------------------------------------------------------------------- */
/**
* Applies the heads's output configuration.
*
* Callback for @ref wlmtk_util_wl_list_for_each.
*
* @param link_ptr
* @param ud_ptr
*
* @return true if the tests & apply methods succeeded.
*/
static bool _wlmaker_output_manager_config_head_apply(
struct wl_list *link_ptr,
void *ud_ptr)
{
struct wlr_output_configuration_head_v1 *head_v1_ptr = BS_CONTAINER_OF(
link_ptr, struct wlr_output_configuration_head_v1, link);
struct wlr_output_state state = {};
_wlmaker_output_manager_config_head_apply_arg_t *arg_ptr = ud_ptr;
// Convenience pointers. Guard against accidental misses.
struct wlr_output *wlr_output_ptr = head_v1_ptr->state.output;
if (NULL == wlr_output_ptr) {
bs_log(BS_ERROR, "Unexpected NULL output in head %p", head_v1_ptr);
return false;
}
wlr_output_head_v1_state_apply(&head_v1_ptr->state, &state);
state.scale = BS_MAX(1.0, state.scale);
if (!wlr_output_test_state(wlr_output_ptr, &state)) return false;
if (!arg_ptr->really) return true;
if (!wlr_output_commit_state(wlr_output_ptr, &state)) return false;
int x = head_v1_ptr->state.x, y = head_v1_ptr->state.y;
struct wlr_output_layout *wlr_output_layout_ptr =
arg_ptr->wlr_output_layout_ptr;
if (head_v1_ptr->state.enabled &&
!wlr_output_layout_add(wlr_output_layout_ptr, wlr_output_ptr, x, y)) {
bs_log(BS_ERROR, "Failed wlr_output_layout_add(%p, %p, %d, %d)",
wlr_output_layout_ptr, wlr_output_ptr, x, y);
return false;
} else if (!head_v1_ptr->state.enabled) {
wlr_output_layout_remove(wlr_output_layout_ptr, wlr_output_ptr);
}
bool has_position = false;
struct wlr_output_layout_output *wlr_output_layout_output_ptr =
wlr_output_layout_get(
arg_ptr->wlr_output_layout_ptr,
wlr_output_ptr);
if (NULL != wlr_output_layout_output_ptr) {
has_position = !wlr_output_layout_output_ptr->auto_configured;
}
wlmbe_output_update_attributes(wlr_output_ptr->data, x, y, has_position);
bs_log(BS_INFO,
"Applied: Output <%s> %s to %dx%d@%.2f x%.2f position (%d,%d) %s",
wlmbe_output_description(wlr_output_ptr->data),
wlr_output_ptr->enabled ? "enabled" : "disabled",
wlr_output_ptr->width, wlr_output_ptr->height,
1e-3 * wlr_output_ptr->refresh,
wlr_output_ptr->scale,
x, y,
has_position ? "explicit" : "auto");
return true;
}
/* ------------------------------------------------------------------------- */
/**
* Tests and applies an output configuration.
*
* @param output_manager_ptr
* @param wlr_output_configuration_v1_ptr
* @param really Whether to not just test, but also apply it.
*
* @return true on success.
*/
bool _wlmbe_output_manager_apply(
wlmbe_output_manager_t *output_manager_ptr,
struct wlr_output_configuration_v1 *wlr_output_configuration_v1_ptr,
bool really)
{
_wlmaker_output_manager_config_head_apply_arg_t arg = {
.wlr_output_layout_ptr = output_manager_ptr->wlr_output_layout_ptr,
.really = really
};
if (!wlmtk_util_wl_list_for_each(
&wlr_output_configuration_v1_ptr->heads,
_wlmaker_output_manager_config_head_apply,
&arg)) {
return false;
}
size_t states_len;
struct wlr_backend_output_state *wlr_backend_output_state_ptr =
wlr_output_configuration_v1_build_state(
wlr_output_configuration_v1_ptr, &states_len);
if (NULL == wlr_backend_output_state_ptr) {
bs_log(BS_ERROR,
"Failed wlr_output_configuration_v1_build_state(%p, %p)",
wlr_output_configuration_v1_ptr, &states_len);
return false;
}
bool rv = wlr_backend_test(
output_manager_ptr->wlr_backend_ptr,
wlr_backend_output_state_ptr,
states_len);
if (rv && really) {
rv = wlr_backend_commit(
output_manager_ptr->wlr_backend_ptr,
wlr_backend_output_state_ptr,
states_len);
}
free(wlr_backend_output_state_ptr);
return rv;
}
/* ------------------------------------------------------------------------- */
/** Handler for wlr_output_manager_v1::events.destroy. Detaches. */
void _wlmbe_output_manager_handle_wlr_om_destroy(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmbe_output_manager_t *output_manager_ptr = BS_CONTAINER_OF(
listener_ptr, wlmbe_output_manager_t, wlr_om_destroy_listener);
if (NULL == output_manager_ptr->wlr_output_manager_v1_ptr) return;
wlmtk_util_disconnect_listener(
&output_manager_ptr->test_listener);
wlmtk_util_disconnect_listener(
&output_manager_ptr->apply_listener);
wlmtk_util_disconnect_listener(
&output_manager_ptr->wlr_om_destroy_listener);
output_manager_ptr->wlr_output_manager_v1_ptr = NULL;
}
/* ------------------------------------------------------------------------- */
/** Handler for wlr_output_manager_v1::events.apply. Cleans up. */
void _wlmbe_output_manager_handle_apply(
struct wl_listener *listener_ptr,
void *data_ptr)
{
wlmbe_output_manager_t *om_ptr = BS_CONTAINER_OF(
listener_ptr, wlmbe_output_manager_t, apply_listener);
struct wlr_output_configuration_v1 *wlr_output_config_ptr = data_ptr;
if (_wlmbe_output_manager_apply(om_ptr, wlr_output_config_ptr, true)) {
wlr_output_configuration_v1_send_succeeded(wlr_output_config_ptr);
} else {
wlr_output_configuration_v1_send_failed(wlr_output_config_ptr);
}
}
/* ------------------------------------------------------------------------- */
/** Handler for wlr_output_manager_v1::events.test. */
void _wlmbe_output_manager_handle_test(
struct wl_listener *listener_ptr,
void *data_ptr)
{
wlmbe_output_manager_t *om_ptr = BS_CONTAINER_OF(
listener_ptr, wlmbe_output_manager_t, test_listener);
struct wlr_output_configuration_v1 *wlr_output_config_ptr = data_ptr;
if (_wlmbe_output_manager_apply(om_ptr, wlr_output_config_ptr, false)) {
wlr_output_configuration_v1_send_succeeded(wlr_output_config_ptr);
} else {
wlr_output_configuration_v1_send_failed(wlr_output_config_ptr);
}
}
/* ------------------------------------------------------------------------- */
/** Handler for wlr_xdg_output_manager_v1::events.destroy. Detaches. */
void _wlmbe_output_manager_handle_xdg_om_destroy(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmbe_output_manager_t *output_manager_ptr = BS_CONTAINER_OF(
listener_ptr, wlmbe_output_manager_t, xdg_om_destroy_listener);
if (NULL == output_manager_ptr->wlr_xdg_output_manager_v1_ptr) return;
wlmtk_util_disconnect_listener(
&output_manager_ptr->xdg_om_destroy_listener);
output_manager_ptr->wlr_xdg_output_manager_v1_ptr = NULL;
}
/* ------------------------------------------------------------------------- */
/** Handles dtor for @ref wlmbe_output_manager_t::wlr_output_layout_ptr. */
void _wlmbe_output_manager_handle_output_layout_destroy(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmbe_output_manager_t *output_manager_ptr = BS_CONTAINER_OF(
listener_ptr,
wlmbe_output_manager_t,
output_layout_destroy_listener);
if (NULL == output_manager_ptr->wlr_output_layout_ptr) return;
wlmtk_util_disconnect_listener(
&output_manager_ptr->output_layout_change_listener);
wlmtk_util_disconnect_listener(
&output_manager_ptr->output_layout_destroy_listener);
output_manager_ptr->wlr_output_layout_ptr = NULL;
}
/* ------------------------------------------------------------------------- */
/** Handles layout change events. */
void _wlmbe_output_manager_handle_output_layout_change(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmbe_output_manager_t *output_manager_ptr = BS_CONTAINER_OF(
listener_ptr, wlmbe_output_manager_t, output_layout_change_listener);
// Guard clause.
if (NULL == output_manager_ptr->wlr_output_manager_v1_ptr) return;
struct wlr_output_configuration_v1 *wlr_output_configuration_v1_ptr =
wlr_output_configuration_v1_create();
if (NULL == wlr_output_configuration_v1_ptr) {
bs_log(BS_ERROR, "Failed wlr_output_configuration_v1_create().");
return;
}
if (wlmtk_util_wl_list_for_each(
&output_manager_ptr->wlr_output_layout_ptr->outputs,
_wlmbe_output_manager_update_output_configuration,
wlr_output_configuration_v1_ptr)) {
wlr_output_manager_v1_set_configuration(
output_manager_ptr->wlr_output_manager_v1_ptr,
wlr_output_configuration_v1_ptr);
return;
}
wlr_output_configuration_v1_destroy(wlr_output_configuration_v1_ptr);
}
/* == End of output_manager.c ============================================== */
wlmaker-0.8.1/src/backend/backend.c 0000644 0001751 0001751 00000067224 15222270710 016512 0 ustar runner runner /* ========================================================================= */
/**
* @file backend.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "backend.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define WLR_USE_UNSTABLE
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#undef WLR_USE_UNSTABLE
#include "output.h"
#include "output_config.h"
#include "output_manager.h"
/* == Declarations ========================================================= */
/** State of the server's backend. */
struct _wlmbe_backend_t {
/** wlroots backend. */
struct wlr_backend *wlr_backend_ptr;
/** wlroots session. Populated from wlr_backend_autocreate(). */
struct wlr_session *wlr_session_ptr;
/** wlroots renderer. */
struct wlr_renderer *wlr_renderer_ptr;
/** The allocator. */
struct wlr_allocator *wlr_allocator_ptr;
/** The scene output layout. */
struct wlr_scene_output_layout *wlr_scene_output_layout_ptr;
/** The compositor is necessary for clients to allocate surfaces. */
struct wlr_compositor *wlr_compositor_ptr;
/**
* The subcompositor allows to assign the role of subsurfaces to
* surfaces.
*/
struct wlr_subcompositor *wlr_subcompositor_ptr;
/** The screencopy manager. */
struct wlr_screencopy_manager_v1 *wlr_screencopy_manager_v1_ptr;
/** The output manager(s). */
wlmbe_output_manager_t *output_manager_ptr;
/** Listener for wlr_backend::events::new_input. */
struct wl_listener new_output_listener;
/** Desired output width, for windowed mode. 0 for no preference. */
uint32_t width;
/** Desired output height, for windowed mode. 0 for no preference. */
uint32_t height;
/**
* A list of @ref wlmbe_output_config_t items, configured through
* wlmaker's configuration file.
*
* Discovered outputs are attempted to matched through
* @ref wlmbe_output_config_fnmatches for configured attributes.
*/
bs_dllist_t output_configs;
/**
* Another list of @ref wlmbe_output_config_t items. This is
* initialized from wlmaker's state file upon startup, ie. in
* @ref wlmbe_backend_create.
*
* If a discovered output equals to one of the nodes here, it's attributes
* will be taken from here. Otherwise, a new entry is created in this list.
* The intent is to memorize state of connected configs, so that
* re-connected outputs are using the same attributes they left with.
*
* These configurations can be saved by calling
* @ref wlmbe_backend_save_ephemeral_output_configs, otherwise any updates
* will be lost on restart.
*/
bs_dllist_t ephemeral_output_configs;
/** List of outputs. Connects @ref wlmbe_output_t::dlnode. */
bs_dllist_t outputs;
// Elements below not owned by @ref wlmbe_backend_t.
/** Back-link to the wlroots scene. */
struct wlr_scene *wlr_scene_ptr;
/** Points to struct wlr_output_layout. */
struct wlr_output_layout *wlr_output_layout_ptr;
/** Name of the file for storing the output's state. */
char *state_fname_ptr;
};
static void _wlmbe_backend_handle_new_output(
struct wl_listener *listener_ptr,
void *data_ptr);
static bool _wlmbe_backend_decode_item(
bspl_object_t *obj_ptr,
size_t i,
void *dest_ptr);
static bspl_object_t *_wlmbe_backend_encode_all(
const union bspl_desc_value *desc_value_ptr,
const void *value_ptr);
static void _wlmbe_backend_decode_fini(void *dest_ptr);
static void _wlmbe_backend_config_dlnode_destroy(
bs_dllist_node_t *dlnode_ptr,
void *ud_ptr);
/* == Data ================================================================= */
/** Descriptor for the output configuration. */
static const bspl_desc_t _wlmbe_output_configs_desc[] = {
BSPL_DESC_ARRAY("Outputs", true, wlmbe_backend_t,
output_configs,
output_configs,
_wlmbe_backend_decode_item,
_wlmbe_backend_encode_all,
NULL, // init.
_wlmbe_backend_decode_fini),
BSPL_DESC_SENTINEL(),
};
/** Descriptor for ephemeral output onfigurations, stored as plist. */
static const bspl_desc_t _wlmbe_outputs_state_desc[] = {
BSPL_DESC_ARRAY("Outputs", true, wlmbe_backend_t,
ephemeral_output_configs,
ephemeral_output_configs,
_wlmbe_backend_decode_item,
_wlmbe_backend_encode_all,
NULL,
_wlmbe_backend_decode_fini),
BSPL_DESC_SENTINEL(),
};
/** Magnification factor: 3rd square root of 2.0. ~10, doubles exactly. */
static const double _wlmbke_backend_magnification = 1.0905077326652577;
/** The window title to set, when on a Wayland backend. */
static const char *_wlmbe_wl_title = "wlmaker";
/** The application ID to set, when on a Wayland backend. */
static const char *_wlmbe_wl_app_id = "wlmaker";
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
wlmbe_backend_t *wlmbe_backend_create(
struct wl_display *wl_display_ptr,
struct wlr_scene *wlr_scene_ptr,
struct wlr_output_layout *wlr_output_layout_ptr,
int width,
int height,
bspl_dict_t *config_dict_ptr,
const char *state_fname_ptr)
{
wlmbe_backend_t *backend_ptr = logged_calloc(1, sizeof(wlmbe_backend_t));
if (NULL == backend_ptr) return NULL;
backend_ptr->wlr_scene_ptr = wlr_scene_ptr;
backend_ptr->wlr_output_layout_ptr = wlr_output_layout_ptr;
backend_ptr->width = width;
backend_ptr->height = height;
if (!bspl_decode_dict(
config_dict_ptr,
_wlmbe_output_configs_desc,
backend_ptr)) {
wlmbe_backend_destroy(backend_ptr);
return NULL;
}
// Loads state (if available).
backend_ptr->state_fname_ptr = logged_strdup(state_fname_ptr);
if (NULL == backend_ptr->state_fname_ptr) {
wlmbe_backend_destroy(backend_ptr);
return NULL;
}
if (bs_file_realpath_is(backend_ptr->state_fname_ptr, S_IFREG)) {
bspl_object_t *o = bspl_create_object_from_plist_file(
backend_ptr->state_fname_ptr);
if (NULL == o) {
wlmbe_backend_destroy(backend_ptr);
return NULL;
}
bool rv = bspl_decode_dict(
bspl_dict_from_object(o),
_wlmbe_outputs_state_desc,
backend_ptr);
bspl_object_unref(o);
if (!rv) {
wlmbe_backend_destroy(backend_ptr);
return NULL;
}
bs_log(BS_INFO, "Loaded output state from \"%s\"", state_fname_ptr);
}
// Auto-create the wlroots backend. Can be X11 or direct.
backend_ptr->wlr_backend_ptr = wlr_backend_autocreate(
wl_display_get_event_loop(wl_display_ptr),
&backend_ptr->wlr_session_ptr);
if (NULL == backend_ptr->wlr_backend_ptr) {
bs_log(BS_ERROR, "Failed wlr_backend_autocreate()");
wlmbe_backend_destroy(backend_ptr);
return NULL;
}
// Auto-create a renderer. Can be specified using WLR_RENDERER env var.
backend_ptr->wlr_renderer_ptr = wlr_renderer_autocreate(
backend_ptr->wlr_backend_ptr);
if (NULL == backend_ptr->wlr_renderer_ptr) {
bs_log(BS_ERROR, "Failed wlr_renderer_autocreate()");
wlmbe_backend_destroy(backend_ptr);
return NULL;
}
if (!wlr_renderer_init_wl_display(
backend_ptr->wlr_renderer_ptr, wl_display_ptr)) {
bs_log(BS_ERROR, "Failed wlr_render_init_wl_display()");
wlmbe_backend_destroy(backend_ptr);
return NULL;
}
backend_ptr->wlr_allocator_ptr = wlr_allocator_autocreate(
backend_ptr->wlr_backend_ptr,
backend_ptr->wlr_renderer_ptr);
if (NULL == backend_ptr->wlr_allocator_ptr) {
bs_log(BS_ERROR, "Failed wlr_allocator_autocreate()");
wlmbe_backend_destroy(backend_ptr);
return NULL;
}
backend_ptr->wlr_scene_output_layout_ptr =
wlr_scene_attach_output_layout(
wlr_scene_ptr,
backend_ptr->wlr_output_layout_ptr);
if (NULL == backend_ptr->wlr_scene_output_layout_ptr) {
bs_log(BS_ERROR, "Failed wlr_scene_attach_output_layout()");
wlmbe_backend_destroy(backend_ptr);
return NULL;
}
backend_ptr->wlr_compositor_ptr = wlr_compositor_create(
wl_display_ptr, 5, backend_ptr->wlr_renderer_ptr);
if (NULL == backend_ptr->wlr_compositor_ptr) {
bs_log(BS_ERROR, "Failed wlr_compositor_create()");
wlmbe_backend_destroy(backend_ptr);
return NULL;
}
backend_ptr->wlr_subcompositor_ptr = wlr_subcompositor_create(
wl_display_ptr);
if (NULL == backend_ptr->wlr_subcompositor_ptr) {
bs_log(BS_ERROR, "Failed wlr_subcompositor_create()");
wlmbe_backend_destroy(backend_ptr);
return NULL;
}
backend_ptr->wlr_screencopy_manager_v1_ptr =
wlr_screencopy_manager_v1_create(wl_display_ptr);
if (NULL == backend_ptr->wlr_screencopy_manager_v1_ptr) {
bs_log(BS_ERROR, "Failed wlr_screencopy_manager_v1_create()");
wlmbe_backend_destroy(backend_ptr);
return NULL;
}
backend_ptr->output_manager_ptr = wlmbe_output_manager_create(
wl_display_ptr,
wlr_scene_ptr,
wlr_output_layout_ptr,
backend_ptr->wlr_backend_ptr);
if (NULL == backend_ptr->output_manager_ptr) {
wlmbe_backend_destroy(backend_ptr);
return NULL;
}
wlmtk_util_connect_listener_signal(
&backend_ptr->wlr_backend_ptr->events.new_output,
&backend_ptr->new_output_listener,
_wlmbe_backend_handle_new_output);
return backend_ptr;
}
/* ------------------------------------------------------------------------- */
void wlmbe_backend_destroy(wlmbe_backend_t *backend_ptr)
{
wlmtk_util_disconnect_listener(&backend_ptr->new_output_listener);
if (NULL != backend_ptr->output_manager_ptr) {
wlmbe_output_manager_destroy(backend_ptr->output_manager_ptr);
backend_ptr->output_manager_ptr = NULL;
}
if (NULL != backend_ptr->wlr_renderer_ptr) {
wlr_renderer_destroy(backend_ptr->wlr_renderer_ptr);
backend_ptr->wlr_renderer_ptr = NULL;
}
if (NULL != backend_ptr->wlr_allocator_ptr) {
wlr_allocator_destroy(backend_ptr->wlr_allocator_ptr);
backend_ptr->wlr_allocator_ptr = NULL;
}
bspl_decoded_destroy(_wlmbe_outputs_state_desc, backend_ptr);
bspl_decoded_destroy(_wlmbe_output_configs_desc, backend_ptr);
// @ref wlmbe_backend_t::wlr_backend_ptr is destroyed from wl_display.
if (NULL != backend_ptr->state_fname_ptr) {
free(backend_ptr->state_fname_ptr);
backend_ptr->state_fname_ptr = NULL;
}
free(backend_ptr);
}
/* ------------------------------------------------------------------------- */
void wlmbe_backend_switch_to_vt(wlmbe_backend_t *backend_ptr, unsigned vt_num)
{
// Guard clause: @ref wlmbe_backend_t::session_ptr will be populated only
// if wlroots created a session, eg. when running from the terminal.
if (NULL == backend_ptr->wlr_session_ptr) {
bs_log(BS_DEBUG, "wlmbe_backend_switch_to_vt: No session, ignored.");
return;
}
if (!wlr_session_change_vt(backend_ptr->wlr_session_ptr, vt_num)) {
bs_log(BS_WARNING, "Failed wlr_session_change_vt(, %u)", vt_num);
}
}
/* ------------------------------------------------------------------------- */
struct wlr_backend *wlmbe_backend_wlr(wlmbe_backend_t *backend_ptr)
{
return backend_ptr->wlr_backend_ptr;
}
/* ------------------------------------------------------------------------- */
struct wlr_compositor *wlmbe_backend_compositor(wlmbe_backend_t *backend_ptr)
{
return backend_ptr->wlr_compositor_ptr;
}
/* ------------------------------------------------------------------------- */
struct wlr_output *wlmbe_primary_output(
struct wlr_output_layout *wlr_output_layout_ptr)
{
if (0 >= wl_list_length(&wlr_output_layout_ptr->outputs)) return NULL;
struct wlr_output_layout_output* wolo = BS_CONTAINER_OF(
wlr_output_layout_ptr->outputs.next,
struct wlr_output_layout_output,
link);
return wolo->output;
}
/* ------------------------------------------------------------------------- */
size_t wlmbe_num_outputs(struct wlr_output_layout *wlr_output_layout_ptr)
{
return wl_list_length(&wlr_output_layout_ptr->outputs);
}
/* ------------------------------------------------------------------------- */
void wlmbe_backend_magnify(wlmbe_backend_t *backend_ptr)
{
wlmbe_output_manager_scale(
backend_ptr->output_manager_ptr,
_wlmbke_backend_magnification);
}
/* ------------------------------------------------------------------------- */
void wlmbe_backend_reduce(wlmbe_backend_t *backend_ptr)
{
wlmbe_output_manager_scale(
backend_ptr->output_manager_ptr,
1.0 / _wlmbke_backend_magnification);
}
/* ------------------------------------------------------------------------- */
bool wlmbe_backend_save_ephemeral_output_configs(wlmbe_backend_t *backend_ptr)
{
bspl_dict_t *dict_ptr = bspl_encode_dict(
_wlmbe_outputs_state_desc,
backend_ptr);
if (NULL != dict_ptr) {
bs_dynbuf_t dynbuf = {};
if (bs_dynbuf_init(&dynbuf, getpagesize(), SIZE_MAX)) {
if (bspl_object_write(
bspl_object_from_dict(dict_ptr),
&dynbuf) &&
bs_dynbuf_write_file(
&dynbuf,
backend_ptr->state_fname_ptr,
S_IRUSR | S_IWUSR)) {
return true;
}
bs_dynbuf_fini(&dynbuf);
}
bspl_dict_unref(dict_ptr);
}
return false;
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/**
* Adds the output to the backend.
*
* @param backend_ptr
* @param output_ptr
*
* @return true on success.
*/
bool _wlmbe_backend_add_output(
wlmbe_backend_t *backend_ptr,
wlmbe_output_t *output_ptr)
{
const wlmbe_output_config_attributes_t *attr_ptr =
wlmbe_output_attributes(output_ptr);
BS_ASSERT(NULL != attr_ptr);
struct wlr_output *wlrop = wlmbe_wlr_output_from_output(output_ptr);
struct wlr_output_layout_output *wlr_output_layout_output_ptr = NULL;
if (attr_ptr->has_position) {
wlr_output_layout_output_ptr = wlr_output_layout_add(
backend_ptr->wlr_output_layout_ptr, wlrop,
attr_ptr->position.x, attr_ptr->position.y);
} else {
wlr_output_layout_output_ptr = wlr_output_layout_add_auto(
backend_ptr->wlr_output_layout_ptr, wlrop);
}
if (NULL == wlr_output_layout_output_ptr) {
bs_log(BS_WARNING,
"Failed wlr_output_layout_add(%p, %p, %d, %d) for \"%s\"",
backend_ptr->wlr_output_layout_ptr, wlrop,
attr_ptr->position.x, attr_ptr->position.y,
wlrop->name);
return false;
}
struct wlr_scene_output *wlr_scene_output_ptr = wlr_scene_output_create(
backend_ptr->wlr_scene_ptr, wlrop);
wlr_scene_output_layout_add_output(
backend_ptr->wlr_scene_output_layout_ptr,
wlr_output_layout_output_ptr,
wlr_scene_output_ptr);
bs_dllist_push_back(
&backend_ptr->outputs,
wlmbe_dlnode_from_output(output_ptr));
bs_log(BS_INFO,
"Created: Output <%s> %s to %dx%d@%.2f x%.2f position (%d,%d) %s",
wlmbe_output_description(output_ptr),
wlrop->enabled ? "enabled" : "disabled",
wlrop->width, wlrop->height,
1e-3 * wlrop->refresh,
wlrop->scale,
wlr_output_layout_output_ptr->x,
wlr_output_layout_output_ptr->y,
attr_ptr->has_position ? "explicit" : "auto");
return true;
}
/* ------------------------------------------------------------------------- */
/** Handles new output events: Creates @ref wlmbe_output_t and adds them. */
void _wlmbe_backend_handle_new_output(
struct wl_listener *listener_ptr,
void *data_ptr)
{
wlmbe_backend_t *backend_ptr = BS_CONTAINER_OF(
listener_ptr, wlmbe_backend_t, new_output_listener);
struct wlr_output *wlr_output_ptr = data_ptr;
// New output: If it's using a Wayland backend, set title & App ID.
if (wlr_output_is_wl(wlr_output_ptr)) {
wlr_wl_output_set_app_id(wlr_output_ptr, _wlmbe_wl_app_id);
wlr_wl_output_set_title(wlr_output_ptr, _wlmbe_wl_title);
}
// See if there is an exact match among the ephemeral output configs. If
// yes, pick that configuration. Otherwise, create a new one.
wlmbe_output_config_t *config_ptr = wlmbe_output_config_from_dlnode(
bs_dllist_find(
&backend_ptr->ephemeral_output_configs,
wlmbe_output_config_equals,
wlr_output_ptr));
if (NULL != config_ptr &&
!wlmbe_output_config_attributes(config_ptr)->enabled) {
// Explicitly configured to be disabled. Skip that output.
wlr_output_destroy(wlr_output_ptr);
return;
}
if (NULL == config_ptr) {
config_ptr = wlmbe_output_config_create_from_wlr(wlr_output_ptr);
bs_dllist_push_front(
&backend_ptr->ephemeral_output_configs,
wlmbe_dlnode_from_output_config(config_ptr));
// See if we have a corresponding entry among configured outputs. If
// yes, apply the attributes to our new config.
wlmbe_output_config_t *outputs_config_ptr =
wlmbe_output_config_from_dlnode(
bs_dllist_find(
&backend_ptr->output_configs,
wlmbe_output_config_fnmatches,
wlr_output_ptr));
if (NULL != outputs_config_ptr) {
wlmbe_output_config_apply_attributes(
config_ptr,
wlmbe_output_config_attributes(outputs_config_ptr));
}
}
wlmbe_output_t *output_ptr = wlmbe_output_create(
wlr_output_ptr,
backend_ptr->wlr_allocator_ptr,
backend_ptr->wlr_renderer_ptr,
backend_ptr->wlr_scene_ptr,
config_ptr,
backend_ptr->width,
backend_ptr->height);
if (NULL != output_ptr) {
if (_wlmbe_backend_add_output(backend_ptr, output_ptr)) return;
wlmbe_output_destroy(output_ptr);
}
wlr_output_destroy(wlr_output_ptr);
}
/* ------------------------------------------------------------------------- */
/** Decodes an item of `Outputs`. */
bool _wlmbe_backend_decode_item(
bspl_object_t *obj_ptr,
size_t i,
void *dest_ptr)
{
bs_dllist_t *dllist_ptr = dest_ptr;
bspl_dict_t *dict_ptr = bspl_dict_from_object(obj_ptr);
if (NULL == dict_ptr) {
bs_log(BS_WARNING, "Element %zu is not a dict", i);
return false;
}
wlmbe_output_config_t *config_ptr =
wlmbe_output_config_create_from_plist(dict_ptr);
if (NULL != config_ptr) {
bs_dllist_push_back(
dllist_ptr,
wlmbe_dlnode_from_output_config(config_ptr));
return true;
}
return false;
}
/* ------------------------------------------------------------------------- */
/** Encodes the dllist item and stores it into the array at `ud_ptr`. */
bool _wlmbe_backend_encode_item(
bs_dllist_node_t *dlnode_ptr,
void *ud_ptr)
{
bspl_array_t *array_ptr = ud_ptr;
wlmbe_output_config_t *c = wlmbe_output_config_from_dlnode(dlnode_ptr);
bspl_dict_t *dict_ptr = wlmbe_output_config_create_into_plist(c);
if (NULL == dict_ptr) return false;
bool rv = bspl_array_push_back(array_ptr, bspl_object_from_dict(dict_ptr));
bspl_dict_unref(dict_ptr);
return rv;
}
/* ------------------------------------------------------------------------- */
/** Encodes all array (items) into a plist array. */
bspl_object_t *_wlmbe_backend_encode_all(
__UNUSED__ const union bspl_desc_value *desc_value_ptr,
const void *value_ptr)
{
const bs_dllist_t *output_configs_ptr = value_ptr;
bspl_array_t *array_ptr = bspl_array_create();
if (NULL != array_ptr &&
bs_dllist_all(
output_configs_ptr,
_wlmbe_backend_encode_item,
array_ptr)) {
return bspl_object_from_array(array_ptr);
}
bspl_array_unref(array_ptr);
return NULL;
}
/* ------------------------------------------------------------------------- */
/** Frees all resources of the @ref wlmbe_output_config_t in the list. */
void _wlmbe_backend_decode_fini(void *dest_ptr)
{
bs_dllist_t *dllist_ptr = dest_ptr;
bs_dllist_for_each(dllist_ptr,
_wlmbe_backend_config_dlnode_destroy,
NULL);
}
/* ------------------------------------------------------------------------- */
/**
* Iterator callback: Destroys a @ref wlmbe_output_config_t.
*
* @param dlnode_ptr To @ref wlmbe_output_config_t::dlnode, and
* identifies the config node to destroy.
* @param ud_ptr unused.
*/
void _wlmbe_backend_config_dlnode_destroy(
bs_dllist_node_t *dlnode_ptr,
__UNUSED__ void *ud_ptr)
{
wlmbe_output_config_destroy(wlmbe_output_config_from_dlnode(dlnode_ptr));
}
/* == Unit tests =========================================================== */
static void _wlmbe_backend_test_find(bs_test_t *test_ptr);
static void _wlmbe_backend_test_encode_output_configs(bs_test_t *test_ptr);
/** Test cases */
static const bs_test_case_t _wlmbe_backend_test_cases[] = {
{ 1, "find", _wlmbe_backend_test_find },
{ 1, "encode_output_configs", _wlmbe_backend_test_encode_output_configs },
BS_TEST_CASE_SENTINEL()
};
const bs_test_set_t wlmbe_backend_test_set = BS_TEST_SET(
true, "backend", _wlmbe_backend_test_cases);
/* ------------------------------------------------------------------------- */
/** Tests that output configurations are found as desired. */
void _wlmbe_backend_test_find(bs_test_t *test_ptr)
{
wlmbe_backend_t be = {};
bspl_dict_t *config_dict_ptr = bspl_dict_from_object(
bspl_create_object_from_plist_string(
"{Outputs = ("
"{Transformation=Normal; Scale=1.0; Name=\"DP-*\"},"
")}"));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, config_dict_ptr);
BS_TEST_VERIFY_TRUE_OR_RETURN(
test_ptr,
bspl_decode_dict(config_dict_ptr, _wlmbe_output_configs_desc, &be));
bspl_dict_t *state_dict_ptr = bspl_dict_from_object(
bspl_create_object_from_plist_string(
"{Outputs = ("
"{Transformation=Normal; Scale=2.0; Name=\"DP-0\"},"
"{Transformation=Flip; Scale=3.0; Name=\"DP-1\"},"
")}"));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, state_dict_ptr);
BS_TEST_VERIFY_TRUE_OR_RETURN(
test_ptr,
bspl_decode_dict(state_dict_ptr, _wlmbe_outputs_state_desc, &be));
BS_TEST_VERIFY_EQ(
test_ptr, 2, bs_dllist_size(&be.ephemeral_output_configs));
wlmbe_output_config_t *o;
struct wlr_output wlr_output = {};
// These are found in the configured state.
wlr_output.name = "DP-0";
o = wlmbe_output_config_from_dlnode(
bs_dllist_find(
&be.ephemeral_output_configs,
wlmbe_output_config_equals,
&wlr_output));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o);
BS_TEST_VERIFY_EQ(test_ptr, 2.0, wlmbe_output_config_attributes(o)->scale);
wlr_output.name = "DP-1";
o = wlmbe_output_config_from_dlnode(
bs_dllist_find(
&be.ephemeral_output_configs,
wlmbe_output_config_equals,
&wlr_output));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o);
BS_TEST_VERIFY_EQ(test_ptr, 3.0, wlmbe_output_config_attributes(o)->scale);
// Only has a fit through config match. Will add a state entry.
wlr_output.name = "DP-2";
o = wlmbe_output_config_from_dlnode(
bs_dllist_find(
&be.output_configs,
wlmbe_output_config_fnmatches,
&wlr_output));
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o);
BS_TEST_VERIFY_EQ(test_ptr, 1.0, wlmbe_output_config_attributes(o)->scale);
bspl_decoded_destroy(_wlmbe_outputs_state_desc, &be);
bspl_decoded_destroy(_wlmbe_output_configs_desc, &be);
bspl_dict_unref(state_dict_ptr);
bspl_dict_unref(config_dict_ptr);
}
/* ------------------------------------------------------------------------- */
/** Tests encoding of the ephemeral output configs */
void _wlmbe_backend_test_encode_output_configs(bs_test_t *test_ptr)
{
wlmbe_backend_t backend = {};
struct wlr_output w = {
.name = "Name0",
.width = 1024,
.height = 768,
.scale = 2.0,
.refresh = 60000,
};
wlmbe_output_config_t *c1 = wlmbe_output_config_create_from_wlr(&w);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, c1);
bs_dllist_push_back(
&backend.ephemeral_output_configs,
wlmbe_dlnode_from_output_config(c1));
w = (struct wlr_output){
.enabled = true,
.name = "Name1",
.width = 640,
.height = 480,
.scale = 1.0,
.refresh = 80000
};
wlmbe_output_config_t *c2 = wlmbe_output_config_create_from_wlr(&w);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, c2);
bs_dllist_push_back(
&backend.ephemeral_output_configs,
wlmbe_dlnode_from_output_config(c2));
bspl_dict_t *dict_ptr = bspl_encode_dict(
_wlmbe_outputs_state_desc,
&backend);
BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, dict_ptr);
bs_dynbuf_t dynbuf = {};
BS_TEST_VERIFY_TRUE(test_ptr, bs_dynbuf_init(&dynbuf, 1024, SIZE_MAX));
BS_TEST_VERIFY_TRUE(
test_ptr,
bspl_object_write(bspl_object_from_dict(dict_ptr), &dynbuf));
static const char *expected =
"{\n"
" Outputs = (\n"
" {\n"
" Enabled = False;\n"
" Mode = \"1024x768@60.0\";\n"
" Name = Name0;\n"
" Scale = \"2.000000e+00\";\n"
" Transformation = Normal;\n"
" },\n"
" {\n"
" Enabled = True;\n"
" Mode = \"640x480@80.0\";\n"
" Name = Name1;\n"
" Scale = \"1.000000e+00\";\n"
" Transformation = Normal;\n"
" }\n"
" );\n"
"}\n";
BS_TEST_VERIFY_MEMEQ(test_ptr, expected, dynbuf.data_ptr, dynbuf.length);
bs_dynbuf_fini(&dynbuf);
bspl_dict_unref(dict_ptr);
wlmbe_output_config_destroy(c2);
wlmbe_output_config_destroy(c1);
}
/* == End of backend.c ===================================================== */
wlmaker-0.8.1/src/backend/output.c 0000644 0001751 0001751 00000027307 15222270710 016461 0 ustar runner runner /* ========================================================================= */
/**
* @file output.c
*
* @copyright
* Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "output.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define WLR_USE_UNSTABLE
#include
#include
#include
#include
#include
#undef WLR_USE_UNSTABLE
/* == Declarations ========================================================= */
/** Handle for a compositor output device. */
struct _wlmbe_output_t {
/** List node for insertion in @ref wlmbe_backend_t::outputs. */
bs_dllist_node_t dlnode;
/** Listener for `destroy` signals raised by `wlr_output`. */
struct wl_listener output_destroy_listener;
/** Listener for `frame` signals raised by `wlr_output`. */
struct wl_listener output_frame_listener;
/** Listener for `request_state` signals raised by `wlr_output`. */
struct wl_listener output_request_state_listener;
/** Descriptive name, showing manufacturer, model and serial. */
char *description_ptr;
// Below: Not owned by @ref wlmbe_output_t.
/** Refers to the compositor output region, from wlroots. */
struct wlr_output *wlr_output_ptr;
/** Refers to the scene graph used. */
struct wlr_scene *wlr_scene_ptr;
/** This output's configuration. */
wlmbe_output_config_t *output_config_ptr;
};
static void _wlmbe_output_handle_destroy(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmbe_output_handle_frame(
struct wl_listener *listener_ptr,
void *data_ptr);
static void _wlmbe_output_handle_request_state(
struct wl_listener *listener_ptr,
void *data_ptr);
/* == Data ================================================================= */
/* == Exported methods ===================================================== */
/* ------------------------------------------------------------------------- */
wlmbe_output_t *wlmbe_output_create(
struct wlr_output *wlr_output_ptr,
struct wlr_allocator *wlr_allocator_ptr,
struct wlr_renderer *wlr_renderer_ptr,
struct wlr_scene *wlr_scene_ptr,
wlmbe_output_config_t *config_ptr,
int width,
int height)
{
wlmbe_output_t *output_ptr = logged_calloc(1, sizeof(wlmbe_output_t));
if (NULL == output_ptr) return NULL;
output_ptr->wlr_output_ptr = wlr_output_ptr;
output_ptr->wlr_scene_ptr = wlr_scene_ptr;
output_ptr->output_config_ptr = config_ptr;
output_ptr->wlr_output_ptr->data = output_ptr;
output_ptr->description_ptr = bs_strdupf(
"\"%s\": Manufacturer: \"%s\", Model \"%s\", Serial \"%s\"",
wlr_output_ptr->name,
wlr_output_ptr->make ? wlr_output_ptr->make : "Unknown",
wlr_output_ptr->model ? wlr_output_ptr->model : "Unknown",
wlr_output_ptr->serial ? wlr_output_ptr->serial : "Unknown");
if (NULL == output_ptr->description_ptr) {
wlmbe_output_destroy(output_ptr);
return NULL;
}
wlmtk_util_connect_listener_signal(
&output_ptr->wlr_output_ptr->events.destroy,
&output_ptr->output_destroy_listener,
_wlmbe_output_handle_destroy);
wlmtk_util_connect_listener_signal(
&output_ptr->wlr_output_ptr->events.frame,
&output_ptr->output_frame_listener,
_wlmbe_output_handle_frame);
wlmtk_util_connect_listener_signal(
&output_ptr->wlr_output_ptr->events.request_state,
&output_ptr->output_request_state_listener,
_wlmbe_output_handle_request_state);
// From tinwywl: Configures the output created by the backend to use our
// allocator and our renderer. Must be done once, before commiting the
// output.
if (!wlr_output_init_render(
output_ptr->wlr_output_ptr,
wlr_allocator_ptr,
wlr_renderer_ptr)) {
bs_log(BS_ERROR, "Failed wlr_output_init_renderer() on %s",
output_ptr->wlr_output_ptr->name);
wlmbe_output_destroy(output_ptr);
return NULL;
}
const wlmbe_output_config_attributes_t *attr_ptr =
wlmbe_output_config_attributes(output_ptr->output_config_ptr);
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_enabled(&state, attr_ptr->enabled);
wlr_output_state_set_scale(&state, BS_MAX(1.0, attr_ptr->scale));
// Issue #97: Found that X11 and transformations do not translate
// cursor coordinates well. Force it to 'Normal'.
enum wl_output_transform transformation =
attr_ptr->transformation;
if (wlr_output_is_x11(wlr_output_ptr) &&
transformation != WL_OUTPUT_TRANSFORM_NORMAL) {
bs_log(BS_WARNING, "X11 backend: Transformation changed to 'Normal'.");
transformation = WL_OUTPUT_TRANSFORM_NORMAL;
}
wlr_output_state_set_transform(&state, transformation);
// Set modes for backends that have them.
if (attr_ptr->has_mode) {
wlr_output_state_set_custom_mode(
&state,
attr_ptr->mode.width,
attr_ptr->mode.height,
attr_ptr->mode.refresh);
} else {
if (!wl_list_empty(&output_ptr->wlr_output_ptr->modes)) {
struct wlr_output_mode *mode_ptr = wlr_output_preferred_mode(
output_ptr->wlr_output_ptr);
bs_log(BS_INFO, "Setting mode %dx%d @ %.2fHz",
mode_ptr->width, mode_ptr->height, 1e-3 * mode_ptr->refresh);
wlr_output_state_set_mode(&state, mode_ptr);
} else {
bs_log(BS_INFO, "No modes available on %s",
output_ptr->wlr_output_ptr->name);
}
}
if ((wlr_output_is_x11(wlr_output_ptr) ||
wlr_output_is_wl(wlr_output_ptr))
&& 0 < width && 0 < height) {
bs_log(BS_INFO, "Overriding output dimensions to %"PRIu32"x%"PRIu32,
width, height);
wlr_output_state_set_custom_mode(
&state, width, height, 0);
}
if (!wlr_output_test_state(output_ptr->wlr_output_ptr, &state)) {
bs_log(BS_ERROR, "Failed wlr_output_test_state() on %s",
output_ptr->wlr_output_ptr->name);
wlmbe_output_destroy(output_ptr);
wlr_output_state_finish(&state);
return NULL;
}
// Enable the output and commit.
bool rv = wlr_output_commit_state(output_ptr->wlr_output_ptr, &state);
wlr_output_state_finish(&state);
if (!rv) {
bs_log(BS_ERROR, "Failed wlr_output_commit_state() on %s",
output_ptr->wlr_output_ptr->name);
wlmbe_output_destroy(output_ptr);
return NULL;
}
return output_ptr;
}
/* ------------------------------------------------------------------------- */
void wlmbe_output_destroy(wlmbe_output_t *output_ptr)
{
if (NULL != output_ptr->wlr_output_ptr) {
bs_log(BS_INFO, "Destroy output %s", output_ptr->wlr_output_ptr->name);
}
_wlmbe_output_handle_destroy(&output_ptr->output_destroy_listener, NULL);
if (NULL != output_ptr->description_ptr) {
free(output_ptr->description_ptr);
output_ptr->description_ptr = NULL;
}
free(output_ptr);
}
/* ------------------------------------------------------------------------- */
const char *wlmbe_output_description(wlmbe_output_t *output_ptr)
{
return output_ptr->description_ptr;
}
/* ------------------------------------------------------------------------- */
struct wlr_output *wlmbe_wlr_output_from_output(wlmbe_output_t *output_ptr)
{
return output_ptr->wlr_output_ptr;
}
/* ------------------------------------------------------------------------- */
const wlmbe_output_config_attributes_t *wlmbe_output_attributes(
wlmbe_output_t *output_ptr)
{
return wlmbe_output_config_attributes(output_ptr->output_config_ptr);
}
/* ------------------------------------------------------------------------- */
void wlmbe_output_update_attributes(
wlmbe_output_t *output_ptr,
int x,
int y,
bool has_position)
{
wlmbe_output_config_update_attributes(
output_ptr->output_config_ptr,
output_ptr->wlr_output_ptr,
x, y, has_position);
}
/* ------------------------------------------------------------------------- */
bs_dllist_node_t *wlmbe_dlnode_from_output(wlmbe_output_t *output_ptr)
{
return &output_ptr->dlnode;
}
/* ------------------------------------------------------------------------- */
wlmbe_output_t *wlmbe_output_from_dlnode(bs_dllist_node_t *dlnode_ptr)
{
if (NULL == dlnode_ptr) return NULL;
return BS_CONTAINER_OF(dlnode_ptr, wlmbe_output_t, dlnode);
}
/* == Local (static) methods =============================================== */
/* ------------------------------------------------------------------------- */
/**
* Event handler for the `destroy` signal raised by `wlr_output`.
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmbe_output_handle_destroy(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmbe_output_t *output_ptr = BS_CONTAINER_OF(
listener_ptr, wlmbe_output_t, output_destroy_listener);
wlmtk_util_disconnect_listener(&output_ptr->output_request_state_listener);
wlmtk_util_disconnect_listener(&output_ptr->output_frame_listener);
wlmtk_util_disconnect_listener(&output_ptr->output_destroy_listener);
output_ptr->wlr_output_ptr = NULL;
}
/* ------------------------------------------------------------------------- */
/**
* Event handler for the `frame` signal raised by `wlr_output`.
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmbe_output_handle_frame(
struct wl_listener *listener_ptr,
__UNUSED__ void *data_ptr)
{
wlmbe_output_t *output_ptr = BS_CONTAINER_OF(
listener_ptr, wlmbe_output_t, output_frame_listener);
struct wlr_scene_output *wlr_scene_output_ptr = wlr_scene_get_scene_output(
output_ptr->wlr_scene_ptr,
output_ptr->wlr_output_ptr);
wlr_scene_output_commit(wlr_scene_output_ptr, NULL);
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
wlr_scene_output_send_frame_done(wlr_scene_output_ptr, &now);
}
/* ------------------------------------------------------------------------- */
/**
* Event handler for the `request_state` signal raised by `wlr_output`.
*
* @param listener_ptr
* @param data_ptr
*/
void _wlmbe_output_handle_request_state(
struct wl_listener *listener_ptr,
void *data_ptr)
{
wlmbe_output_t *output_ptr = BS_CONTAINER_OF(
listener_ptr, wlmbe_output_t, output_request_state_listener);
const struct wlr_output_event_request_state *event_ptr = data_ptr;
if (wlr_output_commit_state(output_ptr->wlr_output_ptr,
event_ptr->state)) {
wlmbe_output_update_attributes(output_ptr, 0, 0, false);
} else {
bs_log(BS_WARNING, "Failed wlr_output_commit_state('%s', %p)",
output_ptr->wlr_output_ptr->name, event_ptr->state);
}
}
/* == End of output.c ====================================================== */
wlmaker-0.8.1/src/backend/CMakeLists.txt 0000644 0001751 0001751 00000003101 15222270710 017477 0 ustar runner runner # Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cmake_minimum_required(VERSION 3.13)
set(public_header_files
backend.h
output.h
output_config.h
output_manager.h)
add_library(backend STATIC
backend.c
output.c
output_config.c
output_manager.c)
target_include_directories(
backend
PUBLIC
"${PROJECT_SOURCE_DIR}/include"
PRIVATE
"${WAYLAND_SERVER_INCLUDE_DIRS}"
"${WLROOTS_INCLUDE_DIRS}"
"${PROJECT_SOURCE_DIR}/include"
)
target_include_directories(
backend
PRIVATE
"${PROJECT_SOURCE_DIR}/include/backend"
)
set_target_properties(
backend PROPERTIES
VERSION 1.0
PUBLIC_HEADER "${public_header_files}"
)
target_compile_options(
backend PRIVATE
"${WAYLAND_SERVER_CFLAGS}"
"${WAYLAND_SERVER_CFLAGS_OTHER}"
)
target_link_libraries(
backend
PUBLIC
libbase
libbase_plist
PkgConfig::WAYLAND_SERVER
PRIVATE
toolkit
PkgConfig::WLROOTS)
if(iwyu_path_and_options)
set_target_properties(
backend PROPERTIES
C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}")
endif()
wlmaker-0.8.1/src/backend/output_config.c 0000644 0001751 0001751 00000067561 15222270710 020014 0 ustar runner runner /* ========================================================================= */
/**
* @file output_config.c
* Copyright (c) 2025 by Philipp Kaeser