kylin-wayland-compositor/0000775000175000017500000000000015160461067014465 5ustar fengfengkylin-wayland-compositor/protocols/0000775000175000017500000000000015160461067016511 5ustar fengfengkylin-wayland-compositor/protocols/text-input-unstable-v2.xml0000664000175000017500000004736215160460057023526 0ustar fengfeng Copyright © 2012, 2013 Intel Corporation Copyright © 2015, 2016 Jan Arne Petersen 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. The zwp_text_input_v2 interface represents text input and input methods associated with a seat. It provides enter/leave events to follow the text input focus for a seat. Requests are used to enable/disable the text-input object and set state information like surrounding and selected text or the content type. The information about the entered text is sent to the text-input object via the pre-edit and commit events. Using this interface removes the need for applications to directly process hardware key events and compose text out of them. Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices have to always point to the first byte of an UTF-8 encoded code point. Lengths are not allowed to contain just a part of an UTF-8 encoded code point. State is sent by the state requests (set_surrounding_text, set_content_type, set_cursor_rectangle and set_preferred_language) and an update_state request. After an enter or an input_method_change event all state information is invalidated and needs to be resent from the client. A reset or entering a new widget on client side also invalidates all current state information. Destroy the wp_text_input object. Also disables all surfaces enabled through this wp_text_input object Enable text input in a surface (usually when a text entry inside of it has focus). This can be called before or after a surface gets text (or keyboard) focus via the enter event. Text input to a surface is only active when it has the current text (or keyboard) focus and is enabled. Disable text input in a surface (typically when there is no focus on any text entry inside the surface). Requests input panels (virtual keyboard) to show. This should be used for example to show a virtual keyboard again (with a tap) after it was closed by pressing on a close button on the keyboard. Requests input panels (virtual keyboard) to hide. Sets the plain surrounding text around the input position. Text is UTF-8 encoded. Cursor is the byte offset within the surrounding text. Anchor is the byte offset of the selection anchor within the surrounding text. If there is no selected text, anchor is the same as cursor. Make sure to always send some text before and after the cursor except when the cursor is at the beginning or end of text. When there was a configure_surrounding_text event take the before_cursor and after_cursor arguments into account for picking how much surrounding text to send. There is a maximum length of wayland messages so text can not be longer than 4000 bytes. Content hint is a bitmask to allow to modify the behavior of the text input. The content purpose allows to specify the primary purpose of a text input. This allows an input method to show special purpose input panels with extra characters or to disallow some characters. Sets the content purpose and content hint. While the purpose is the basic purpose of an input field, the hint flags allow to modify some of the behavior. When no content type is explicitly set, a normal content purpose with none hint should be assumed. Sets the cursor outline as a x, y, width, height rectangle in surface local coordinates. Allows the compositor to put a window with word suggestions near the cursor. Sets a specific language. This allows for example a virtual keyboard to show a language specific layout. The "language" argument is a RFC-3066 format language tag. It could be used for example in a word processor to indicate language of currently edited document or in an instant message application which tracks languages of contacts. Defines the reason for sending an updated state. Allows to atomically send state updates from client. This request should follow after a batch of state updating requests like set_surrounding_text, set_content_type, set_cursor_rectangle and set_preferred_language. The flags field indicates why an updated state is sent to the input method. Reset should be used by an editor widget after the text was changed outside of the normal input method flow. For "change" it is enough to send the changed state, else the full state should be send. Serial should be set to the serial from the last enter or input_method_changed event. To make sure to not receive outdated input method events after a reset or switching to a new widget wl_display_sync() should be used after update_state in these cases. Notification that this seat's text-input focus is on a certain surface. When the seat has the keyboard capability the text-input focus follows the keyboard focus. Notification that this seat's text-input focus is no longer on a certain surface. The leave notification is sent before the enter notification for the new focus. When the seat has the keyboard capability the text-input focus follows the keyboard focus. Notification that the visibility of the input panel (virtual keyboard) changed. The rectangle x, y, width, height defines the area overlapped by the input panel (virtual keyboard) on the surface having the text focus in surface local coordinates. That can be used to make sure widgets are visible and not covered by a virtual keyboard. Notify when a new composing text (pre-edit) should be set around the current cursor position. Any previously set composing text should be removed. The commit text can be used to replace the composing text in some cases (for example when losing focus). The text input should also handle all preedit_style and preedit_cursor events occurring directly before preedit_string. Sets styling information on composing text. The style is applied for length bytes from index relative to the beginning of the composing text (as byte offset). Multiple styles can be applied to a composing text by sending multiple preedit_styling events. This event is handled as part of a following preedit_string event. Sets the cursor position inside the composing text (as byte offset) relative to the start of the composing text. When index is a negative number no cursor is shown. When no preedit_cursor event is sent the cursor will be at the end of the composing text by default. This event is handled as part of a following preedit_string event. Notify when text should be inserted into the editor widget. The text to commit could be either just a single character after a key press or the result of some composing (pre-edit). It could be also an empty text when some text should be removed (see delete_surrounding_text) or when the input cursor should be moved (see cursor_position). Any previously set composing text should be removed. Notify when the cursor or anchor position should be modified. This event should be handled as part of a following commit_string event. The text between anchor and index should be selected. Notify when the text around the current cursor position should be deleted. BeforeLength and afterLength is the length (in bytes) of text before and after the current cursor position (excluding the selection) to delete. This event should be handled as part of a following commit_string or preedit_string event. Transfer an array of 0-terminated modifiers names. The position in the array is the index of the modifier as used in the modifiers bitmask in the keysym event. Notify when a key event was sent. Key events should not be used for normal text input operations, which should be done with commit_string, delete_surrounding_text, etc. The key event follows the wl_keyboard key event convention. Sym is a XKB keysym, state a wl_keyboard key_state. Modifiers are a mask for effective modifiers (where the modifier indices are set by the modifiers_map event) Sets the language of the input text. The "language" argument is a RFC-3066 format language tag. Sets the text direction of input text. It is mainly needed for showing input cursor on correct side of the editor when there is no input yet done and making sure neutral direction text is laid out properly. Configure what amount of surrounding text is expected by the input method. The surrounding text will be sent in the set_surrounding_text request on the following state information updates. The input method changed on compositor side, which invalidates all current state information. New state information should be sent from the client via state requests (set_surrounding_text, set_content_hint, ...) and update_state. A factory for text-input objects. This object is a global singleton. Destroy the wp_text_input_manager object. Creates a new text-input object for a given seat. kylin-wayland-compositor/protocols/kywc-output-v1.xml0000664000175000017500000002461215160460057022075 0ustar fengfeng This interface is a manager that allows reading and writing the current output device configuration. This event introduces a new output. This happens whenever a new output appears (e.g. a monitor is plugged in) or after the output manager is bound. This events is sent when the output will become the primary output. This event is sent after all information has been sent after binding to the output manager object and after any subsequent changes. This applies to child output and mode objects as well. This allows changes to the output configuration to be seen as atomic, even if they happen via multiple events. Indicates the client no longer wishes to receive events for output configuration changes. However the compositor may emit further events, until the finished event is emitted. The client must not send any more requests after this one. This event indicates that the compositor is done sending manager events. The compositor will destroy the object immediately after sending this event, so it will become invalid and the client should release any resources associated with it. A output has some read-only properties: modes, name, description, physical_size. These cannot be changed by clients. Properties sent via this interface are applied atomically via the kywc_output_manager.done event. No guarantees are made regarding the order in which properties are sent. This event describes the output name. The name event is sent after a kywc_output object is created. This event is only sent once per object, and the name does not change over the lifetime of the kywc_output object. This event describes the manufacturer of the output monitor. This event describes the model of the output monitor. This event describes the serial number of the output monitor. This event describes a human-readable description of the output. This event describes the physical size of the monitor. This event is only sent if has a physical size (e.g. is not a projector or a virtual device). This event introduces a mode for this monitor. It is sent once per supported mode. Describes what capabilities this device has. What capabilities this output has, sent on startup before the first done event. This event describes whether the output is enabled. A disabled output is not mapped to a region of the global compositor space. When a output is disabled, some properties (current_mode, position, transform and scale) are irrelevant. This event describes the mode currently in use for this output. It is only sent if the output is enabled. This events describes the position of the output in the global compositor space. It is only sent if the output is enabled. This event describes the transformation currently applied to the output. It is only sent if the output is enabled. This events describes the scale of the output in the global compositor space. It is only sent if the output is enabled. This events describes the power(dpms) state. This events describes the output brightness. This events describes the output color temperature. This event indicates that the output is no longer available. The output object becomes inert. Clients should send a destroy request and release any resources associated with it. This request indicates that the client will no longer use this output object. This object describes an output mode. Properties sent via this interface are applied atomically via the kywc_output_manager.done event. No guarantees are made regarding the order in which properties are sent. This event describes the mode size. The size is given in physical hardware units of the output device. This is not necessarily the same as the output size in the global compositor space. For instance, the output may be scaled or transformed. This event describes the mode's fixed vertical refresh rate. It is only sent if the mode has a fixed refresh rate. This event advertises this mode as preferred. This event indicates that the mode is no longer available. The mode object becomes inert. Clients should send a destroy request and release any resources associated with it. This request indicates that the client will no longer use this mode object. kylin-wayland-compositor/protocols/xdg-toplevel-icon-v1.xml0000664000175000017500000002253715160461067023130 0ustar fengfeng Copyright © 2023-2024 Matthias Klumpp Copyright © 2024 David Edmundson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol allows clients to set icons for their toplevel surfaces either via the XDG icon stock (using an icon name), or from pixel data. A toplevel icon represents the individual toplevel (unlike the application or launcher icon, which represents the application as a whole), and may be shown in window switchers, window overviews and taskbars that list individual windows. This document adheres to RFC 2119 when using words like "must", "should", "may", etc. Warning! The protocol described in this file is currently in the testing phase. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes can only be done by creating a new major version of the extension. This interface allows clients to create toplevel window icons and set them on toplevel windows to be displayed to the user. Destroy the toplevel icon manager. This does not destroy objects created with the manager. Creates a new icon object. This icon can then be attached to a xdg_toplevel via the 'set_icon' request. This request assigns the icon 'icon' to 'toplevel', or clears the toplevel icon if 'icon' was null. This state is double-buffered and is applied on the next wl_surface.commit of the toplevel. After making this call, the xdg_toplevel_icon_v1 provided as 'icon' can be destroyed by the client without 'toplevel' losing its icon. The xdg_toplevel_icon_v1 is immutable from this point, and any future attempts to change it must raise the 'xdg_toplevel_icon_v1.immutable' protocol error. The compositor must set the toplevel icon from either the pixel data the icon provides, or by loading a stock icon using the icon name. See the description of 'xdg_toplevel_icon_v1' for details. If 'icon' is set to null, the icon of the respective toplevel is reset to its default icon (usually the icon of the application, derived from its desktop-entry file, or a placeholder icon). If this request is passed an icon with no pixel buffers or icon name assigned, the icon must be reset just like if 'icon' was null. This event indicates an icon size the compositor prefers to be available if the client has scalable icons and can render to any size. When the 'xdg_toplevel_icon_manager_v1' object is created, the compositor may send one or more 'icon_size' events to describe the list of preferred icon sizes. If the compositor has no size preference, it may not send any 'icon_size' event, and it is up to the client to decide a suitable icon size. A sequence of 'icon_size' events must be finished with a 'done' event. If the compositor has no size preferences, it must still send the 'done' event, without any preceding 'icon_size' events. This event is sent after all 'icon_size' events have been sent. This interface defines a toplevel icon. An icon can have a name, and multiple buffers. In order to be applied, the icon must have either a name, or at least one buffer assigned. Applying an empty icon (with no buffer or name) to a toplevel should reset its icon to the default icon. It is up to compositor policy whether to prefer using a buffer or loading an icon via its name. See 'set_name' and 'add_buffer' for details. Destroys the 'xdg_toplevel_icon_v1' object. The icon must still remain set on every toplevel it was assigned to, until the toplevel icon is reset explicitly. This request assigns an icon name to this icon. Any previously set name is overridden. The compositor must resolve 'icon_name' according to the lookup rules described in the XDG icon theme specification[1] using the environment's current icon theme. If the compositor does not support icon names or cannot resolve 'icon_name' according to the XDG icon theme specification it must fall back to using pixel buffer data instead. If this request is made after the icon has been assigned to a toplevel via 'set_icon', a 'immutable' error must be raised. [1]: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html This request adds pixel data supplied as wl_buffer to the icon. The client should add pixel data for all icon sizes and scales that it can provide, or which are explicitly requested by the compositor via 'icon_size' events on xdg_toplevel_icon_manager_v1. The wl_buffer supplying pixel data as 'buffer' must be backed by wl_shm and must be a square (width and height being equal). If any of these buffer requirements are not fulfilled, a 'invalid_buffer' error must be raised. If this icon instance already has a buffer of the same size and scale from a previous 'add_buffer' request, data from the last request overrides the preexisting pixel data. The wl_buffer must be kept alive for as long as the xdg_toplevel_icon it is associated with is not destroyed, otherwise a 'no_buffer' error is raised. The buffer contents must not be modified after it was assigned to the icon. As a result, the region of the wl_shm_pool's backing storage used for the wl_buffer must not be modified after this request is sent. The wl_buffer.release event is unused. If this request is made after the icon has been assigned to a toplevel via 'set_icon', a 'immutable' error must be raised. kylin-wayland-compositor/protocols/meson.build0000664000175000017500000001162615160461067020661 0ustar fengfengwl_protocol_dir = wayland_protos.get_variable('pkgdatadir') wayland_scanner = find_program('wayland-scanner') protocols = { 'wl-drm': 'drm.xml', 'ext-idle-notifier-v1': wl_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml', 'xdg-shell': wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', 'xdg-output-unstable-v1': wl_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', 'tablet-v2': wl_protocol_dir / 'stable/tablet/tablet-v2.xml', 'zwp-text-input-v1': wl_protocol_dir / 'unstable/text-input/text-input-unstable-v1.xml', 'zwp-text-input-v2': 'text-input-unstable-v2.xml', 'xdg-decoration': wl_protocol_dir / 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml', 'xdg-dialog': wl_protocol_dir / 'staging/xdg-dialog/xdg-dialog-v1.xml', 'org-kde-kwin-idle': 'idle.xml', 'zwp-pointer-constraints-v1': wl_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', 'ext-transient-seat-v1': wl_protocol_dir / 'staging/ext-transient-seat/ext-transient-seat-v1.xml', 'xdg-toplevel-drag-manager-v1': wl_protocol_dir / 'staging/xdg-toplevel-drag/xdg-toplevel-drag-v1.xml', 'wp-tearing-control-manager-v1': wl_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml', 'wp-cursor-shape-manager-v1': wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', 'single-pixel-buffer-v1': wl_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', } protocols += { 'kywc-capture-manager-v1': 'kywc-capture-v1.xml', 'kywc-output-manager-v1': 'kywc-output-v1.xml', 'kywc-toplevel-manager-v1': 'kywc-toplevel-v1.xml', 'kywc-workspace-manager-v1': 'kywc-workspace-v1.xml', 'wlr-screencopy-unstable-v1': 'wlr-screencopy-unstable-v1.xml', } if have_drm_lease_device protocols += { 'drm-lease-v1': wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', } endif if have_alpha_modifier protocols += { 'alpha-modifier-v1': wl_protocol_dir / 'staging/alpha-modifier/alpha-modifier-v1.xml', } endif if have_kde_output protocols += { 'kde-output-device-v2': 'kde-output-device-v2.xml', 'kde-output-management-v2' : 'kde-output-management-v2.xml', 'kde-primary-output-v1': 'kde-primary-output-v1.xml', 'org-kde-kwin-dpms-manager': 'dpms.xml', } endif if have_kde_virtual_desktop protocols += { 'org-kde-plasma-virtual-desktop-management': 'plasma-virtual-desktop.xml', } endif if have_wlr_output protocols += { 'zwlr-output-power-manager-v1': 'wlr-output-power-management-unstable-v1.xml', } endif if have_wlr_layer_shell protocols += { 'zwlr-layer-shell-v1': 'wlr-layer-shell-unstable-v1.xml', } endif if have_kde_plasma_shell protocols += { 'org-kde-plasma-shell': 'plasma-shell.xml', } endif if have_kde_plasma_window_management protocols += { 'org-kde-plasma-window-management': 'plasma-window-management.xml', } endif if have_kde_blur protocols += { 'org-kde-kwin-blur-manager': 'blur.xml', } endif if have_kde_slide protocols += { 'org-kde-kwin-slide-manager': 'slide.xml', } endif if have_kde_keystate protocols += { 'org-kde-kwin-keystate-manager': 'kde-keystate.xml', } endif if have_ukui_shell protocols += { 'ukui-shell-v1': 'ukui-shell-v1.xml', } endif if have_ukui_window_management protocols += { 'ukui-window-management': 'ukui-window-management.xml', } endif if have_ukui_output protocols += { 'ukui-output-v1': 'ukui-output-v1.xml', } endif if have_ukui_blur protocols += { 'ukui-blur': 'ukui-blur-v1.xml', } endif if have_ukui_effect protocols += { 'ukui-effect-v1': 'ukui-effect-v1.xml', } endif if have_ukui_startup protocols += { 'ukui-startup-v2': 'ukui-startup-v2.xml', } endif if have_xdg_toplevel_icon protocols += { 'xdg-toplevel-icon-v1': 'xdg-toplevel-icon-v1.xml', } endif if have_tools protocols += { 'virtual-keyboard-unstable-v1': 'virtual-keyboard-unstable-v1.xml', 'wlr-virtual-pointer-unstable-v1': 'wlr-virtual-pointer-unstable-v1.xml', } endif protocols_code = {} protocols_server_header = {} protocols_client_header = {} foreach name, path : protocols code = custom_target( name.underscorify() + '_c', input: path, output: '@BASENAME@-protocol.c', command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], ) wlcom_sources += code server_header = custom_target( name.underscorify() + '_server_h', input: path, output: '@BASENAME@-protocol.h', command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'], ) wlcom_sources += server_header client_header = custom_target( name.underscorify() + '_client_h', input: path, output: '@BASENAME@-client-protocol.h', command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'], build_by_default: false, ) protocols_code += { name: code } protocols_server_header += { name: server_header } protocols_client_header += { name: client_header } endforeach kylin-wayland-compositor/protocols/kde-output-device-v2.xml0000664000175000017500000004260615160460057023124 0ustar fengfeng SPDX-FileCopyrightText: 2021 Méven Car SPDX-License-Identifier: Expat-CMU ]]> An output device describes a display device available to the compositor. output_device is similar to wl_output, but focuses on output configuration management. A client can query all global output_device objects to enlist all available display devices, even those that may currently not be represented by the compositor as a wl_output. The client sends configuration changes to the server through the outputconfiguration interface, and the server applies the configuration changes to the hardware and signals changes to the output devices accordingly. This object is published as global during start up for every available display devices, or when one later becomes available, for example by being hotplugged via a physical connector. This enumeration describes how the physical pixels on an output are laid out. This describes the transform, that a compositor will apply to a surface to compensate for the rotation or mirroring of an output device. The flipped values correspond to an initial flip around a vertical axis followed by rotation. The purpose is mainly to allow clients to render accordingly and tell the compositor, so that for fullscreen surfaces, the compositor is still able to scan out directly client surfaces. The geometry event describes geometric properties of the output. The event is sent when binding to the output object and whenever any of the properties change. This event describes the mode currently in use for this head. It is only sent if the output is enabled. The mode event describes an available mode for the output. When the client binds to the output_device object, the server sends this event once for every available mode the output_device can be operated by. There will always be at least one event sent out on initial binding, which represents the current mode. Later if an output changes, its mode event is sent again for the eventual added modes and lastly the current mode. In other words, the current mode is always represented by the latest event sent with the current flag set. The size of a mode is given in physical hardware units of the output device. This is not necessarily the same as the output size in the global compositor space. For instance, the output may be scaled, as described in kde_output_device_v2.scale, or transformed, as described in kde_output_device_v2.transform. This event is sent after all other properties have been sent on binding to the output object as well as after any other output property change have been applied later on. This allows to see changes to the output properties as atomic, even if multiple events successively announce them. This event contains scaling geometry information that is not in the geometry event. It may be sent after binding the output object or if the output scale changes later. If it is not sent, the client should assume a scale of 1. A scale larger than 1 means that the compositor will automatically scale surface buffers by this amount when rendering. This is used for high resolution displays where applications rendering at the native resolution would be too small to be legible. It is intended that scaling aware clients track the current output of a surface, and if it is on a scaled output it should use wl_surface.set_buffer_scale with the scale of the output. That way the compositor can avoid scaling the surface, and the client can supply a higher detail image. The edid event encapsulates the EDID data for the outputdevice. The event is sent when binding to the output object. The EDID data may be empty, in which case this event is sent anyway. If the EDID information is empty, you can fall back to the name et al. properties of the outputdevice. The enabled event notifies whether this output is currently enabled and used for displaying content by the server. The event is sent when binding to the output object and whenever later on an output changes its state by becoming enabled or disabled. The uuid can be used to identify the output. It's controlled by the server entirely. The server should make sure the uuid is persistent across restarts. An empty uuid is considered invalid. Serial ID of the monitor, sent on startup before the first done event. EISA ID of the monitor, sent on startup before the first done event. Describes what capabilities this device has. What capabilities this device has, sent on startup before the first done event. Overscan value of the monitor in percent, sent on startup before the first done event. Describes when the compositor may employ variable refresh rate What policy the compositor will employ regarding its use of variable refresh rate. Whether full or limited color range should be used What rgb range the compositor is using for this output Name of the output, it's useful to cross-reference to an zxdg_output_v1 and ultimately QScreen Whether or not high dynamic range is enabled for this output If high dynamic range is used, this value defines the brightness in nits for content that's in standard dynamic range format. Note that while the value is in nits, that doesn't necessarily translate to the same brightness on the screen. Whether or not the use of a wide color gamut is enabled for this output This can be used to provide the colors users assume sRGB applications should have based on the default experience on many modern sRGB screens. This is the brightness modifier of the output. It doesn't specify any absolute values, but is merely a multiplier on top of other brightness values, like sdr_brightness and brightness_metadata. 0 is the minimum brightness (not completely dark) and 10000 is the maximum brightness. This is currently only supported / meaningful while HDR is active. This object describes an output mode. Some heads don't support output modes, in which case modes won't be advertised. Properties sent via this interface are applied atomically via the kde_output_device.done event. No guarantees are made regarding the order in which properties are sent. This event describes the mode size. The size is given in physical hardware units of the output device. This is not necessarily the same as the output size in the global compositor space. For instance, the output may be scaled or transformed. This event describes the mode's fixed vertical refresh rate. It is only sent if the mode has a fixed refresh rate. This event advertises this mode as preferred. The compositor will destroy the object immediately after sending this event, so it will become invalid and the client should release any resources associated with it. kylin-wayland-compositor/protocols/ukui-output-v1.xml0000664000175000017500000000537115160460057022076 0ustar fengfeng This interface is a manager that allows show some additional output info. This event introduces a new output. This happens whenever a new output appears (e.g. a monitor is plugged in) or after the ukui_output_management_v1 is bound. This event is sent after all information has been sent after binding to the output manager object and after any subsequent changes. This applies to child output as well. A output has some read-only properties: name, usable_area. These cannot be changed by clients. This event describes the name of output. The name event is sent after a ukui_output_v1 object is created. This event is only sent once per object, and the name does not change over the lifetime of the ukui_output_v1 object. This event describes the usable_area of output. This event indicates that the ukui_output_v1 is no longer available. Clients should send a destroy request and release any resources associated with it. kylin-wayland-compositor/protocols/kde-output-management-v2.xml0000664000175000017500000003430115160460057023772 0ustar fengfeng SPDX-FileCopyrightText: 2021 Méven Car SPDX-FileCopyrightText: 2023 Xaver Hugl SPDX-License-Identifier: Expat-CMU ]]> This interface enables clients to set properties of output devices for screen configuration purposes via the server. To this end output devices are referenced by global kde_output_device_v2 objects. outputmanagement (wl_global) -------------------------- request: * create_configuration -> outputconfiguration (wl_resource) outputconfiguration (wl_resource) -------------------------- requests: * enable(outputdevice, bool) * mode(outputdevice, mode) * transformation(outputdevice, flag) * position(outputdevice, x, y) * apply events: * applied * failed The server registers one outputmanagement object as a global object. In order to configure outputs a client requests create_configuration, which provides a resource referencing an outputconfiguration for one-time configuration. That way the server knows which requests belong together and can group them by that. On the outputconfiguration object the client calls for each output whether the output should be enabled, which mode should be set (by referencing the mode from the list of announced modes) and the output's global position. Once all outputs are configured that way, the client calls apply. At that point and not earlier the server should try to apply the configuration. If this succeeds the server emits the applied signal, otherwise the failed signal, such that the configuring client is noticed about the success of its configuration request. Through this design the interface enables atomic output configuration changes if internally supported by the server. Request an outputconfiguration object through which the client can configure output devices. outputconfiguration is a client-specific resource that can be used to ask the server to apply changes to available output devices. The client receives a list of output devices from the registry. When it wants to apply new settings, it creates a configuration object from the outputmanagement global, writes changes through this object's enable, scale, transform and mode calls. It then asks the server to apply these settings in an atomic fashion, for example through Linux' DRM interface. The server signals back whether the new settings have applied successfully or failed to apply. outputdevice objects are updated after the changes have been applied to the hardware and before the server side sends the applied event. These error can be emitted in response to kde_output_configuration_v2 requests. Mark the output as enabled or disabled. Sets the mode for a given output. Sets the transformation for a given output. Sets the position for this output device. (x,y) describe the top-left corner of the output in global space, whereby the origin (0,0) of the global space has to be aligned with the top-left corner of the most left and in case this does not define a single one the top output. There may be no gaps or overlaps between outputs, i.e. the outputs are stacked horizontally, vertically, or both on each other. Sets the scaling factor for this output device. Asks the server to apply property changes requested through this outputconfiguration object to all outputs on the server side. The output configuration can be applied only once. The already_applied protocol error will be posted if the apply request is called the second time. Sent after the server has successfully applied the changes. . Sent if the server rejects the changes or failed to apply them. Set the overscan value of this output device with a value in percent. Describes when the compositor may employ variable refresh rate Set what policy the compositor should employ regarding its use of variable refresh rate. Whether this output should use full or limited rgb. Whether full or limited color range should be used The order of outputs can be used to assign desktop environment components to a specific screen, see kde_output_order_v1 for details. The priority is 1-based for outputs that will be enabled after this changeset is applied, all outputs that are disabled need to have the index set to zero. Sets whether or not the output should be set to HDR mode. Sets the brightness of standard dynamic range content in nits. Only has an effect while the output is in HDR mode. Note that while the value is in nits, that doesn't necessarily translate to the same brightness on the screen. Whether or not the output should use a wide color gamut This can be used to provide the colors users assume sRGB applications should have based on the default experience on many modern sRGB screens. Set the brightness modifier of the output. It doesn't specify any absolute values, but is merely a multiplier on top of other brightness values, like sdr_brightness and brightness_metadata. 0 is the minimum brightness (not completely dark) and 10000 is the maximum brightness. This is currently only supported / meaningful while HDR is active. kylin-wayland-compositor/protocols/virtual-keyboard-unstable-v1.xml0000664000175000017500000001142615160460057024660 0ustar fengfeng Copyright © 2008-2011 Kristian Høgsberg Copyright © 2010-2013 Intel Corporation Copyright © 2012-2013 Collabora, Ltd. Copyright © 2018 Purism SPC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The virtual keyboard provides an application with requests which emulate the behaviour of a physical keyboard. This interface can be used by clients on its own to provide raw input events, or it can accompany the input method protocol. Provide a file descriptor to the compositor which can be memory-mapped to provide a keyboard mapping description. Format carries a value from the keymap_format enumeration. A key was pressed or released. The time argument is a timestamp with millisecond granularity, with an undefined base. All requests regarding a single object must share the same clock. Keymap must be set before issuing this request. State carries a value from the key_state enumeration. Notifies the compositor that the modifier and/or group state has changed, and it should update state. The client should use wl_keyboard.modifiers event to synchronize its internal state with seat state. Keymap must be set before issuing this request. A virtual keyboard manager allows an application to provide keyboard input events as if they came from a physical keyboard. Creates a new virtual keyboard associated to a seat. If the compositor enables a keyboard to perform arbitrary actions, it should present an error when an untrusted client requests a new keyboard. kylin-wayland-compositor/protocols/plasma-window-management.xml0000664000175000017500000004614315160460057024135 0ustar fengfeng This interface manages application windows. It provides requests to show and hide the desktop and emits an event every time a window is created so that the client can use it to manage the window. Only one client can bind this interface at a time. Tell the compositor to show/hide the desktop. Deprecated: use get_window_by_uuid This event will be sent whenever the show desktop mode changes. E.g. when it is entered or left. On binding the interface the current state is sent. This event will be sent immediately after a window is mapped. This event will be sent when stacking order changed and on bind. With version 17 this event is deprecated and will no longer be sent. This event will be sent when stacking order changed and on bind. With version 17 this event is deprecated and will no longer be sent. This event will be sent immediately after a window is mapped. This event will be sent when stacking order changed. Manages and control an application window. Only one client can bind this interface at a time. Set window state. Values for state argument are described by org_kde_plasma_window_management.state and can be used together in a bitfield. The flags bitfield describes which flags are supposed to be set, the state bitfield the value for the set flags Deprecated: use enter_virtual_desktop Maps the window to a different virtual desktop. To show the window on all virtual desktops, call the org_kde_plasma_window.set_state request and specify a on_all_desktops state in the bitfield. Sets the geometry of the taskbar entry for this window. The geometry is relative to a panel in particular. Remove the task geometry information for a particular panel. Close this window. Request an interactive move for this window. Request an interactive resize for this window. Removes the resource bound for this org_kde_plasma_window. The compositor will write the window icon into the provided file descriptor. The data is a serialized QIcon with QDataStream. This event will be sent as soon as the window title is changed. This event will be sent as soon as the application identifier is changed. This event will be sent as soon as the window state changes. Values for state argument are described by org_kde_plasma_window_management.state. DEPRECATED: use virtual_desktop_entered and virtual_desktop_left instead This event will be sent when a window is moved to another virtual desktop. It is not sent if it becomes visible on all virtual desktops though. This event will be sent whenever the themed icon name changes. May be null. This event will be sent immediately after the window is closed and its surface is unmapped. This event will be sent immediately after all initial state been sent to the client. If the Plasma window is already unmapped, the unmapped event will be sent before the initial_state event. This event will be sent whenever the parent window of this org_kde_plasma_window changes. The passed parent is another org_kde_plasma_window and this org_kde_plasma_window is a transient window to the parent window. If the parent argument is null, this org_kde_plasma_window does not have a parent window. This event will be sent whenever the window geometry of this org_kde_plasma_window changes. The coordinates are in absolute coordinates of the windowing system. This event will be sent whenever the icon of the window changes, but there is no themed icon name. Common examples are Xwayland windows which have a pixmap based icon. The client can request the icon using get_icon. This event will be sent when the compositor has set the process id this window belongs to. This should be set once before the initial_state is sent. Make the window enter a virtual desktop. A window can enter more than one virtual desktop. if the id is empty or invalid, no action will be performed. RFC: do this with an empty id to request_enter_virtual_desktop? Make the window enter a new virtual desktop. If the server consents the request, it will create a new virtual desktop and assign the window to it. Make the window exit a virtual desktop. If it exits all desktops it will be considered on all of them. This event will be sent when the window has entered a new virtual desktop. The window can be on more than one desktop, or none: then is considered on all of them. This event will be sent when the window left a virtual desktop. If the window leaves all desktops, it can be considered on all. If the window gets manually added on all desktops, the server has to send virtual_desktop_left for every previous desktop it was in for the window to be really considered on all desktops. This event will be sent after the application menu for the window has changed. Make the window enter an activity. A window can enter more activity. If the id is empty or invalid, no action will be performed. Make the window exit a an activity. If it exits all activities it will be considered on all of them. This event will be sent when the window has entered an activity. The window can be on more than one activity, or none: then is considered on all of them. This event will be sent when the window left an activity. If the window leaves all activities, it will be considered on all. If the window gets manually added on all activities, the server has to send activity_left for every previous activity it was in for the window to be really considered on all activities. Requests this window to be displayed in a specific output. This event will be sent when the X11 resource name of the window has changed. This is only set for XWayland windows. The activation manager interface provides a way to get notified when an application is about to be activated. Destroy the activation manager object. The activation objects introduced by this manager object will be unaffected. Will be issued when an app is set to be activated. It offers an instance of org_kde_plasma_activation that will tell us the app_id and the extent of the activation. Notify the compositor that the org_kde_plasma_activation object will no longer be used. When this object is created, the compositor sends a window event for each window in the stacking order, and afterwards sends the done event and destroys this object. kylin-wayland-compositor/protocols/plasma-shell.xml0000664000175000017500000002747315160460057021630 0ustar fengfeng This interface is used by KF5 powered Wayland shells to communicate with the compositor and can only be bound one time. Create a shell surface for an existing surface. Only one shell surface can be associated with a given surface. An interface that may be implemented by a wl_surface, for implementations that provide the shell user interface. It provides requests to set surface roles, assign an output or set the position in output coordinates. On the server side the object is automatically destroyed when the related wl_surface is destroyed. On client side, org_kde_plasma_surface.destroy() must be called before destroying the wl_surface object. The org_kde_plasma_surface interface is removed from the wl_surface object that was turned into a shell surface with the org_kde_plasma_shell.get_surface request. The shell surface role is lost and wl_surface is unmapped. Assign an output to this shell surface. The compositor will use this information to set the position when org_kde_plasma_surface.set_position request is called. Move the surface to new coordinates. Coordinates are global, for example 50,50 for a 1920,0+1920x1080 output is 1970,50 in global coordinates space. Use org_kde_plasma_surface.set_output to assign an output to this surface. Assign a role to a shell surface. The compositor handles surfaces depending on their role. See the explanation below. This request fails if the surface already has a role, this means the surface role may be assigned only once. == Surfaces with splash role == Splash surfaces are placed above every other surface during the shell startup phase. The surfaces are placed according to the output coordinates. No size is imposed to those surfaces, the shell has to resize them according to output size. These surfaces are meant to hide the desktop during the startup phase so that the user will always see a ready to work desktop. A shell might not create splash surfaces if the compositor reveals the desktop in an alternative fashion, for example with a fade in effect. That depends on how much time the desktop usually need to prepare the workspace or specific design decisions. This specification doesn't impose any particular design. When the startup phase is finished, the shell will send the org_kde_plasma.desktop_ready request to the compositor. == Surfaces with desktop role == Desktop surfaces are placed below all other surfaces and are used to show the actual desktop view with icons, search results or controls the user will interact with. What to show depends on the shell implementation. The surfaces are placed according to the output coordinates. No size is imposed to those surfaces, the shell has to resize them according to output size. Only one surface per output can have the desktop role. == Surfaces with dashboard role == Dashboard surfaces are placed above desktop surfaces and are used to show additional widgets and controls. The surfaces are placed according to the output coordinates. No size is imposed to those surfaces, the shell has to resize them according to output size. Only one surface per output can have the dashboard role. == Surfaces with config role == A configuration surface is shown when the user wants to configure panel or desktop views. Only one surface per output can have the config role. TODO: This should grab the input like popup menus, right? == Surfaces with overlay role == Overlays are special surfaces that shows for a limited amount of time. Such surfaces are useful to display things like volume, brightness and status changes. Compositors may decide to show those surfaces in a layer above all surfaces, even full screen ones if so is desired. == Surfaces with notification role == Notification surfaces display informative content for a limited amount of time. The compositor may decide to show them in a corner depending on the configuration. These surfaces are shown in a layer above all other surfaces except for full screen ones. == Surfaces with lock role == The lock surface is shown by the compositor when the session is locked, users interact with it to unlock the session. Compositors should move lock surfaces to 0,0 in output coordinates space and hide all other surfaces for security sake. For the same reason it is recommended that clients make the lock surface as big as the screen. Only one surface per output can have the lock role. The panel is on top of other surfaces, windows cannot cover (full screen windows excluded). The panel is hidden automatically and restored when the mouse is over. Windows can cover the panel. Maximized windows take the whole screen space but the panel is above the windows. Set flags bitmask as described by the flag enum. Pass 0 to unset any flag, the surface will adjust its behavior to the default. Deprecated in Plasma 6. Setting this flag will have no effect. Applications should use layer shell where appropriate. Setting this bit to the window, will make it say it prefers to not be listed in the taskbar. Taskbar implementations may or may not follow this hint. A panel surface with panel_behavior auto_hide can perform this request to hide the panel on a screen edge without unmapping it. The compositor informs the client about the panel being hidden with the event auto_hidden_panel_hidden. The compositor will restore the visibility state of the surface when the pointer touches the screen edge the panel borders. Once the compositor restores the visibility the event auto_hidden_panel_shown will be sent. This event will also be sent if the compositor is unable to hide the panel. The client can also request to show the panel again with the request panel_auto_hide_show. A panel surface with panel_behavior auto_hide can perform this request to show the panel again which got hidden with panel_auto_hide_hide. By default various org_kde_plasma_surface roles do not take focus and cannot be activated. With this request the compositor can be instructed to pass focus also to this org_kde_plasma_surface. An auto-hiding panel got hidden by the compositor. An auto-hiding panel got shown by the compositor. Setting this bit will indicate that the window prefers not to be listed in a switcher. Request the initial position of this surface to be under the current cursor position. Has to be called before attaching any buffer to this surface. kylin-wayland-compositor/protocols/wlr-virtual-pointer-unstable-v1.xml0000664000175000017500000001535715160460057025351 0ustar fengfeng Copyright © 2019 Josef Gajdusek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol allows clients to emulate a physical pointer device. The requests are mostly mirror opposites of those specified in wl_pointer. The pointer has moved by a relative amount to the previous request. Values are in the global compositor space. The pointer has moved in an absolute coordinate frame. Value of x can range from 0 to x_extent, value of y can range from 0 to y_extent. A button was pressed or released. Scroll and other axis requests. Indicates the set of events that logically belong together. Source information for scroll and other axis. Stop notification for scroll and other axes. Discrete step information for scroll and other axes. This event allows the client to extend data normally sent using the axis event with discrete value. This object allows clients to create individual virtual pointer objects. Creates a new virtual pointer. The optional seat is a suggestion to the compositor. Creates a new virtual pointer. The seat and the output arguments are optional. If the seat argument is set, the compositor should assign the input device to the requested seat. If the output argument is set, the compositor should map the input device to the requested output. kylin-wayland-compositor/protocols/ukui-blur-v1.xml0000664000175000017500000001007315160461067021477 0ustar fengfeng This protocol provides a way to improve visuals of translucent surfaces by blurring background behind them. Warning! The protocol described in this file is currently in the testing phase. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes can only be done by creating a new major version of the extension. Informs the server that the client will no longer be using this protocol object. Existing objects created by this object are not affected. Instantiate an interface extension for the given wl_surface to blur background behind it. If the given wl_surface already has a ukui_blur_surface_v1 object associated, the blur_exists protocol error will be raised. The blur object provides a way to specify a region behind a surface that should be blurred by the compositor. If the wl_surface associated with the ukui_blur_surface_v1 object has been destroyed, this object becomes inert. Informs the server that the client will no longer be using this protocol object. The blur region will be unset on the next commit. This request sets the region of the surface that will have its background blurred. The blur region is specified in the surface-local coordinates. The initial value for the blur region is empty. Setting the pending blur region has copy semantics, and the wl_region object can be destroyed immediately. A NULL wl_region causes the blur region to be set to infinite. The blur region is double-buffered state, and will be applied on the next wl_surface.commit. The blur algorithm is subject to compositor policies. Change blur algorithm level in compositor. The range of level is 1 to 15. The compositor default blur algorithm level is 5. The blur level will be applied on the next wl_surface.commit. The opacity of blur, value between 0 and 1.0. The mixture of original background and blur. Default value is 1.0. The blur opacity will be applied on the next wl_surface.commit. kylin-wayland-compositor/protocols/ukui-effect-v1.xml0000664000175000017500000002157215160460057021773 0ustar fengfeng This interface adds or modifies the surface effect. It provides requests to create effect animation and effect for the surface. Only one effect can be associated for the surface on the same stage. Create a new private effect animation use bezier curves. X1 and y1 correspond to the first control point (P1), x2 and y2 correspond to the second control point (P2). Need to divide the value by 100. The new created animation can only be used by itself. Create a fade effect for an existing surface. Values for stage argument are described by surface_effect_v1.stage and can be used together in a bitfield, these stages will use the same parameters. Create a slide effect for an existing surface. Values for stage argument are described by surface_effect_v1.stage and can be used together in a bitfield, these stages will use the same parameters. Emitted after bind surface_effect_v1. Every builtin animation will send an event. This event is sent after all builtin animation curves have been sent on binding to the surface_effect_v1 object. This request indicates that the client will no longer use this animation object. An 'set_animation' request must be called once per fade effect. It will use default curve when curve set to be null. Duration is the time between effect start and end. The default value of fade effect has two different values. Duration is 220ms when used at view map stage, and is 180ms when used at view unmap stage. An 'set_scale' request must be called once per scale effect. If value is 80, means scaled to 80% of the original width or height. Value range is 0-100. If value is 80, means the alpha value of surface color is 0.8. It will use default value when not call this request to set opacity. This request indicates that the client will no longer use this effect object. Duration is the time between slide effect start and end. An 'set_position' request must be called once per slide effect. Value range is 0-100. If value is 80, means the alpha value of surface color is 0.8. It will use default value when not call this request to set opacity. The default value is slide_on_node. location is edge of the node, offset is relative to edge of the node when type is slide_on_node. location is edge of the screen, offset is relative to edge of the screen when type is slide_on_output. This request indicates that the client will no longer use this effect object. kylin-wayland-compositor/protocols/ukui-window-management.xml0000664000175000017500000004170215160460057023631 0ustar fengfeng This interface manages application windows. It provides requests to show and hide the desktop and emits an event every time a window is created so that the client can use it to manage the window. Tell the compositor to show/hide the desktop. This event will be sent whenever the show desktop mode changes. E.g. when it is entered or left. On binding the interface the current state is sent. This event will be sent when stacking order changed and on bind This event will be sent immediately after a window is mapped. Manages and control an application window. Set window state. Can set multiple states at once. Values for state argument are described by ukui_window_management.state and can be used together in a bitfield. The flags bitfield describes which flags are supposed to be set, the state bitfield the value for the set flags. The state argument is not a boolean value, but a bitfield, so it is possible to set multiple states at once. Only the states that are set in the flags bitfield will be changed. For example: If flags was 0x14(keep_above|maximized) and state was 0x04(maximized), the window would be maximized, but not keep above. If flags was 0x04(maximized) and state was 0x14(keep_above|maximized), the window would be maximized but not change keep above state. Sets the geometry of the taskbar/desktop entry for this window. The geometry is relative to a panel/desktop in particular. Sets the geometry of the taskbar entry for this window. The geometry is relative to a panel in particular. Remove the task geometry information for a particular panel. Close this window. Request an interactive move for this window. The pointer will move to the center of the window and move with the pointer. When mouse button is released, the interactive move ends. Request an interactive resize for this window. The pointer will move to window right bottom corner and resize with the pointer. When mouse button is released, the interactive resize ends. Removes the resource bound for this ukui_window. The compositor will write the window icon into the provided file descriptor. The data is a serialized QIcon with QDataStream. This event will be sent as soon as the window title is changed. This event will be sent as soon as the application identifier is changed. This event will be sent as soon as the window state changes. Values for state argument are described by ukui_window_management.state. It contains the whole new state of the window. This event will be sent whenever the themed icon name changes. May be null. This event will be sent immediately after the window is closed and its surface is unmapped. This event will be sent immediately after all initial state been sent to the client. If the Plasma window is already unmapped, the unmapped event will be sent before the initial_state event. This event will be sent whenever the parent window of this ukui_window changes. The passed parent is another ukui_window and this ukui_window is a transient window to the parent window. If the parent argument is null, this ukui_window does not have a parent window. This event will be sent whenever the window geometry of this ukui_window changes. The coordinates are in absolute coordinates of the windowing system. This event will be sent whenever the icon of the window changes, but there is no themed icon name. Common examples are Xwayland windows which have a pixmap based icon. The client can request the icon using get_icon. This event will be sent when the compositor has set the process id this window belongs to. This should be set once before the initial_state is sent. Make the window enter a virtual desktop. A window can enter more than one virtual desktop. if the id is empty or invalid, no action will be performed. RFC: do this with an empty id to request_enter_virtual_desktop? Make the window enter a new virtual desktop. If the server consents the request, it will create a new virtual desktop and assign the window to it. Make the window exit a virtual desktop. If it exits all desktops it will be considered on all of them. This event will be sent when the window has entered a new virtual desktop. The window can be on more than one desktop, or none: then is considered on all of them. This event will be sent when the window left a virtual desktop. If the window leaves all desktops, it can be considered on all. If the window gets manually added on all desktops, the server has to send virtual_desktop_left for every previous desktop it was in for the window to be really considered on all desktops. This event will be sent after the application menu for the window has changed. Make the window enter an activity. A window can enter more activity. If the id is empty or invalid, no action will be performed. Make the window exit a an activity. If it exits all activities it will be considered on all of them. This event will be sent when the window has entered an activity. The window can be on more than one activity, or none: then is considered on all of them. This event will be sent when the window left an activity. If the window leaves all activities, it will be considered on all. If the window gets manually added on all activities, the server has to send activity_left for every previous activity it was in for the window to be really considered on all activities. Requests this window to be displayed in a specific output. This event will be sent when the X11 resource name of the window has changed. This is only set for XWayland windows. Tell the compositor to highlight this window. Tell the compositor to unset highlight window. The activation manager interface provides a way to get notified when an application is about to be activated. Destroy the activation manager object. The activation objects introduced by this manager object will be unaffected. Will be issued when an app is set to be activated. It offers an instance of org_ukui_activation that will tell us the app_id and the extent of the activation. Notify the compositor that the org_ukui_activation object will no longer be used. kylin-wayland-compositor/protocols/drm.xml0000664000175000017500000001744715160460057020030 0ustar fengfeng Copyright © 2008-2011 Kristian Høgsberg Copyright © 2010-2011 Intel Corporation Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that\n 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. Bitmask of capabilities. kylin-wayland-compositor/protocols/wlr-output-power-management-unstable-v1.xml0000664000175000017500000001273315160460057027004 0ustar fengfeng Copyright © 2019 Purism SPC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol allows clients to control power management modes of outputs that are currently part of the compositor space. The intent is to allow special clients like desktop shells to power down outputs when the system is idle. To modify outputs not currently part of the compositor space see wlr-output-management. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This interface is a manager that allows creating per-output power management mode controls. Create a output power management mode control that can be used to adjust the power management mode for a given output. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object offers requests to set the power management mode of an output. Set an output's power save mode to the given mode. The mode change is effective immediately. If the output does not support the given mode a failed event is sent. Report the power management mode change of an output. The mode event is sent after an output changed its power management mode. The reason can be a client using set_mode or the compositor deciding to change an output's mode. This event is also sent immediately when the object is created so the client is informed about the current power management mode. This event indicates that the output power management mode control is no longer valid. This can happen for a number of reasons, including: - The output doesn't support power management - Another client already has exclusive power management mode control for this output - The output disappeared Upon receiving this event, the client should destroy this object. Destroys the output power management mode control object. kylin-wayland-compositor/protocols/plasma-virtual-desktop.xml0000664000175000017500000001254315160460057023646 0ustar fengfeng Given the id of a particular virtual desktop, get the corresponding org_kde_plasma_virtual_desktop which represents only the desktop with that id. Ask the server to create a new virtual desktop, and position it at a specified position. If the position is zero or less, it will be positioned at the beginning, if the position is the count or more, it will be positioned at the end. Ask the server to get rid of a virtual desktop, the server may or may not acconsent to the request. This event is sent after all other properties has been sent after binding to the desktop manager object and after any other property changes done after that. This allows changes to the org_kde_plasma_virtual_desktop_management properties to be seen as atomic, even if they happen via multiple events. Request the server to set the status of this desktop to active: The server is free to consent or deny the request. This will be the new "current" virtual desktop of the system. The format of the id is decided by the compositor implementation. A desktop id univocally identifies a virtual desktop and must be guaranteed to never exist two desktops with the same id. The format of the string id is up to the server implementation. The desktop will be the new "current" desktop of the system. The server may support either one virtual desktop active at a time, or other combinations such as one virtual desktop active per screen. Windows associated to this virtual desktop will be shown. Windows that were associated only to this desktop will be hidden. This event is sent after all other properties has been sent after binding to the desktop object and after any other property changes done after that. This allows changes to the org_kde_plasma_virtual_desktop properties to be seen as atomic, even if they happen via multiple events. This virtual desktop has just been removed by the server: All windows will lose the association to this desktop. kylin-wayland-compositor/protocols/idle.xml0000664000175000017500000000341015160460057020144 0ustar fengfeng This interface allows to monitor user idle time on a given seat. The interface allows to register timers which trigger after no user activity was registered on the seat for a given interval. It notifies when user activity resumes. This is useful for applications wanting to perform actions when the user is not interacting with the system, e.g. chat applications setting the user as away, power management features to dim screen, etc.. kylin-wayland-compositor/protocols/blur.xml0000664000175000017500000000175515160461067020207 0ustar fengfeng kylin-wayland-compositor/protocols/wlr-layer-shell-unstable-v1.xml0000664000175000017500000004403615160460057024422 0ustar fengfeng 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. kylin-wayland-compositor/protocols/slide.xml0000664000175000017500000000312315160460057020330 0ustar fengfeng Ask the compositor to move the surface from a location to another with a slide animation. The from argument provides a clue about where the slide animation begins, offset is the distance from screen edge to begin the animation. kylin-wayland-compositor/protocols/wlr-screencopy-unstable-v1.xml0000664000175000017500000002366615160460057024361 0ustar fengfeng Copyright © 2018 Simon Ser Copyright © 2019 Andri Yngvason Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol allows clients to ask the compositor to copy part of the screen content to a client buffer. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This object is a manager which offers requests to start capturing from a source. Capture the next frame of an entire output. Capture the next frame of an output's region. The region is given in output logical coordinates, see xdg_output.logical_size. The region will be clipped to the output's extents. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object represents a single frame. When created, a series of buffer events will be sent, each representing a supported buffer type. The "buffer_done" event is sent afterwards to indicate that all supported buffer types have been enumerated. The client will then be able to send a "copy" request. If the capture is successful, the compositor will send a "flags" followed by a "ready" event. For objects version 2 or lower, wl_shm buffers are always supported, ie. the "buffer" event is guaranteed to be sent. If the capture failed, the "failed" event is sent. This can happen anytime before the "ready" event. Once either a "ready" or a "failed" event is received, the client should destroy the frame. Provides information about wl_shm buffer parameters that need to be used for this frame. This event is sent once after the frame is created if wl_shm buffers are supported. Copy the frame to the supplied buffer. The buffer must have a the correct size, see zwlr_screencopy_frame_v1.buffer and zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a supported format. If the frame is successfully copied, a "flags" and a "ready" events are sent. Otherwise, a "failed" event is sent. Provides flags about the frame. This event is sent once before the "ready" event. Called as soon as the frame is copied, indicating it is available for reading. This event includes the time at which presentation happened at. The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, each component being an unsigned 32-bit value. Whole seconds are in tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, and the additional fractional part in tv_nsec as nanoseconds. Hence, for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part may have an arbitrary offset at start. After receiving this event, the client should destroy the object. This event indicates that the attempted frame copy has failed. After receiving this event, the client should destroy the object. Destroys the frame. This request can be sent at any time by the client. Same as copy, except it waits until there is damage to copy. This event is sent right before the ready event when copy_with_damage is requested. It may be generated multiple times for each copy_with_damage request. The arguments describe a box around an area that has changed since the last copy request that was derived from the current screencopy manager instance. The union of all regions received between the call to copy_with_damage and a ready event is the total damage since the prior ready event. Provides information about linux-dmabuf buffer parameters that need to be used for this frame. This event is sent once after the frame is created if linux-dmabuf buffers are supported. This event is sent once after all buffer events have been sent. The client should proceed to create a buffer of one of the supported types, and send a "copy" request. kylin-wayland-compositor/protocols/kywc-capture-v1.xml0000664000175000017500000001670115160460057022200 0ustar fengfeng This object is a manager which offers requests to start capturing from a source. Capture the next frame of an entire output. Capture the next frame of an entire workspace in one output. Capture the next frame of an entire toplevel. Capture the cursor of the seat and frame. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. Destroys the frame. This request can be sent at any time by the client. This event indicates that the attempted frame copy has failed. After receiving this event, the client should destroy the object. Sent if the compositor cancels the frame because the source has gone. Upon receiving this event, the client should destroy this object. Provides information about buffer parameters that need to be used for this frame. Mark the buffer is not used by client. This request is sent after buffer event. Provides information about buffer parameters with the plane index that need to be used for this frame. When the seat or frame is destroyed, this object becomes inert. Destroys the object. This request can be sent at any time by the client. This event indicates that when a cursor enters the frame. It shall be generated before the "position" and "hotspot" events. If the pointer object referenced by the seat does not exist, this event will not be triggered. This event indicates that when a cursor leaves the frame. No "position" or "hotspot" event is generated for the cursor until the cursor enters the frame again. If the pointer object referenced by the seat is removed, this event will be sent. This event informs the cursor position. The given position is relative to the frame's top left corner in transformed buffer pixel coordinates. Note, if the frame parameter is set to null in the capture_cursor request, the given position coordinates are global. This event informs the cursor hotspot. The hotspot describes the offset between the cursor image and the position of the input device. The parameters x and y represent the offset of the new hotspot relative to the origin of the cursor image, which is typically the top-left corner of the image. kylin-wayland-compositor/protocols/kde-primary-output-v1.xml0000664000175000017500000000171615160460057023344 0ustar fengfeng SPDX-License-Identifier: Expat-CMU ]]> Protocol for telling which is the primary display among the selection of enabled outputs. Specifies which output is the primary one identified by their uuid. See kde_output_device_v2 uuid event for more information about it. kylin-wayland-compositor/protocols/kywc-toplevel-v1.xml0000664000175000017500000003036015160460057022364 0ustar fengfeng A toplevel is defined as a surface with a role similar to xdg_toplevel. XWayland surfaces may be treated like toplevels in this protocol. After a client binds the global, each mapped toplevel window will be sent using the toplevel event. This event is emitted whenever a new toplevel window is created. It is emitted for all toplevels, regardless of the app that has created them. All initial properties of the toplevel (identifier, title, app_id) will be sent immediately after this event using the corresponding events for kywc_toplevel. The compositor will use the done event to indicate when all data has been sent. This event indicates that the compositor is done sending events to this object. The client should destroy the object. See destroy for more information. The compositor must not send any more toplevel events after this event. This request indicates that the client no longer wishes to receive events for new toplevels. The Wayland protocol is asynchronous, meaning the compositor may send further toplevel events until the stop request is processed. The client should wait for a finished event before destroying this object. A kywc_toplevel_v1 object represents a mapped toplevel window. A single app may have multiple mapped toplevels. This request should be used when the client will no longer use the toplevel or after the closed event has been received to allow destruction of the object. The server will emit no further events on the kywc_toplevel_v1 after this event. Any requests received aside from the destroy request must be ignored. Upon receiving this event, the client should destroy the handle. This event is sent after all changes in the toplevel state have been sent. The title of the toplevel has changed. The app id of the topleve.done for details. This event is emitted whenever the output that the largest area of this toplevel is displayed on has changed. This event is emitted whenever the toplevel becomes visible on the given workspace. A toplevel may be visible on multiple workspaces. This event is emitted whenever the toplevel stops being visible on the given workspace. It is guaranteed that an entered-workspace event with the same workspace has been emitted before this event. Describes what capabilities this toplevel has. What capabilities this toplevel has, sent on startup before the first done event. The different states that a toplevel can have. This event is emitted immediately after the kywc_toplevel_v1 is created and each time the toplevel state changes, either because of a compositor action or because of a request in this protocol. This event is emitted whenever the parent of the toplevel changes. No event is emitted when the parent handle is destroyed by the client. This event will be sent whenever the icon changes. May be null. This event will be sent whenever the toplevel geometry changes. Requests that the toplevel be maximized. If the maximized state actually changes, this will be indicated by the state event. Requests that the toplevel be unmaximized. If the maximized state actually changes, this will be indicated by the state event. Requests that the toplevel be minimized. If the minimized state actually changes, this will be indicated by the state event. Requests that the toplevel be unminimized. If the minimized state actually changes, this will be indicated by the state event. Requests that the toplevel be fullscreened on the given output. If the fullscreen state and/or the outputs the toplevel is visible on actually change, this will be indicated by the state and output_enter/leave events. The output parameter is only a hint to the compositor. Also, if output is NULL, the compositor should decide which output the toplevel will be fullscreened on, if at all. Requests that the toplevel be unfullscreened. If the fullscreen state actually changes, this will be indicated by the state event. Request that this toplevel be activated. There is no guarantee the toplevel will be actually activated. Send a request to the toplevel to close itself. The compositor would typically use a shell-specific method to carry out this request, for example by sending the xdg_toplevel.close event. However, this gives no guarantees the toplevel will actually be destroyed. If and when this happens, the closed event will be emitted. Make the toplevel enter a workspace. A toplevel can enter more than one workspace. if the id is empty or invalid, no action will be performed. Make the toplevel exit a workspace. Requests this toplevel to be displayed only in a specific workspace. Requests this toplevel to be displayed in a specific output. Move the window to new coordinates. Set the window to new size. Window size will be consider compositor window size limits. This event will only be emitted once, before the first "done" event occurs. kylin-wayland-compositor/protocols/dpms.xml0000664000175000017500000001016515160460057020177 0ustar fengfeng The Dpms manager allows to get a org_kde_kwin_dpms for a given wl_output. The org_kde_kwin_dpms provides the currently used VESA Display Power Management Signaling state (see https://en.wikipedia.org/wiki/VESA_Display_Power_Management_Signaling ). In addition it allows to request a state change. A compositor is not obliged to honor it and will normally automatically switch back to on state. Factory request to get the org_kde_kwin_dpms for a given wl_output. This interface provides information about the VESA DPMS state for a wl_output. It gets created through the request get on the org_kde_kwin_dpms_manager interface. On creating the resource the server will push whether DPSM is supported for the output, the currently used DPMS state and notifies the client through the done event once all states are pushed. Whenever a state changes the set of changes is committed with the done event. This event gets pushed on binding the resource and indicates whether the wl_output supports DPMS. There are operation modes of a Wayland server where DPMS might not make sense (e.g. nested compositors). This mode gets pushed on binding the resource and provides the currently used DPMS mode. It also gets pushed if DPMS is not supported for the wl_output, in that case the value will be On. The event is also pushed whenever the state changes. This event gets pushed on binding the resource once all other states are pushed. In addition it gets pushed whenever a state changes to tell the client that all state changes have been pushed. Requests that the compositor puts the wl_output into the passed mode. The compositor is not obliged to change the state. In addition the compositor might leave the mode whenever it seems suitable. E.g. the compositor might return to On state on user input. The client should not assume that the mode changed after requesting a new mode. Instead the client should listen for the mode event. kylin-wayland-compositor/protocols/kywc-workspace-v1.xml0000664000175000017500000001527115160460057022534 0ustar fengfeng Workspaces, also called virtual desktops, are groups of surfaces. A compositor with a concept of workspaces may only show some such groups of surfaces (those of 'active' workspaces) at a time. 'Activating' a workspace is a request for the compositor to display that workspace's surfaces as normal, whereas the compositor may hide or otherwise de-emphasise surfaces that are associated only with 'inactive' workspaces. After a client binds the kywc_workspace_manager_v1, each workspace will be sent via the workspace event. This event is emitted whenever a new workspace has been created. All initial details of the workspace (name, position, activated) will be sent immediately after this event via the corresponding events in kywc_workspace_v1. Request that the compositor create a new workspace with the given name and position it at a specified position. If the position is zero or less, it will be positioned at the beginning, if the position is the count or more, it will be positioned at the end. There is no guarantee that the compositor will create a new workspace, or that the created workspace will have the provided name and position. This event is sent after all changes in all workspaces have been sent. This allows changes to one or more kywc_workspace_v1 properties to be seen as atomic, even if they happen via multiple events. This event indicates that the compositor is done sending events to the kywc_workspace_manager_v1. The server will destroy the object immediately after sending this request, so it will become invalid and the client should free any resources associated with it. Indicates the client no longer wishes to receive events for new workspace. However the compositor may emit further workspace events, until the finished event is emitted. The compositor is expected to send the finished event eventually once the stop request has been processed. The client must not send any more requests after this one. A kywc_workspace_v1 object represents a workspace that handles a group of surfaces. Each workspace has a name, conveyed to the client with the name event; and a position, conveyed to the client with the position event. The client may request that the compositor activate or deactivate the workspace. This event is emitted immediately after the kywc_workspace_v1 is created and whenever the name of the workspace changes. The workspace will be the new "current" workspace of the system. surfaces that were associated only to this workspace will be hidden. This event means the kywc_workspace_v1 has been destroyed. It is guaranteed there won't be any more events for this kywc_workspace_v1. The kywc_workspace_v1 becomes inert so any requests will be ignored except the destroy request. Destroys the kywc_workspace_v1 object. This request should be called either when the client does not want to use the workspace object any more or after the remove event to finalize the destruction of the object. If the position is zero or less, it will be positioned at the beginning, if the position is the count or more, it will be positioned at the end. Request that this workspace be activated. There is no guarantee the workspace will be actually activated, and behaviour may be compositor-dependentption Request that this workspace be removed. There is no guarantee the workspace will be actually removed. Set the workspace to new name. kylin-wayland-compositor/protocols/ukui-startup-v2.xml0000664000175000017500000000443615160460057022242 0ustar fengfeng This interface manages startup infos. The startup_info object provides a way sets the startup geometry of the taskbar/desktop entry for app. The object will auto destroy when arrive the retire time. The retire time is 5000ms. Sets the startup geometry of the taskbar/desktop entry. Coordinates are global. The pid determines which process will use the startup_geometry. The child process of this process will also use the startup_geometry. The appid determines which app should use the startup_geometry. It only uses when not set pid or not find the app from pid. Must call destroy after set info completely. kylin-wayland-compositor/protocols/kde-keystate.xml0000664000175000017500000000261715160460057021631 0ustar fengfeng SPDX-License-Identifier: LGPL-2.1-or-later ]]> Keeps track of the states of the different keys that have a state attached to it. kylin-wayland-compositor/protocols/ukui-shell-v1.xml0000664000175000017500000003477115160461067021655 0ustar fengfeng This interface is used by UKUI Wayland shells to communicate with the compositor and can only be bound one time. Create a shell surface for an existing surface. Only one shell surface can be associated with a given surface. Should create ukui_surface_v1 before create xdg_surface and surface map. Request get the output under cursor per seat. Send current_output and done event after this request. Create a shell decoration for an existing surface. Only one shell decoration can be associated with a given surface. This request needs be called before surface map. Emitted after bind ukui_shell or receive get_current_output request. This event is sent after all information have been sent on binding to the ukui shell object. An interface that may be implemented by a wl_surface, for implementations that provide the shell user interface. It provides requests to set surface roles, state or set the position in global coordinates. On the server side the object is automatically destroyed when the related wl_surface is destroyed. On client side, ukui_surface_v1.destroy() must be called before destroying the wl_surface object. The ukui_surface_v1 interface is removed from the wl_surface object that was turned into a shell surface with the ukui_shell_v1.get_surface request. Move the surface to new coordinates. Coordinates are global. Setting this will say the surface prefers to not be listed in the taskbar. This request needs be called before surface map. Setting this will say the surface prefers to not be listed in the switcher. This request needs be called before surface map. Assign a role to a shell surface. The compositor handles surfaces depending on their role. This request needs be called before surface map. Set surface state. Can set multiple states at once. Values for state argument are described by ukui_surface.state and can be used together in a bitfield. The flags bitfield describes which flags are supposed to be set, the state bitfield the value for the set flags. The state argument is not a boolean value, but a bitfield, so it is possible to set multiple states at once. Only the states that are set in the flags bitfield will be changed. The keep_above and keep_below can only used for the normal window. For example: If flags was 0x12(movable|maximizable) and state was 0x02(maximizable), the window would be maximizable, but not movable. Setting this bit will indicate that the panel area not to be calculate in output usable area. Request the initial position of this surface to be under the current cursor position. This request needs be called before surface map. This request makes the created surface take an explicit keyboard grab. This keyboard grab will be dismissed when the client destroys the surface. Grab all keyboard when seat is set to null. Setting server decoration icon by icon name. Set icon name to null will use app_id to find icon. This request needs be called before surface map. This event informs the client that the position of the surface is about to change. This request makes the created surface to be activated. There is no guarantee the surface will be actually activated, and behaviour may be compositor-dependentption. Sets the startup geometry of the taskbar/desktop entry for this surface. Coordinates are global. One established way of doing this is through the UKUI_SURFACE_STARTUP_GEOMETRY environment variable of a newly launched child process. The child process should unset the environment variable again right after reading it out in order to avoid propagating it to other child processes. This request needs be called before surface map. This request is for showing tile flyout on the client side decoration. The parameters represent a mouse region, tile flyout will autohide when mouse leave the region. Coordinates are relative to the surface. Use default seat when seat is set to null. This request needs be called after surface map. This event will be sent as soon as the surface state changes. Values for state argument are described by ukui_surface_v1.surface_state. It contains the whole new state of the window. Set the restore geometry for this surface. The restore geometry is used for window doing restore from maximized/fullscreen state. Coordinates are global. Move the surface to new coordinates. Coordinates are global. The position will be applied on the next wl_surface.commit. It provides requests to set surface need to draw corner/shadow/border or not. On the server side the object is automatically destroyed when the related wl_surface is destroyed. On client side, ukui_decoration_v1.destroy() must be called before destroying the wl_surface object. Setting this will say the surface prefers not to show titlebar. This request needs be called before surface map. The flags bitfield describes which components of decoration are supposed to be set and can be set together in a bitfield. Draw decoration will be based on the flags. This flags needs be called before surface map and become immutable when surface is mapped. This request is only for xdg_popup now. kylin-wayland-compositor/src/0000775000175000017500000000000015160461067015254 5ustar fengfengkylin-wayland-compositor/src/config/0000775000175000017500000000000015160461067016521 5ustar fengfengkylin-wayland-compositor/src/config/kde_input.c0000664000175000017500000007622315160461067020661 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include "config_p.h" #include "input/input.h" #include "kywc/output.h" #include "server.h" #include "util/dbus.h" static const char *service_path = "/org/kde/KWin/InputDevice"; static const char *kde_input_path = "/org/kde/KWin/InputDevice/"; static const char *service_interface = "org.kde.KWin.InputDeviceManager"; static const char *kde_input_interface = "org.kde.KWin.InputDevice"; #define KDE_PROP(name, type, read) \ SD_BUS_PROPERTY(name, type, read, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) #define KDE_WPROP(name, type, read, write) \ SD_BUS_WRITABLE_PROPERTY(name, type, read, write, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) struct kde_input { struct wl_list link; struct dbus_object *dbus; char *sys_name; struct input *input; struct wl_listener destroy; }; struct kde_input_manager { struct wl_list inputs; struct config *config; struct wl_listener new_input; struct wl_listener destroy; }; static struct kde_input_manager *kde_input_manager = NULL; static int current_state(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { CK(sd_bus_message_open_container(reply, 'a', "s")); struct kde_input_manager *manager = userdata; struct kde_input *input; wl_list_for_each(input, &manager->inputs, link) { CK(sd_bus_message_append_basic(reply, 's', input->sys_name)); } CK(sd_bus_message_close_container(reply)); return 1; } static const sd_bus_vtable service_vtable[] = { SD_BUS_VTABLE_START(0), KDE_PROP("devicesSysNames", "as", current_state), SD_BUS_VTABLE_END, }; static int is_pointer(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; /** * The client will differentiate between the pointer and the touchpad again, * and here any pointer will be returned without distinguishing between the pointer and the * touchpad. */ uint32_t is_pointer = input->input->prop.type == WLR_INPUT_DEVICE_POINTER; return sd_bus_message_append_basic(reply, 'b', &is_pointer); } static int is_keyboard(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t is_keyboard = input->input->prop.type == WLR_INPUT_DEVICE_KEYBOARD; return sd_bus_message_append_basic(reply, 'b', &is_keyboard); } static int is_touchpad(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t is_touchpad = input->input->prop.type == WLR_INPUT_DEVICE_POINTER && input->input->prop.tap_finger_count; return sd_bus_message_append_basic(reply, 'b', &is_touchpad); } static int is_touch(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t is_touch = input->input->prop.type == WLR_INPUT_DEVICE_TOUCH; return sd_bus_message_append_basic(reply, 'b', &is_touch); } static int is_tablet_tool(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t is_tablet_tool = input->input->prop.type == WLR_INPUT_DEVICE_TABLET; return sd_bus_message_append_basic(reply, 'b', &is_tablet_tool); } static int is_tablet_pad(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t is_tablet_pad = input->input->prop.type == WLR_INPUT_DEVICE_TABLET_PAD; return sd_bus_message_append_basic(reply, 'b', &is_tablet_pad); } static int is_switch(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t is_switch = input->input->prop.type == WLR_INPUT_DEVICE_SWITCH; return sd_bus_message_append_basic(reply, 'b', &is_switch); } static int name(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; return sd_bus_message_append_basic(reply, 's', input->input->name); } static int sys_name(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; return sd_bus_message_append_basic(reply, 's', input->sys_name); } static int product(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t product = input->input->prop.product; return sd_bus_message_append_basic(reply, 'i', &product); } static int vendor(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t vendor = input->input->prop.vendor; return sd_bus_message_append_basic(reply, 'i', &vendor); } static int supports_disable_events(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t event = input->input->prop.send_events_modes; uint32_t enable = event & LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; return sd_bus_message_append_basic(reply, 'b', &enable); } static int supports_disable_events_on_external_mouse(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t event = input->input->prop.send_events_modes; uint32_t enable = event & LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; return sd_bus_message_append_basic(reply, 'b', &enable); } static int get_touchpad_enable(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t mode = input->input->state.send_events_mode; uint32_t enable = mode == LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; return sd_bus_message_append_basic(reply, 'b', &enable); } static int set_touchpad_enable(sd_bus *bus, const char *_path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { uint32_t touchpad_enable; CK(sd_bus_message_read(reply, "b", &touchpad_enable)); struct kde_input *input = userdata; struct input_state state = input->input->state; state.send_events_mode = touchpad_enable ? LIBINPUT_CONFIG_SEND_EVENTS_ENABLED : LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; input_set_state(input->input, &state); return sd_bus_reply_method_return(reply, NULL); } static int supports_pointer_acceleration(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t acceleration = input->input->prop.has_pointer_accel; return sd_bus_message_append_basic(reply, 'b', &acceleration); } static int default_pointer_acceleration(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; double acceleration = input->input->default_state.pointer_accel_speed; return sd_bus_message_append_basic(reply, 'd', &acceleration); } static int get_accel_speed(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; double accel_speed = input->input->state.pointer_accel_speed; return sd_bus_message_append_basic(reply, 'd', &accel_speed); } static int set_accel_speed(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { double accel_speed; CK(sd_bus_message_read(reply, "d", &accel_speed)); struct kde_input *input = userdata; struct input_state state = input->input->state; state.pointer_accel_speed = accel_speed; input_set_state(input->input, &state); return sd_bus_reply_method_return(reply, NULL); } static int supports_pointer_acceleration_profile_adaptive(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t acceleration = input->input->prop.accel_profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; return sd_bus_message_append_basic(reply, 'b', &acceleration); } static int default_pointer_acceleration_profile_adaptive(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t acceleration = input->input->default_state.accel_profile & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; return sd_bus_message_append_basic(reply, 'b', &acceleration); } static int get_accel_profile(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t accel_profile = input->input->state.accel_profile & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; return sd_bus_message_append_basic(reply, 'b', &accel_profile); } static int set_accel_profile(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { uint32_t accel_profile; CK(sd_bus_message_read(reply, "b", &accel_profile)); struct kde_input *input = userdata; struct input_state state = input->input->state; state.accel_profile = accel_profile ? LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE : LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; input_set_state(input->input, &state); return sd_bus_reply_method_return(reply, NULL); } static int supports_left_handed(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t left_handed = input->input->prop.has_left_handed; return sd_bus_message_append_basic(reply, 'b', &left_handed); } static int left_handed_enabled_by_default(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t left_handed = input->input->default_state.left_handed; return sd_bus_message_append_basic(reply, 'b', &left_handed); } static int get_left_handed(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t left_handed = input->input->state.left_handed; return sd_bus_message_append_basic(reply, 'b', &left_handed); } static int set_left_handed(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { uint32_t left_handed; CK(sd_bus_message_read(reply, "b", &left_handed)); struct kde_input *input = userdata; struct input_state state = input->input->state; state.left_handed = left_handed; input_set_state(input->input, &state); return sd_bus_reply_method_return(reply, NULL); } static int supports_natural_scroll(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t natural_scroll = input->input->prop.has_natural_scroll; return sd_bus_message_append_basic(reply, 'b', &natural_scroll); } static int natural_scroll_enabled_by_default(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t natural_scroll = input->input->default_state.natural_scroll; return sd_bus_message_append_basic(reply, 'b', &natural_scroll); } static int get_natural_scroll(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t natural_scroll = input->input->state.natural_scroll; return sd_bus_message_append_basic(reply, 'b', &natural_scroll); } static int set_natural_scroll(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { uint32_t natural_scroll; CK(sd_bus_message_read(reply, "b", &natural_scroll)); struct kde_input *input = userdata; struct input_state state = input->input->state; state.natural_scroll = natural_scroll; input_set_state(input->input, &state); return sd_bus_reply_method_return(reply, NULL); } static int tap_finger_count(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t tap_finger_count = input->input->prop.tap_finger_count; return sd_bus_message_append_basic(reply, 'i', &tap_finger_count); } static int tap_to_click_enabled_by_default(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t tap_to_click = input->input->default_state.tap_to_click; return sd_bus_message_append_basic(reply, 'b', &tap_to_click); } static int get_tap_click(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t tap_to_click = input->input->state.tap_to_click; return sd_bus_message_append_basic(reply, 'b', &tap_to_click); } static int set_tap_click(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { uint32_t is_tap_click; CK(sd_bus_message_read(reply, "b", &is_tap_click)); struct kde_input *input = userdata; struct input_state state = input->input->state; state.tap_to_click = is_tap_click; input_set_state(input->input, &state); return sd_bus_reply_method_return(reply, NULL); } static int supports_scroll_two_finger(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t scroll_methods = input->input->prop.scroll_methods; uint32_t two_finger = scroll_methods & LIBINPUT_CONFIG_SCROLL_2FG; return sd_bus_message_append_basic(reply, 'b', &two_finger); } static int scroll_two_finger_enabled_by_default(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t scroll_methods = input->input->default_state.scroll_method; uint32_t two_finger = scroll_methods & LIBINPUT_CONFIG_SCROLL_2FG; return sd_bus_message_append_basic(reply, 'b', &two_finger); } static int get_scroll_two_finger(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t scroll_methods = input->input->state.scroll_method; uint32_t two_finger = scroll_methods & LIBINPUT_CONFIG_SCROLL_2FG; return sd_bus_message_append_basic(reply, 'b', &two_finger); } static int set_scroll_two_finger(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { uint32_t method; CK(sd_bus_message_read(reply, "b", &method)); struct kde_input *input = userdata; int32_t current = input->input->state.scroll_method; method = method ? LIBINPUT_CONFIG_SCROLL_2FG : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; if ((current != LIBINPUT_CONFIG_SCROLL_2FG && method == LIBINPUT_CONFIG_SCROLL_2FG) || (current != LIBINPUT_CONFIG_SCROLL_EDGE && method == LIBINPUT_CONFIG_SCROLL_NO_SCROLL)) { struct input_state state = input->input->state; state.scroll_method = method; input_set_state(input->input, &state); } return sd_bus_reply_method_return(reply, NULL); } static int supports_scroll_edge(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t scroll_methods = input->input->prop.scroll_methods; uint32_t is_scroll_edge = scroll_methods & LIBINPUT_CONFIG_SCROLL_EDGE; return sd_bus_message_append_basic(reply, 'b', &is_scroll_edge); } static int scroll_edge_enabled_by_default(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t scroll_method = input->input->default_state.scroll_method; uint32_t is_scroll_edge = scroll_method & LIBINPUT_CONFIG_SCROLL_EDGE; return sd_bus_message_append_basic(reply, 'b', &is_scroll_edge); } static int get_scroll_edge(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t scroll_method = input->input->state.scroll_method; uint32_t is_scroll_edge = scroll_method & LIBINPUT_CONFIG_SCROLL_EDGE; return sd_bus_message_append_basic(reply, 'b', &is_scroll_edge); } static int set_scroll_edge(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { uint32_t method; CK(sd_bus_message_read(reply, "b", &method)); struct kde_input *input = userdata; uint32_t current = input->input->state.scroll_method; method = method ? LIBINPUT_CONFIG_SCROLL_EDGE : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; if ((current != LIBINPUT_CONFIG_SCROLL_EDGE && method == LIBINPUT_CONFIG_SCROLL_EDGE) || (current != LIBINPUT_CONFIG_SCROLL_2FG && method == LIBINPUT_CONFIG_SCROLL_NO_SCROLL)) { struct input_state state = input->input->state; state.scroll_method = method; input_set_state(input->input, &state); } return sd_bus_reply_method_return(reply, NULL); } static int supports_disable_while_typing(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t dwt = input->input->prop.has_dwt; return sd_bus_message_append_basic(reply, 'b', &dwt); } static int disable_while_typing_enabled_by_default(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t dwt = input->input->default_state.dwt; return sd_bus_message_append_basic(reply, 'b', &dwt); } static int get_disable_while_typing(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; uint32_t dwt = input->input->state.dwt; return sd_bus_message_append_basic(reply, 'b', &dwt); } static int set_disable_while_typing(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { uint32_t dwt; CK(sd_bus_message_read(reply, "b", &dwt)); struct kde_input *input = userdata; struct input_state state = input->input->state; state.dwt = dwt; input_set_state(input->input, &state); return sd_bus_reply_method_return(reply, NULL); } static int get_mapped_output(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct kde_input *input = userdata; const char *mapped_to_output = input->input->state.mapped_to_output; return sd_bus_message_append_basic(reply, 's', mapped_to_output ? mapped_to_output : ""); } static int set_mapped_output(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { const char *output_name = NULL; CK(sd_bus_message_read(reply, "s", &output_name)); bool none_output = !strcmp(output_name, ""); if (!none_output) { struct kywc_output *kywc_output = kywc_output_by_name(output_name); if (!kywc_output || !kywc_output->state.enabled) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid output or disabled."); return sd_bus_reply_method_error(reply, &error); } } struct kde_input *input = userdata; const char *current = input->input->mapped_output ? input->input->state.mapped_to_output : NULL; if (input->input->prop.support_mapped_to_output && (!current || strcmp(current, output_name))) { struct input_state state = input->input->state; state.mapped_to_output = none_output ? NULL : output_name; input_set_state(input->input, &state); } return sd_bus_reply_method_return(reply, NULL); } static const sd_bus_vtable input_vtable[] = { SD_BUS_VTABLE_START(0), KDE_PROP("pointer", "b", is_pointer), KDE_PROP("keyboard", "b", is_keyboard), KDE_PROP("touchpad", "b", is_touchpad), KDE_PROP("touch", "b", is_touch), KDE_PROP("tabletTool", "b", is_tablet_tool), KDE_PROP("tabletPad", "b", is_tablet_pad), KDE_PROP("switchDevice", "b", is_switch), KDE_PROP("name", "s", name), KDE_PROP("sysName", "s", sys_name), KDE_PROP("product", "i", product), KDE_PROP("vendor", "i", vendor), KDE_PROP("supportsDisableEvents", "b", supports_disable_events), KDE_PROP("supportsDisableEventsOnExternalMouse", "b", supports_disable_events_on_external_mouse), KDE_WPROP("enabled", "b", get_touchpad_enable, set_touchpad_enable), KDE_PROP("supportsPointerAcceleration", "b", supports_pointer_acceleration), KDE_PROP("defaultPointerAcceleration", "d", default_pointer_acceleration), KDE_WPROP("pointerAcceleration", "d", get_accel_speed, set_accel_speed), KDE_PROP("supportsLeftHanded", "b", supports_left_handed), KDE_PROP("leftHandedEnabledByDefault", "b", left_handed_enabled_by_default), KDE_WPROP("leftHanded", "b", get_left_handed, set_left_handed), KDE_PROP("supportsPointerAccelerationProfileAdaptive", "b", supports_pointer_acceleration_profile_adaptive), KDE_PROP("defaultPointerAccelerationProfileAdaptive", "b", default_pointer_acceleration_profile_adaptive), KDE_WPROP("pointerAccelerationProfileAdaptive", "b", get_accel_profile, set_accel_profile), KDE_PROP("supportsNaturalScroll", "b", supports_natural_scroll), KDE_PROP("naturalScrollEnabledByDefault", "b", natural_scroll_enabled_by_default), KDE_WPROP("naturalScroll", "b", get_natural_scroll, set_natural_scroll), KDE_PROP("tapFingerCount", "b", tap_finger_count), KDE_PROP("tapToClickEnabledByDefault", "b", tap_to_click_enabled_by_default), KDE_WPROP("tapToClick", "b", get_tap_click, set_tap_click), KDE_PROP("supportsScrollTwoFinger", "b", supports_scroll_two_finger), KDE_PROP("scrollTwoFingerEnabledByDefault", "b", scroll_two_finger_enabled_by_default), KDE_WPROP("scrollTwoFinger", "b", get_scroll_two_finger, set_scroll_two_finger), KDE_PROP("supportsScrollEdge", "b", supports_scroll_edge), KDE_PROP("scrollEdgeEnabledByDefault", "b", scroll_edge_enabled_by_default), KDE_WPROP("scrollEdge", "b", get_scroll_edge, set_scroll_edge), KDE_PROP("supportsDisableWhileTyping", "b", supports_disable_while_typing), KDE_PROP("disableWhileTypingEnabledByDefault", "b", disable_while_typing_enabled_by_default), KDE_WPROP("disableWhileTyping", "b", get_disable_while_typing, set_disable_while_typing), KDE_WPROP("outputName", "s", get_mapped_output, set_mapped_output), SD_BUS_VTABLE_END, }; static void kde_input_destroy(struct kde_input *input) { dbus_emit_signal(service_path, service_interface, "deviceRemoved", "s", input->sys_name); wl_list_remove(&input->link); wl_list_remove(&input->destroy.link); dbus_unregister_object(input->dbus); free(input->sys_name); free(input); } static void handle_kde_input_destroy(struct wl_listener *listener, void *data) { struct kde_input *input = wl_container_of(listener, input, destroy); kde_input_destroy(input); } static void handle_new_kde_input(struct wl_listener *listener, void *data) { struct input *input = data; if (!input->device) { return; } struct kde_input *kde_input = calloc(1, sizeof(*kde_input)); if (!kde_input) { return; } wl_list_insert(&kde_input_manager->inputs, &kde_input->link); kde_input->destroy.notify = handle_kde_input_destroy; wl_signal_add(&input->events.destroy, &kde_input->destroy); const char *sys_name = libinput_device_get_sysname(input->device); kde_input->sys_name = strdup(sys_name); kde_input->input = input; size_t size = 1 + strlen(kde_input_path) + strlen(sys_name); char *path = calloc(size, sizeof(char)); snprintf(path, size, "%s%s", kde_input_path, kde_input->sys_name); kde_input->dbus = dbus_register_object(NULL, path, kde_input_interface, input_vtable, kde_input); free(path); dbus_emit_signal(service_path, service_interface, "deviceAdded", "s", kde_input->sys_name); } static void handle_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&kde_input_manager->new_input.link); wl_list_remove(&kde_input_manager->destroy.link); /* destroy kde_input devices*/ struct kde_input *input, *input_tmp; wl_list_for_each_safe(input, input_tmp, &kde_input_manager->inputs, link) { kde_input_destroy(input); } free(kde_input_manager); kde_input_manager = NULL; } bool kde_input_manager_create(struct config_manager *config_manager) { kde_input_manager = calloc(1, sizeof(*kde_input_manager)); if (!kde_input_manager) { return false; } if (!dbus_register_object("org.kde.KWin", service_path, service_interface, service_vtable, kde_input_manager)) { free(kde_input_manager); kde_input_manager = NULL; return false; } wl_list_init(&kde_input_manager->inputs); kde_input_manager->new_input.notify = handle_new_kde_input; input_add_new_listener(&kde_input_manager->new_input); kde_input_manager->destroy.notify = handle_destroy; server_add_destroy_listener(config_manager->server, &kde_input_manager->destroy); return true; } kylin-wayland-compositor/src/config/config_p.h0000664000175000017500000000331715160460057020460 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _CONFIG_P_H_ #define _CONFIG_P_H_ #include "config.h" struct config_manager { struct server *server; struct wl_list configs; const char *file; json_object *json; /* user config */ json_object *sys_json; /* system default config */ struct wl_listener server_ready; struct wl_listener server_destroy; }; bool config_manager_common_init(struct config_manager *config_manager); #if HAVE_KDE_GLOBAL_ACCEL bool kde_global_accel_manager_create(struct config_manager *config_manager); #else static __attribute__((unused)) inline bool kde_global_accel_manager_create(struct config_manager *config_manager) { return false; } #endif #if HAVE_KDE_INPUT bool kde_input_manager_create(struct config_manager *config_manager); #else static __attribute__((unused)) inline bool kde_input_manager_create(struct config_manager *config_manager) { return false; } #endif #if HAVE_UKUI_SHORTCUT bool ukui_shortcut_manager_create(struct config_manager *config_manager); #else static __attribute__((unused)) inline bool ukui_shortcut_manager_create(struct config_manager *config_manager) { return false; } #endif #if HAVE_UKUI_GSETTINGS bool ukui_gsettings_create(struct config_manager *config_manager); #else static __attribute__((unused)) inline bool ukui_gsettings_create(struct config_manager *config_manager) { return false; } #endif #if HAVE_UKUI_VIEW_MODE bool ukui_view_mode_manager_create(struct config_manager *config_manager); #else static __attribute__((unused)) inline bool ukui_view_mode_manager_create(struct config_manager *config_manager) { return false; } #endif #endif /* _CONFIG_P_H_ */ kylin-wayland-compositor/src/config/qtkey_mapper.c0000664000175000017500000010271315160460057021370 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include enum QtKey { Key_Escape = 0x01000000, // misc keys Key_Tab = 0x01000001, Key_Backtab = 0x01000002, Key_Backspace = 0x01000003, Key_Return = 0x01000004, Key_Enter = 0x01000005, Key_Insert = 0x01000006, Key_Delete = 0x01000007, Key_Pause = 0x01000008, Key_Print = 0x01000009, // print screen Key_SysReq = 0x0100000a, Key_Clear = 0x0100000b, Key_Home = 0x01000010, // cursor movement Key_End = 0x01000011, Key_Left = 0x01000012, Key_Up = 0x01000013, Key_Right = 0x01000014, Key_Down = 0x01000015, Key_PageUp = 0x01000016, Key_PageDown = 0x01000017, Key_Shift = 0x01000020, // modifiers Key_Control = 0x01000021, Key_Meta = 0x01000022, Key_Alt = 0x01000023, Key_CapsLock = 0x01000024, Key_NumLock = 0x01000025, Key_ScrollLock = 0x01000026, Key_F1 = 0x01000030, // function keys Key_F2 = 0x01000031, Key_F3 = 0x01000032, Key_F4 = 0x01000033, Key_F5 = 0x01000034, Key_F6 = 0x01000035, Key_F7 = 0x01000036, Key_F8 = 0x01000037, Key_F9 = 0x01000038, Key_F10 = 0x01000039, Key_F11 = 0x0100003a, Key_F12 = 0x0100003b, Key_F13 = 0x0100003c, Key_F14 = 0x0100003d, Key_F15 = 0x0100003e, Key_F16 = 0x0100003f, Key_F17 = 0x01000040, Key_F18 = 0x01000041, Key_F19 = 0x01000042, Key_F20 = 0x01000043, Key_F21 = 0x01000044, Key_F22 = 0x01000045, Key_F23 = 0x01000046, Key_F24 = 0x01000047, Key_F25 = 0x01000048, // F25 .. F35 only on X11 Key_F26 = 0x01000049, Key_F27 = 0x0100004a, Key_F28 = 0x0100004b, Key_F29 = 0x0100004c, Key_F30 = 0x0100004d, Key_F31 = 0x0100004e, Key_F32 = 0x0100004f, Key_F33 = 0x01000050, Key_F34 = 0x01000051, Key_F35 = 0x01000052, Key_Super_L = 0x01000053, // extra keys Key_Super_R = 0x01000054, Key_Menu = 0x01000055, Key_Hyper_L = 0x01000056, Key_Hyper_R = 0x01000057, Key_Help = 0x01000058, Key_Direction_L = 0x01000059, Key_Direction_R = 0x01000060, Key_Space = 0x20, // 7 bit printable ASCII Key_Any = Key_Space, Key_Exclam = 0x21, Key_QuoteDbl = 0x22, Key_NumberSign = 0x23, Key_Dollar = 0x24, Key_Percent = 0x25, Key_Ampersand = 0x26, Key_Apostrophe = 0x27, Key_ParenLeft = 0x28, Key_ParenRight = 0x29, Key_Asterisk = 0x2a, Key_Plus = 0x2b, Key_Comma = 0x2c, Key_Minus = 0x2d, Key_Period = 0x2e, Key_Slash = 0x2f, Key_0 = 0x30, Key_1 = 0x31, Key_2 = 0x32, Key_3 = 0x33, Key_4 = 0x34, Key_5 = 0x35, Key_6 = 0x36, Key_7 = 0x37, Key_8 = 0x38, Key_9 = 0x39, Key_Colon = 0x3a, Key_Semicolon = 0x3b, Key_Less = 0x3c, Key_Equal = 0x3d, Key_Greater = 0x3e, Key_Question = 0x3f, Key_At = 0x40, Key_A = 0x41, Key_B = 0x42, Key_C = 0x43, Key_D = 0x44, Key_E = 0x45, Key_F = 0x46, Key_G = 0x47, Key_H = 0x48, Key_I = 0x49, Key_J = 0x4a, Key_K = 0x4b, Key_L = 0x4c, Key_M = 0x4d, Key_N = 0x4e, Key_O = 0x4f, Key_P = 0x50, Key_Q = 0x51, Key_R = 0x52, Key_S = 0x53, Key_T = 0x54, Key_U = 0x55, Key_V = 0x56, Key_W = 0x57, Key_X = 0x58, Key_Y = 0x59, Key_Z = 0x5a, Key_BracketLeft = 0x5b, Key_Backslash = 0x5c, Key_BracketRight = 0x5d, Key_AsciiCircum = 0x5e, Key_Underscore = 0x5f, Key_QuoteLeft = 0x60, Key_BraceLeft = 0x7b, Key_Bar = 0x7c, Key_BraceRight = 0x7d, Key_AsciiTilde = 0x7e, Key_nobreakspace = 0x0a0, Key_exclamdown = 0x0a1, Key_cent = 0x0a2, Key_sterling = 0x0a3, Key_currency = 0x0a4, Key_yen = 0x0a5, Key_brokenbar = 0x0a6, Key_section = 0x0a7, Key_diaeresis = 0x0a8, Key_copyright = 0x0a9, Key_ordfeminine = 0x0aa, Key_guillemotleft = 0x0ab, // left angle quotation mark Key_notsign = 0x0ac, Key_hyphen = 0x0ad, Key_registered = 0x0ae, Key_macron = 0x0af, Key_degree = 0x0b0, Key_plusminus = 0x0b1, Key_twosuperior = 0x0b2, Key_threesuperior = 0x0b3, Key_acute = 0x0b4, Key_mu = 0x0b5, Key_paragraph = 0x0b6, Key_periodcentered = 0x0b7, Key_cedilla = 0x0b8, Key_onesuperior = 0x0b9, Key_masculine = 0x0ba, Key_guillemotright = 0x0bb, // right angle quotation mark Key_onequarter = 0x0bc, Key_onehalf = 0x0bd, Key_threequarters = 0x0be, Key_questiondown = 0x0bf, Key_Agrave = 0x0c0, Key_Aacute = 0x0c1, Key_Acircumflex = 0x0c2, Key_Atilde = 0x0c3, Key_Adiaeresis = 0x0c4, Key_Aring = 0x0c5, Key_AE = 0x0c6, Key_Ccedilla = 0x0c7, Key_Egrave = 0x0c8, Key_Eacute = 0x0c9, Key_Ecircumflex = 0x0ca, Key_Ediaeresis = 0x0cb, Key_Igrave = 0x0cc, Key_Iacute = 0x0cd, Key_Icircumflex = 0x0ce, Key_Idiaeresis = 0x0cf, Key_ETH = 0x0d0, Key_Ntilde = 0x0d1, Key_Ograve = 0x0d2, Key_Oacute = 0x0d3, Key_Ocircumflex = 0x0d4, Key_Otilde = 0x0d5, Key_Odiaeresis = 0x0d6, Key_multiply = 0x0d7, Key_Ooblique = 0x0d8, Key_Ugrave = 0x0d9, Key_Uacute = 0x0da, Key_Ucircumflex = 0x0db, Key_Udiaeresis = 0x0dc, Key_Yacute = 0x0dd, Key_THORN = 0x0de, Key_ssharp = 0x0df, Key_division = 0x0f7, Key_ydiaeresis = 0x0ff, // International input method support (X keycode - 0xEE00, the // definition follows Qt/Embedded 2.3.7) Only interesting if // you are writing your own input method // International & multi-key character composition Key_AltGr = 0x01001103, Key_Multi_key = 0x01001120, // Multi-key character compose Key_Codeinput = 0x01001137, Key_SingleCandidate = 0x0100113c, Key_MultipleCandidate = 0x0100113d, Key_PreviousCandidate = 0x0100113e, // Misc Functions Key_Mode_switch = 0x0100117e, // Character set switch // Key_script_switch = 0x0100117e, // Alias for mode_switch // Japanese keyboard support Key_Kanji = 0x01001121, // Kanji, Kanji convert Key_Muhenkan = 0x01001122, // Cancel Conversion // Key_Henkan_Mode = 0x01001123, // Start/Stop Conversion Key_Henkan = 0x01001123, // Alias for Henkan_Mode Key_Romaji = 0x01001124, // to Romaji Key_Hiragana = 0x01001125, // to Hiragana Key_Katakana = 0x01001126, // to Katakana Key_Hiragana_Katakana = 0x01001127, // Hiragana/Katakana toggle Key_Zenkaku = 0x01001128, // to Zenkaku Key_Hankaku = 0x01001129, // to Hankaku Key_Zenkaku_Hankaku = 0x0100112a, // Zenkaku/Hankaku toggle Key_Touroku = 0x0100112b, // Add to Dictionary Key_Massyo = 0x0100112c, // Delete from Dictionary Key_Kana_Lock = 0x0100112d, // Kana Lock Key_Kana_Shift = 0x0100112e, // Kana Shift Key_Eisu_Shift = 0x0100112f, // Alphanumeric Shift Key_Eisu_toggle = 0x01001130, // Alphanumeric toggle // Key_Kanji_Bangou = 0x01001137, // Codeinput // Key_Zen_Koho = 0x0100113d, // Multiple/All Candidate(s) // Key_Mae_Koho = 0x0100113e, // Previous Candidate // Korean keyboard support // // In fact, many Korean users need only 2 keys, Key_Hangul and // Key_Hangul_Hanja. But rest of the keys are good for future. Key_Hangul = 0x01001131, // Hangul start/stop(toggle) Key_Hangul_Start = 0x01001132, // Hangul start Key_Hangul_End = 0x01001133, // Hangul end, English start Key_Hangul_Hanja = 0x01001134, // Start Hangul->Hanja Conversion Key_Hangul_Jamo = 0x01001135, // Hangul Jamo mode Key_Hangul_Romaja = 0x01001136, // Hangul Romaja mode // Key_Hangul_Codeinput = 0x01001137, // Hangul code input mode Key_Hangul_Jeonja = 0x01001138, // Jeonja mode Key_Hangul_Banja = 0x01001139, // Banja mode Key_Hangul_PreHanja = 0x0100113a, // Pre Hanja conversion Key_Hangul_PostHanja = 0x0100113b, // Post Hanja conversion // Key_Hangul_SingleCandidate = 0x0100113c, // Single candidate // Key_Hangul_MultipleCandidate = 0x0100113d, // Multiple candidate // Key_Hangul_PreviousCandidate = 0x0100113e, // Previous candidate Key_Hangul_Special = 0x0100113f, // Special symbols // Key_Hangul_switch = 0x0100117e, // Alias for mode_switch // dead keys (X keycode - 0xED00 to avoid the conflict) Key_Dead_Grave = 0x01001250, Key_Dead_Acute = 0x01001251, Key_Dead_Circumflex = 0x01001252, Key_Dead_Tilde = 0x01001253, Key_Dead_Macron = 0x01001254, Key_Dead_Breve = 0x01001255, Key_Dead_Abovedot = 0x01001256, Key_Dead_Diaeresis = 0x01001257, Key_Dead_Abovering = 0x01001258, Key_Dead_Doubleacute = 0x01001259, Key_Dead_Caron = 0x0100125a, Key_Dead_Cedilla = 0x0100125b, Key_Dead_Ogonek = 0x0100125c, Key_Dead_Iota = 0x0100125d, Key_Dead_Voiced_Sound = 0x0100125e, Key_Dead_Semivoiced_Sound = 0x0100125f, Key_Dead_Belowdot = 0x01001260, Key_Dead_Hook = 0x01001261, Key_Dead_Horn = 0x01001262, Key_Dead_Stroke = 0x01001263, Key_Dead_Abovecomma = 0x01001264, Key_Dead_Abovereversedcomma = 0x01001265, Key_Dead_Doublegrave = 0x01001266, Key_Dead_Belowring = 0x01001267, Key_Dead_Belowmacron = 0x01001268, Key_Dead_Belowcircumflex = 0x01001269, Key_Dead_Belowtilde = 0x0100126a, Key_Dead_Belowbreve = 0x0100126b, Key_Dead_Belowdiaeresis = 0x0100126c, Key_Dead_Invertedbreve = 0x0100126d, Key_Dead_Belowcomma = 0x0100126e, Key_Dead_Currency = 0x0100126f, Key_Dead_a = 0x01001280, Key_Dead_A = 0x01001281, Key_Dead_e = 0x01001282, Key_Dead_E = 0x01001283, Key_Dead_i = 0x01001284, Key_Dead_I = 0x01001285, Key_Dead_o = 0x01001286, Key_Dead_O = 0x01001287, Key_Dead_u = 0x01001288, Key_Dead_U = 0x01001289, Key_Dead_Small_Schwa = 0x0100128a, Key_Dead_Capital_Schwa = 0x0100128b, Key_Dead_Greek = 0x0100128c, Key_Dead_Lowline = 0x01001290, Key_Dead_Aboveverticalline = 0x01001291, Key_Dead_Belowverticalline = 0x01001292, Key_Dead_Longsolidusoverlay = 0x01001293, // multimedia/internet keys - ignored by default - see QKeyEvent c'tor Key_Back = 0x01000061, Key_Forward = 0x01000062, Key_Stop = 0x01000063, Key_Refresh = 0x01000064, Key_VolumeDown = 0x01000070, Key_VolumeMute = 0x01000071, Key_VolumeUp = 0x01000072, Key_BassBoost = 0x01000073, Key_BassUp = 0x01000074, Key_BassDown = 0x01000075, Key_TrebleUp = 0x01000076, Key_TrebleDown = 0x01000077, Key_MediaPlay = 0x01000080, Key_MediaStop = 0x01000081, Key_MediaPrevious = 0x01000082, Key_MediaNext = 0x01000083, Key_MediaRecord = 0x01000084, Key_MediaPause = 0x1000085, Key_MediaTogglePlayPause = 0x1000086, Key_HomePage = 0x01000090, Key_Favorites = 0x01000091, Key_Search = 0x01000092, Key_Standby = 0x01000093, Key_OpenUrl = 0x01000094, Key_LaunchMail = 0x010000a0, Key_LaunchMedia = 0x010000a1, Key_Launch0 = 0x010000a2, Key_Launch1 = 0x010000a3, Key_Launch2 = 0x010000a4, Key_Launch3 = 0x010000a5, Key_Launch4 = 0x010000a6, Key_Launch5 = 0x010000a7, Key_Launch6 = 0x010000a8, Key_Launch7 = 0x010000a9, Key_Launch8 = 0x010000aa, Key_Launch9 = 0x010000ab, Key_LaunchA = 0x010000ac, Key_LaunchB = 0x010000ad, Key_LaunchC = 0x010000ae, Key_LaunchD = 0x010000af, Key_LaunchE = 0x010000b0, Key_LaunchF = 0x010000b1, Key_MonBrightnessUp = 0x010000b2, Key_MonBrightnessDown = 0x010000b3, Key_KeyboardLightOnOff = 0x010000b4, Key_KeyboardBrightnessUp = 0x010000b5, Key_KeyboardBrightnessDown = 0x010000b6, Key_PowerOff = 0x010000b7, Key_WakeUp = 0x010000b8, Key_Eject = 0x010000b9, Key_ScreenSaver = 0x010000ba, Key_WWW = 0x010000bb, Key_Memo = 0x010000bc, Key_LightBulb = 0x010000bd, Key_Shop = 0x010000be, Key_History = 0x010000bf, Key_AddFavorite = 0x010000c0, Key_HotLinks = 0x010000c1, Key_BrightnessAdjust = 0x010000c2, Key_Finance = 0x010000c3, Key_Community = 0x010000c4, Key_AudioRewind = 0x010000c5, // Media rewind Key_BackForward = 0x010000c6, Key_ApplicationLeft = 0x010000c7, Key_ApplicationRight = 0x010000c8, Key_Book = 0x010000c9, Key_CD = 0x010000ca, Key_Calculator = 0x010000cb, Key_ToDoList = 0x010000cc, Key_ClearGrab = 0x010000cd, Key_Close = 0x010000ce, Key_Copy = 0x010000cf, Key_Cut = 0x010000d0, Key_Display = 0x010000d1, // Output switch key Key_DOS = 0x010000d2, Key_Documents = 0x010000d3, Key_Excel = 0x010000d4, Key_Explorer = 0x010000d5, Key_Game = 0x010000d6, Key_Go = 0x010000d7, Key_iTouch = 0x010000d8, Key_LogOff = 0x010000d9, Key_Market = 0x010000da, Key_Meeting = 0x010000db, Key_MenuKB = 0x010000dc, Key_MenuPB = 0x010000dd, Key_MySites = 0x010000de, Key_News = 0x010000df, Key_OfficeHome = 0x010000e0, Key_Option = 0x010000e1, Key_Paste = 0x010000e2, Key_Phone = 0x010000e3, Key_Calendar = 0x010000e4, Key_Reply = 0x010000e5, Key_Reload = 0x010000e6, Key_RotateWindows = 0x010000e7, Key_RotationPB = 0x010000e8, Key_RotationKB = 0x010000e9, Key_Save = 0x010000ea, Key_Send = 0x010000eb, Key_Spell = 0x010000ec, Key_SplitScreen = 0x010000ed, Key_Support = 0x010000ee, Key_TaskPane = 0x010000ef, Key_Terminal = 0x010000f0, Key_Tools = 0x010000f1, Key_Travel = 0x010000f2, Key_Video = 0x010000f3, Key_Word = 0x010000f4, Key_Xfer = 0x010000f5, Key_ZoomIn = 0x010000f6, Key_ZoomOut = 0x010000f7, Key_Away = 0x010000f8, Key_Messenger = 0x010000f9, Key_WebCam = 0x010000fa, Key_MailForward = 0x010000fb, Key_Pictures = 0x010000fc, Key_Music = 0x010000fd, Key_Battery = 0x010000fe, Key_Bluetooth = 0x010000ff, Key_WLAN = 0x01000100, Key_UWB = 0x01000101, Key_AudioForward = 0x01000102, // Media fast-forward Key_AudioRepeat = 0x01000103, // Toggle repeat mode Key_AudioRandomPlay = 0x01000104, // Toggle shuffle mode Key_Subtitle = 0x01000105, Key_AudioCycleTrack = 0x01000106, Key_Time = 0x01000107, Key_Hibernate = 0x01000108, Key_View = 0x01000109, Key_TopMenu = 0x0100010a, Key_PowerDown = 0x0100010b, Key_Suspend = 0x0100010c, Key_ContrastAdjust = 0x0100010d, Key_LaunchG = 0x0100010e, Key_LaunchH = 0x0100010f, Key_TouchpadToggle = 0x01000110, Key_TouchpadOn = 0x01000111, Key_TouchpadOff = 0x01000112, Key_MicMute = 0x01000113, Key_Red = 0x01000114, Key_Green = 0x01000115, Key_Yellow = 0x01000116, Key_Blue = 0x01000117, Key_ChannelUp = 0x01000118, Key_ChannelDown = 0x01000119, Key_Guide = 0x0100011a, Key_Info = 0x0100011b, Key_Settings = 0x0100011c, Key_MicVolumeUp = 0x0100011d, Key_MicVolumeDown = 0x0100011e, Key_New = 0x01000120, Key_Open = 0x01000121, Key_Find = 0x01000122, Key_Undo = 0x01000123, Key_Redo = 0x01000124, Key_RFKill = 0x01000125, Key_MediaLast = 0x0100ffff, // Keypad navigation keys Key_Select = 0x01010000, Key_Yes = 0x01010001, Key_No = 0x01010002, // Newer misc keys Key_Cancel = 0x01020001, Key_Printer = 0x01020002, Key_Execute = 0x01020003, Key_Sleep = 0x01020004, Key_Play = 0x01020005, // Not the same as Key_MediaPlay Key_Zoom = 0x01020006, // Key_Jisho = 0x01020007, // IME: Dictionary key // Key_Oyayubi_Left = 0x01020008, // IME: Left Oyayubi key // Key_Oyayubi_Right = 0x01020009, // IME: Right Oyayubi key Key_Exit = 0x0102000a, // Device keys Key_Context1 = 0x01100000, Key_Context2 = 0x01100001, Key_Context3 = 0x01100002, Key_Context4 = 0x01100003, Key_Call = 0x01100004, // set absolute state to in a call (do not toggle state) Key_Hangup = 0x01100005, // set absolute state to hang up (do not toggle state) Key_Flip = 0x01100006, Key_ToggleCallHangup = 0x01100007, // a toggle key for answering, or hanging up Key_VoiceDial = 0x01100008, Key_LastNumberRedial = 0x01100009, Key_Camera = 0x01100020, Key_CameraFocus = 0x01100021, Key_unknown = 0x01ffffff }; static struct Xkb2Qt { uint32_t keysym; enum QtKey qtkey; } KeyTbl[] = { // misc keys { XKB_KEY_Escape, Key_Escape }, { XKB_KEY_Tab, Key_Tab }, { XKB_KEY_ISO_Left_Tab, Key_Backtab }, { XKB_KEY_BackSpace, Key_Backspace }, { XKB_KEY_Return, Key_Return }, { XKB_KEY_Insert, Key_Insert }, { XKB_KEY_Delete, Key_Delete }, // { XKB_KEY_Clear, Key_Delete }, { XKB_KEY_Pause, Key_Pause }, { XKB_KEY_Print, Key_Print }, { 0x1005FF60, Key_SysReq }, // hardcoded Sun SysReq // { 0x1007ff00, Key_SysReq }, // hardcoded X386 SysReq // cursor movement { XKB_KEY_Home, Key_Home }, { XKB_KEY_End, Key_End }, { XKB_KEY_Left, Key_Left }, { XKB_KEY_Up, Key_Up }, { XKB_KEY_Right, Key_Right }, { XKB_KEY_Down, Key_Down }, { XKB_KEY_Prior, Key_PageUp }, { XKB_KEY_Next, Key_PageDown }, // modifiers // { XKB_KEY_Shift_L, Key_Shift }, // { XKB_KEY_Shift_R, Key_Shift }, // { XKB_KEY_Shift_Lock, Key_Shift }, // { XKB_KEY_Control_L, Key_Control }, // { XKB_KEY_Control_R, Key_Control }, // { XKB_KEY_Meta_L, Key_Meta }, // { XKB_KEY_Meta_R, Key_Meta }, // { XKB_KEY_Alt_L, Key_Alt }, // { XKB_KEY_Alt_R, Key_Alt }, { XKB_KEY_Caps_Lock, Key_CapsLock }, { XKB_KEY_Num_Lock, Key_NumLock }, { XKB_KEY_Scroll_Lock, Key_ScrollLock }, { XKB_KEY_Super_L, Key_Super_L }, { XKB_KEY_Super_R, Key_Super_R }, { XKB_KEY_Menu, Key_Menu }, { XKB_KEY_Hyper_L, Key_Hyper_L }, { XKB_KEY_Hyper_R, Key_Hyper_R }, { XKB_KEY_Help, Key_Help }, // { 0x1000FF74, Key_Backtab }, // hardcoded HP backtab { 0x1005FF10, Key_F11 }, // hardcoded Sun F36 (labeled F11) { 0x1005FF11, Key_F12 }, // hardcoded Sun F37 (labeled F12) // numeric and function keypad keys // { XKB_KEY_KP_Space, Key_Space }, // { XKB_KEY_KP_Tab, Key_Tab }, // { XKB_KEY_KP_Enter, Key_Enter }, // { XKB_KEY_KP_Home, Key_Home }, // { XKB_KEY_KP_Left, Key_Left }, // { XKB_KEY_KP_Up, Key_Up }, // { XKB_KEY_KP_Right, Key_Right }, // { XKB_KEY_KP_Down, Key_Down }, // { XKB_KEY_KP_Prior, Key_PageUp }, // { XKB_KEY_KP_Next, Key_PageDown }, // { XKB_KEY_KP_End, Key_End }, // { XKB_KEY_KP_Begin, Key_Clear }, // { XKB_KEY_KP_Insert, Key_Insert }, // { XKB_KEY_KP_Delete, Key_Delete }, // { XKB_KEY_KP_Equal, Key_Equal }, // { XKB_KEY_KP_Multiply, Key_Asterisk }, // { XKB_KEY_KP_Add, Key_Plus }, // { XKB_KEY_KP_Separator, Key_Comma }, // { XKB_KEY_KP_Subtract, Key_Minus }, // { XKB_KEY_KP_Decimal, Key_Period }, // { XKB_KEY_KP_Divide, Key_Slash }, // special non-XF86 function keys { XKB_KEY_Undo, Key_Undo }, { XKB_KEY_Redo, Key_Redo }, { XKB_KEY_Find, Key_Find }, { XKB_KEY_Cancel, Key_Cancel }, // International input method support keys // International & multi-key character composition { XKB_KEY_ISO_Level3_Shift, Key_AltGr }, { XKB_KEY_Multi_key, Key_Multi_key }, { XKB_KEY_Codeinput, Key_Codeinput }, { XKB_KEY_SingleCandidate, Key_SingleCandidate }, { XKB_KEY_MultipleCandidate, Key_MultipleCandidate }, { XKB_KEY_PreviousCandidate, Key_PreviousCandidate }, // Misc Functions { XKB_KEY_Mode_switch, Key_Mode_switch }, // { XKB_KEY_script_switch, Key_Mode_switch }, // Japanese keyboard support { XKB_KEY_Kanji, Key_Kanji }, { XKB_KEY_Muhenkan, Key_Muhenkan }, // { XKB_KEY_Henkan_Mode, Key_Henkan_Mode}, { XKB_KEY_Henkan_Mode, Key_Henkan }, // { XKB_KEY_Henkan, Key_Henkan }, { XKB_KEY_Romaji, Key_Romaji }, { XKB_KEY_Hiragana, Key_Hiragana }, { XKB_KEY_Katakana, Key_Katakana }, { XKB_KEY_Hiragana_Katakana, Key_Hiragana_Katakana }, { XKB_KEY_Zenkaku, Key_Zenkaku }, { XKB_KEY_Hankaku, Key_Hankaku }, { XKB_KEY_Zenkaku_Hankaku, Key_Zenkaku_Hankaku }, { XKB_KEY_Touroku, Key_Touroku }, { XKB_KEY_Massyo, Key_Massyo }, { XKB_KEY_Kana_Lock, Key_Kana_Lock }, { XKB_KEY_Kana_Shift, Key_Kana_Shift }, { XKB_KEY_Eisu_Shift, Key_Eisu_Shift }, { XKB_KEY_Eisu_toggle, Key_Eisu_toggle }, // { XKB_KEY_Kanji_Bangou, Key_Kanji_Bangou}, // { XKB_KEY_Zen_Koho, Key_Zen_Koho}, // { XKB_KEY_Mae_Koho, Key_Mae_Koho}, // { XKB_KEY_Kanji_Bangou, Key_Codeinput }, // { XKB_KEY_Zen_Koho, Key_MultipleCandidate }, // { XKB_KEY_Mae_Koho, Key_PreviousCandidate }, // Korean keyboard support { XKB_KEY_Hangul, Key_Hangul }, { XKB_KEY_Hangul_Start, Key_Hangul_Start }, { XKB_KEY_Hangul_End, Key_Hangul_End }, { XKB_KEY_Hangul_Hanja, Key_Hangul_Hanja }, { XKB_KEY_Hangul_Jamo, Key_Hangul_Jamo }, { XKB_KEY_Hangul_Romaja, Key_Hangul_Romaja }, // { XKB_KEY_Hangul_Codeinput, Key_Hangul_Codeinput}, // { XKB_KEY_Hangul_Codeinput, Key_Codeinput }, { XKB_KEY_Hangul_Jeonja, Key_Hangul_Jeonja }, { XKB_KEY_Hangul_Banja, Key_Hangul_Banja }, { XKB_KEY_Hangul_PreHanja, Key_Hangul_PreHanja }, { XKB_KEY_Hangul_PostHanja, Key_Hangul_PostHanja }, // { XKB_KEY_Hangul_SingleCandidate,Key_Hangul_SingleCandidate}, // { XKB_KEY_Hangul_MultipleCandidate,Key_Hangul_MultipleCandidate}, // { XKB_KEY_Hangul_PreviousCandidate,Key_Hangul_PreviousCandidate}, // { XKB_KEY_Hangul_SingleCandidate, Key_SingleCandidate }, // { XKB_KEY_Hangul_MultipleCandidate, Key_MultipleCandidate }, // { XKB_KEY_Hangul_PreviousCandidate, Key_PreviousCandidate }, { XKB_KEY_Hangul_Special, Key_Hangul_Special }, // { XKB_KEY_Hangul_switch, Key_Hangul_switch}, // { XKB_KEY_Hangul_switch, Key_Mode_switch }, // dead keys { XKB_KEY_dead_grave, Key_Dead_Grave }, { XKB_KEY_dead_acute, Key_Dead_Acute }, { XKB_KEY_dead_circumflex, Key_Dead_Circumflex }, { XKB_KEY_dead_tilde, Key_Dead_Tilde }, { XKB_KEY_dead_macron, Key_Dead_Macron }, { XKB_KEY_dead_breve, Key_Dead_Breve }, { XKB_KEY_dead_abovedot, Key_Dead_Abovedot }, { XKB_KEY_dead_diaeresis, Key_Dead_Diaeresis }, { XKB_KEY_dead_abovering, Key_Dead_Abovering }, { XKB_KEY_dead_doubleacute, Key_Dead_Doubleacute }, { XKB_KEY_dead_caron, Key_Dead_Caron }, { XKB_KEY_dead_cedilla, Key_Dead_Cedilla }, { XKB_KEY_dead_ogonek, Key_Dead_Ogonek }, { XKB_KEY_dead_iota, Key_Dead_Iota }, { XKB_KEY_dead_voiced_sound, Key_Dead_Voiced_Sound }, { XKB_KEY_dead_semivoiced_sound, Key_Dead_Semivoiced_Sound }, { XKB_KEY_dead_belowdot, Key_Dead_Belowdot }, { XKB_KEY_dead_hook, Key_Dead_Hook }, { XKB_KEY_dead_horn, Key_Dead_Horn }, { XKB_KEY_dead_stroke, Key_Dead_Stroke }, { XKB_KEY_dead_abovecomma, Key_Dead_Abovecomma }, { XKB_KEY_dead_abovereversedcomma, Key_Dead_Abovereversedcomma }, { XKB_KEY_dead_doublegrave, Key_Dead_Doublegrave }, { XKB_KEY_dead_belowring, Key_Dead_Belowring }, { XKB_KEY_dead_belowmacron, Key_Dead_Belowmacron }, { XKB_KEY_dead_belowcircumflex, Key_Dead_Belowcircumflex }, { XKB_KEY_dead_belowtilde, Key_Dead_Belowtilde }, { XKB_KEY_dead_belowbreve, Key_Dead_Belowbreve }, { XKB_KEY_dead_belowdiaeresis, Key_Dead_Belowdiaeresis }, { XKB_KEY_dead_invertedbreve, Key_Dead_Invertedbreve }, { XKB_KEY_dead_belowcomma, Key_Dead_Belowcomma }, { XKB_KEY_dead_currency, Key_Dead_Currency }, { XKB_KEY_dead_a, Key_Dead_a }, { XKB_KEY_dead_A, Key_Dead_A }, { XKB_KEY_dead_e, Key_Dead_e }, { XKB_KEY_dead_E, Key_Dead_E }, { XKB_KEY_dead_i, Key_Dead_i }, { XKB_KEY_dead_I, Key_Dead_I }, { XKB_KEY_dead_o, Key_Dead_o }, { XKB_KEY_dead_O, Key_Dead_O }, { XKB_KEY_dead_u, Key_Dead_u }, { XKB_KEY_dead_U, Key_Dead_U }, { XKB_KEY_dead_small_schwa, Key_Dead_Small_Schwa }, { XKB_KEY_dead_capital_schwa, Key_Dead_Capital_Schwa }, { XKB_KEY_dead_greek, Key_Dead_Greek }, { 0xfe90, Key_Dead_Lowline }, { 0xfe91, Key_Dead_Aboveverticalline }, { 0xfe92, Key_Dead_Belowverticalline }, { 0xfe93, Key_Dead_Longsolidusoverlay }, // Special keys from X.org - This include multimedia keys, // wireless/bluetooth/uwb keys, special launcher keys, etc. { XKB_KEY_XF86Back, Key_Back }, { XKB_KEY_XF86Forward, Key_Forward }, { XKB_KEY_XF86Stop, Key_Stop }, { XKB_KEY_XF86Refresh, Key_Refresh }, { XKB_KEY_XF86Favorites, Key_Favorites }, { XKB_KEY_XF86AudioMedia, Key_LaunchMedia }, { XKB_KEY_XF86OpenURL, Key_OpenUrl }, { XKB_KEY_XF86HomePage, Key_HomePage }, { XKB_KEY_XF86Search, Key_Search }, { XKB_KEY_XF86AudioLowerVolume, Key_VolumeDown }, { XKB_KEY_XF86AudioMute, Key_VolumeMute }, { XKB_KEY_XF86AudioRaiseVolume, Key_VolumeUp }, { XKB_KEY_XF86AudioPlay, Key_MediaPlay }, { XKB_KEY_XF86AudioStop, Key_MediaStop }, { XKB_KEY_XF86AudioPrev, Key_MediaPrevious }, { XKB_KEY_XF86AudioNext, Key_MediaNext }, { XKB_KEY_XF86AudioRecord, Key_MediaRecord }, { XKB_KEY_XF86AudioPause, Key_MediaPause }, { XKB_KEY_XF86Mail, Key_LaunchMail }, { XKB_KEY_XF86MyComputer, Key_Launch0 }, // ### Qt 6: remap properly { XKB_KEY_XF86Calculator, Key_Launch1 }, { XKB_KEY_XF86Memo, Key_Memo }, { XKB_KEY_XF86ToDoList, Key_ToDoList }, { XKB_KEY_XF86Calendar, Key_Calendar }, { XKB_KEY_XF86PowerDown, Key_PowerDown }, { XKB_KEY_XF86ContrastAdjust, Key_ContrastAdjust }, { XKB_KEY_XF86Standby, Key_Standby }, { XKB_KEY_XF86MonBrightnessUp, Key_MonBrightnessUp }, { XKB_KEY_XF86MonBrightnessDown, Key_MonBrightnessDown }, { XKB_KEY_XF86KbdLightOnOff, Key_KeyboardLightOnOff }, { XKB_KEY_XF86KbdBrightnessUp, Key_KeyboardBrightnessUp }, { XKB_KEY_XF86KbdBrightnessDown, Key_KeyboardBrightnessDown }, { XKB_KEY_XF86PowerOff, Key_PowerOff }, { XKB_KEY_XF86WakeUp, Key_WakeUp }, { XKB_KEY_XF86Eject, Key_Eject }, { XKB_KEY_XF86ScreenSaver, Key_ScreenSaver }, { XKB_KEY_XF86WWW, Key_WWW }, { XKB_KEY_XF86Sleep, Key_Sleep }, { XKB_KEY_XF86LightBulb, Key_LightBulb }, { XKB_KEY_XF86Shop, Key_Shop }, { XKB_KEY_XF86History, Key_History }, { XKB_KEY_XF86AddFavorite, Key_AddFavorite }, { XKB_KEY_XF86HotLinks, Key_HotLinks }, { XKB_KEY_XF86BrightnessAdjust, Key_BrightnessAdjust }, { XKB_KEY_XF86Finance, Key_Finance }, { XKB_KEY_XF86Community, Key_Community }, { XKB_KEY_XF86AudioRewind, Key_AudioRewind }, { XKB_KEY_XF86BackForward, Key_BackForward }, { XKB_KEY_XF86ApplicationLeft, Key_ApplicationLeft }, { XKB_KEY_XF86ApplicationRight, Key_ApplicationRight }, { XKB_KEY_XF86Book, Key_Book }, { XKB_KEY_XF86CD, Key_CD }, { XKB_KEY_XF86Calculator, Key_Calculator }, { XKB_KEY_XF86Clear, Key_Clear }, { XKB_KEY_XF86ClearGrab, Key_ClearGrab }, { XKB_KEY_XF86Close, Key_Close }, { XKB_KEY_XF86Copy, Key_Copy }, { XKB_KEY_XF86Cut, Key_Cut }, { XKB_KEY_XF86Display, Key_Display }, { XKB_KEY_XF86DOS, Key_DOS }, { XKB_KEY_XF86Documents, Key_Documents }, { XKB_KEY_XF86Excel, Key_Excel }, { XKB_KEY_XF86Explorer, Key_Explorer }, { XKB_KEY_XF86Game, Key_Game }, { XKB_KEY_XF86Go, Key_Go }, { XKB_KEY_XF86iTouch, Key_iTouch }, { XKB_KEY_XF86LogOff, Key_LogOff }, { XKB_KEY_XF86Market, Key_Market }, { XKB_KEY_XF86Meeting, Key_Meeting }, { XKB_KEY_XF86MenuKB, Key_MenuKB }, { XKB_KEY_XF86MenuPB, Key_MenuPB }, { XKB_KEY_XF86MySites, Key_MySites }, { XKB_KEY_XF86New, Key_New }, { XKB_KEY_XF86News, Key_News }, { XKB_KEY_XF86OfficeHome, Key_OfficeHome }, { XKB_KEY_XF86Open, Key_Open }, { XKB_KEY_XF86Option, Key_Option }, { XKB_KEY_XF86Paste, Key_Paste }, { XKB_KEY_XF86Phone, Key_Phone }, { XKB_KEY_XF86Reply, Key_Reply }, { XKB_KEY_XF86Reload, Key_Reload }, { XKB_KEY_XF86RotateWindows, Key_RotateWindows }, { XKB_KEY_XF86RotationPB, Key_RotationPB }, { XKB_KEY_XF86RotationKB, Key_RotationKB }, { XKB_KEY_XF86Save, Key_Save }, { XKB_KEY_XF86Send, Key_Send }, { XKB_KEY_XF86Spell, Key_Spell }, { XKB_KEY_XF86SplitScreen, Key_SplitScreen }, { XKB_KEY_XF86Support, Key_Support }, { XKB_KEY_XF86TaskPane, Key_TaskPane }, { XKB_KEY_XF86Terminal, Key_Terminal }, { XKB_KEY_XF86Tools, Key_Tools }, { XKB_KEY_XF86Travel, Key_Travel }, { XKB_KEY_XF86Video, Key_Video }, { XKB_KEY_XF86Word, Key_Word }, { XKB_KEY_XF86Xfer, Key_Xfer }, { XKB_KEY_XF86ZoomIn, Key_ZoomIn }, { XKB_KEY_XF86ZoomOut, Key_ZoomOut }, { XKB_KEY_XF86Away, Key_Away }, { XKB_KEY_XF86Messenger, Key_Messenger }, { XKB_KEY_XF86WebCam, Key_WebCam }, { XKB_KEY_XF86MailForward, Key_MailForward }, { XKB_KEY_XF86Pictures, Key_Pictures }, { XKB_KEY_XF86Music, Key_Music }, { XKB_KEY_XF86Battery, Key_Battery }, { XKB_KEY_XF86WLAN, Key_WLAN }, { XKB_KEY_XF86UWB, Key_UWB }, { XKB_KEY_XF86AudioForward, Key_AudioForward }, { XKB_KEY_XF86AudioRepeat, Key_AudioRepeat }, { XKB_KEY_XF86AudioRandomPlay, Key_AudioRandomPlay }, { XKB_KEY_XF86Subtitle, Key_Subtitle }, { XKB_KEY_XF86AudioCycleTrack, Key_AudioCycleTrack }, { XKB_KEY_XF86Time, Key_Time }, { XKB_KEY_XF86Select, Key_Select }, { XKB_KEY_XF86View, Key_View }, { XKB_KEY_XF86TopMenu, Key_TopMenu }, { XKB_KEY_XF86Red, Key_Red }, { XKB_KEY_XF86Green, Key_Green }, { XKB_KEY_XF86Yellow, Key_Yellow }, { XKB_KEY_XF86Blue, Key_Blue }, { XKB_KEY_XF86Bluetooth, Key_Bluetooth }, { XKB_KEY_XF86Suspend, Key_Suspend }, { XKB_KEY_XF86Hibernate, Key_Hibernate }, { XKB_KEY_XF86TouchpadToggle, Key_TouchpadToggle }, { XKB_KEY_XF86TouchpadOn, Key_TouchpadOn }, { XKB_KEY_XF86TouchpadOff, Key_TouchpadOff }, { XKB_KEY_XF86AudioMicMute, Key_MicMute }, { XKB_KEY_XF86RFKill, Key_RFKill }, { XKB_KEY_XF86Launch0, Key_Launch2 }, // ### Qt 6: remap properly { XKB_KEY_XF86Launch1, Key_Launch3 }, { XKB_KEY_XF86Launch2, Key_Launch4 }, { XKB_KEY_XF86Launch3, Key_Launch5 }, { XKB_KEY_XF86Launch4, Key_Launch6 }, { XKB_KEY_XF86Launch5, Key_Launch7 }, { XKB_KEY_XF86Launch6, Key_Launch8 }, { XKB_KEY_XF86Launch7, Key_Launch9 }, { XKB_KEY_XF86Launch8, Key_LaunchA }, { XKB_KEY_XF86Launch9, Key_LaunchB }, { XKB_KEY_XF86LaunchA, Key_LaunchC }, { XKB_KEY_XF86LaunchB, Key_LaunchD }, { XKB_KEY_XF86LaunchC, Key_LaunchE }, { XKB_KEY_XF86LaunchD, Key_LaunchF }, { XKB_KEY_XF86LaunchE, Key_LaunchG }, { XKB_KEY_XF86LaunchF, Key_LaunchH }, }; static int compare_qtkey(const void *p1, const void *p2) { enum QtKey k1 = ((struct Xkb2Qt *)p1)->qtkey; enum QtKey k2 = ((struct Xkb2Qt *)p2)->qtkey; // one qtkey may has multiple xkb keysym, fixup it please if (k1 == k2) { fprintf(stderr, "// something goes wrong, multiple 0x%08x\n", k1); } return k1 > k2 ? 1 : (k1 == k2 ? 0 : -1); } static int compare_keysym(const void *p1, const void *p2) { int32_t k1 = ((struct Xkb2Qt *)p1)->keysym; int32_t k2 = ((struct Xkb2Qt *)p2)->keysym; // one qtkey may has multiple xkb keysym, fixup it please if (k1 == k2) { fprintf(stderr, "// something goes wrong, multiple 0x%08x\n", k1); } return k1 > k2 ? 1 : (k1 == k2 ? 0 : -1); } int main(int argc, char *argv[]) { size_t count = sizeof(KeyTbl) / sizeof(struct Xkb2Qt); /* sort the table */ qsort(KeyTbl, count, sizeof(struct Xkb2Qt), compare_qtkey); /* print the result to file */ fprintf(stdout, "/* Generated by qtkey-mapper */\n\n" "#ifndef _QTKEY_MAP_H_\n" "#define _QTKEY_MAP_H_\n\n" "#define KEY_MAP_COUNT (%lu)\n\n", count); fprintf(stdout, "static const struct qtkey_map {\n" " unsigned int qtkey;\n" " unsigned int keysym;\n" "} qtkey_map_table[] = {\n"); const struct Xkb2Qt *map; size_t row = count / 3; size_t left = count % 3; for (size_t i = 0; i < row; i++) { map = &KeyTbl[i * 3]; fprintf(stdout, " { 0x%08x, 0x%08x }, { 0x%08x, 0x%08x }, { 0x%08x, 0x%08x },\n", map->qtkey, map->keysym, (map + 1)->qtkey, (map + 1)->keysym, (map + 2)->qtkey, (map + 2)->keysym); } if (left) { fprintf(stdout, " "); } for (size_t i = 0; i < left; i++) { map = &KeyTbl[row * 3 + i]; fprintf(stdout, "{ 0x%08x, 0x%08x }, ", map->qtkey, map->keysym); } if (left) { fprintf(stdout, "\n"); } fprintf(stdout, "};\n\n"); size_t i, j; struct Xkb2Qt temp; for (i = 0; i < count - 1; i++) { for (j = 0; j < count - 1 - i; j++) { if (KeyTbl[j].keysym > KeyTbl[j + 1].keysym) { temp = KeyTbl[j]; KeyTbl[j] = KeyTbl[j + 1]; KeyTbl[j + 1] = temp; } } } /* sort the table */ qsort(KeyTbl, count, sizeof(struct Xkb2Qt), compare_keysym); fprintf(stdout, "static const struct qtkey_map keysym_map_table[] = {\n"); for (size_t i = 0; i < row; i++) { map = &KeyTbl[i * 3]; fprintf(stdout, " { 0x%08x, 0x%08x }, { 0x%08x, 0x%08x }, { 0x%08x, 0x%08x },\n", map->qtkey, map->keysym, (map + 1)->qtkey, (map + 1)->keysym, (map + 2)->qtkey, (map + 2)->keysym); } if (left) { fprintf(stdout, " "); } for (size_t i = 0; i < left; i++) { map = &KeyTbl[row * 3 + i]; fprintf(stdout, "{ 0x%08x, 0x%08x }, ", map->qtkey, map->keysym); } if (left) { fprintf(stdout, "\n"); } fprintf(stdout, "};\n\n#endif\n"); return 0; } kylin-wayland-compositor/src/config/meson.build0000664000175000017500000000142015160460057020656 0ustar fengfengwlcom_sources += files( 'common.c', 'config.c', ) if have_kde_global_accel qtkey_mapper_gen = executable('qtkey-mapper', 'qtkey_mapper.c') map_table_h = custom_target( 'qtkey_map_table.h', output : 'qtkey_map_table.h', capture: true, command : [qtkey_mapper_gen], ) wlcom_sources += map_table_h wlcom_sources += files( 'kde_global_accel.c', ) endif if have_kde_input wlcom_sources += files( 'kde_input.c', ) endif if have_ukui_shortcut wlcom_sources += files( 'ukui_shortcut.c', ) endif if have_ukui_gsettings wlcom_sources += files( 'ukui_gsettings.c', ) glib = dependency('glib-2.0', version: '>= 2.64') wlcom_deps += glib endif if have_ukui_view_mode wlcom_sources += files( 'ukui_view_mode.c', ) endif kylin-wayland-compositor/src/config/ukui_gsettings.c0000664000175000017500000003264615160461067021744 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "config_p.h" #include "effect/effect.h" #include "input/input.h" #include "server.h" #include "theme.h" #include "util/color.h" #include "util/dbus.h" #include "util/macros.h" struct ukui_settings { struct { GSettings *settings; char *theme; int size; } cursor; struct { GSettings *settings; char *font_name; char *font_size; enum theme_type type; char *widget_theme; bool window_radius_is_string; } style; struct { GSettings *settings; char *picture_file; int picture_options; int primary_color; } background; struct wl_listener destroy; }; #define UKUI_THEME_LIGHT "ukui-light" #define UKUI_THEME_DARK "ukui-dark" static const char *cursor_path = "/org/ukui/desktop/peripherals/mouse/"; #define CURSOR_PATH_LEN (36) static const char *style_path = "/org/ukui/style/"; #define STYLE_PATH_LEN (16) static const char *background_path = "/org/mate/desktop/background/"; #define BACKGROUND_PATH_LEN (29) static const char *cursor_schema = "org.ukui.peripherals-mouse"; static const char *cursor_theme_key = "cursor-theme"; static const char *cursor_size_key = "cursor-size"; static const char *locate_pointer_key = "locate-pointer"; static const char *shake_cursor_key = "shake-cursor"; static const char *style_schema = "org.ukui.style"; static const char *style_name_key = "style-name"; static const char *icon_theme_key = "icon-theme-name"; static const char *widget_theme_key = "widget-theme-name"; static const char *font_name_key = "system-font"; static const char *font_size_key = "system-font-size"; static const char *accent_color_key = "theme-color"; static const char *window_radius_key = "window-radius"; static const char *menu_transparency_key = "menu-transparency"; static const char *background_schema = "org.mate.background"; static const char *background_picture_key = "picture-filename"; static const char *background_options_key = "picture-options"; static const char *background_color_key = "primary-color"; static const struct ukui_accent_color { char *name; int32_t color; } ukui_accent_colors[] = { { "daybreakBlue", 0x3790FA }, { "jamPurple", 0x722ED1 }, { "magenta", 0xEB3096 }, { "sunRed", 0xF3222D }, { "sunsetOrange", 0xF68C27 }, { "dustGold", 0xF9C53D }, { "polarGreen", 0x52C429 }, }; static const struct { char *name; enum background_option option; } background_options[] = { { "scaled", BACKGROUND_OPTION_SCALED }, { "wallpaper", BACKGROUND_OPTION_WALLPAPER }, { "centered", BACKGROUND_OPTION_CENTERED }, { "stretched", BACKGROUND_OPTION_STRETCHED }, { "zoom", BACKGROUND_OPTION_ZOOM }, { "spanned", BACKGROUND_OPTION_SPANNED }, }; static struct ukui_settings *settings = NULL; static GSettingsSchema *is_schema_installed(const char *schema_id) { GSettingsSchemaSource *source = g_settings_schema_source_get_default(); return g_settings_schema_source_lookup(source, schema_id, TRUE); } static void handle_cursor_settings_changed(GSettingsSchema *schema, GSettings *mouse, const char *key) { if (strcmp(key, locate_pointer_key) == 0) { bool enabled = g_settings_get_boolean(mouse, key); struct effect *effect = effect_by_name("locate_pointer"); if (effect) { effect_set_enabled(effect, enabled); } return; } else if (strcmp(key, shake_cursor_key) == 0) { bool enabled = g_settings_get_boolean(mouse, key); struct effect *effect = effect_by_name("shake_cursor"); if (effect) { effect_set_enabled(effect, enabled); } return; } if (strcmp(key, cursor_theme_key) == 0) { free(settings->cursor.theme); settings->cursor.theme = g_settings_get_string(mouse, key); } else if (strcmp(key, cursor_size_key) == 0) { settings->cursor.size = g_settings_get_int(mouse, key); } else { return; } if (settings->cursor.theme && settings->cursor.size > 0) { input_set_all_cursor(settings->cursor.theme, settings->cursor.size); } } static void style_name_changed(GSettings *style, const char *key) { if (strcmp(widget_theme_key, key) == 0) { free(settings->style.widget_theme); settings->style.widget_theme = g_settings_get_string(style, key); } else { const char *style_name = g_settings_get_string(style, key); if (!strcmp(style_name, UKUI_THEME_LIGHT)) { settings->style.type = THEME_TYPE_LIGHT; } else if (!strcmp(style_name, UKUI_THEME_DARK)) { settings->style.type = THEME_TYPE_DARK; } free((void *)style_name); } if (settings->style.type != THEME_TYPE_UNDEFINED) { theme_manager_set_widget_theme(settings->style.widget_theme, settings->style.type); } } static void icon_theme_changed(GSettings *style, const char *key) { const char *icon_theme = g_settings_get_string(style, key); theme_manager_set_icon_theme(icon_theme); free((void *)icon_theme); } static void font_style_changed(GSettings *style, const char *key) { if (strcmp(key, font_name_key) == 0) { free(settings->style.font_name); settings->style.font_name = g_settings_get_string(style, key); } else { free(settings->style.font_size); settings->style.font_size = g_settings_get_string(style, key); } if (settings->style.font_name && settings->style.font_size) { theme_manager_set_font(settings->style.font_name, round(atof(settings->style.font_size))); } } static void accent_color_changed(GSettings *style, const char *key) { const char *accent_color = g_settings_get_string(style, key); /* try '6,192,199,1' format first */ struct color color; int num = sscanf(accent_color, "%hhu,%hhu,%hhu,%f", &color.r, &color.g, &color.b, &color.a); if (num == 4) { theme_manager_set_accent_color(color_to_uint24(&color)); free((void *)accent_color); return; } /* fallback to old format */ for (size_t i = 0; i < ARRAY_SIZE(ukui_accent_colors); i++) { if (strcmp(accent_color, ukui_accent_colors[i].name) == 0) { theme_manager_set_accent_color(ukui_accent_colors[i].color); break; } } free((void *)accent_color); } static void window_radius_changed(GSettings *style, const char *key) { int window_radius = -1, menu_radius = -1; if (settings->style.window_radius_is_string) { const char *radius = g_settings_get_string(style, key); int num = sscanf(radius, "%d,%d", &window_radius, &menu_radius); free((void *)radius); if (num < 1 || window_radius < 0) { return; } } else { window_radius = g_settings_get_int(style, key); } theme_manager_set_corner_radius(window_radius, menu_radius); } static void menu_transparency_changed(GSettings *style, const char *key) { int menu_transparency = g_settings_get_int(style, key); theme_manager_set_opacity(menu_transparency); } static void handle_style_settings_changed(GSettingsSchema *schema, GSettings *style, const char *key) { if (strcmp(key, style_name_key) == 0 || (strcmp(key, widget_theme_key) == 0)) { style_name_changed(style, key); } else if (strcmp(key, icon_theme_key) == 0) { icon_theme_changed(style, key); } else if (strcmp(key, font_name_key) == 0 || strcmp(key, font_size_key) == 0) { font_style_changed(style, key); } else if (strcmp(key, accent_color_key) == 0) { accent_color_changed(style, key); } else if (strcmp(key, window_radius_key) == 0) { if (schema) { GSettingsSchemaKey *schema_key = g_settings_schema_get_key(schema, key); const GVariantType *value_type = g_settings_schema_key_get_value_type(schema_key); if (g_variant_type_equal(value_type, G_VARIANT_TYPE_STRING)) { settings->style.window_radius_is_string = true; } g_settings_schema_key_unref(schema_key); } window_radius_changed(style, key); } else if (strcmp(key, menu_transparency_key) == 0) { menu_transparency_changed(style, key); } } static int options_strings_to_option(const char *options) { size_t num = sizeof(background_options) / sizeof(background_options[0]); for (size_t i = 0; i < num; i++) { if (strcmp(options, background_options[i].name) == 0) { return background_options[i].option; } } return BACKGROUND_OPTION_STRETCHED; } static void handle_background_settings_changed(GSettingsSchema *schema, GSettings *background, const char *key) { if (strcmp(key, background_picture_key) == 0) { free(settings->background.picture_file); settings->background.picture_file = g_settings_get_string(background, key); } else if (strcmp(key, background_options_key) == 0) { const char *options = g_settings_get_string(background, key); settings->background.picture_options = options_strings_to_option(options); free((void *)options); } else if (strcmp(key, background_color_key) == 0) { const char *color = g_settings_get_string(background, key); sscanf(color + 1, "%x", &settings->background.primary_color); settings->background.primary_color &= 0xffffff; free((void *)color); } else { return; } bool has_picture = settings->background.picture_file && *settings->background.picture_file && settings->background.picture_options != 0; bool has_solid = settings->background.picture_file && !*settings->background.picture_file && settings->background.primary_color != 0; if (has_picture || has_solid) { theme_manager_set_background(has_picture ? settings->background.picture_file : NULL, settings->background.picture_options, settings->background.primary_color); } } static int dconf_notify(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *prefix; CK(sd_bus_message_read_basic(msg, 's', &prefix)); CK(sd_bus_message_enter_container(msg, 'a', "s")); const char *change; char key[128]; int ret = 0; /* get current changed key */ while (true) { ret = sd_bus_message_read(msg, "s", &change); if (ret < 0) { return ret; } else if (ret == 0) { break; } snprintf(key, 128, "%s%s", prefix, change); if (strncmp(key, style_path, STYLE_PATH_LEN) == 0) { handle_style_settings_changed(NULL, settings->style.settings, key + STYLE_PATH_LEN); } else if (strncmp(key, cursor_path, CURSOR_PATH_LEN) == 0) { handle_cursor_settings_changed(NULL, settings->cursor.settings, key + CURSOR_PATH_LEN); } else if (strncmp(key, background_path, BACKGROUND_PATH_LEN) == 0) { handle_background_settings_changed(NULL, settings->background.settings, key + BACKGROUND_PATH_LEN); } } return 0; } static GSettings *init_schema_settings(const char *schema_id, void (*handler)(GSettingsSchema *schema, GSettings *settings, const char *key)) { GSettingsSchema *schema = is_schema_installed(schema_id); if (!schema) { return NULL; } GSettings *settings = g_settings_new(schema_id); if (!settings) { g_settings_schema_unref(schema); return NULL; } gchar **keys = g_settings_schema_list_keys(schema); for (int i = 0; keys && keys[i] != NULL; i++) { handler(schema, settings, keys[i]); } g_strfreev(keys); g_settings_schema_unref(schema); return settings; } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&settings->destroy.link); g_object_unref(settings->cursor.settings); g_object_unref(settings->style.settings); g_object_unref(settings->background.settings); free(settings->cursor.theme); free(settings->style.font_name); free(settings->style.font_size); free(settings->style.widget_theme); free(settings->background.picture_file); free(settings); } bool ukui_gsettings_create(struct config_manager *config_manager) { settings = calloc(1, sizeof(*settings)); if (!settings) { return false; } settings->style.type = THEME_TYPE_UNDEFINED; settings->cursor.settings = init_schema_settings(cursor_schema, handle_cursor_settings_changed); settings->style.settings = init_schema_settings(style_schema, handle_style_settings_changed); if (!settings->cursor.settings && !settings->style.settings) { free(settings); settings = NULL; return false; } settings->background.settings = init_schema_settings(background_schema, handle_background_settings_changed); /* monitor dconf dbus notify */ dbus_match_signal(NULL, "/ca/desrt/dconf/Writer/user", "ca.desrt.dconf.Writer", "Notify", dconf_notify, NULL); settings->destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(config_manager->server->display, &settings->destroy); return true; } kylin-wayland-compositor/src/config/ukui_shortcut.c0000664000175000017500000001441115160461067021576 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include "config_p.h" #include "server.h" #include "util/dbus.h" struct shortcut_service { char *name; uint32_t black_mask; /* blacklist mask */ uint32_t white_mask; /* whitelist mask */ bool block_all; struct wl_list link; }; struct ukui_shortcut_manager { struct wl_list services; struct wl_listener destroy; }; const char *service_path = "/org/ukui/settingsDaemon/shortcut"; const char *service_interface = "org.ukui.settingsDaemon.shortcut"; static struct ukui_shortcut_manager *shortcut_manager = NULL; static int block_shortcuts_message_handler(sd_bus_message *msg, void *userdata, sd_bus_error *error) { struct shortcut_service *service = userdata; CK(sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY, "s")); const char *type_name; while (sd_bus_message_read(msg, "s", &type_name) > 0) { bool found; enum key_binding_type type = kywc_key_binding_type_by_name(type_name, &found); if (!found) { continue; } kywc_log(KYWC_INFO, "Block keybinding type: %s", type_name); /* blocking all */ if (type == KEY_BINDING_TYPE_NUM) { service->block_all = true; kywc_key_binding_block_all(true); break; } /* blocking types based on backlist */ kywc_key_binding_block_type(type, true); service->black_mask |= 1 << type; } CK(sd_bus_message_exit_container(msg)); return 0; } static int unblock_shortcuts_message_handler(sd_bus_message *msg, void *userdata, sd_bus_error *error) { struct shortcut_service *service = userdata; CK(sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY, "s")); const char *type_name; while (sd_bus_message_read(msg, "s", &type_name) > 0) { bool found; enum key_binding_type type = kywc_key_binding_type_by_name(type_name, &found); if (!found) { continue; } service->white_mask |= 1 << type; } kywc_log(KYWC_INFO, "Unblock whitelist mask: %x", service->white_mask); if (service->white_mask) { for (size_t i = KEY_BINDING_TYPE_CUSTOM_DEF; i < KEY_BINDING_TYPE_NUM; i++) { /* blocking type is not in the whitelist */ if (!(service->white_mask & (1 << i))) { kywc_log(KYWC_DEBUG, "Block by binding type index: %ld", i); kywc_key_binding_block_type(i, true); continue; } } } CK(sd_bus_message_exit_container(msg)); return 0; } static void shortcut_service_destroy(struct shortcut_service *service) { for (size_t i = KEY_BINDING_TYPE_CUSTOM_DEF; i < KEY_BINDING_TYPE_NUM; i++) { /* recovery of blocked types that are not on the whitelist */ if (service->white_mask && !(service->white_mask & (1 << i))) { kywc_key_binding_block_type(i, false); } /* this type is not in the backlist */ if (!(service->black_mask & (1 << i))) { continue; } /* recovery of blocked types based on backlist */ kywc_key_binding_block_type(i, false); } if (service->block_all) { kywc_key_binding_block_all(false); } wl_list_remove(&service->link); free(service->name); free(service); } static void ukui_shortcut_service_destroy(const char *name) { struct shortcut_service *service, *service_tmp; wl_list_for_each_safe(service, service_tmp, &shortcut_manager->services, link) { if (strcmp(service->name, name)) { continue; } shortcut_service_destroy(service); } } static void ukui_shortcut_service_create(const char *name) { struct shortcut_service *service = calloc(1, sizeof(*service)); if (!service) { return; } if (!dbus_call_method(name, service_path, service_interface, "blockShortcuts", block_shortcuts_message_handler, service)) { kywc_log_errno(KYWC_ERROR, "Dbus call service:%s bolckShortcuts method failed", name); } if (!dbus_call_method(name, service_path, service_interface, "unblockShortcuts", unblock_shortcuts_message_handler, service)) { kywc_log_errno(KYWC_ERROR, "Dbus call service:%s unblockShortcuts method failed", name); } service->name = strdup(name); wl_list_insert(&shortcut_manager->services, &service->link); } static int service_message_handler(sd_bus_message *msg, void *userdata, sd_bus_error *error) { const char *name = NULL, *old_owner = NULL, *new_owner = NULL; CK(sd_bus_message_read(msg, "sss", &name, &old_owner, &new_owner)); if (!name || (strncmp(name, "org.ukui.settingsDaemon", 23))) { return 0; } if (old_owner && !*old_owner) { ukui_shortcut_service_create(name); kywc_log(KYWC_INFO, "Service: %s registered with owner %s", name, new_owner); } else if (new_owner && !*new_owner) { ukui_shortcut_service_destroy(name); kywc_log(KYWC_INFO, "Service: %s unregistered from owner %s", name, old_owner); } return 0; } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&shortcut_manager->destroy.link); struct shortcut_service *service, *service_tmp; wl_list_for_each_safe(service, service_tmp, &shortcut_manager->services, link) { shortcut_service_destroy(service); } free(shortcut_manager); } bool ukui_shortcut_manager_create(struct config_manager *config_manager) { const char *match = "type=signal,interface=org.freedesktop.DBus,member=NameOwnerChanged"; if (!dbus_add_match(match, service_message_handler, NULL)) { kywc_log(KYWC_ERROR, "Ukui_shortcuts_manager listener dbus service failed"); return false; } shortcut_manager = calloc(1, sizeof(*shortcut_manager)); if (!shortcut_manager) { return false; } wl_list_init(&shortcut_manager->services); shortcut_manager->destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(config_manager->server->display, &shortcut_manager->destroy); return true; } kylin-wayland-compositor/src/config/kde_global_accel.c0000664000175000017500000011617715160461067022114 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include // for enum wlr_keyboard_modifier #include #include #include #include "config_p.h" #include "qtkey_map_table.h" #include "server.h" #include "util/dbus.h" /** * convert qtkey to xkb keysym and modifiers */ enum Key { Key_0 = 0x30, Key_9 = 0x39, Key_F1 = 0x01000030, NoModifier = 0x00000000, ShiftModifier = 0x02000000, ControlModifier = 0x04000000, AltModifier = 0x08000000, MetaModifier = 0x10000000, KeypadModifier = 0x20000000, GroupSwitchModifier = 0x40000000, // Do not extend the mask to include 0x01000000 KeyboardModifierMask = 0xfe000000, KeyMask = ~KeyboardModifierMask, }; static int compare_key(const void *p1, const void *p2) { int32_t k1 = ((struct qtkey_map *)p1)->qtkey; int32_t k2 = ((struct qtkey_map *)p2)->qtkey; return k1 > k2 ? 1 : (k1 == k2 ? 0 : -1); } static uint32_t qtkey_to_modifiers(int32_t key) { uint32_t modifiers = 0; if (key & ShiftModifier) { modifiers |= WLR_MODIFIER_SHIFT; } if (key & ControlModifier) { modifiers |= WLR_MODIFIER_CTRL; } if (key & AltModifier) { modifiers |= WLR_MODIFIER_ALT; } if (key & MetaModifier) { modifiers |= WLR_MODIFIER_LOGO; } if (key & KeypadModifier) { modifiers |= WLR_MODIFIER_MOD2; } if (key & GroupSwitchModifier) { modifiers |= WLR_MODIFIER_MOD5; } return modifiers; } static uint32_t qtkey_to_keysym(int32_t key) { uint32_t sym = XKB_KEY_NoSymbol; uint32_t qtKey = key & ~KeyboardModifierMask; if (key & KeypadModifier && qtKey >= Key_0 && qtKey <= Key_9) { sym = XKB_KEY_KP_0 + qtKey - Key_0; } else if (qtKey < 0x1000 && !(key & KeypadModifier)) { sym = tolower(qtKey); } /* bsearch the key_map_table */ if (sym == XKB_KEY_NoSymbol) { struct qtkey_map k = { .qtkey = qtKey }; struct qtkey_map *map = bsearch(&k, qtkey_map_table, KEY_MAP_COUNT, sizeof(struct qtkey_map), compare_key); if (map) { sym = map->keysym; } } if (sym == XKB_KEY_NoSymbol) { kywc_log(KYWC_WARN, "Cannot covert qtkey 0x%08x to xkb symbol", key); } return sym; } static int compare_keysym(const void *p1, const void *p2) { int32_t k1 = ((struct qtkey_map *)p1)->keysym; int32_t k2 = ((struct qtkey_map *)p2)->keysym; return k1 > k2 ? 1 : (k1 == k2 ? 0 : -1); } static uint32_t modifiers_to_qtkey(int32_t key) { uint32_t qtkey = 0; if (key & WLR_MODIFIER_SHIFT) { qtkey |= ShiftModifier; } if (key & WLR_MODIFIER_CTRL) { qtkey |= ControlModifier; } if (key & WLR_MODIFIER_ALT) { qtkey |= AltModifier; } if (key & WLR_MODIFIER_LOGO) { qtkey |= MetaModifier; } if (key & WLR_MODIFIER_MOD2) { qtkey |= KeypadModifier; } if (key & WLR_MODIFIER_MOD5) { qtkey |= GroupSwitchModifier; } return qtkey; } static uint32_t keysym_to_qtkey(int32_t keysym) { uint32_t qtKey = KeyMask; if (keysym >= XKB_KEY_KP_0 && keysym <= XKB_KEY_KP_9) { qtKey = keysym + Key_0 - XKB_KEY_KP_0; } else if (keysym < XKB_KEY_ydiaeresis) { qtKey = toupper(keysym); } else if (keysym >= XKB_KEY_F1 && keysym <= XKB_KEY_R15) { qtKey = keysym - XKB_KEY_F1 + Key_F1; } if (qtKey == KeyMask) { struct qtkey_map k = { .keysym = keysym }; struct qtkey_map *map = bsearch(&k, keysym_map_table, KEY_MAP_COUNT, sizeof(struct qtkey_map), compare_keysym); if (map) { qtKey = map->qtkey; } } if (qtKey == KeyMask) { kywc_log(KYWC_WARN, "Cannot covert xkb symbol 0x%08x to qtkey", keysym); } return qtKey; } /** * dbus server for kde kglobalaccel */ /* Index for actionId QStringLists */ enum actionIdFields { ComponentUnique = 0, //!< Components Unique Name (ID) ActionUnique = 1, //!< Actions Unique Name(ID) ComponentFriendly = 2, //!< Components Friendly Translated Name ActionFriendly = 3, //!< Actions Friendly Translated Name }; enum SetShortcutFlag { SetPresent = 2, NoAutoloading = 4, IsDefault = 8, IsGrab = 16, }; struct global_shortcut_registry { struct wl_list components; struct wl_listener destroy; }; struct global_shortcut_component { struct wl_list link; /* dbus for per component */ const char *dbus_path; char *unique_name; char *friendly_name; struct wl_list contexts; /* current context in component, "default" "Default Context" */ struct global_shortcut_context *current; }; struct global_shortcut_context { struct wl_list link; struct global_shortcut_component *component; char *unique_name; char *friendly_name; struct wl_list shortcuts; }; struct global_shortcut { struct wl_list link; struct global_shortcut_context *context; char *unique_name; char *friendly_name; struct key_binding *binding; /* keys. default_keys, mostly combined with modifiers */ int32_t key; int32_t default_key; /* means the associated application is present */ bool is_present; /* means the shortcut is registered with key_binding */ bool is_registered; /* means the shortcut is new */ bool is_fresh; /* means if the shorctut is triggered in grab */ bool is_grab; }; // TODO: add global_shortcut_action to support key list static const char *registry_bus = "org.kde.kglobalaccel"; static const char *registry_path = "/kglobalaccel"; static const char *registry_interface = "org.kde.KGlobalAccel"; static const char *component_path_prefix = "/component"; static const char *component_interface = "org.kde.kglobalaccel.Component"; struct global_shortcut_registry *registry = NULL; static struct global_shortcut_component * global_shortcut_registry_get_component(const char *unique_name) { struct global_shortcut_component *component; wl_list_for_each(component, ®istry->components, link) { if (strcmp(unique_name, component->unique_name) == 0) { return component; } } return NULL; } static struct global_shortcut *global_shortcut_create(struct global_shortcut_context *context, const char *unique_name, const char *friendly_name) { struct global_shortcut *shortcut = calloc(1, sizeof(*shortcut)); if (!shortcut) { return NULL; } shortcut->is_fresh = true; shortcut->is_present = false; shortcut->is_registered = false; shortcut->unique_name = strdup(unique_name); shortcut->friendly_name = strdup(friendly_name); shortcut->context = context; wl_list_insert(&context->shortcuts, &shortcut->link); return shortcut; } static void global_shortcut_destroy(struct global_shortcut *shortcut, bool clean) { wl_list_remove(&shortcut->link); free(shortcut->unique_name); free(shortcut->friendly_name); /* destroyed in runtime */ if (clean && shortcut->binding) { kywc_key_binding_destroy(shortcut->binding); } free(shortcut); } static struct global_shortcut_context * global_shortcut_context_create(struct global_shortcut_component *component, const char *unique_name, const char *friendly_name) { struct global_shortcut_context *context = calloc(1, sizeof(*context)); if (!context) { return NULL; } wl_list_init(&context->shortcuts); wl_list_insert(&component->contexts, &context->link); context->component = component; context->unique_name = strdup(unique_name); context->friendly_name = strdup(friendly_name); return context; } static void global_shortcut_context_destroy(struct global_shortcut_context *context) { wl_list_remove(&context->link); free(context->unique_name); free(context->friendly_name); struct global_shortcut *shortcut, *tmp; wl_list_for_each_safe(shortcut, tmp, &context->shortcuts, link) { global_shortcut_destroy(shortcut, false); } free(context); } static struct global_shortcut_context * global_shortcut_component_get_context(struct global_shortcut_component *component, const char *context_name) { struct global_shortcut_context *context; wl_list_for_each(context, &component->contexts, link) { if (strcmp(context->unique_name, context_name) == 0) { return context; } } return NULL; } static struct global_shortcut * global_shortcut_context_get_shortcut_by_key(struct global_shortcut_context *context, int32_t key) { struct global_shortcut *shortcut = NULL; wl_list_for_each(shortcut, &context->shortcuts, link) { if (shortcut->key == key) { return shortcut; } } return NULL; } static struct global_shortcut * global_shortcut_context_get_shortcut_by_name(struct global_shortcut_context *context, const char *shortcut_unique) { struct global_shortcut *shortcut = NULL; wl_list_for_each(shortcut, &context->shortcuts, link) { if (strcmp(shortcut_unique, shortcut->unique_name) == 0) { return shortcut; } } return NULL; } /* get shortcut in component current context */ static struct global_shortcut *global_shortcut_registry_get_shortcut_by_key(int32_t key) { struct global_shortcut *shortcut; struct global_shortcut_component *component; wl_list_for_each(component, ®istry->components, link) { shortcut = global_shortcut_context_get_shortcut_by_key(component->current, key); if (shortcut) { return shortcut; } } return NULL; } static struct global_shortcut * global_shortcut_registry_get_shortcut_by_name(const char *component_unique, const char *shortcut_unique) { struct global_shortcut_component *component = global_shortcut_registry_get_component(component_unique); if (component) { struct global_shortcut *shortcut = global_shortcut_context_get_shortcut_by_name(component->current, shortcut_unique); if (shortcut) { return shortcut; } } return NULL; } static void global_shortcut_action(struct key_binding *bindbing, void *data) { struct global_shortcut *shortcut = data; dbus_emit_signal(shortcut->context->component->dbus_path, component_interface, "globalShortcutPressed", "ssx", shortcut->context->component->unique_name, shortcut->unique_name, 0); } static void global_shortcut_create_binding(struct global_shortcut *shortcut) { uint32_t keysym = qtkey_to_keysym(shortcut->key); uint32_t modifiers = qtkey_to_modifiers(shortcut->key); if (shortcut->binding) { kywc_key_binding_update(shortcut->binding, keysym, modifiers, NULL); } else { shortcut->binding = kywc_key_binding_create_by_symbol( keysym, modifiers, false, shortcut->is_grab, shortcut->unique_name); } } static void global_shortcut_set_active(struct global_shortcut *shortcut) { if (!shortcut->is_present || shortcut->is_registered || !shortcut->binding) { return; } /* register the key binding */ shortcut->is_registered = kywc_key_binding_register( shortcut->binding, KEY_BINDING_TYPE_CUSTOM_DEF, global_shortcut_action, shortcut); if (!shortcut->is_registered) { kywc_key_binding_destroy(shortcut->binding); shortcut->binding = NULL; } } static void global_shortcut_set_inactive(struct global_shortcut *shortcut) { if (!shortcut->is_registered) { return; } kywc_key_binding_unregister(shortcut->binding); shortcut->is_registered = false; } /** * dbus process functions for org.kde.kglobalaccel.Component */ // SD_BUS_PROPERTY("friendlyName", "s", friendly_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), static int friendly_name(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct global_shortcut_component *component = userdata; CK(sd_bus_message_append_basic(reply, 's', component->friendly_name)); return 0; } static int unique_name(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct global_shortcut_component *component = userdata; CK(sd_bus_message_append_basic(reply, 's', component->unique_name)); return 0; } // SD_BUS_METHOD("allShortcutInfos", "s", "a(ssssssaiai)", all_shortcut_infos_ex, 0), static int all_shortcut_infos(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *context_unique = NULL; CK(sd_bus_message_read(msg, "s", &context_unique)); struct global_shortcut_component *component = userdata; struct global_shortcut_context *context = global_shortcut_component_get_context( component, context_unique ? context_unique : "default"); sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(msg, &reply)); CK(sd_bus_message_open_container(reply, 'a', "(ssssssaiai)")); if (context) { struct global_shortcut *shortcut; wl_list_for_each(shortcut, &context->shortcuts, link) { CK(sd_bus_message_append(reply, "(ssssssaiai)", context->unique_name, context->friendly_name, component->unique_name, component->friendly_name, shortcut->unique_name, shortcut->friendly_name, 4, shortcut->key, 0, 0, 0, 4, shortcut->default_key, 0, 0, 0)); } } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } // SD_BUS_METHOD("cleanUp", "", "b", clean_up, 0), static int clean_up(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { struct global_shortcut_component *component = userdata; bool changed = false; struct global_shortcut *shortcut; wl_list_for_each(shortcut, &component->current->shortcuts, link) { if (!shortcut->is_present) { changed = true; global_shortcut_destroy(shortcut, true); } } return sd_bus_reply_method_return(msg, "b", changed); } // SD_BUS_METHOD("getShortcutContexts", "", "as", get_shortcut_contexts, 0), static int get_shortcut_contexts(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { struct global_shortcut_component *component = userdata; sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(msg, &reply)); CK(sd_bus_message_open_container(reply, 'a', "s")); struct global_shortcut_context *context; wl_list_for_each(context, &component->contexts, link) { CK(sd_bus_message_append(reply, "s", context->unique_name)); } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } // SD_BUS_METHOD("invokeShortcut", "ss", "", invoke_shortcut, 0), static int invoke_shortcut(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *shortcut_unique, *context_unique = NULL; CK(sd_bus_message_read(msg, "ss", &shortcut_unique, &context_unique)); struct global_shortcut_component *component = userdata; struct global_shortcut_context *context = global_shortcut_component_get_context( component, context_unique ? context_unique : "default"); if (context) { struct global_shortcut *shortcut = global_shortcut_context_get_shortcut_by_name(context, shortcut_unique); if (shortcut) { dbus_emit_signal(shortcut->context->component->dbus_path, component_interface, "globalShortcutPressed", "ssx", shortcut->context->component->unique_name, shortcut->unique_name, 0); } } return sd_bus_reply_method_return(msg, NULL); } // SD_BUS_METHOD("isActive", "", "b", is_active, 0), static int is_active(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { struct global_shortcut_component *component = userdata; bool is_active = false; // The component is active if at least one of it's global shortcuts is present. struct global_shortcut *shortcut; wl_list_for_each(shortcut, &component->current->shortcuts, link) { if (shortcut->is_present) { is_active = true; break; } } return sd_bus_reply_method_return(msg, "b", is_active); } // SD_BUS_METHOD("shortcutNames", "s", "as", shortcut_names_ex, 0), static int shortcut_names(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *context_unique = NULL; CK(sd_bus_message_read(msg, "s", &context_unique)); struct global_shortcut_component *component = userdata; struct global_shortcut_context *context = global_shortcut_component_get_context( component, context_unique ? context_unique : "default"); sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(msg, &reply)); CK(sd_bus_message_open_container(reply, 'a', "s")); if (context) { struct global_shortcut *shortcut; wl_list_for_each(shortcut, &context->shortcuts, link) { CK(sd_bus_message_append(reply, "s", shortcut->unique_name)); } } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } static const sd_bus_vtable kglobalaccel_component_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_SIGNAL("globalShortcutPressed", "ssx", 0), SD_BUS_PROPERTY("friendlyName", "s", friendly_name, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("uniqueName", "s", unique_name, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_METHOD("allShortcutInfos", "s", "a(ssssssaiai)", all_shortcut_infos, 0), SD_BUS_METHOD("cleanUp", "", "b", clean_up, 0), SD_BUS_METHOD("getShortcutContexts", "", "as", get_shortcut_contexts, 0), SD_BUS_METHOD("invokeShortcut", "ss", "", invoke_shortcut, 0), SD_BUS_METHOD("isActive", "", "b", is_active, 0), SD_BUS_METHOD("shortcutNames", "s", "as", shortcut_names, 0), SD_BUS_VTABLE_END, }; static const char *get_dbus_path(const char *name) { int len = snprintf(NULL, 0, "%s/%s", component_path_prefix, name) + 1; char *path = malloc(len); if (!path) { return NULL; } snprintf(path, len, "%s/%s", component_path_prefix, name); /* DBus path can only contain ASCII characters */ char *p = path + strlen(component_path_prefix) + 1; for (; *p; ++p) { if (!isalnum(*p) || (*p & ~0x7f) != 0) { *p = '_'; } } return path; } static struct global_shortcut_component *global_shortcut_component_create(const char *unique_name, const char *friendly_name) { struct global_shortcut_component *component = calloc(1, sizeof(*component)); if (!component) { return NULL; } wl_list_init(&component->contexts); wl_list_insert(®istry->components, &component->link); component->unique_name = strdup(unique_name); component->friendly_name = strdup(friendly_name); /* create the default context in the component */ component->current = global_shortcut_context_create(component, "default", "Default Context"); /* register component dbus */ component->dbus_path = get_dbus_path(unique_name); dbus_register_object(NULL, component->dbus_path, component_interface, kglobalaccel_component_vtable, component); return component; } static void global_shortcut_component_destroy(struct global_shortcut_component *component) { wl_list_remove(&component->link); free(component->unique_name); free(component->friendly_name); free((void *)component->dbus_path); struct global_shortcut_context *context, *tmp; wl_list_for_each_safe(context, tmp, &component->contexts, link) { global_shortcut_context_destroy(context); } free(component); } /** * dbus process functions for org.kde.KGlobalAccel */ // SD_BUS_METHOD("actionList", "(ai)", "as", action_list, 0), static int action_list(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { int32_t key; CK(sd_bus_message_read(msg, "(ai)", 4, &key, NULL, NULL, NULL)); sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(msg, &reply)); struct global_shortcut *shortcut = global_shortcut_registry_get_shortcut_by_key(key); if (shortcut) { CK(sd_bus_message_append(reply, "as", 4, shortcut->context->component->unique_name, shortcut->unique_name, shortcut->context->component->friendly_name, shortcut->friendly_name)); } CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } // SD_BUS_METHOD("activateGlobalShortcutContext", "ss", "", activate_global_shortcut_context, 0), static int activate_global_shortcut_context(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *component_unique, *context_unique; CK(sd_bus_message_read(msg, "ss", &component_unique, &context_unique)); struct global_shortcut_component *component = global_shortcut_registry_get_component(component_unique); if (component) { struct global_shortcut_context *context = global_shortcut_component_get_context(component, context_unique); if (context) { struct global_shortcut *shortcut; wl_list_for_each(shortcut, &component->current->shortcuts, link) { global_shortcut_set_inactive(shortcut); } component->current = context; } } return sd_bus_reply_method_return(msg, NULL); } // SD_BUS_METHOD("allActionsForComponent", "as", "aas", all_actions_for_component, 0), static int all_actions_for_component(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *component_unique; /* only need component unique name in action_id */ CK(sd_bus_message_read(msg, "as", 4, &component_unique, NULL, NULL, NULL)); sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(msg, &reply)); CK(sd_bus_message_open_container(reply, 'a', "as")); struct global_shortcut_component *component = global_shortcut_registry_get_component(component_unique); if (component) { struct global_shortcut_context *context = global_shortcut_component_get_context(component, "default"); struct global_shortcut *shortcut; wl_list_for_each(shortcut, &context->shortcuts, link) { if (shortcut->is_fresh) { continue; } CK(sd_bus_message_append(reply, "as", 4, component->unique_name, shortcut->unique_name, component->friendly_name, shortcut->friendly_name)); } } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } // SD_BUS_METHOD("allComponents", "", "ao", all_components, 0), static int all_components(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(msg, &reply)); CK(sd_bus_message_open_container(reply, 'a', "o")); struct global_shortcut_component *component; wl_list_for_each(component, ®istry->components, link) { CK(sd_bus_message_append_basic(reply, 'o', component->dbus_path)); } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } // SD_BUS_METHOD("allMainComponents", "", "aas", all_main_components, 0), static int all_main_components(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(msg, &reply)); CK(sd_bus_message_open_container(reply, 'a', "as")); struct global_shortcut_component *component; wl_list_for_each(component, ®istry->components, link) { CK(sd_bus_message_append(reply, "as", 4, component->unique_name, "", component->friendly_name, "")); } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } // SD_BUS_METHOD("blockGlobalShortcuts", "b", "", block_global_shortcuts, 0), static int block_global_shortcuts(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { uint32_t block; CK(sd_bus_message_read(msg, "b", &block)); /* activate and deactivate all shortcuts in component current context */ kywc_key_binding_block_all(block); return sd_bus_reply_method_return(msg, NULL); } // SD_BUS_METHOD("defaultShortcutKeys", "as", "a(ai)", default_shortcut_keys, 0), static int default_shortcut_keys(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *component_unique, *action_unique; CK(sd_bus_message_read(msg, "as", 4, &component_unique, &action_unique, NULL, NULL)); sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(msg, &reply)); CK(sd_bus_message_open_container(reply, 'a', "(ai)")); struct global_shortcut *shortcut = global_shortcut_registry_get_shortcut_by_name(component_unique, action_unique); if (shortcut) { CK(sd_bus_message_append(reply, "(ai)", 4, shortcut->default_key, 0, 0, 0)); } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } // SD_BUS_METHOD("doRegister", "as", "", do_register, 0), static int do_register(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *component_unique, *action_unique, *component_friendly, *action_friendly; CK(sd_bus_message_read(msg, "as", 4, &component_unique, &action_unique, &component_friendly, &action_friendly)); struct global_shortcut *shortcut = global_shortcut_registry_get_shortcut_by_name(component_unique, action_unique); if (shortcut) { /* replace friendly names */ struct global_shortcut_component *component = shortcut->context->component; if (*component_friendly && strcmp(component->friendly_name, component_friendly)) { free(component->friendly_name); component->friendly_name = strdup(component_friendly); } if (*action_friendly && strcmp(shortcut->friendly_name, action_friendly)) { free(shortcut->friendly_name); shortcut->friendly_name = strdup(action_friendly); } } else { /* create a shortcut */ struct global_shortcut_component *component = global_shortcut_registry_get_component(component_unique); /* Create the component if necessary */ if (!component) { component = global_shortcut_component_create(component_unique, component_friendly); } global_shortcut_create(component->current, action_unique, action_friendly); } return sd_bus_reply_method_return(msg, NULL); } // SD_BUS_METHOD("getComponent", "s", "o", get_component, 0), static int get_component(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *component_unique; CK(sd_bus_message_read(msg, "s", &component_unique)); struct global_shortcut_component *component = global_shortcut_registry_get_component(component_unique); if (!component) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST("org.kde.kglobalaccel.NoSuchComponent", "The component doesn't exist."); return sd_bus_reply_method_error(msg, &error); } return sd_bus_reply_method_return(msg, "o", component->dbus_path); } // SD_BUS_METHOD("globalShortcutsByKey", "(ai)i", "a(ssssssaiai)", global_shortcuts_by_key, 0), static int global_shortcuts_by_key(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { int32_t key, type; CK(sd_bus_message_read(msg, "(ai)i", 4, &key, NULL, NULL, NULL, &type)); struct global_shortcut_component *component; struct global_shortcut_context *context; struct global_shortcut *shortcut; sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(msg, &reply)); CK(sd_bus_message_open_container(reply, 'a', "(ssssssaiai)")); wl_list_for_each(component, ®istry->components, link) { wl_list_for_each(context, &component->contexts, link) { shortcut = global_shortcut_context_get_shortcut_by_key(context, key); if (shortcut) { CK(sd_bus_message_append(reply, "(ssssssaiai)", context->unique_name, context->friendly_name, component->unique_name, component->friendly_name, shortcut->unique_name, shortcut->friendly_name, 4, shortcut->key, 0, 0, 0, 4, shortcut->default_key, 0, 0, 0)); } } } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } // SD_BUS_METHOD("globalShortcutAvailable", "(ai)s", "b", global_shortcut_available, 0), static int global_shortcut_available(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *component_unique; int32_t key; CK(sd_bus_message_read(msg, "(ai)s", 4, &key, NULL, NULL, NULL, &component_unique)); struct global_shortcut *shortcut = NULL; struct global_shortcut_component *component = global_shortcut_registry_get_component(component_unique); if (component) { struct global_shortcut_context *context; wl_list_for_each(context, &component->contexts, link) { shortcut = global_shortcut_context_get_shortcut_by_key(context, key); if (shortcut) { break; } } } return sd_bus_reply_method_return(msg, "b", !!shortcut); } // SD_BUS_METHOD("setForeignShortcutKeys", "asa(ai)", "", set_foreign_shortcut_keys, 0), static int set_foreign_shortcut_keys(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *component_unique, *action_unique; int32_t key; CK(sd_bus_message_read(msg, "as", 4, &component_unique, &action_unique, NULL, NULL)); CK(sd_bus_message_enter_container(msg, 'a', "(ai)")); CK(sd_bus_message_read(msg, "(ai)", 4, &key, NULL, NULL, NULL)); // TODO: check key list: sd_bus_message_read(msg, "(ai)", 4, NULL, NULL, NULL, NULL) == 0 CK(sd_bus_message_exit_container(msg)); struct global_shortcut *shortcut = global_shortcut_registry_get_shortcut_by_name(component_unique, action_unique); if (shortcut) { shortcut->key = key; dbus_emit_signal(registry_path, registry_interface, "yourShortcutsChanged", "asa(ai)", 4, shortcut->context->component->unique_name, shortcut->unique_name, shortcut->context->component->friendly_name, shortcut->friendly_name, 1, 4, shortcut->key, 0, 0, 0); } return sd_bus_reply_method_return(msg, NULL); } // SD_BUS_METHOD("setInactive", "as", "", set_inactive, 0), static int set_inactive(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *component_unique, *action_unique; CK(sd_bus_message_read(msg, "as", 4, &component_unique, &action_unique, NULL, NULL)); struct global_shortcut *shortcut = global_shortcut_registry_get_shortcut_by_name(component_unique, action_unique); if (shortcut) { shortcut->is_present = false; global_shortcut_set_inactive(shortcut); } return sd_bus_reply_method_return(msg, NULL); } // SD_BUS_METHOD("setShortcutKeys", "asa(ai)u", "a(ai)", set_shortcut_keys, 0), static int set_shortcut_keys(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *component_unique, *action_unique; int32_t key; uint32_t flags; CK(sd_bus_message_read(msg, "as", 4, &component_unique, &action_unique, NULL, NULL)); CK(sd_bus_message_enter_container(msg, 'a', "(ai)")); CK(sd_bus_message_read(msg, "(ai)", 4, &key, NULL, NULL, NULL)); // TODO: check key list: sd_bus_message_read(msg, "(ai)", 4, NULL, NULL, NULL, NULL) == 0 CK(sd_bus_message_exit_container(msg)); CK(sd_bus_message_read(msg, "u", &flags)); sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(msg, &reply)); CK(sd_bus_message_open_container(reply, 'a', "(ai)")); struct global_shortcut *shortcut = global_shortcut_registry_get_shortcut_by_name(component_unique, action_unique); if (shortcut) { bool setPresent = (flags & SetPresent); bool isAutoloading = !(flags & NoAutoloading); bool isDefault = (flags & IsDefault); bool isGrab = (flags & IsGrab); shortcut->is_grab = isGrab; // default shortcuts cannot clash because they don't do anything if (isDefault) { shortcut->default_key = key; CK(sd_bus_message_append(reply, "(ai)", 4, key, 0, 0, 0)); goto out; } if (isAutoloading && !shortcut->is_fresh) { if (!shortcut->is_present && setPresent) { shortcut->is_present = true; global_shortcut_set_active(shortcut); } // We are finished here. Return the list of current active keys. CK(sd_bus_message_append(reply, "(ai)", 4, shortcut->key, 0, 0, 0)); goto out; } // now we are actually changing the shortcut of the action shortcut->key = key; global_shortcut_create_binding(shortcut); if (setPresent) { shortcut->is_present = true; global_shortcut_set_active(shortcut); } shortcut->is_fresh = false; CK(sd_bus_message_append(reply, "(ai)", 4, shortcut->key, 0, 0, 0)); } out: CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } // SD_BUS_METHOD("shortcutKeys", "as", "a(ai)", shortcut_keys, 0), static int shortcut_keys(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *component_unique, *action_unique; CK(sd_bus_message_read(msg, "as", 4, &component_unique, &action_unique, NULL, NULL)); sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(msg, &reply)); CK(sd_bus_message_open_container(reply, 'a', "(ai)")); struct global_shortcut *shortcut = global_shortcut_registry_get_shortcut_by_name(component_unique, action_unique); if (shortcut) { CK(sd_bus_message_append(reply, "(ai)", 4, shortcut->key, 0, 0, 0)); } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } // SD_BUS_METHOD("unregister", "ss", "b", un_register, 0), static int un_register(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *component_unique, *shortcut_unique; CK(sd_bus_message_read(msg, "ss", &component_unique, &shortcut_unique)); struct global_shortcut *shortcut = global_shortcut_registry_get_shortcut_by_name(component_unique, shortcut_unique); if (shortcut) { global_shortcut_destroy(shortcut, true); } return sd_bus_reply_method_return(msg, "b", !!shortcut); } /* only support kglobalaccel >= 5.90 */ static const sd_bus_vtable kglobalaccel_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_SIGNAL("yourShortcutsChanged", "asa(ai)", 0), SD_BUS_METHOD("actionList", "(ai)", "as", action_list, 0), SD_BUS_METHOD("activateGlobalShortcutContext", "ss", "", activate_global_shortcut_context, 0), SD_BUS_METHOD("allActionsForComponent", "as", "aas", all_actions_for_component, 0), SD_BUS_METHOD("allComponents", "", "ao", all_components, 0), SD_BUS_METHOD("allMainComponents", "", "aas", all_main_components, 0), SD_BUS_METHOD("blockGlobalShortcuts", "b", "", block_global_shortcuts, 0), SD_BUS_METHOD("defaultShortcutKeys", "as", "a(ai)", default_shortcut_keys, 0), SD_BUS_METHOD("doRegister", "as", "", do_register, 0), SD_BUS_METHOD("getComponent", "s", "o", get_component, 0), SD_BUS_METHOD("globalShortcutAvailable", "(ai)s", "b", global_shortcut_available, 0), SD_BUS_METHOD("globalShortcutsByKey", "(ai)i", "a(ssssssaiai)", global_shortcuts_by_key, 0), SD_BUS_METHOD("setForeignShortcutKeys", "asa(ai)", "", set_foreign_shortcut_keys, 0), SD_BUS_METHOD("setInactive", "as", "", set_inactive, 0), SD_BUS_METHOD("setShortcutKeys", "asa(ai)u", "a(ai)", set_shortcut_keys, 0), SD_BUS_METHOD("shortcutKeys", "as", "a(ai)", shortcut_keys, 0), SD_BUS_METHOD("unregister", "ss", "b", un_register, 0), SD_BUS_VTABLE_END, }; static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(®istry->destroy.link); /* free all components contexts shortcuts */ struct global_shortcut_component *component, *tmp; wl_list_for_each_safe(component, tmp, ®istry->components, link) { global_shortcut_component_destroy(component); } free(registry); registry = NULL; } static const char *component_builtin = "kylin-wlcom"; static bool kglobalaccel_builtin_shortcuts(struct key_binding *binding, char *unique_name, char *friendly_name, int32_t modifiers, int32_t key) { struct global_shortcut *shortcut = global_shortcut_registry_get_shortcut_by_name(component_builtin, unique_name); if (!shortcut) { /* create a shortcut */ struct global_shortcut_component *component = global_shortcut_registry_get_component(component_builtin); /* Create the component if necessary */ if (!component) { component = global_shortcut_component_create(component_builtin, friendly_name); } shortcut = global_shortcut_create(component->current, unique_name, friendly_name); } shortcut->binding = binding; shortcut->key = modifiers_to_qtkey(modifiers) | keysym_to_qtkey(key); shortcut->is_present = true; shortcut->is_registered = true; return false; } bool kde_global_accel_manager_create(struct config_manager *config_manager) { registry = calloc(1, sizeof(*registry)); if (!registry) { return false; } if (!dbus_register_object(registry_bus, registry_path, registry_interface, kglobalaccel_vtable, registry)) { free(registry); registry = NULL; return false; } wl_list_init(®istry->components); kywc_key_binding_for_each(kglobalaccel_builtin_shortcuts); registry->destroy.notify = handle_server_destroy; server_add_destroy_listener(config_manager->server, ®istry->destroy); return true; } kylin-wayland-compositor/src/config/ukui_view_mode.c0000664000175000017500000000255015160460057021700 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "config_p.h" #include "util/dbus.h" #include "view/view.h" static int handle_mode_change_signal(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { int tablet_mode = 0; int ret = sd_bus_message_read(m, "b", &tablet_mode); if (ret < 0) { kywc_log(KYWC_WARN, "Failed to parse D-Bus response for mode change: %s", strerror(-ret)); return 0; } const char *name = tablet_mode ? "tablet_mode" : "stack_mode"; view_manager_set_view_mode(name); return 0; } bool ukui_view_mode_manager_create(struct config_manager *config_manager) { if (!dbus_match_signal("com.kylin.statusmanager.interface", "/", "com.kylin.statusmanager.interface", "mode_change_signal", handle_mode_change_signal, NULL)) { kywc_log(KYWC_ERROR, "Ukui_view_mode_manager match mode_change_signal error"); return false; } if (!dbus_call_method("com.kylin.statusmanager.interface", "/", "com.kylin.statusmanager.interface", "get_current_tabletmode", handle_mode_change_signal, NULL)) { kywc_log(KYWC_ERROR, "Ukui_view_mode_manager get current mode error"); return false; } return true; } kylin-wayland-compositor/src/config/common.c0000664000175000017500000000602115160461067020154 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "config_p.h" #include "server.h" #include "util/dbus.h" #include "util/logger.h" static const char *service_path = "/com/kylin/Wlcom"; static const char *service_interface = "com.kylin.Wlcom"; static struct { struct wl_listener destroy; struct wl_event_source *timer; } config = { 0 }; static int set_log_level(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { uint32_t level = 0; CK(sd_bus_message_read(m, "u", &level)); if (level < KYWC_LOG_LEVEL_LAST) { logger_set_level(level); return sd_bus_reply_method_return(m, NULL); } const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid args, please input [0-5]."); return sd_bus_reply_method_error(m, &error); } static int set_log_rate_limit(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { uint32_t interval, burst; CK(sd_bus_message_read(m, "uu", &interval, &burst)); logger_set_rate_limit(interval, burst); return sd_bus_reply_method_return(m, NULL); } static int print_config(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct config_manager *cm = userdata; const char *config = json_object_to_json_string(cm->json); return sd_bus_reply_method_return(m, "s", config); } static int handle_timeout(void *data) { wl_event_source_timer_update(config.timer, 5000); malloc_trim(4096); return 0; } static int trim_memory(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { uint32_t enabled = 0; CK(sd_bus_message_read(m, "b", &enabled)); if (!!enabled == !!config.timer) { return sd_bus_reply_method_return(m, NULL); } struct config_manager *cm = userdata; if (enabled) { config.timer = wl_event_loop_add_timer(cm->server->event_loop, handle_timeout, cm); wl_event_source_timer_update(config.timer, 5000); } else { wl_event_source_remove(config.timer); config.timer = NULL; } return sd_bus_reply_method_return(m, NULL); } static const sd_bus_vtable service_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("SetLogLevel", "u", "", set_log_level, 0), SD_BUS_METHOD("SetLogRateLimit", "uu", "", set_log_rate_limit, 0), SD_BUS_METHOD("PrintConfig", "", "s", print_config, 0), SD_BUS_METHOD("TrimMemory", "b", "", trim_memory, 0), SD_BUS_VTABLE_END, }; static void handle_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&config.destroy.link); if (config.timer) { wl_event_source_remove(config.timer); } } bool config_manager_common_init(struct config_manager *config_manager) { if (!dbus_register_object(NULL, service_path, service_interface, service_vtable, config_manager)) { return false; } config.destroy.notify = handle_destroy; wl_display_add_destroy_listener(config_manager->server->display, &config.destroy); return true; } kylin-wayland-compositor/src/config/config.c0000664000175000017500000001053715160461067020140 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "config_p.h" #include "input/input.h" #include "server.h" #include "util/file.h" #include "util/string.h" static struct config_manager *config_manager = NULL; static const char *check_config_file(void) { char *config_dir = string_expand_path("~/.config/kylin-wlcom"); if (!config_dir) { return NULL; } /* now check config dir */ if (!file_exists(config_dir)) { kywc_log(KYWC_INFO, "Configure dir %s not exist, create it", config_dir); int ret = mkdir(config_dir, S_IRWXU | S_IRWXG); if (ret) { kywc_log_errno(KYWC_ERROR, "Create configure dir failed"); free(config_dir); return NULL; } } const char *fullpath = string_join_path(config_dir, NULL, "config.json"); free(config_dir); return fullpath; } void config_manager_sync(void) { if (!config_manager->file || !config_manager->json) { return; } json_object_to_file_ext(config_manager->file, config_manager->json, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&config_manager->server_destroy.link); wl_list_remove(&config_manager->server_ready.link); struct config *config, *config_tmp; wl_list_for_each_safe(config, config_tmp, &config_manager->configs, link) { config_destroy(config); } config_manager_sync(); json_object_put(config_manager->json); json_object_put(config_manager->sys_json); free((void *)config_manager->file); free(config_manager); config_manager = NULL; } static void handle_server_ready(struct wl_listener *listener, void *data) { input_action_manager_create(config_manager->server); kde_global_accel_manager_create(config_manager); kde_input_manager_create(config_manager); ukui_gsettings_create(config_manager); ukui_shortcut_manager_create(config_manager); ukui_view_mode_manager_create(config_manager); } struct config_manager *config_manager_create(struct server *server) { config_manager = calloc(1, sizeof(*config_manager)); if (!config_manager) { return NULL; } /* read config file */ config_manager->file = check_config_file(); if (config_manager->file) { config_manager->json = json_object_from_file(config_manager->file); } /* get system default config */ config_manager->sys_json = json_object_from_file("/etc/kylin-wlcom/config.json"); kywc_log(KYWC_INFO, "Get the sys default config from the etc directory"); if (!config_manager->sys_json) { kywc_log(KYWC_WARN, "The default config does not exist"); } /* create one if empty */ if (!config_manager->json) { config_manager->json = json_object_new_object(); } config_manager->server = server; wl_list_init(&config_manager->configs); config_manager->server_ready.notify = handle_server_ready; wl_signal_add(&server->events.ready, &config_manager->server_ready); config_manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &config_manager->server_destroy); config_manager_common_init(config_manager); return config_manager; } struct config *config_manager_add_config(const char *name) { struct config *config = calloc(1, sizeof(*config)); if (!config) { return NULL; } json_object *object = config_manager->json; json_object *sys_object = config_manager->sys_json; if (name) { object = json_object_object_get(config_manager->json, name); if (!object) { object = json_object_new_object(); json_object_object_add(config_manager->json, name, object); } sys_object = json_object_object_get(config_manager->sys_json, name); } config->json = object; config->sys_json = sys_object; wl_signal_init(&config->events.destroy); wl_list_insert(&config_manager->configs, &config->link); return config; } void config_destroy(struct config *config) { wl_signal_emit_mutable(&config->events.destroy, NULL); assert(wl_list_empty(&config->events.destroy.listener_list)); wl_list_remove(&config->link); free(config); } kylin-wayland-compositor/src/input/0000775000175000017500000000000015160461067016413 5ustar fengfengkylin-wayland-compositor/src/input/meson.build0000664000175000017500000000072015160460057020552 0ustar fengfengwlcom_sources += files( 'action.c', 'binding.c', 'config.c', 'cursor.c', 'event.c', 'gesture.c', 'idle.c', 'idle_inhibit.c', 'input.c', 'text_input.c', 'keyboard.c', 'keyboard_group.c', 'libinput.c', 'monitor.c', 'tablet.c', 'text_input_v1.c', 'text_input_v2.c', 'toplevel_drag.c', 'touch.c', 'transient_seat.c', 'seat.c', 'selection.c', ) if have_kde_keystate wlcom_sources += files( 'kde_keystate.c', ) endif kylin-wayland-compositor/src/input/input.c0000664000175000017500000006163715160461067017733 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "backend/eis.h" #include "backend/libinput.h" #include "input/input.h" #include "input/seat.h" #include "input_p.h" #include "output.h" #include "server.h" static struct input_manager *input_manager = NULL; void input_set_all_cursor(const char *cursor_theme, uint32_t cursor_size) { struct seat *seat; wl_list_for_each(seat, &input_manager->seats, link) { seat_set_cursor(seat, cursor_theme, cursor_size); } } void input_add_new_listener(struct wl_listener *listener) { wl_signal_add(&input_manager->events.new_input, listener); } static void handle_touchpad_raw_tap(struct wl_listener *listener, void *data) { struct input *input = wl_container_of(listener, input, raw_tap); struct pointer_raw_tap_event *raw_tap_event = data; kywc_log(KYWC_DEBUG, "handle raw_tap fingers: %d", raw_tap_event->fingers); gesture_state_begin(input->tap_gestures, GESTURE_TYPE_TAP, GESTURE_DEVICE_TOUCHPAD, GESTURE_EDGE_NONE, raw_tap_event->fingers); } static void handle_server_destroy(struct wl_listener *listener, void *data) { assert(wl_list_empty(&input_manager->events.new_input.listener_list)); assert(wl_list_empty(&input_manager->events.new_seat.listener_list)); wl_list_remove(&input_manager->server_destroy.link); struct seat *seat, *seat_tmp; wl_list_for_each_safe(seat, seat_tmp, &input_manager->seats, link) { seat_destroy(seat); } struct input_keymap *keymap, *keymap_tmp; wl_list_for_each_safe(keymap, keymap_tmp, &input_manager->keymaps, link) { wl_list_remove(&keymap->link); xkb_keymap_unref(keymap->keymap); free(keymap); } queue_fence_finish(&input_manager->fence); free(input_manager); input_manager = NULL; } static void handle_primary_output(struct wl_listener *listener, void *data) { struct kywc_output *primary = data; if (!primary) { return; } struct input *input = wl_container_of(listener, input, primary_output); wl_list_remove(&input->primary_output.link); wl_list_init(&input->primary_output.link); struct input_state state = input->state; state.mapped_to_output = primary->name; input_set_state(input, &state); } static void handle_mapped_output_disable(struct wl_listener *listener, void *data) { struct input *input = wl_container_of(listener, input, mapped_output_disable); /* if output backend is destroyed before input backend */ if (input_manager->server->terminate) { wl_list_remove(&input->mapped_output_disable.link); wl_list_init(&input->mapped_output_disable.link); wl_list_remove(&input->viewport.link); wl_list_init(&input->viewport.link); return; } /* current mapped output is being off or destroyed */ struct input_state state = input->state; state.mapped_to_output = NULL; if (input->prop.type == WLR_INPUT_DEVICE_TOUCH) { struct kywc_output *primary = kywc_output_get_primary(); /* if it is primary, map to NULL and listen for primary change to remap */ /* otherwise, map to the primary */ if (!primary || primary->destroying) { kywc_output_add_primary_listener(&input->primary_output); } else { state.mapped_to_output = primary->name; } } input_set_state(input, &state); } static void handle_mapped_output_viewport(struct wl_listener *listener, void *data) { struct input *input = wl_container_of(listener, input, viewport); struct wlr_cursor *wlr_cursor = input->seat->cursor->wlr_cursor; struct output *output = output_from_kywc_output(input->mapped_output); if (!wlr_box_empty(&output->scene_output->viewport.src)) { wlr_cursor_map_input_to_region(wlr_cursor, input->wlr_input, &output->scene_output->viewport.src); } else { wlr_cursor_map_input_to_output(wlr_cursor, input->wlr_input, output->wlr_output); } } static void input_destroy(struct input *input) { /* Tell the user the name of the removed device. */ input_notify_destroy(input); wl_signal_emit_mutable(&input->events.destroy, NULL); assert(wl_list_empty(&input->events.destroy.listener_list)); wl_list_remove(&input->link); wl_list_remove(&input->mapped_output_disable.link); wl_list_remove(&input->primary_output.link); wl_list_remove(&input->viewport.link); wl_list_remove(&input->raw_tap.link); kywc_log(KYWC_DEBUG, "Input device %s destroy", input->name); if (input->seat) { seat_remove_input(input); } if (input->tap_gestures) { gesture_state_finish(input->tap_gestures); free(input->tap_gestures); } free((void *)input->name); free(input); } static void handle_input_destroy(struct wl_listener *listener, void *data) { struct input *input = wl_container_of(listener, input, destroy); wl_list_remove(&input->destroy.link); input_destroy(input); } static void input_get_prop(struct input *input, struct input_prop *prop) { struct wlr_input_device *wlr_input = input->wlr_input; input->prop.type = wlr_input->type; input->prop.is_virtual = input->name && strncmp(input->name, "V_", 2) == 0; input->prop.support_mapped_to_output = wlr_input->type == WLR_INPUT_DEVICE_POINTER || wlr_input->type == WLR_INPUT_DEVICE_TOUCH || wlr_input->type == WLR_INPUT_DEVICE_TABLET; if (input->device) { libinput_get_prop(input, prop); } } static void input_get_state(struct input *input, struct input_state *state) { struct wlr_input_device *wlr_input = input->wlr_input; state->seat = input->seat ? input->seat->name : NULL; state->mapped_to_output = input->mapped_output ? input->mapped_output->name : NULL; state->scroll_factor = input->state.scroll_factor > 0 ? input->state.scroll_factor : input->default_state.scroll_factor; state->double_click_time = input->state.double_click_time > 0 ? input->state.double_click_time : input->default_state.double_click_time; if (input->prop.type == WLR_INPUT_DEVICE_KEYBOARD) { struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(wlr_input); state->repeat_rate = wlr_keyboard->repeat_info.rate; state->repeat_delay = wlr_keyboard->repeat_info.delay; } if (input->device) { libinput_get_state(input, state); } } static void input_get_default_state(struct input *input, struct input_state *state) { state->seat = NULL; state->mapped_to_output = NULL; state->scroll_factor = 1.0; state->double_click_time = DEFAULT_DOUBLE_CLICK_TIME; if (input->prop.type == WLR_INPUT_DEVICE_KEYBOARD) { state->repeat_rate = 25; state->repeat_delay = 600; } if (input->device) { libinput_get_default_state(input, state); } } struct input *input_create(struct wlr_input_device *wlr_input, bool virtual) { struct input *input = calloc(1, sizeof(*input)); if (!input) { return NULL; } input->wlr_input = wlr_input; wlr_input->data = input; input->destroy.notify = handle_input_destroy; wl_signal_add(&wlr_input->events.destroy, &input->destroy); input->manager = input_manager; wl_signal_init(&input->events.destroy); wl_list_insert(&input_manager->inputs, &input->link); input->mapped_output_disable.notify = handle_mapped_output_disable; input->primary_output.notify = handle_primary_output; input->viewport.notify = handle_mapped_output_viewport; input->raw_tap.notify = handle_touchpad_raw_tap; wl_list_init(&input->mapped_output_disable.link); wl_list_init(&input->primary_output.link); wl_list_init(&input->viewport.link); wl_list_init(&input->raw_tap.link); if (wlr_input_device_is_libinput(wlr_input)) { input->device = wlr_libinput_get_device_handle(wlr_input); } if (virtual) { input->name = kywc_identifier_generate("V_%s", wlr_input->name); } input_get_prop(input, &input->prop); if (!input->prop.is_virtual) { input->name = kywc_identifier_generate("%d:%d:%d:%s", wlr_input->type, input->prop.vendor, input->prop.product, wlr_input->name); } input_get_default_state(input, &input->default_state); input_get_state(input, &input->state); // touchpad checked if (input->prop.type == WLR_INPUT_DEVICE_POINTER && input->prop.tap_finger_count && input->device) { input->tap_gestures = calloc(1, sizeof(*input->tap_gestures)); gesture_state_init(input->tap_gestures, input_manager->server->display); wlr_libinput_add_raw_tap_listener(input->wlr_input, &input->raw_tap); } struct input_state state = input->state; bool found = input_read_config(input, &state); if (!found) { // keep default } // map touch screen to primary output by default if (input->prop.type == WLR_INPUT_DEVICE_TOUCH) { struct kywc_output *primary = kywc_output_get_primary(); state.mapped_to_output = primary ? primary->name : NULL; } input_set_state(input, &state); wl_signal_emit_mutable(&input_manager->events.new_input, input); if (kywc_log_get_level() == KYWC_DEBUG) { kywc_log(KYWC_DEBUG, "Input device %s create", input->name); input_prop_and_state_debug(input); } /* Tell the user the name of the added device. */ input_notify_create(input); return input; } static void handle_new_input(struct wl_listener *listener, void *data) { struct wlr_input_device *wlr_input = data; input_create(wlr_input, false); } static void handle_new_virtual_pointer(struct wl_listener *listener, void *data) { struct wlr_virtual_pointer_v1_new_pointer_event *event = data; struct wlr_virtual_pointer_v1 *pointer = event->new_pointer; struct wlr_input_device *wlr_input = &pointer->pointer.base; struct input *input = input_create(wlr_input, true); if (!input) { return; } /* apply suggested seat and output */ if (event->suggested_seat || event->suggested_output) { struct input_state state = input->state; if (event->suggested_seat) { state.seat = event->suggested_seat->name; } if (event->suggested_output) { state.mapped_to_output = event->suggested_output->name; } input_set_state(input, &state); } } static void handle_new_virtual_keyboard(struct wl_listener *listener, void *data) { struct wlr_virtual_keyboard_v1 *keyboard = data; struct wlr_input_device *wlr_input = &keyboard->keyboard.base; struct input *input = input_create(wlr_input, true); if (!input) { return; } /* apply keyboard seat */ if (strcmp(input->seat->name, keyboard->seat->name)) { struct input_state state = input->state; state.seat = keyboard->seat->name; input_set_state(input, &state); } } uint32_t input_manager_for_each_seat(seat_iterator_func_t iterator, void *data) { uint32_t index = 0; struct seat *seat; wl_list_for_each(seat, &input_manager->seats, link) { if (iterator(seat, index++, data)) { break; } } return index; } static void handle_keyboard_shortcuts_inhibitor_destroy(struct wl_listener *listener, void *data) { struct seat_keyboard_shortcuts_inhibitor *shortcuts_inhibitor = wl_container_of(listener, shortcuts_inhibitor, destroy); wl_list_remove(&shortcuts_inhibitor->link); wl_list_remove(&shortcuts_inhibitor->destroy.link); free(shortcuts_inhibitor); } static void handle_new_shortcuts_inhibitor(struct wl_listener *listener, void *data) { struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = data; struct seat_keyboard_shortcuts_inhibitor *shortcuts_inhibitor = calloc(1, sizeof(*shortcuts_inhibitor)); if (!shortcuts_inhibitor) { return; } shortcuts_inhibitor->inhibitor = inhibitor; shortcuts_inhibitor->destroy.notify = handle_keyboard_shortcuts_inhibitor_destroy; wl_signal_add(&inhibitor->events.destroy, &shortcuts_inhibitor->destroy); struct seat *seat = seat_from_wlr_seat(inhibitor->seat); wl_list_insert(&seat->keyboard_shortcuts_inhibitors, &shortcuts_inhibitor->link); wlr_keyboard_shortcuts_inhibitor_v1_activate(inhibitor); } static void handle_new_pointer_constraint(struct wl_listener *listener, void *data) { struct wlr_pointer_constraint_v1 *constraint = data; struct seat *seat = seat_from_wlr_seat(constraint->seat); cursor_constraint_create(seat->cursor, constraint); } static void handle_request_set_cursor_shape(struct wl_listener *listener, void *data) { const struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; struct seat *seat = seat_from_wlr_seat(event->seat_client->seat); struct wlr_seat_client *focused_client = seat->wlr_seat->pointer_state.focused_client; if (seat->pointer_grab || focused_client != event->seat_client) { return; } cursor_set_image(seat->cursor, (enum cursor_name)event->shape); } struct xkb_keymap *input_get_or_create_keymap(struct keymap_rules *rules, bool wait) { if (wait) { queue_fence_wait(&input_manager->fence); } struct input_keymap *keymap; wl_list_for_each(keymap, &input_manager->keymaps, link) { if (!keyboard_check_keymap_rules(&keymap->rules, rules)) { return keymap->keymap; } } keymap = calloc(1, sizeof(*keymap)); if (!keymap) { return NULL; } keymap->keymap = keyboard_compile_keymap(&keymap->rules); if (!keymap->keymap) { kywc_log(KYWC_ERROR, "Keymap compile failed, text input is broken"); free(keymap); return NULL; } keymap->rules = *rules; wl_list_insert(&input_manager->keymaps, &keymap->link); return keymap->keymap; } static void compile_keymap(void *job, void *gdata, int index) { struct keymap_rules rules = { 0 }; input_get_or_create_keymap(&rules, false); } static void handle_backend_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&input_manager->backend_destroy.link); wl_list_remove(&input_manager->new_input.link); wl_list_remove(&input_manager->new_virtual_keyboard.link); wl_list_remove(&input_manager->new_virtual_pointer.link); wl_list_remove(&input_manager->new_shortcuts_inhibit.link); wl_list_remove(&input_manager->new_pointer_constraint.link); wl_list_remove(&input_manager->request_set_cursor_shape.link); } struct input_manager *input_manager_create(struct server *server) { input_manager = calloc(1, sizeof(*input_manager)); if (!input_manager) { return NULL; } input_manager->server = server; wl_list_init(&input_manager->seats); wl_list_init(&input_manager->inputs); wl_signal_init(&input_manager->events.new_input); wl_signal_init(&input_manager->events.new_seat); input_manager->backend_destroy.notify = handle_backend_destroy; wl_signal_add(&server->backend->events.destroy, &input_manager->backend_destroy); input_manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &input_manager->server_destroy); wl_list_init(&input_manager->keymaps); queue_fence_init(&input_manager->fence); if (!queue_add_job(server->queue, input_manager, &input_manager->fence, compile_keymap, NULL)) { compile_keymap(input_manager, server, -1); } input_manager->new_input.notify = handle_new_input; wl_signal_add(&server->backend->events.new_input, &input_manager->new_input); struct wlr_backend *eis = eis_backend_create(server->display, server->layout); if (eis) { wlr_multi_backend_add(server->backend, eis); } input_manager->virtual_pointer = wlr_virtual_pointer_manager_v1_create(server->display); input_manager->new_virtual_pointer.notify = handle_new_virtual_pointer; wl_signal_add(&input_manager->virtual_pointer->events.new_virtual_pointer, &input_manager->new_virtual_pointer); input_manager->virtual_keyboard = wlr_virtual_keyboard_manager_v1_create(server->display); input_manager->new_virtual_keyboard.notify = handle_new_virtual_keyboard; wl_signal_add(&input_manager->virtual_keyboard->events.new_virtual_keyboard, &input_manager->new_virtual_keyboard); input_manager->pointer_gestures = wlr_pointer_gestures_v1_create(server->display); input_manager->relative_pointer = wlr_relative_pointer_manager_v1_create(server->display); input_manager->shortcuts_inhibit = wlr_keyboard_shortcuts_inhibit_v1_create(server->display); input_manager->new_shortcuts_inhibit.notify = handle_new_shortcuts_inhibitor; wl_signal_add(&input_manager->shortcuts_inhibit->events.new_inhibitor, &input_manager->new_shortcuts_inhibit); input_manager->pointer_constraints = wlr_pointer_constraints_v1_create(server->display); input_manager->new_pointer_constraint.notify = handle_new_pointer_constraint; wl_signal_add(&input_manager->pointer_constraints->events.new_constraint, &input_manager->new_pointer_constraint); input_manager->cursor_shape = wlr_cursor_shape_manager_v1_create(server->display, 1); input_manager->request_set_cursor_shape.notify = handle_request_set_cursor_shape; wl_signal_add(&input_manager->cursor_shape->events.request_set_shape, &input_manager->request_set_cursor_shape); input_manager_config_init(input_manager); selection_manager_create(input_manager); input_monitor_create(input_manager); bindings_create(input_manager); input_method_manager_create(input_manager); touch_manager_create(input_manager); tablet_manager_create(input_manager); kde_keystate_manager_create(input_manager); transient_seat_manager_create(input_manager); idle_manager_create(server); idle_inhibit_manager_create(server); /* create the default seat */ input_manager->default_seat = seat_create(input_manager, "seat0"); input_manager->bind_seat = input_set_seat; return input_manager; } void input_set_seat(struct input *input, const char *seat) { /* already have attached to seat */ if (input->seat) { if (!strcmp(seat, input->seat->name)) { return; } else { /* remove from prev seat */ seat_remove_input(input); } } input->seat = seat_by_name(seat); /* create a new seat */ if (!input->seat) { input->seat = seat_create(input_manager, seat); } seat_add_input(input->seat, input); } static bool _input_set_state(struct input *input, struct input_state *state) { struct wlr_input_device *wlr_input = input->wlr_input; /* config keyboard with input state */ if (input->prop.type == WLR_INPUT_DEVICE_KEYBOARD) { struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(wlr_input); bool keymap_changed = !wlr_keyboard->keymap || keyboard_check_keymap_rules(&input->state.rules, &state->rules); bool repeat_info_changed = wlr_keyboard->repeat_info.rate != state->repeat_rate || wlr_keyboard->repeat_info.delay != state->repeat_delay; /* we need remove this input and add later */ if (!input->prop.is_virtual && wlr_keyboard->group && (keymap_changed || repeat_info_changed)) { kywc_log(KYWC_DEBUG, "Input %s is removed and be added later", input->name); seat_remove_input(input); } if (keymap_changed) { struct xkb_keymap *keymap = input_get_or_create_keymap(&state->rules, true); wlr_keyboard_set_keymap(wlr_keyboard, keymap); } if (repeat_info_changed) { wlr_keyboard_set_repeat_info(wlr_keyboard, state->repeat_rate, state->repeat_delay); } } input->state.scroll_factor = state->scroll_factor; input->state.double_click_time = state->double_click_time; /* choose a suitable seat, add the input device to the seat */ input_manager->bind_seat(input, state->seat ? state->seat : "seat0"); if (input->prop.support_mapped_to_output) { struct kywc_output *mapped_output = NULL; if (state->mapped_to_output) { mapped_output = kywc_output_by_name(state->mapped_to_output); if (mapped_output && (!mapped_output->state.enabled || mapped_output->destroying)) { kywc_log(KYWC_WARN, "mapped output %s is not available", mapped_output->name); mapped_output = NULL; } } struct wlr_cursor *wlr_cursor = input->seat->cursor->wlr_cursor; struct wlr_output *wlr_output = mapped_output ? output_from_kywc_output(mapped_output)->wlr_output : NULL; wlr_cursor_map_input_to_output(wlr_cursor, wlr_input, wlr_output); input->mapped_output = mapped_output; } if (input->device) { return libinput_set_state(input, state); } return true; } bool input_set_state(struct input *input, struct input_state *state) { struct kywc_output *old_mapped_output = input->mapped_output; bool success = _input_set_state(input, state); /* update state anyway */ input_get_state(input, &input->state); if (old_mapped_output != input->mapped_output) { wl_list_remove(&input->mapped_output_disable.link); wl_list_init(&input->mapped_output_disable.link); wl_list_remove(&input->viewport.link); wl_list_init(&input->viewport.link); if (input->mapped_output) { struct output *output = output_from_kywc_output(input->mapped_output); struct ky_scene_output *scene_output = output->scene_output; wl_signal_add(&scene_output->events.destroy, &input->mapped_output_disable); wl_signal_add(&scene_output->events.viewport, &input->viewport); handle_mapped_output_viewport(&input->viewport, NULL); } } if (!input->prop.is_virtual) { input_write_config(input); } return success; } struct input *input_by_name(const char *name) { struct input *input; wl_list_for_each(input, &input_manager->inputs, link) { if (!strcmp(input->name, name)) { return input; } } return NULL; } struct input *input_from_wlr_input(struct wlr_input_device *wlr_input) { return wlr_input->data; } struct seat *input_manager_get_default_seat(void) { return input_manager->default_seat; } void input_manager_set_event_filter(event_filter_func_t filter, void *data) { input_manager->event_filter = filter; input_manager->event_filter_data = data; } bool input_event_filter(struct seat *seat, struct input *input, enum input_event_type type, void *event) { idle_manager_notify_activity(seat); if (!input_manager->event_filter) { return false; } struct input_event ev = { .seat = seat, .input = input, .type = type, .event = event, }; return input_manager->event_filter(&ev, input_manager->event_filter_data); } struct output *input_current_output(struct seat *seat) { struct wlr_output *wlr_output = wlr_output_layout_output_at(seat->layout, seat->cursor->lx, seat->cursor->ly); if (!wlr_output) { /* sync the position in wlr_cursor */ cursor_move(seat->cursor, NULL, 0, 0, true, false); wlr_output = wlr_output_layout_output_at(seat->layout, seat->cursor->lx, seat->cursor->ly); } return wlr_output ? output_from_wlr_output(wlr_output) : NULL; } void input_manager_switch_vt(unsigned vt) { struct server *server = input_manager->server; if (server->vtnr == vt) { return; } struct wlr_session *session = server->session; if (session) { server->active = false; if (!wlr_session_change_vt(session, vt)) { server->active = true; } } } struct seat *seat_by_name(const char *seat_name) { struct seat *seat; wl_list_for_each(seat, &input_manager->seats, link) { if (strcmp(seat->name, seat_name) == 0) { return seat; } } return NULL; } void seat_add_new_listener(struct wl_listener *listener) { wl_signal_add(&input_manager->events.new_seat, listener); } kylin-wayland-compositor/src/input/text_input_v1.c0000664000175000017500000002776015160461067021404 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include "text-input-unstable-v1-protocol.h" #include "text_input_v1.h" static void text_input_destroy(struct text_input_v1 *text_input) { wl_signal_emit_mutable(&text_input->events.destroy, NULL); assert(wl_list_empty(&text_input->events.activate.listener_list)); assert(wl_list_empty(&text_input->events.deactivate.listener_list)); assert(wl_list_empty(&text_input->events.commit.listener_list)); assert(wl_list_empty(&text_input->events.destroy.listener_list)); wl_resource_set_user_data(text_input->resource, NULL); wl_list_remove(&text_input->surface_destroy.link); wl_list_remove(&text_input->seat_destroy.link); wl_list_remove(&text_input->link); free(text_input->surrounding.text); free(text_input); } static void text_input_activate(struct wl_client *client, struct wl_resource *resource, struct wl_resource *seat, struct wl_resource *surface) { struct text_input_v1 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat); if (!seat_client) { return; } text_input->seat = seat_client->seat; wl_signal_add(&seat_client->events.destroy, &text_input->seat_destroy); text_input->surface = wlr_surface_from_resource(surface); wl_signal_add(&text_input->surface->events.destroy, &text_input->surface_destroy); text_input->activated = true; wl_signal_emit_mutable(&text_input->events.activate, NULL); } static void text_input_deactivate(struct wl_client *client, struct wl_resource *resource, struct wl_resource *seat) { struct text_input_v1 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat); if (!seat_client) { return; } if (text_input->seat != seat_client->seat) { return; } text_input->activated = false; wl_signal_emit_mutable(&text_input->events.deactivate, NULL); text_input->surface = NULL; text_input->seat = NULL; wl_list_remove(&text_input->surface_destroy.link); wl_list_init(&text_input->surface_destroy.link); wl_list_remove(&text_input->seat_destroy.link); wl_list_init(&text_input->seat_destroy.link); } static void text_input_show_input_panel(struct wl_client *client, struct wl_resource *resource) { // Not implemented yet } static void text_input_hide_input_panel(struct wl_client *client, struct wl_resource *resource) { // Not implemented yet } static void text_input_reset(struct wl_client *client, struct wl_resource *resource) { struct text_input_v1 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } text_input->surrounding.pending = false; text_input->content_type.pending = false; } static void text_input_set_surrounding_text(struct wl_client *client, struct wl_resource *resource, const char *text, uint32_t cursor, uint32_t anchor) { struct text_input_v1 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } free(text_input->surrounding.text); text_input->surrounding.text = strdup(text); if (!text_input->surrounding.text) { wl_client_post_no_memory(client); } text_input->surrounding.cursor = cursor; text_input->surrounding.anchor = anchor; text_input->surrounding.pending = true; } static void text_input_set_content_type(struct wl_client *client, struct wl_resource *resource, uint32_t hint, uint32_t purpose) { struct text_input_v1 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } /* convert text_input_v1 to text_input_v3 */ text_input->content_type.hint = hint == ZWP_TEXT_INPUT_V1_CONTENT_HINT_DEFAULT ? ZWP_TEXT_INPUT_V1_CONTENT_HINT_NONE : hint; text_input->content_type.purpose = purpose > ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_PASSWORD ? purpose + 1 : purpose; text_input->content_type.pending = true; } static void text_input_set_cursor_rectangle(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) { struct text_input_v1 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } text_input->cursor_rectangle.x = x; text_input->cursor_rectangle.y = y; text_input->cursor_rectangle.width = width; text_input->cursor_rectangle.height = height; } static void text_input_set_preferred_language(struct wl_client *client, struct wl_resource *resource, const char *language) { // Not implemented yet } static void text_input_commit_state(struct wl_client *client, struct wl_resource *resource, uint32_t serial) { struct text_input_v1 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } if (text_input->surface == NULL) { kywc_log(KYWC_DEBUG, "Text input commit when surface destroyed"); } text_input->serial = serial; wl_signal_emit_mutable(&text_input->events.commit, NULL); } static void text_input_invoke_action(struct wl_client *client, struct wl_resource *resource, uint32_t button, uint32_t index) { // Not implemented yet } static const struct zwp_text_input_v1_interface text_input_impl = { .activate = text_input_activate, .deactivate = text_input_deactivate, .show_input_panel = text_input_show_input_panel, .hide_input_panel = text_input_hide_input_panel, .reset = text_input_reset, .set_surrounding_text = text_input_set_surrounding_text, .set_content_type = text_input_set_content_type, .set_cursor_rectangle = text_input_set_cursor_rectangle, .set_preferred_language = text_input_set_preferred_language, .commit_state = text_input_commit_state, .invoke_action = text_input_invoke_action, }; static void text_input_resource_destroy(struct wl_resource *resource) { struct text_input_v1 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } text_input_destroy(text_input); } static void text_input_handle_surface_destroy(struct wl_listener *listener, void *data) { struct text_input_v1 *text_input = wl_container_of(listener, text_input, surface_destroy); text_input_destroy(text_input); } static void text_input_handle_seat_destroy(struct wl_listener *listener, void *data) { struct text_input_v1 *text_input = wl_container_of(listener, text_input, seat_destroy); text_input_destroy(text_input); } static void text_input_manager_create_text_input(struct wl_client *client, struct wl_resource *resource, uint32_t id) { int version = wl_resource_get_version(resource); struct wl_resource *text_input_resource = wl_resource_create(client, &zwp_text_input_v1_interface, version, id); if (text_input_resource == NULL) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(text_input_resource, &text_input_impl, NULL, text_input_resource_destroy); struct text_input_v1 *text_input = calloc(1, sizeof(*text_input)); if (!text_input) { wl_client_post_no_memory(client); return; } wl_signal_init(&text_input->events.activate); wl_signal_init(&text_input->events.deactivate); wl_signal_init(&text_input->events.commit); wl_signal_init(&text_input->events.destroy); text_input->resource = text_input_resource; wl_resource_set_user_data(text_input_resource, text_input); text_input->seat_destroy.notify = text_input_handle_seat_destroy; wl_list_init(&text_input->seat_destroy.link); text_input->surface_destroy.notify = text_input_handle_surface_destroy; wl_list_init(&text_input->surface_destroy.link); struct text_input_manager_v1 *manager = wl_resource_get_user_data(resource); wl_list_insert(&manager->text_inputs, &text_input->link); wl_signal_emit_mutable(&manager->events.text_input, text_input); } static const struct zwp_text_input_manager_v1_interface text_input_manager_impl = { .create_text_input = text_input_manager_create_text_input, }; static void text_input_manager_bind(struct wl_client *wl_client, void *data, uint32_t version, uint32_t id) { struct text_input_manager_v1 *manager = data; assert(wl_client && manager); struct wl_resource *resource = wl_resource_create(wl_client, &zwp_text_input_manager_v1_interface, version, id); if (resource == NULL) { wl_client_post_no_memory(wl_client); return; } wl_resource_set_implementation(resource, &text_input_manager_impl, manager, NULL); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct text_input_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); assert(&manager->events.destroy.listener_list); assert(&manager->events.text_input.listener_list); wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); } struct text_input_manager_v1 *text_input_manager_v1_create(struct wl_display *display) { struct text_input_manager_v1 *manager = calloc(1, sizeof(*manager)); if (!manager) { return NULL; } wl_list_init(&manager->text_inputs); wl_signal_init(&manager->events.text_input); wl_signal_init(&manager->events.destroy); manager->global = wl_global_create(display, &zwp_text_input_manager_v1_interface, 1, manager, text_input_manager_bind); if (!manager->global) { free(manager); return NULL; } manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(display, &manager->display_destroy); return manager; } void text_input_v1_send_enter(struct text_input_v1 *text_input, struct wlr_surface *surface) { assert(surface == text_input->surface); zwp_text_input_v1_send_enter(text_input->resource, text_input->surface->resource); } void text_input_v1_send_leave(struct text_input_v1 *text_input) { zwp_text_input_v1_send_leave(text_input->resource); text_input->surface = NULL; wl_list_remove(&text_input->surface_destroy.link); wl_list_init(&text_input->surface_destroy.link); } void text_input_v1_send_preedit_string(struct text_input_v1 *text_input, const char *text, int32_t cursor_begin) { zwp_text_input_v1_send_preedit_cursor(text_input->resource, cursor_begin); zwp_text_input_v1_send_preedit_styling(text_input->resource, 0, text ? strlen(text) : 0, ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_DEFAULT); zwp_text_input_v1_send_preedit_string(text_input->resource, text_input->serial, text ? text : "", ""); } void text_input_v1_send_commit_string(struct text_input_v1 *text_input, const char *text) { zwp_text_input_v1_send_commit_string(text_input->resource, text_input->serial, text); } void text_input_v1_send_delete_surrounding_text(struct text_input_v1 *text_input, const char *text, uint32_t before_length, uint32_t after_length) { zwp_text_input_v1_send_delete_surrounding_text( text_input->resource, strlen(text) - before_length, after_length + before_length); text_input_v1_send_commit_string(text_input, text); } kylin-wayland-compositor/src/input/idle_inhibit.c0000664000175000017500000001407115160460057021203 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "input_p.h" #include "server.h" #include "view/view.h" struct idle_inhibit_manager { struct wlr_idle_inhibit_manager_v1 *idle_inhibit; struct wl_list idle_inhibitors; struct wl_listener new_idle_inhibitor; struct wl_listener destroy; }; struct idle_inhibitor { struct wl_list link; struct idle_inhibit_manager *manager; struct wl_listener inhibitor_destroy; struct wlr_surface *surface; struct wl_listener surface_map; struct wl_listener surface_unmap; struct view *view; struct wl_listener view_map; struct wl_listener view_minimize; bool visible; }; static void idle_inhibitor_set_inhibit(struct idle_inhibitor *idle_inhibitor) { if (idle_inhibitor->visible) { idle_manager_set_inhibited(true); return; } struct idle_inhibit_manager *manager = idle_inhibitor->manager; struct idle_inhibitor *inhibitor; wl_list_for_each(inhibitor, &manager->idle_inhibitors, link) { if (inhibitor->visible) { idle_manager_set_inhibited(true); return; } } idle_manager_set_inhibited(false); } static void handle_view_map(struct wl_listener *listener, void *data) { struct idle_inhibitor *idle_inhibitor = wl_container_of(listener, idle_inhibitor, view_map); idle_inhibitor->visible = !idle_inhibitor->view->base.minimized; idle_inhibitor_set_inhibit(idle_inhibitor); } static void handle_view_minimize(struct wl_listener *listener, void *data) { struct idle_inhibitor *idle_inhibitor = wl_container_of(listener, idle_inhibitor, view_minimize); idle_inhibitor->visible = !idle_inhibitor->view->base.minimized; idle_inhibitor_set_inhibit(idle_inhibitor); } static void handle_surface_map(struct wl_listener *listener, void *data) { struct idle_inhibitor *idle_inhibitor = wl_container_of(listener, idle_inhibitor, surface_map); struct view *view = view_try_from_wlr_surface(idle_inhibitor->surface); if (!view) { wl_list_init(&idle_inhibitor->view_map.link); wl_list_init(&idle_inhibitor->view_minimize.link); idle_inhibitor->visible = true; idle_inhibitor_set_inhibit(idle_inhibitor); return; } idle_inhibitor->view = view; wl_signal_add(&view->base.events.map, &idle_inhibitor->view_map); wl_signal_add(&view->base.events.minimize, &idle_inhibitor->view_minimize); if (view->base.mapped) { handle_view_map(&idle_inhibitor->view_map, NULL); } } static void handle_surface_unmap(struct wl_listener *listener, void *data) { struct idle_inhibitor *idle_inhibitor = wl_container_of(listener, idle_inhibitor, surface_unmap); wl_list_remove(&idle_inhibitor->view_map.link); wl_list_init(&idle_inhibitor->view_map.link); wl_list_remove(&idle_inhibitor->view_minimize.link); wl_list_init(&idle_inhibitor->view_minimize.link); idle_inhibitor->visible = false; idle_inhibitor_set_inhibit(idle_inhibitor); } static void handle_inhibitor_destroy(struct wl_listener *listener, void *data) { struct idle_inhibitor *idle_inhibitor = wl_container_of(listener, idle_inhibitor, inhibitor_destroy); wl_list_remove(&idle_inhibitor->link); wl_list_remove(&idle_inhibitor->inhibitor_destroy.link); wl_list_remove(&idle_inhibitor->surface_map.link); wl_list_remove(&idle_inhibitor->surface_unmap.link); wl_list_remove(&idle_inhibitor->view_map.link); wl_list_remove(&idle_inhibitor->view_minimize.link); if (idle_inhibitor->visible) { idle_inhibitor->visible = false; idle_inhibitor_set_inhibit(idle_inhibitor); } free(idle_inhibitor); } static void handle_new_idle_inhibitor(struct wl_listener *listener, void *data) { struct idle_inhibit_manager *manager = wl_container_of(listener, manager, new_idle_inhibitor); struct wlr_idle_inhibitor_v1 *wlr_idle_inhibitor = data; struct idle_inhibitor *idle_inhibitor = calloc(1, sizeof(*idle_inhibitor)); if (!idle_inhibitor) { return; } idle_inhibitor->manager = manager; wl_list_insert(&manager->idle_inhibitors, &idle_inhibitor->link); idle_inhibitor->inhibitor_destroy.notify = handle_inhibitor_destroy; wl_signal_add(&wlr_idle_inhibitor->events.destroy, &idle_inhibitor->inhibitor_destroy); idle_inhibitor->surface = wlr_idle_inhibitor->surface; idle_inhibitor->surface_map.notify = handle_surface_map; wl_signal_add(&idle_inhibitor->surface->events.map, &idle_inhibitor->surface_map); idle_inhibitor->surface_unmap.notify = handle_surface_unmap; wl_signal_add(&idle_inhibitor->surface->events.unmap, &idle_inhibitor->surface_unmap); idle_inhibitor->view_map.notify = handle_view_map; wl_list_init(&idle_inhibitor->view_map.link); idle_inhibitor->view_minimize.notify = handle_view_minimize; wl_list_init(&idle_inhibitor->view_minimize.link); if (idle_inhibitor->surface->mapped) { handle_surface_map(&idle_inhibitor->surface_map, NULL); } } static void handle_destroy(struct wl_listener *listener, void *data) { struct idle_inhibit_manager *manager = wl_container_of(listener, manager, destroy); wl_list_remove(&manager->destroy.link); wl_list_remove(&manager->new_idle_inhibitor.link); free(manager); } bool idle_inhibit_manager_create(struct server *server) { struct idle_inhibit_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->idle_inhibit = wlr_idle_inhibit_v1_create(server->display); if (!manager->idle_inhibit) { free(manager); return false; } wl_list_init(&manager->idle_inhibitors); manager->new_idle_inhibitor.notify = handle_new_idle_inhibitor; wl_signal_add(&manager->idle_inhibit->events.new_inhibitor, &manager->new_idle_inhibitor); manager->destroy.notify = handle_destroy; wl_signal_add(&manager->idle_inhibit->events.destroy, &manager->destroy); return true; } kylin-wayland-compositor/src/input/input_p.h0000664000175000017500000001673615160461067020257 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _INPUT_P_H_ #define _INPUT_P_H_ #include "input/cursor.h" #include "input/gesture.h" #include "input/keyboard.h" #include "input/seat.h" #include "util/queue.h" enum input_lock_key { INPUT_KEY_CAPSLOCK, INPUT_KEY_NUMLOCK, INPUT_KEY_SCROLLLOCK, }; struct input_keymap { struct wl_list link; struct xkb_keymap *keymap; struct keymap_rules rules; }; struct input_manager { struct server *server; struct wl_list seats; struct wl_list inputs; struct wl_list keymaps; struct queue_fence fence; struct { struct wl_signal new_input; struct wl_signal new_seat; } events; struct seat *default_seat; void (*bind_seat)(struct input *input, const char *seat); event_filter_func_t event_filter; void *event_filter_data; struct config *config; struct config *seat_config; struct wl_listener new_input; struct wl_listener backend_destroy; struct wl_listener server_destroy; struct wlr_pointer_gestures_v1 *pointer_gestures; struct wlr_relative_pointer_manager_v1 *relative_pointer; struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard; struct wl_listener new_virtual_keyboard; struct wlr_virtual_pointer_manager_v1 *virtual_pointer; struct wl_listener new_virtual_pointer; struct wlr_keyboard_shortcuts_inhibit_manager_v1 *shortcuts_inhibit; struct wl_listener new_shortcuts_inhibit; struct wlr_pointer_constraints_v1 *pointer_constraints; struct wl_listener new_pointer_constraint; struct wlr_cursor_shape_manager_v1 *cursor_shape; struct wl_listener request_set_cursor_shape; }; bool input_manager_config_init(struct input_manager *input_manager); bool input_read_config(struct input *input, struct input_state *state); void input_write_config(struct input *input); void input_prop_and_state_debug(struct input *input); void input_manager_switch_vt(unsigned vt); void input_notify_destroy(struct input *input); void input_notify_create(struct input *input); bool input_event_filter(struct seat *seat, struct input *input, enum input_event_type type, void *event); struct xkb_keymap *input_get_or_create_keymap(struct keymap_rules *rules, bool wait); struct seat *seat_by_name(const char *seat_name); void cursor_set_xcursor_manager(struct cursor *cursor, const char *theme, uint32_t size, bool saved); void cursor_set_surface(struct cursor *cursor, struct wlr_surface *surface, int32_t hotspot_x, int32_t hotspot_y, struct wl_client *client); bool seat_read_config(struct seat *seat); void seat_write_config(struct seat *seat); void seat_feed_pointer_motion(struct seat *seat, double x, double y, bool absolute); void seat_feed_pointer_button(struct seat *seat, uint32_t button, bool pressed); void seat_feed_pointer_axis(struct seat *seat, uint32_t axis, double step); void seat_feed_keyboard_key(struct seat *seat, uint32_t key, bool pressed); /** * libinput helper functions */ void libinput_get_prop(struct input *input, struct input_prop *prop); void libinput_get_state(struct input *input, struct input_state *state); void libinput_get_default_state(struct input *input, struct input_state *state); bool libinput_set_state(struct input *input, struct input_state *state); /** * monitor for input cursor and others */ struct input_monitor *input_monitor_create(struct input_manager *input_manager); /** * idle manager */ bool idle_manager_create(struct server *server); void idle_manager_notify_activity(struct seat *seat); /** * idle inhibitor manager */ bool idle_inhibit_manager_create(struct server *server); /** * input method and text input */ bool input_method_manager_create(struct input_manager *input_manager); void input_method_set_focus(struct seat *seat, struct wlr_surface *wlr_surface); bool input_method_handle_key(struct keyboard *keyboard, uint32_t time, uint32_t key, uint32_t state); bool input_method_handle_modifiers(struct keyboard *keyboard); bool keyboard_is_from_input_method(struct keyboard *keyboard); /** * selection drag icon */ bool selection_manager_create(struct input_manager *input_manager); void selection_handle_cursor_move(struct seat *seat, int lx, int ly); bool selection_is_dragging(struct seat *seat); /** * tablet manager */ struct wlr_tablet_tool_axis_event; struct wlr_tablet_tool_proximity_event; struct wlr_tablet_tool_tip_event; struct wlr_tablet_tool_button_event; bool tablet_manager_create(struct input_manager *input_manager); void tablet_set_focus(struct seat *seat, struct wlr_surface *surface); void tablet_handle_tool_axis(struct wlr_tablet_tool_axis_event *event); bool tablet_handle_tool_proximity(struct wlr_tablet_tool_proximity_event *event); bool tablet_handle_tool_tip(struct wlr_tablet_tool_tip_event *event); bool tablet_handle_tool_button(struct wlr_tablet_tool_button_event *event); bool tablet_has_implicit_grab(struct seat *seat); /** * touchscreen manager */ struct wlr_touch_up_event; struct wlr_touch_down_event; struct wlr_touch_motion_event; struct wlr_touch_cancel_event; bool touch_manager_create(struct input_manager *input_manager); bool touch_handle_down(struct wlr_touch_down_event *event); void touch_handle_up(struct wlr_touch_up_event *event, bool handle); void touch_handle_motion(struct wlr_touch_motion_event *event, bool handle); void touch_handle_cancel(struct wlr_touch_cancel_event *event, bool handle); void touch_reset_gesture(struct input_manager *input_manager); /** * binding manager for keysym, gesture */ bool bindings_create(struct input_manager *input_manager); struct key_binding *bindings_get_key_binding(struct keyboard_state *keyboard_state); bool bindings_get_key_binding_bypass_grab(struct key_binding *binding); bool bindings_get_key_binding_no_repeat(struct key_binding *binding); bool bindings_handle_key_binding(struct key_binding *binding, bool *repeat); bool bindings_handle_gesture_binding(struct gesture_state *gesture_state); /** * seat pointer and keyboard feed event */ void cursor_feed_motion(struct cursor *cursor, uint32_t time, struct wlr_input_device *device, double dx, double dy, double dx_unaccel, double dy_unaccel); void cursor_feed_button(struct cursor *cursor, uint32_t button, bool pressed, uint32_t time, uint32_t double_click_time); void cursor_feed_axis(struct cursor *cursor, uint32_t orientation, uint32_t source, double delta, int32_t delta_discrete, int32_t relative_direction, uint32_t time); /** * seat pointer constraint */ struct cursor_constraint *cursor_constraint_create(struct cursor *cursor, struct wlr_pointer_constraint_v1 *constraint); void cursor_constraint_set_focus(struct seat *seat, struct wlr_surface *surface); /** * keeps track of the states of lock and modifier keys */ #if HAVE_KDE_KEYSTATE bool kde_keystate_manager_create(struct input_manager *input_manager); #else static __attribute__((unused)) inline bool kde_keystate_manager_create(struct input_manager *input_manager) { return false; } #endif bool transient_seat_manager_create(struct input_manager *input_manager); /** * xdg toplevel drag support */ struct wlr_data_source; bool toplevel_drag_manager_create(struct server *server); bool toplevel_drag_move(struct wlr_data_source *source, int lx, int ly); #endif /* _INPUT_P_H_ */ kylin-wayland-compositor/src/input/text_input_v1.h0000664000175000017500000000351215160460057021374 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _TEXT_INPUT_V1_H_ #define _TEXT_INPUT_V1_H_ #include #include struct text_input_v1 { struct wl_resource *resource; struct wl_list link; struct wlr_seat *seat; struct wl_listener seat_destroy; struct wlr_surface *surface; struct wl_listener surface_destroy; struct wlr_box cursor_rectangle; struct { bool pending; char *text; uint32_t cursor; uint32_t anchor; } surrounding; struct { bool pending; uint32_t hint; uint32_t purpose; } content_type; uint32_t serial; bool activated; struct { struct wl_signal activate; struct wl_signal deactivate; struct wl_signal commit; struct wl_signal destroy; } events; }; struct text_input_manager_v1 { struct wl_global *global; struct wl_list text_inputs; struct wl_listener display_destroy; struct { struct wl_signal text_input; struct wl_signal destroy; } events; }; struct text_input_manager_v1 *text_input_manager_v1_create(struct wl_display *display); void text_input_v1_send_enter(struct text_input_v1 *text_input, struct wlr_surface *surface); void text_input_v1_send_leave(struct text_input_v1 *text_input); void text_input_v1_send_preedit_string(struct text_input_v1 *text_input, const char *text, int32_t cursor_begin); void text_input_v1_send_commit_string(struct text_input_v1 *text_input, const char *text); void text_input_v1_send_delete_surrounding_text(struct text_input_v1 *text_input, const char *text, uint32_t before_length, uint32_t after_length); #endif /* _TEXT_INPUT_V1_H_ */ kylin-wayland-compositor/src/input/toplevel_drag.c0000664000175000017500000002011215160460057021400 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "xdg-toplevel-drag-v1-protocol.h" #include "input_p.h" #include "server.h" #include "view/view.h" struct toplevel_drag_manager { struct wl_global *global; struct wl_list drags; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct toplevel_drag { struct wl_resource *resource; struct wl_list link; struct wlr_data_source *source; struct wl_listener source_destroy; struct wlr_xdg_toplevel *toplevel; struct wl_listener toplevel_unmap; struct wl_listener toplevel_destroy; int32_t off_x, off_y; }; static struct toplevel_drag_manager *manager = NULL; static struct view *toplevel_set_input_bypassed(struct wlr_xdg_toplevel *toplevel, bool bypassed) { if (!toplevel) { return NULL; } struct view *view = view_try_from_wlr_surface(toplevel->base->surface); if (!view) { return NULL; } ky_scene_node_set_input_bypassed(&view->tree->node, bypassed); return view; } static void toplevel_drag_destroy(struct toplevel_drag *drag) { toplevel_set_input_bypassed(drag->toplevel, false); wl_list_remove(&drag->source_destroy.link); wl_list_remove(&drag->toplevel_unmap.link); wl_list_remove(&drag->toplevel_destroy.link); wl_list_remove(&drag->link); free(drag); } static void toplevel_drag_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void toplevel_drag_handle_attach(struct wl_client *client, struct wl_resource *resource, struct wl_resource *toplevel, int32_t x_offset, int32_t y_offset) { struct toplevel_drag *drag = wl_resource_get_user_data(resource); if (!drag) { return; } if (drag->toplevel) { wl_resource_post_error(resource, XDG_TOPLEVEL_DRAG_V1_ERROR_TOPLEVEL_ATTACHED, "valid toplevel already attached"); return; } drag->toplevel = wlr_xdg_toplevel_from_resource(toplevel); if (!drag->toplevel) { return; } wl_list_remove(&drag->toplevel_unmap.link); wl_signal_add(&drag->toplevel->base->surface->events.unmap, &drag->toplevel_unmap); wl_list_remove(&drag->toplevel_destroy.link); wl_signal_add(&drag->toplevel->base->events.destroy, &drag->toplevel_destroy); drag->off_x = x_offset; drag->off_y = y_offset; } static const struct xdg_toplevel_drag_v1_interface toplevel_drag_impl = { .destroy = toplevel_drag_handle_destroy, .attach = toplevel_drag_handle_attach, }; static void toplevel_drag_handle_resource_destroy(struct wl_resource *resource) { struct toplevel_drag *drag = wl_resource_get_user_data(resource); if (drag) { toplevel_drag_destroy(drag); } } static void toplevel_drag_handle_source_destroy(struct wl_listener *listener, void *data) { struct toplevel_drag *drag = wl_container_of(listener, drag, source_destroy); wl_resource_set_user_data(drag->resource, NULL); toplevel_drag_destroy(drag); } static void toplevel_drag_reset_toplevel(struct toplevel_drag *drag) { wl_list_remove(&drag->toplevel_unmap.link); wl_list_remove(&drag->toplevel_destroy.link); wl_list_init(&drag->toplevel_unmap.link); wl_list_init(&drag->toplevel_destroy.link); toplevel_set_input_bypassed(drag->toplevel, false); drag->toplevel = NULL; } static void toplevel_drag_handle_toplevel_unmap(struct wl_listener *listener, void *data) { struct toplevel_drag *drag = wl_container_of(listener, drag, toplevel_unmap); toplevel_drag_reset_toplevel(drag); } static void toplevel_drag_handle_toplevel_destroy(struct wl_listener *listener, void *data) { struct toplevel_drag *drag = wl_container_of(listener, drag, toplevel_destroy); toplevel_drag_reset_toplevel(drag); } static void manager_handle_get_toplevel_drag(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *data_source) { // trick to get wlr_data_source as wlr_client_data_source is private struct wlr_data_source *source = wl_resource_get_user_data(data_source); if (!source) { wl_client_post_implementation_error(client, "invalid data source"); return; } struct toplevel_drag *drag = calloc(1, sizeof(*drag)); if (!drag) { wl_client_post_no_memory(client); return; } int version = wl_resource_get_version(resource); drag->resource = wl_resource_create(client, &xdg_toplevel_drag_v1_interface, version, id); if (!drag->resource) { free(drag); wl_client_post_no_memory(client); return; } wl_list_insert(&manager->drags, &drag->link); wl_resource_set_implementation(drag->resource, &toplevel_drag_impl, drag, toplevel_drag_handle_resource_destroy); drag->source = source; drag->source_destroy.notify = toplevel_drag_handle_source_destroy; wl_signal_add(&source->events.destroy, &drag->source_destroy); drag->toplevel_unmap.notify = toplevel_drag_handle_toplevel_unmap; wl_list_init(&drag->toplevel_unmap.link); drag->toplevel_destroy.notify = toplevel_drag_handle_toplevel_destroy; wl_list_init(&drag->toplevel_destroy.link); } static void manager_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct xdg_toplevel_drag_manager_v1_interface toplevel_drag_manager_impl = { .destroy = manager_handle_destroy, .get_xdg_toplevel_drag = manager_handle_get_toplevel_drag, }; static void toplevel_drag_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &xdg_toplevel_drag_manager_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &toplevel_drag_manager_impl, manager, NULL); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->server_destroy.link); free(manager); manager = NULL; } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); } bool toplevel_drag_manager_create(struct server *server) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->global = wl_global_create(server->display, &xdg_toplevel_drag_manager_v1_interface, 1, manager, toplevel_drag_manager_bind); if (!manager->global) { kywc_log(KYWC_WARN, "Failed to create xdg_toplevel_drag_manager_v1"); free(manager); manager = NULL; return false; } wl_list_init(&manager->drags); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &manager->display_destroy); return true; } static struct toplevel_drag *toplevel_drag_from_data_source(struct wlr_data_source *source) { struct toplevel_drag *drag; wl_list_for_each(drag, &manager->drags, link) { if (drag->source == source) { return drag; } } return NULL; } bool toplevel_drag_move(struct wlr_data_source *source, int lx, int ly) { if (!manager || !source) { return false; } struct toplevel_drag *drag = toplevel_drag_from_data_source(source); if (!drag || !drag->toplevel) { return false; } struct view *view = toplevel_set_input_bypassed(drag->toplevel, true); if (view) { view_do_move(view, lx - drag->off_x, ly - drag->off_y); } return true; } kylin-wayland-compositor/src/input/binding.c0000664000175000017500000005764415160461067020211 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "input_p.h" #include "server.h" #include "util/macros.h" #include "util/string.h" struct key_binding { struct wl_list link; uint32_t modifiers; uint32_t keysym; bool no_repeat; bool bypass_grab; char *keybind; char *desc; void (*action)(struct key_binding *binding, void *data); void *data; }; struct keysyms_binding { struct wl_list bindings; /* key binding */ }; struct gesture_binding { struct wl_list link; enum gesture_type type; enum gesture_stage stage; uint8_t fingers; uint32_t devices; uint32_t directions; uint32_t follow_direction; uint32_t edges; double follow_threshold; char *desc; void (*action)(struct gesture_binding *binding, void *data, double dx, double dy); void *data; }; static struct bindings { struct keysyms_binding keysyms_binding[KEY_BINDING_TYPE_NUM]; struct wl_list gesture_bindings; struct wl_listener server_destroy; size_t keysym_bindings_block; size_t type_nlocks[KEY_BINDING_TYPE_NUM]; uint32_t keysyms_binding_masks; /* binding mask */ } *bindings = NULL; const struct key_binding_type2string { enum key_binding_type type; const char *name; } key_binding_table[] = { { KEY_BINDING_TYPE_CUSTOM_DEF, "WLCOM_CUSTOM_DEF" }, { KEY_BINDING_TYPE_WIN_MENU, "WLCOM_WIN_MENU" }, { KEY_BINDING_TYPE_SWITCH_WORKSPACE, "WLCOM_SWITCH_WORKSPACE" }, { KEY_BINDING_TYPE_WINDOW_ACTION_MINIMIZE, "WLCOM_WINDOW_ACTION_MINIMIZE" }, { KEY_BINDING_TYPE_WINDOW_ACTION_MAXIMIZE, "WLCOM_WINDOW_ACTION_MAXIMIZE" }, { KEY_BINDING_TYPE_WINDOW_ACTION_CLOSE, "WLCOM_WINDOW_ACTION_CLOSE" }, { KEY_BINDING_TYPE_WINDOW_ACTION_MENU, "WLCOM_WINDOW_ACTION_MENU" }, { KEY_BINDING_TYPE_WINDOW_ACTION_TILED, "WLCOM_WINDOW_ACTION_TILED" }, { KEY_BINDING_TYPE_WINDOW_ACTION_OUTPUT, "WLCOM_WINDOW_ACTION_OUTPUT" }, { KEY_BINDING_TYPE_WINDOW_ACTION_SEND, "WLCOM_WINDOW_ACTION_SEND" }, { KEY_BINDING_TYPE_WINDOW_ACTION_CAPTURE, "WLCOM_WINDOW_ACTION_CAPTURE" }, { KEY_BINDING_TYPE_MAXIMIZED_VIEWS, "WLCOM_MAXIMIZED_VIEWS" }, { KEY_BINDING_TYPE_TOGGLE_SHOW_DESKTOP, "WLCOM_TOGGLE_SHOW_DESKTOP" }, { KEY_BINDING_TYPE_SHOW_DESKTOP, "WLCOM_SHOW_DESKTOP" }, { KEY_BINDING_TYPE_RESTORE_DESKTOP, "WLCOM_RESTORE_DESKTOP" }, { KEY_BINDING_TYPE_TOGGLE_SHOW_VIEWS, "WLCOM_TOGGLE_SHOW_VIEWS" }, { KEY_BINDING_TYPE_TOGGLE_SHOW_WINDOWS, "WLCOM_TOGGLE_SHOW_WINDOWS" }, { KEY_BINDING_TYPE_COLOR_FILTER, "WLCOM_COLOR_FILTER" }, { KEY_BINDING_TYPE_ZOOM, "WLCOM_ZOOM" }, { KEY_BINDING_TYPE_NUM, "WLCOM_ALL" }, }; struct key_binding *kywc_key_binding_create(const char *keybind, const char *desc) { struct key_binding *binding = calloc(1, sizeof(*binding)); if (!binding) { return NULL; } /* check no_repeat first */ size_t length = 0; char **bind_str = string_split(keybind, ":", &length); binding->no_repeat = length == 2 && strcmp(bind_str[1], "no") == 0; size_t len = 0; char **split_str = string_split(length == 2 ? bind_str[0] : keybind, "+", &len); string_free_split(bind_str); for (size_t i = 0; i < len; i++) { uint32_t mod = keyboard_get_modifier_mask_by_name(split_str[i]); if (mod) { binding->modifiers |= mod; continue; } /* whatever keep the lower keysym */ xkb_keysym_t sym = xkb_keysym_from_name(split_str[i], XKB_KEYSYM_CASE_INSENSITIVE); binding->keysym = xkb_keysym_to_lower(sym); } string_free_split(split_str); if (desc) { binding->desc = strdup(desc); } binding->keybind = strdup(keybind); wl_list_init(&binding->link); kywc_log(KYWC_DEBUG, "Keybind %s: %s", keybind, desc); return binding; } struct key_binding *kywc_key_binding_create_by_symbol(unsigned int keysym, unsigned int modifiers, bool no_repeat, bool bypass_grab, const char *desc) { struct key_binding *binding = calloc(1, sizeof(*binding)); if (!binding) { return NULL; } binding->modifiers = modifiers; binding->keysym = keysym; binding->no_repeat = no_repeat; binding->bypass_grab = bypass_grab; if (desc) { binding->desc = strdup(desc); } char name[64]; if (xkb_keysym_get_name(keysym, name, sizeof(name)) > 0) { const char *mods = keyboard_get_modifier_names(modifiers, '+'); binding->keybind = string_create("%s+%s", mods, name); } wl_list_init(&binding->link); return binding; } void kywc_key_binding_destroy(struct key_binding *binding) { wl_list_remove(&binding->link); free(binding->keybind); free(binding->desc); free(binding); } static bool key_binding_is_valid(struct key_binding *binding, uint32_t keysym, uint32_t modifiers) { for (int i = 0; i < KEY_BINDING_TYPE_NUM; ++i) { struct key_binding *bind; struct keysyms_binding *keysyms_binding = &bindings->keysyms_binding[i]; wl_list_for_each(bind, &keysyms_binding->bindings, link) { /* skip itself */ if (bind == binding) { continue; } if (!(modifiers ^ bind->modifiers) && keysym == bind->keysym) { kywc_log(KYWC_WARN, "Keybind %s(%s) is already registered by %s(%s)", binding->keybind, binding->desc, bind->keybind, bind->desc); return false; } } } return true; } bool kywc_key_binding_register(struct key_binding *binding, enum key_binding_type type, void (*action)(struct key_binding *binding, void *data), void *data) { if (kywc_key_binding_is_registered(binding)) { return true; } if (!key_binding_is_valid(binding, binding->keysym, binding->modifiers)) { return false; } struct keysyms_binding *keysyms_binding = &bindings->keysyms_binding[type]; wl_list_insert(&keysyms_binding->bindings, &binding->link); /* Make sure the type is unblocking */ if (!bindings->type_nlocks[type]) { bindings->keysyms_binding_masks |= 1 << type; } if (action) { binding->action = action; } if (data) { binding->data = data; } return true; } bool kywc_key_binding_update(struct key_binding *binding, unsigned int keysym, unsigned int modifiers, const char *desc) { if (kywc_key_binding_is_registered(binding) && !key_binding_is_valid(binding, keysym, modifiers)) { return false; } binding->keysym = keysym; binding->modifiers = modifiers; if (desc) { free(binding->desc); binding->desc = strdup(desc); } return true; } void kywc_key_binding_unregister(struct key_binding *binding) { wl_list_remove(&binding->link); wl_list_init(&binding->link); } bool kywc_key_binding_is_registered(struct key_binding *binding) { return !wl_list_empty(&binding->link); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&bindings->server_destroy.link); for (size_t i = 0; i < KEY_BINDING_TYPE_NUM; ++i) { struct keysyms_binding *keysyms_binding = &bindings->keysyms_binding[i]; struct key_binding *key_binding, *key_binding_tmp; wl_list_for_each_safe(key_binding, key_binding_tmp, &keysyms_binding->bindings, link) { kywc_key_binding_destroy(key_binding); } } struct gesture_binding *gesture_binding, *gesture_binding_tmp; wl_list_for_each_safe(gesture_binding, gesture_binding_tmp, &bindings->gesture_bindings, link) { kywc_gesture_binding_destroy(gesture_binding); } free(bindings); bindings = NULL; } bool bindings_create(struct input_manager *input_manager) { bindings = calloc(1, sizeof(*bindings)); if (!bindings) { return false; } for (size_t i = 0; i < KEY_BINDING_TYPE_NUM; ++i) { struct keysyms_binding *keysyms_binding = &bindings->keysyms_binding[i]; wl_list_init(&keysyms_binding->bindings); } wl_list_init(&bindings->gesture_bindings); bindings->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(input_manager->server, &bindings->server_destroy); return true; } static bool match_key_binding(struct keyboard_state *keyboard_state, struct key_binding *binding) { for (size_t j = 0; j < keyboard_state->npressed; j++) { uint32_t keysym = keyboard_state->pressed_keysyms[j]; if (binding->keysym == xkb_keysym_to_lower(keysym)) { return true; } } return false; } struct key_binding *bindings_get_key_binding(struct keyboard_state *keyboard_state) { if (bindings->keysym_bindings_block) { return NULL; } for (size_t i = 0; i < KEY_BINDING_TYPE_NUM; ++i) { if (!(bindings->keysyms_binding_masks & (1 << i))) { continue; } struct key_binding *binding; struct keysyms_binding *keysyms_binding = &bindings->keysyms_binding[i]; wl_list_for_each(binding, &keysyms_binding->bindings, link) { if ((keyboard_state->only_one_modifier && binding->keysym) || (!keyboard_state->only_one_modifier && !binding->keysym)) { continue; } if (keyboard_state->last_modifiers ^ binding->modifiers) { continue; } if (keyboard_state->npressed < 1 && binding->keysym) { continue; } if (!binding->keysym || match_key_binding(keyboard_state, binding)) { return binding; } } } return NULL; } bool bindings_get_key_binding_bypass_grab(struct key_binding *binding) { if (!binding) { return false; } return binding->bypass_grab; } bool bindings_get_key_binding_no_repeat(struct key_binding *binding) { if (!binding) { return false; } return binding->no_repeat; } bool bindings_handle_key_binding(struct key_binding *binding, bool *repeat) { if (!binding) { *repeat = false; return false; } if (binding->action) { binding->action(binding, binding->data); } *repeat = !binding->no_repeat; return true; } void kywc_gesture_binding_destroy(struct gesture_binding *binding) { wl_list_remove(&binding->link); free(binding->desc); free(binding); } static bool gesture_binding_is_valid(struct gesture_binding *binding, enum gesture_type type, enum gesture_stage stage, uint32_t devices, uint32_t directions, uint32_t follow_direction, uint8_t fingers, uint8_t edges) { struct gesture_binding *bind; wl_list_for_each(bind, &bindings->gesture_bindings, link) { /* skip itself */ if (bind == binding) { continue; } if (bind->type == type && (bind->devices & devices) && (bind->stage == stage) && (GESTURE_DIRECTION_NONE == bind->directions || (bind->directions & directions)) && (GESTURE_EDGE_NONE == bind->edges || (edges & bind->edges)) && bind->fingers == fingers && bind->follow_direction == follow_direction) { return false; } } return true; } static uint32_t gesture_string_parse_directions(const char *str) { uint32_t directions = GESTURE_DIRECTION_NONE; size_t len = 0; char **split_str = string_split(str, "+", &len); for (size_t i = 0; i < len; i++) { if (strcmp(split_str[i], "none") == 0) { directions |= GESTURE_DIRECTION_NONE; } else if (strcmp(split_str[i], "up") == 0) { directions |= GESTURE_DIRECTION_UP; } else if (strcmp(split_str[i], "down") == 0) { directions |= GESTURE_DIRECTION_DOWN; } else if (strcmp(split_str[i], "left") == 0) { directions |= GESTURE_DIRECTION_LEFT; } else if (strcmp(split_str[i], "right") == 0) { directions |= GESTURE_DIRECTION_RIGHT; } else if (strcmp(split_str[i], "inward") == 0) { directions |= GESTURE_DIRECTION_INWARD; } else if (strcmp(split_str[i], "outward") == 0) { directions |= GESTURE_DIRECTION_OUTWARD; } else if (strcmp(split_str[i], "clockwise") == 0) { directions |= GESTURE_DIRECTION_CLOCKWISE; } else if (strcmp(split_str[i], "counterclockwise") == 0) { directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE; } else { kywc_log(KYWC_WARN, "Expected directions, got %s", str); } } string_free_split(split_str); return directions; } static uint32_t gesture_string_parse_edges(const char *str) { uint32_t edges = GESTURE_EDGE_NONE; size_t len = 0; char **split_str = string_split(str, "+", &len); for (size_t i = 0; i < len; i++) { if (strcmp(split_str[i], "none") == 0) { edges |= GESTURE_EDGE_NONE; } else if (strcmp(split_str[i], "top") == 0) { edges |= GESTURE_EDGE_TOP; } else if (strcmp(split_str[i], "bottom") == 0) { edges |= GESTURE_EDGE_BOTTOM; } else if (strcmp(split_str[i], "left") == 0) { edges |= GESTURE_EDGE_LEFT; } else if (strcmp(split_str[i], "right") == 0) { edges |= GESTURE_EDGE_RIGHT; } else { kywc_log(KYWC_WARN, "Expected edges, got %s", str); } } string_free_split(split_str); return edges; } struct gesture_binding *kywc_gesture_binding_create_by_string(const char *gestures, const char *desc) { enum gesture_type type; uint8_t fingers; uint32_t devices; uint32_t directions; uint32_t edges = GESTURE_EDGE_NONE; enum gesture_stage stage = GESTURE_STAGE_TRIGGER; double follow_threshold = 0.0; uint32_t follow_direction = GESTURE_DIRECTION_NONE; size_t len = 0; char **split_str = string_split(gestures, ":", &len); if (len != 4 && len != 5 && len != 6 && len != 7 && len != 8) { kywc_log(KYWC_ERROR, "Expected :::" "[:edges][:stage][:follow_threshold][:follow_direction]," " got %s", gestures); goto err; } // type if (strcmp(split_str[0], "tap") == 0) { #if HAVE_LIBINPUT_RAW_TAP type = GESTURE_TYPE_TAP; #else type = GESTURE_TYPE_HOLD; #endif } else if (strcmp(split_str[0], "hold") == 0) { type = GESTURE_TYPE_HOLD; } else if (strcmp(split_str[0], "pinch") == 0) { type = GESTURE_TYPE_PINCH; } else if (strcmp(split_str[0], "swipe") == 0) { type = GESTURE_TYPE_SWIPE; } else { kywc_log(KYWC_ERROR, "Expected hold|pinch|swipe, got %s", gestures); goto err; } // device if (strcmp(split_str[1], "any") == 0) { devices = GESTURE_DEVICE_TOUCHPAD | GESTURE_DEVICE_TOUCHSCREEN; } else if (strcmp(split_str[1], "touch") == 0) { devices = GESTURE_DEVICE_TOUCHSCREEN; } else if (strcmp(split_str[1], "touchpad") == 0) { devices = GESTURE_DEVICE_TOUCHPAD; } else { kywc_log(KYWC_ERROR, "Expected any|touch|touchpad, got %s", gestures); goto err; } /* fingers: 1 - 9 */ /* directions: up down left right inward outward clockwise counterclockwise */ if ('1' <= split_str[2][0] && split_str[2][0] <= '9') { fingers = atoi(split_str[2]); directions = gesture_string_parse_directions(split_str[3]); } else { kywc_log(KYWC_ERROR, "Expected 1 - 9, got %s", gestures); goto err; } /* edge: top bottom left right */ /* stage: before trigger after stop */ switch (len) { case 8: follow_direction = gesture_string_parse_directions(split_str[7]); // fallthrough case 7: follow_threshold = atof(split_str[6]); // fallthrough case 6: if (strcmp(split_str[5], "before") == 0) { stage = GESTURE_STAGE_BEFORE; } else if (strcmp(split_str[5], "trigger") == 0) { stage = GESTURE_STAGE_TRIGGER; } else if (strcmp(split_str[5], "after") == 0) { stage = GESTURE_STAGE_AFTER; } else if (strcmp(split_str[5], "stop") == 0) { stage = GESTURE_STAGE_STOP; } else { kywc_log(KYWC_ERROR, "Expected before|trigger|after|stop, got %s", gestures); goto err; } // fallthrough case 5: edges = gesture_string_parse_edges(split_str[4]); break; } string_free_split(split_str); kywc_log(KYWC_DEBUG, "Gesture binding: %s", gestures); return kywc_gesture_binding_create(type, stage, devices, directions, edges, fingers, follow_direction, follow_threshold, desc); err: string_free_split(split_str); return NULL; } static bool gesture_checked(enum gesture_type type, uint32_t devices, uint32_t directions, uint32_t edges, uint8_t fingers) { /* gesture type、devices and fingers cannot be 0 */ if (type == GESTURE_TYPE_NONE || devices == GESTURE_DEVICE_NONE || fingers == 0) { return false; } /* pinch gesture fingers require no less than 2 */ if (type == GESTURE_TYPE_PINCH && fingers < 2) { return false; } /* pinch or touchpad all gestures edges need to be 0 */ if ((type == GESTURE_TYPE_PINCH || devices & GESTURE_DEVICE_TOUCHPAD) && edges != GESTURE_EDGE_NONE) { return false; } /* the number of fingers for touchpad gestures need to be greater than 2 */ if (type == GESTURE_TYPE_TAP && devices & GESTURE_DEVICE_TOUCHPAD && fingers < 3) { return false; } /* the number of fingers for touchpad swipe gestures need to be greater than 1 */ if (type == GESTURE_TYPE_SWIPE && devices & GESTURE_DEVICE_TOUCHPAD && fingers == 1) { return false; } /* the edge of the swipe gesture for one finger of the touchscreen cannot be 0 */ if (type == GESTURE_TYPE_SWIPE && edges == GESTURE_EDGE_NONE && fingers == 1) { return false; } /* swipe gesture from edge, the start edges and directions need to be set properly */ if (edges != GESTURE_EDGE_NONE && ((directions & GESTURE_DIRECTION_LEFT && !(edges & GESTURE_EDGE_RIGHT)) || (directions & GESTURE_DIRECTION_RIGHT && !(edges & GESTURE_EDGE_LEFT)) || (directions & GESTURE_DIRECTION_UP && !(edges & GESTURE_EDGE_BOTTOM)) || (directions & GESTURE_DIRECTION_DOWN && !(edges & GESTURE_EDGE_TOP)))) { return false; } return true; } struct gesture_binding *kywc_gesture_binding_create(enum gesture_type type, enum gesture_stage stage, uint32_t devices, uint32_t directions, uint32_t edges, uint8_t fingers, uint32_t follow_direction, double follow_threshold, const char *desc) { if (!gesture_checked(type, devices, directions, edges, fingers)) { kywc_log(KYWC_ERROR, "Gesture checks are illega"); return NULL; } struct gesture_binding *binding = calloc(1, sizeof(*binding)); if (!binding) { return NULL; } binding->type = type; binding->stage = stage; binding->devices = devices; binding->directions = directions; binding->fingers = fingers; binding->edges = edges; binding->follow_threshold = follow_threshold > 0.0 ? follow_threshold : 0.0; binding->follow_direction = follow_direction; if (desc) { binding->desc = strdup(desc); } wl_list_init(&binding->link); return binding; } bool kywc_gesture_binding_register(struct gesture_binding *binding, void (*action)(struct gesture_binding *binding, void *data, double dx, double dy), void *data) { if (!wl_list_empty(&binding->link)) { return true; } if (!gesture_binding_is_valid(binding, binding->type, binding->stage, binding->devices, binding->directions, binding->follow_direction, binding->fingers, binding->edges)) { return false; } wl_list_insert(&bindings->gesture_bindings, &binding->link); binding->action = action; binding->data = data; return true; } bool bindings_handle_gesture_binding(struct gesture_state *gesture_state) { struct gesture_binding *binding; wl_list_for_each(binding, &bindings->gesture_bindings, link) { if (gesture_state->type != binding->type) { continue; } if (gesture_state->fingers != binding->fingers) { continue; } if (gesture_state->stage != binding->stage) { continue; } /* do not trigger follow if interval < follow_threshold */ if (gesture_state->stage == GESTURE_STAGE_BEFORE || gesture_state->stage == GESTURE_STAGE_AFTER) { if ((gesture_state->follow_direction != binding->follow_direction) || (gesture_state->follow_dx > -binding->follow_threshold && gesture_state->follow_dx < binding->follow_threshold && gesture_state->follow_dy > -binding->follow_threshold && gesture_state->follow_dy < binding->follow_threshold)) { continue; } } if ((gesture_state->device & binding->devices) && (binding->directions == GESTURE_DIRECTION_NONE || binding->stage == GESTURE_STAGE_BEFORE || gesture_state->directions & binding->directions) && (binding->edges == GESTURE_EDGE_NONE || gesture_state->edge & binding->edges)) { kywc_log(KYWC_DEBUG, "Start gesture binding: %s", binding->desc); if (binding->action) { binding->action(binding, binding->data, gesture_state->dx, gesture_state->dy); } return true; } } return false; } void kywc_key_binding_for_each(binding_iterator_func_t iterator) { for (size_t i = 0; i < KEY_BINDING_TYPE_NUM; ++i) { struct key_binding *binding; struct keysyms_binding *keysyms_binding = &bindings->keysyms_binding[i]; wl_list_for_each(binding, &keysyms_binding->bindings, link) { if (iterator(binding, binding->keybind, binding->desc, binding->modifiers, binding->keysym)) { return; } } } } void kywc_key_binding_block_all(bool block) { if (block) { bindings->keysym_bindings_block++; } else { assert(bindings->keysym_bindings_block > 0); bindings->keysym_bindings_block--; } } void kywc_key_binding_block_type(enum key_binding_type type, bool block) { if (block) { bindings->keysyms_binding_masks &= ~(1 << type); bindings->type_nlocks[type]++; } else { assert(bindings->type_nlocks[type] > 0); bindings->type_nlocks[type]--; if (!bindings->type_nlocks[type]) { bindings->keysyms_binding_masks |= (1 << type); } } } enum key_binding_type kywc_key_binding_type_by_name(const char *name, bool *found) { for (size_t i = 0; i < ARRAY_SIZE(key_binding_table); ++i) { if (strcmp(name, key_binding_table[i].name)) { continue; } if (found) { *found = true; } return key_binding_table[i].type; } if (found) { *found = false; } return KEY_BINDING_TYPE_CUSTOM_DEF; } kylin-wayland-compositor/src/input/gesture.c0000664000175000017500000002103315160461067020234 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "input/gesture.h" #include "input_p.h" #define GESTURE_DEFAULT_TIMEOUT (200) #define GESTURE_TOUCHPAD_TIMEOUT (20) #define GESTURE_TOUCHPAD_TAP_TIMEOUT (1) #define GESTURE_TOUCHSCREEN_HOLD_TIMEOUT (800) /** * touchpad: * Relative motion deltas are normalized to represent those of a device with 1000dpi resolution * https://wayland.freedesktop.org/libinput/doc/latest/api/group__event__gesture.html#ga3888052854155ad133fa837e4f28d771 * some devices are below 1000dpi, so we chase kwin, default 50 */ #define GESTURE_TOUCHPAD_TRIGGER_THRESHOLD (10) /* touchscreen: chase kwin, full width height is 1 */ #define GESTURE_TOUCHSCREEN_TRIGGER_THRESHOLD (0.15) static char *gestures[] = { "none", "tap", "pinch", "swipe", "hold" }; static void gesture_state_reset(struct gesture_state *state) { state->type = GESTURE_TYPE_NONE; state->stage = GESTURE_STAGE_NONE; state->device = GESTURE_DEVICE_NONE; state->directions = GESTURE_DIRECTION_NONE; state->follow_direction = GESTURE_DIRECTION_NONE; state->edge = GESTURE_EDGE_NONE; state->triggered = false; state->handled = false; if (state->timer) { wl_event_source_timer_update(state->timer, 0); } } static void gesture_state_trigger(struct gesture_state *state) { // Ignore gesture under some threshold const double min_rotation = 5; const double min_scale_delta = 0.1; // Determine direction switch (state->type) { // Gestures with scale and rotation case GESTURE_TYPE_PINCH: if (state->rotation > min_rotation) { state->directions |= GESTURE_DIRECTION_CLOCKWISE; } if (state->rotation < -min_rotation) { state->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE; } if (state->scale > (1.0 + min_scale_delta)) { state->directions |= GESTURE_DIRECTION_OUTWARD; } if (state->scale < (1.0 - min_scale_delta)) { state->directions |= GESTURE_DIRECTION_INWARD; } // fallthrough // Gestures with dx and dy case GESTURE_TYPE_SWIPE: if (fabs(state->dx) > fabs(state->dy)) { if (state->dx > 0) { state->directions |= GESTURE_DIRECTION_RIGHT; } else { state->directions |= GESTURE_DIRECTION_LEFT; } } else { if (state->dy > 0) { state->directions |= GESTURE_DIRECTION_DOWN; } else { state->directions |= GESTURE_DIRECTION_UP; } } // Gesture without any direction case GESTURE_TYPE_HOLD: case GESTURE_TYPE_TAP: break; // Not tracking any gesture case GESTURE_TYPE_NONE: assert("Not tracking any gesture."); break; } kywc_log(KYWC_DEBUG, "Gesture %s triggered: fingers: %u, directions: %d", gestures[state->type], state->fingers, state->directions); state->triggered = true; state->stage = GESTURE_STAGE_TRIGGER; state->follow_dx = 0.0; state->follow_dy = 0.0; state->follow_direction = GESTURE_DIRECTION_NONE; state->handled = bindings_handle_gesture_binding(state); } static void gesture_state_follow(struct gesture_state *state, double dx, double dy, bool before_triggered) { // only support swipe for now if (state->type != GESTURE_TYPE_SWIPE) { return; } state->follow_dx += dx; state->follow_dy += dy; if (fabs(dx) > fabs(dy)) { if (dx > 0) { state->follow_direction = GESTURE_DIRECTION_RIGHT; } else { state->follow_direction = GESTURE_DIRECTION_LEFT; } } else { if (dy > 0) { state->follow_direction = GESTURE_DIRECTION_DOWN; } else { state->follow_direction = GESTURE_DIRECTION_UP; } } state->stage = before_triggered ? GESTURE_STAGE_BEFORE : GESTURE_STAGE_AFTER; state->handled = bindings_handle_gesture_binding(state); if (state->handled) { state->follow_dx = 0.0; state->follow_dy = 0.0; } } static void gesture_state_stop(struct gesture_state *state) { // only support swipe for now if (state->type != GESTURE_TYPE_SWIPE) { return; } state->stage = GESTURE_STAGE_STOP; state->handled = bindings_handle_gesture_binding(state); } static int gesture_handle_timer(void *data) { struct gesture_state *state = data; gesture_state_trigger(state); return 0; } static void gesture_swipe_state_update(struct gesture_state *state, double dx, double dy) { state->dx += dx; state->dy += dy; if (!state->triggered) { if ((state->device == GESTURE_DEVICE_TOUCHSCREEN && (state->dx < -GESTURE_TOUCHSCREEN_TRIGGER_THRESHOLD || state->dx > GESTURE_TOUCHSCREEN_TRIGGER_THRESHOLD || state->dy < -GESTURE_TOUCHSCREEN_TRIGGER_THRESHOLD || state->dy > GESTURE_TOUCHSCREEN_TRIGGER_THRESHOLD)) || (state->device == GESTURE_DEVICE_TOUCHPAD && (state->dx < -GESTURE_TOUCHPAD_TRIGGER_THRESHOLD || state->dx > GESTURE_TOUCHPAD_TRIGGER_THRESHOLD || state->dy < -GESTURE_TOUCHPAD_TRIGGER_THRESHOLD || state->dy > GESTURE_TOUCHPAD_TRIGGER_THRESHOLD))) { gesture_state_trigger(state); } else { gesture_state_follow(state, dx, dy, true); } } else { gesture_state_follow(state, dx, dy, false); } } void gesture_state_init(struct gesture_state *state, void *display) { struct wl_event_loop *loop = wl_display_get_event_loop(display); state->timer = wl_event_loop_add_timer(loop, gesture_handle_timer, state); gesture_state_reset(state); } void gesture_state_finish(struct gesture_state *state) { if (state->timer) { wl_event_source_remove(state->timer); } } void gesture_state_begin(struct gesture_state *state, enum gesture_type type, enum gesture_device device, enum gesture_edge edge, uint8_t fingers) { state->type = type; state->device = device; state->fingers = fingers; state->dx = 0.0; state->dy = 0.0; state->scale = 1.0; state->rotation = 0.0; state->edge = edge; if (state->timer && state->type != GESTURE_TYPE_SWIPE) { int timeout = GESTURE_DEFAULT_TIMEOUT; if (state->device == GESTURE_DEVICE_TOUCHPAD) { timeout = state->type == GESTURE_TYPE_TAP ? GESTURE_TOUCHPAD_TAP_TIMEOUT : GESTURE_TOUCHPAD_TIMEOUT; } else if (state->type == GESTURE_TYPE_HOLD && state->device == GESTURE_DEVICE_TOUCHSCREEN) { timeout = GESTURE_TOUCHSCREEN_HOLD_TIMEOUT; } wl_event_source_timer_update(state->timer, timeout); } kywc_log(KYWC_DEBUG, "Gesture %s state begin: fingers: %u", gestures[type], fingers); } void gesture_state_update(struct gesture_state *state, enum gesture_type type, enum gesture_device device, double dx, double dy, double scale, double rotation) { if (state->type != type || state->device != device) { return; } if (state->type == GESTURE_TYPE_HOLD || state->type == GESTURE_TYPE_TAP) { assert("tap or hold does not update."); return; } if (state->type == GESTURE_TYPE_PINCH) { state->dx += dx; state->dy += dy; state->scale = scale; state->rotation += rotation; } if (state->type == GESTURE_TYPE_SWIPE) { gesture_swipe_state_update(state, dx, dy); } kywc_log(KYWC_DEBUG, "gesture %s state update: fingers: %u gesture: %f %f %f %f", gestures[state->type], state->fingers, state->dx, state->dy, state->scale, state->rotation); } bool gesture_state_end(struct gesture_state *state, enum gesture_type type, enum gesture_device device, bool cancelled) { if (state->type != type || state->device != device) { return false; } if (cancelled) { kywc_log(KYWC_DEBUG, "gesture %s state cancelled", gestures[state->type]); gesture_state_stop(state); gesture_state_reset(state); return false; } if (!state->triggered && state->type == GESTURE_TYPE_PINCH) { gesture_state_trigger(state); } bool handled = state->handled; gesture_state_stop(state); gesture_state_reset(state); return handled; } kylin-wayland-compositor/src/input/seat.c0000664000175000017500000003534115160461067017521 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "input/keyboard.h" #include "input/seat.h" #include "input_p.h" #include "server.h" #include "util/time.h" #include "xwayland.h" static void handle_server_start(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, server_start); /* there is no hardware keyboard input device */ if (!seat->keyboard->wlr_keyboard->keymap) { struct keymap_rules rules = { 0 }; struct xkb_keymap *keymap = input_get_or_create_keymap(&rules, true); wlr_keyboard_set_keymap(seat->keyboard->wlr_keyboard, keymap); } wlr_seat_set_keyboard(seat->wlr_seat, seat->keyboard->wlr_keyboard); if (seat->state.keyboard_lock & (1 << INPUT_KEY_CAPSLOCK)) { seat_feed_keyboard_key(seat, KEY_CAPSLOCK, true); seat_feed_keyboard_key(seat, KEY_CAPSLOCK, false); } if (seat->state.keyboard_lock & (1 << INPUT_KEY_NUMLOCK)) { seat_feed_keyboard_key(seat, KEY_NUMLOCK, true); seat_feed_keyboard_key(seat, KEY_NUMLOCK, false); } if (seat->state.keyboard_lock & (1 << INPUT_KEY_SCROLLLOCK)) { seat_feed_keyboard_key(seat, KEY_SCROLLLOCK, true); seat_feed_keyboard_key(seat, KEY_SCROLLLOCK, false); } } static void handle_server_active(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, server_active); wlr_seat_set_keyboard(seat->wlr_seat, seat->keyboard->wlr_keyboard); } static void _seat_destroy(struct seat *seat) { kywc_log(KYWC_DEBUG, "Seat(%s) destroy", seat->name); wl_signal_emit_mutable(&seat->events.destroy, NULL); assert(wl_list_empty(&seat->events.cursor_motion.listener_list)); assert(wl_list_empty(&seat->events.cursor_configure.listener_list)); assert(wl_list_empty(&seat->events.keyboard_key.listener_list)); assert(wl_list_empty(&seat->events.keyboard_modifiers.listener_list)); assert(wl_list_empty(&seat->events.capability.listener_list)); assert(wl_list_empty(&seat->events.destroy.listener_list)); wl_list_remove(&seat->destroy.link); wl_list_remove(&seat->link); wl_list_remove(&seat->server_start.link); wl_list_remove(&seat->server_active.link); seat->state.keyboard_lock = keyboard_get_locks(seat->keyboard); seat_write_config(seat); /* cancel grab when seat destroy */ if (seat->pointer_grab && seat->pointer_grab->interface->cancel) { seat->pointer_grab->interface->cancel(seat->pointer_grab); } if (seat->keyboard_grab && seat->keyboard_grab->interface->cancel) { seat->keyboard_grab->interface->cancel(seat->keyboard_grab); } if (seat->touch_grab && seat->touch_grab->interface->cancel) { seat->touch_grab->interface->cancel(seat->touch_grab); } struct input *input, *tmp; wl_list_for_each_safe(input, tmp, &seat->inputs, seat_link) { seat_remove_input(input); } struct keyboard *keyboard, *keyboard_tmp; wl_list_for_each_safe(keyboard, keyboard_tmp, &seat->keyboards, link) { keyboard_destroy(keyboard); } cursor_destroy(seat->cursor); free((void *)seat->state.cursor_theme); free(seat->name); free(seat); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, destroy); /* called when display destroy */ _seat_destroy(seat); } struct seat *seat_create(struct input_manager *input_manager, const char *name) { struct seat *seat = calloc(1, sizeof(*seat)); if (!seat) { return NULL; } seat->wlr_seat = wlr_seat_create(input_manager->server->display, name); if (!seat->wlr_seat) { free(seat); return NULL; } seat->wlr_seat->data = seat; seat->scene = input_manager->server->scene; seat->layout = input_manager->server->layout; seat->pointer_gestures = input_manager->pointer_gestures; seat->manager = input_manager; seat->server_start.notify = handle_server_start; wl_signal_add(&input_manager->server->events.start, &seat->server_start); seat->server_active.notify = handle_server_active; wl_signal_add(&input_manager->server->events.active, &seat->server_active); seat->destroy.notify = handle_seat_destroy; wl_signal_add(&seat->wlr_seat->events.destroy, &seat->destroy); seat->name = strdup(name); wl_list_init(&seat->inputs); wl_list_init(&seat->keyboards); wl_list_init(&seat->keyboard_shortcuts_inhibitors); wl_signal_init(&seat->events.cursor_motion); wl_signal_init(&seat->events.cursor_configure); wl_signal_init(&seat->events.keyboard_key); wl_signal_init(&seat->events.keyboard_modifiers); wl_signal_init(&seat->events.capability); wl_signal_init(&seat->events.destroy); seat->state.cursor_theme = NULL; seat->state.cursor_size = 24; seat->state.keyboard_lock_mode = 2; if (!seat_read_config(seat)) { kywc_log(KYWC_ERROR, "Seat(%s) read config error!", seat->name); } seat->cursor = cursor_create(seat); seat->keyboard = keyboard_create(seat, NULL); cursor_set_hidden(seat->cursor, true); wl_list_insert(&input_manager->seats, &seat->link); kywc_log(KYWC_DEBUG, "Seat(%s) is created", seat->name); wl_signal_emit_mutable(&input_manager->events.new_seat, seat); return seat; } void seat_destroy(struct seat *seat) { struct wlr_seat *wlr_seat = seat->wlr_seat; _seat_destroy(seat); wlr_seat_destroy(wlr_seat); } static void seat_update_capabilities(struct seat *seat) { uint32_t old_caps = seat->caps; seat->caps = 0; struct input *input; wl_list_for_each(input, &seat->inputs, seat_link) { switch (input->prop.type) { case WLR_INPUT_DEVICE_KEYBOARD: seat->caps |= WL_SEAT_CAPABILITY_KEYBOARD; break; case WLR_INPUT_DEVICE_POINTER: case WLR_INPUT_DEVICE_TABLET: seat->caps |= WL_SEAT_CAPABILITY_POINTER; break; case WLR_INPUT_DEVICE_TOUCH: seat->caps |= WL_SEAT_CAPABILITY_TOUCH; break; case WLR_INPUT_DEVICE_SWITCH: case WLR_INPUT_DEVICE_TABLET_PAD: break; } } wlr_seat_set_capabilities(seat->wlr_seat, seat->caps); /* auto hide cursor if no pointer cap */ cursor_set_hidden(seat->cursor, !(seat->caps & WL_SEAT_CAPABILITY_POINTER)); if (old_caps != seat->caps) { wl_signal_emit_mutable(&seat->events.capability, NULL); } } void seat_add_input(struct seat *seat, struct input *input) { input->seat = seat; wl_list_insert(&seat->inputs, &input->seat_link); switch (input->prop.type) { case WLR_INPUT_DEVICE_POINTER: case WLR_INPUT_DEVICE_TOUCH: case WLR_INPUT_DEVICE_TABLET: curosr_add_input(seat, input); break; case WLR_INPUT_DEVICE_KEYBOARD: keyboard_add_input(seat, input); break; case WLR_INPUT_DEVICE_TABLET_PAD: break; case WLR_INPUT_DEVICE_SWITCH: break; } seat_update_capabilities(seat); } void seat_remove_input(struct input *input) { struct seat *seat = input->seat; if (!seat) { return; } switch (input->prop.type) { case WLR_INPUT_DEVICE_POINTER: case WLR_INPUT_DEVICE_TOUCH: case WLR_INPUT_DEVICE_TABLET: cursor_remove_input(input); break; case WLR_INPUT_DEVICE_KEYBOARD: keyboard_remove_input(input); break; case WLR_INPUT_DEVICE_TABLET_PAD: break; case WLR_INPUT_DEVICE_SWITCH: break; } wl_list_remove(&input->seat_link); input->seat = NULL; seat_update_capabilities(seat); } struct seat *seat_from_resource(struct wl_resource *resource) { struct wlr_seat_client *seat_client = wl_resource_get_user_data(resource); struct wlr_seat *wlr_seat = seat_client->seat; return wlr_seat ? wlr_seat->data : NULL; } struct seat *seat_from_wlr_seat(struct wlr_seat *wlr_seat) { return wlr_seat->data; } void seat_set_cursor(struct seat *seat, const char *cursor_theme, uint32_t cursor_size) { cursor_set_xcursor_manager(seat->cursor, cursor_theme, cursor_size, true); xwayland_set_cursor(seat); seat_write_config(seat); } void seat_start_pointer_grab(struct seat *seat, struct seat_pointer_grab *pointer_grab) { struct seat_pointer_grab *grab = seat->pointer_grab; if (grab == pointer_grab) { return; } pointer_grab->seat = seat; seat->pointer_grab = pointer_grab; if (grab && grab->interface->cancel) { grab->interface->cancel(grab); } /* when we move quickly with left button pressed at view edges, * hold_mode is entered by a cursor motion event then client requests move */ seat->cursor->hold_mode = false; } void seat_end_pointer_grab(struct seat *seat, struct seat_pointer_grab *pointer_grab) { if (seat->pointer_grab == pointer_grab) { seat->pointer_grab = NULL; } } void seat_start_keyboard_grab(struct seat *seat, struct seat_keyboard_grab *keyboard_grab) { struct seat_keyboard_grab *grab = seat->keyboard_grab; if (grab == keyboard_grab) { return; } keyboard_grab->seat = seat; seat->keyboard_grab = keyboard_grab; if (grab && grab->interface->cancel) { grab->interface->cancel(grab); } } void seat_end_keyboard_grab(struct seat *seat, struct seat_keyboard_grab *keyboard_grab) { if (seat->keyboard_grab == keyboard_grab) { seat->keyboard_grab = NULL; } } void seat_start_touch_grab(struct seat *seat, struct seat_touch_grab *touch_grab) { struct seat_touch_grab *grab = seat->touch_grab; if (grab == touch_grab) { return; } touch_grab->seat = seat; seat->touch_grab = touch_grab; if (grab && grab->interface->cancel) { grab->interface->cancel(grab); } } void seat_end_touch_grab(struct seat *seat, struct seat_touch_grab *touch_grab) { if (seat->touch_grab == touch_grab) { seat->touch_grab = NULL; } } void seat_reset_input_gesture(struct seat *seat) { /* canceling gesture in the touch grab */ touch_reset_gesture(seat->manager); } void seat_notify_motion(struct seat *seat, struct wlr_surface *surface, uint32_t time, double sx, double sy, bool first_enter) { struct wlr_seat *wlr_seat = seat->wlr_seat; struct wlr_surface *prev = wlr_seat->pointer_state.focused_surface; if (first_enter || surface != prev) { wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy); kywc_log(KYWC_DEBUG, "Pointer enter surface %p", surface); } wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy); } void seat_notify_button(struct seat *seat, uint32_t time, uint32_t button, bool pressed) { struct wlr_seat *wlr_seat = seat->wlr_seat; enum wl_pointer_button_state state = pressed ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED; wlr_seat_pointer_notify_button(wlr_seat, time, button, state); } void seat_notify_leave(struct seat *seat, struct wlr_surface *surface) { struct wlr_seat *wlr_seat = seat->wlr_seat; struct wlr_surface *prev = wlr_seat->pointer_state.focused_surface; if (!surface || surface == prev) { wlr_seat_pointer_notify_clear_focus(wlr_seat); } } void seat_focus_surface(struct seat *seat, struct wlr_surface *surface) { struct wlr_seat *wlr_seat = seat->wlr_seat; struct wlr_surface *last_surface = wlr_seat->keyboard_state.focused_surface; if (last_surface != surface) { struct seat_keyboard_shortcuts_inhibitor *shortcuts_inhibitor = NULL; wl_list_for_each(shortcuts_inhibitor, &seat->keyboard_shortcuts_inhibitors, link) { if (shortcuts_inhibitor->inhibitor->surface == last_surface) { wlr_keyboard_shortcuts_inhibitor_v1_deactivate(shortcuts_inhibitor->inhibitor); } else if (shortcuts_inhibitor->inhibitor->surface == surface) { wlr_keyboard_shortcuts_inhibitor_v1_activate(shortcuts_inhibitor->inhibitor); } } } if (surface) { struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(wlr_seat); if (keyboard) { wlr_seat_keyboard_notify_enter( wlr_seat, surface, seat->keyboard_grab ? NULL : keyboard->keycodes, seat->keyboard_grab ? 0 : keyboard->num_keycodes, &keyboard->modifiers); } else { wlr_seat_keyboard_notify_enter(wlr_seat, surface, NULL, 0, NULL); } } else { wlr_seat_keyboard_notify_clear_focus(wlr_seat); } tablet_set_focus(seat, surface); input_method_set_focus(seat, surface); cursor_constraint_set_focus(seat, surface); } void seat_feed_pointer_motion(struct seat *seat, double x, double y, bool absolute) { if (absolute) { x = x - seat->cursor->lx; y = y - seat->cursor->ly; } cursor_feed_motion(seat->cursor, current_time_msec(), NULL, x, y, x, y); wlr_seat_pointer_notify_frame(seat->wlr_seat); } void seat_feed_pointer_button(struct seat *seat, uint32_t button, bool pressed) { cursor_feed_button(seat->cursor, button, pressed, current_time_msec(), DEFAULT_DOUBLE_CLICK_TIME); wlr_seat_pointer_notify_frame(seat->wlr_seat); } void seat_feed_pointer_axis(struct seat *seat, uint32_t axis, double step) { cursor_feed_axis(seat->cursor, axis, WL_POINTER_AXIS_SOURCE_WHEEL, step, 0, 0, current_time_msec()); wlr_seat_pointer_notify_frame(seat->wlr_seat); } void seat_feed_keyboard_key(struct seat *seat, uint32_t key, bool pressed) { /* pick a keyboard to send key event */ struct keyboard *keyboard = seat->keyboard; /* trying to find a keyboard that has input device */ if (keyboard_has_no_input(keyboard)) { struct keyboard *kb; wl_list_for_each(kb, &seat->keyboards, link) { if (keyboard_has_no_input(kb)) { continue; } keyboard = kb; } } keyboard_send_key(keyboard, key, pressed); } bool seat_is_keyboard_shortcuts_inhibited(struct seat *seat) { struct wlr_surface *wlr_surface = seat->wlr_seat->keyboard_state.focused_surface; struct seat_keyboard_shortcuts_inhibitor *shortcuts_inhibitor; wl_list_for_each(shortcuts_inhibitor, &seat->keyboard_shortcuts_inhibitors, link) { if (shortcuts_inhibitor->inhibitor->surface == wlr_surface) { return shortcuts_inhibitor->inhibitor->active; } } return false; } bool seat_is_dragging(struct seat *seat) { return selection_is_dragging(seat) || xwayland_is_dragging_x11(seat); } kylin-wayland-compositor/src/input/libinput.c0000664000175000017500000003040315160460057020403 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "input_p.h" void libinput_get_prop(struct input *input, struct input_prop *prop) { struct libinput_device *device = input->device; if (!device) { kywc_log(KYWC_DEBUG, "Input %s is not a libinput device", input->name); return; } prop->send_events_modes = libinput_device_config_send_events_get_modes(device); prop->click_methods = libinput_device_config_click_get_methods(device); prop->scroll_methods = libinput_device_config_scroll_get_methods(device); prop->accel_profiles = libinput_device_config_accel_get_profiles(device); prop->tap_finger_count = libinput_device_config_tap_get_finger_count(device); prop->has_calibration_matrix = libinput_device_config_calibration_has_matrix(device); prop->has_pointer_accel = libinput_device_config_accel_is_available(device); prop->has_natural_scroll = libinput_device_config_scroll_has_natural_scroll(device); prop->has_left_handed = libinput_device_config_left_handed_is_available(device); prop->has_middle_emulation = libinput_device_config_middle_emulation_is_available(device); prop->has_dwt = libinput_device_config_dwt_is_available(device); prop->has_dwtp = libinput_device_config_dwtp_is_available(device); prop->has_rotation = libinput_device_config_rotation_is_available(device); } void libinput_get_state(struct input *input, struct input_state *state) { struct libinput_device *device = input->device; if (!device) { kywc_log(KYWC_DEBUG, "Input %s is not a libinput device", input->name); return; } struct input_prop *prop = &input->prop; state->send_events_mode = libinput_device_config_send_events_get_mode(device); state->click_method = libinput_device_config_click_get_method(device); if (prop->tap_finger_count > 0) { state->tap_to_click = libinput_device_config_tap_get_enabled(device); state->tap_button_map = libinput_device_config_tap_get_button_map(device); state->tap_and_drag = libinput_device_config_tap_get_drag_enabled(device); state->tap_drag_lock = libinput_device_config_tap_get_drag_lock_enabled(device); } if (prop->has_calibration_matrix) { libinput_device_config_calibration_get_matrix(device, state->calibration_matrix); } if (prop->has_pointer_accel) { state->pointer_accel_speed = libinput_device_config_accel_get_speed(device); state->accel_profile = libinput_device_config_accel_get_profile(device); } if (prop->has_natural_scroll) { state->natural_scroll = libinput_device_config_scroll_get_natural_scroll_enabled(device); } if (prop->has_left_handed) { state->left_handed = libinput_device_config_left_handed_get(device); } if (prop->has_middle_emulation) { state->middle_emulation = libinput_device_config_middle_emulation_get_enabled(device); } state->scroll_method = libinput_device_config_scroll_get_method(device); if (state->scroll_method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) { state->scroll_button = libinput_device_config_scroll_get_button(device); state->scroll_button_lock = libinput_device_config_scroll_get_button_lock(device); } if (prop->has_dwt) { state->dwt = libinput_device_config_dwt_get_enabled(device); } if (prop->has_dwtp) { state->dwtp = libinput_device_config_dwtp_get_enabled(device); } if (prop->has_rotation) { state->rotation_angle = libinput_device_config_rotation_get_angle(device); } } void libinput_get_default_state(struct input *input, struct input_state *state) { struct libinput_device *device = input->device; if (!device) { kywc_log(KYWC_DEBUG, "Input %s is not a libinput device", input->name); return; } struct input_prop *prop = &input->prop; state->send_events_mode = libinput_device_config_send_events_get_default_mode(device); state->click_method = libinput_device_config_click_get_default_method(device); if (prop->tap_finger_count > 0) { state->tap_to_click = libinput_device_config_tap_get_default_enabled(device); state->tap_button_map = libinput_device_config_tap_get_default_button_map(device); state->tap_and_drag = libinput_device_config_tap_get_default_drag_enabled(device); state->tap_drag_lock = libinput_device_config_tap_get_default_drag_lock_enabled(device); } if (prop->has_calibration_matrix) { libinput_device_config_calibration_get_default_matrix(device, state->calibration_matrix); } if (prop->has_pointer_accel) { state->pointer_accel_speed = libinput_device_config_accel_get_default_speed(device); state->accel_profile = libinput_device_config_accel_get_default_profile(device); } if (prop->has_natural_scroll) { state->natural_scroll = libinput_device_config_scroll_get_default_natural_scroll_enabled(device); } if (prop->has_left_handed) { state->left_handed = libinput_device_config_left_handed_get_default(device); } if (prop->has_middle_emulation) { state->middle_emulation = libinput_device_config_middle_emulation_get_default_enabled(device); } state->scroll_method = libinput_device_config_scroll_get_default_method(device); if (state->scroll_method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) { state->scroll_button = libinput_device_config_scroll_get_default_button(device); state->scroll_button_lock = libinput_device_config_scroll_get_default_button_lock(device); } if (prop->has_dwt) { state->dwt = libinput_device_config_dwt_get_default_enabled(device); } if (prop->has_dwtp) { state->dwtp = libinput_device_config_dwtp_get_default_enabled(device); } if (prop->has_rotation) { state->rotation_angle = libinput_device_config_rotation_get_default_angle(device); } } static bool log_status(enum libinput_config_status status) { if (status != LIBINPUT_CONFIG_STATUS_SUCCESS) { kywc_log(KYWC_ERROR, "Failed to apply libinput config: %s", libinput_config_status_to_str(status)); return false; } return true; } bool libinput_set_state(struct input *input, struct input_state *state) { struct libinput_device *device = input->device; if (!device) { kywc_log(KYWC_DEBUG, "Input %s is not a libinput device", input->name); return false; } struct input_state *current = &input->state; bool success = true; if (current->send_events_mode != state->send_events_mode) { kywc_log(KYWC_DEBUG, "Do send_events_set_mode(%d)", state->send_events_mode); success &= log_status( libinput_device_config_send_events_set_mode(device, state->send_events_mode)); } if (current->click_method != state->click_method) { kywc_log(KYWC_DEBUG, "Do click_set_method(%d)", state->click_method); success &= log_status(libinput_device_config_click_set_method(device, state->click_method)); } if (input->prop.tap_finger_count > 0) { if (current->tap_to_click != state->tap_to_click) { kywc_log(KYWC_DEBUG, "Do tap_set_enabled(%d)", state->tap_to_click); success &= log_status(libinput_device_config_tap_set_enabled(device, state->tap_to_click)); } // XXX: skip others ? // if (!state->tap_to_click) { // } if (current->tap_and_drag != state->tap_and_drag) { kywc_log(KYWC_DEBUG, "Do tap_set_drag_enabled(%d)", state->tap_and_drag); success &= log_status( libinput_device_config_tap_set_drag_enabled(device, state->tap_and_drag)); } if (current->tap_drag_lock != state->tap_drag_lock) { kywc_log(KYWC_DEBUG, "Do tap_set_drag_lock_enabled(%d)", state->tap_drag_lock); success &= log_status( libinput_device_config_tap_set_drag_lock_enabled(device, state->tap_drag_lock)); } if (current->tap_button_map != state->tap_button_map) { kywc_log(KYWC_DEBUG, "Do tap_set_button_map(%d)", state->tap_button_map); success &= log_status( libinput_device_config_tap_set_button_map(device, state->tap_button_map)); } } if (input->prop.has_calibration_matrix) { bool changed = false; for (int i = 0; i < 6; i++) { if (current->calibration_matrix[i] != state->calibration_matrix[i]) { changed = true; break; } } if (changed) { kywc_log(KYWC_DEBUG, "Do calibration_set_matrix(%f, %f, %f, %f, %f, %f)", state->calibration_matrix[0], state->calibration_matrix[1], state->calibration_matrix[2], state->calibration_matrix[3], state->calibration_matrix[4], state->calibration_matrix[5]); success &= log_status( libinput_device_config_calibration_set_matrix(device, state->calibration_matrix)); } } if (input->prop.has_pointer_accel) { if (current->pointer_accel_speed != state->pointer_accel_speed) { kywc_log(KYWC_DEBUG, "Do accel_set_speed(%f)", state->pointer_accel_speed); success &= log_status( libinput_device_config_accel_set_speed(device, state->pointer_accel_speed)); } if (current->accel_profile != state->accel_profile) { kywc_log(KYWC_DEBUG, "Do accel_set_profile(%d)", state->accel_profile); success &= log_status(libinput_device_config_accel_set_profile(device, state->accel_profile)); } } if (input->prop.has_natural_scroll && current->natural_scroll != state->natural_scroll) { kywc_log(KYWC_DEBUG, "Do scroll_set_natural_scroll_enabled(%d)", state->natural_scroll); success &= log_status(libinput_device_config_scroll_set_natural_scroll_enabled( device, state->natural_scroll)); } if (input->prop.has_left_handed && current->left_handed != state->left_handed) { kywc_log(KYWC_DEBUG, "Do left_handed_set(%d)", state->left_handed); success &= log_status(libinput_device_config_left_handed_set(device, state->left_handed)); } if (input->prop.has_middle_emulation && current->middle_emulation != state->middle_emulation) { kywc_log(KYWC_DEBUG, "Do middle_emulation_set_enabled(%d)", state->middle_emulation); success &= log_status( libinput_device_config_middle_emulation_set_enabled(device, state->middle_emulation)); } if (current->scroll_method != state->scroll_method) { kywc_log(KYWC_DEBUG, "Do scroll_set_method(%d)", state->scroll_method); success &= log_status(libinput_device_config_scroll_set_method(device, state->scroll_method)); } if (state->scroll_method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) { if (current->scroll_button != state->scroll_button) { kywc_log(KYWC_DEBUG, "Do scroll_set_button(%d)", state->scroll_button); success &= log_status(libinput_device_config_scroll_set_button(device, state->scroll_button)); } if (current->scroll_button_lock != state->scroll_button_lock) { kywc_log(KYWC_DEBUG, "Do scroll_set_button_lock(%d)", state->scroll_button_lock); success &= log_status( libinput_device_config_scroll_set_button_lock(device, state->scroll_button_lock)); } } if (input->prop.has_dwt && current->dwt != state->dwt) { kywc_log(KYWC_DEBUG, "Do dwt_set_enabled(%d)", state->dwt); success &= log_status(libinput_device_config_dwt_set_enabled(device, state->dwt)); } if (input->prop.has_dwtp && current->dwtp != state->dwtp) { kywc_log(KYWC_DEBUG, "Do dwtp_set_enabled(%d)", state->dwtp); success &= log_status(libinput_device_config_dwtp_set_enabled(device, state->dwtp)); } if (input->prop.has_rotation && current->rotation_angle != state->rotation_angle) { kywc_log(KYWC_DEBUG, "Do rotation_set_angle(%d)", state->rotation_angle); success &= log_status(libinput_device_config_rotation_set_angle(device, state->rotation_angle)); } return success; } kylin-wayland-compositor/src/input/kde_keystate.c0000664000175000017500000002414115160461067021235 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "input/seat.h" #include "input_p.h" #include "kde-keystate-protocol.h" #include "server.h" #define ORG_KDE_KWIN_KEYSTATE_VERSION 5 #define SETBIT(x, y) ((x) |= (1 << (y))) #define CLRBIT(x, y) ((x) &= ~(1 << (y))) #define REVBIT(x, y) ((x) ^= (1 << (y))) #define GETBIT(x, y) (((x) >> (y)) & 1) struct keystate_manager { struct wl_global *global; uint32_t keyboard_lock; uint32_t release_record; struct wl_list resources; struct wl_list keyboards; struct wl_listener new_seat; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct keystate_keyboard { struct wl_list link; uint32_t keycode; struct wl_listener key; struct wl_listener modifiers; struct wl_listener destroy; }; static struct keystate_manager *keystate_manager = NULL; static void kde_keystate_fetchstates(struct wl_client *client, struct wl_resource *resource) { uint32_t state = 0; state = GETBIT(keystate_manager->keyboard_lock, INPUT_KEY_CAPSLOCK) ? ORG_KDE_KWIN_KEYSTATE_STATE_LOCKED : ORG_KDE_KWIN_KEYSTATE_STATE_UNLOCKED; org_kde_kwin_keystate_send_stateChanged(resource, ORG_KDE_KWIN_KEYSTATE_KEY_CAPSLOCK, state); state = GETBIT(keystate_manager->keyboard_lock, INPUT_KEY_NUMLOCK) ? ORG_KDE_KWIN_KEYSTATE_STATE_LOCKED : ORG_KDE_KWIN_KEYSTATE_STATE_UNLOCKED; org_kde_kwin_keystate_send_stateChanged(resource, ORG_KDE_KWIN_KEYSTATE_KEY_NUMLOCK, state); state = GETBIT(keystate_manager->keyboard_lock, INPUT_KEY_SCROLLLOCK) ? ORG_KDE_KWIN_KEYSTATE_STATE_LOCKED : ORG_KDE_KWIN_KEYSTATE_STATE_UNLOCKED; org_kde_kwin_keystate_send_stateChanged(resource, ORG_KDE_KWIN_KEYSTATE_KEY_SCROLLLOCK, state); uint32_t version = wl_resource_get_version(resource); if (version < ORG_KDE_KWIN_KEYSTATE_VERSION) { return; } struct seat *seat = input_manager_get_default_seat(); struct wlr_keyboard_modifiers *wlr_keyboard_modifiers = &seat->keyboard->wlr_keyboard->modifiers; if (wlr_keyboard_modifiers->depressed & WLR_MODIFIER_ALT) { org_kde_kwin_keystate_send_stateChanged(resource, ORG_KDE_KWIN_KEYSTATE_KEY_ALT, ORG_KDE_KWIN_KEYSTATE_STATE_PRESSED); } else if (wlr_keyboard_modifiers->depressed & WLR_MODIFIER_CTRL) { org_kde_kwin_keystate_send_stateChanged(resource, ORG_KDE_KWIN_KEYSTATE_KEY_CONTROL, ORG_KDE_KWIN_KEYSTATE_STATE_PRESSED); } else if (wlr_keyboard_modifiers->depressed & WLR_MODIFIER_SHIFT) { org_kde_kwin_keystate_send_stateChanged(resource, ORG_KDE_KWIN_KEYSTATE_KEY_SHIFT, ORG_KDE_KWIN_KEYSTATE_STATE_PRESSED); } else if (wlr_keyboard_modifiers->depressed & WLR_MODIFIER_LOGO) { org_kde_kwin_keystate_send_stateChanged(resource, ORG_KDE_KWIN_KEYSTATE_KEY_META, ORG_KDE_KWIN_KEYSTATE_STATE_PRESSED); } } static void kde_keystate_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct org_kde_kwin_keystate_interface kde_keystate_impl = { .fetchStates = kde_keystate_fetchstates, .destroy = kde_keystate_destroy, }; static void kde_keystate_unbind(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static void kde_keystate_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &org_kde_kwin_keystate_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &kde_keystate_impl, NULL, kde_keystate_unbind); wl_list_insert(&keystate_manager->resources, wl_resource_get_link(resource)); } static void keystate_keyboard_destroy(struct keystate_keyboard *keyboard) { wl_list_remove(&keyboard->destroy.link); wl_list_remove(&keyboard->modifiers.link); wl_list_remove(&keyboard->key.link); wl_list_remove(&keyboard->link); free(keyboard); } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&keystate_manager->display_destroy.link); wl_list_remove(&keystate_manager->new_seat.link); if (keystate_manager->global) { wl_global_destroy(keystate_manager->global); } struct keystate_keyboard *keyboard, *tmp; wl_list_for_each_safe(keyboard, tmp, &keystate_manager->keyboards, link) { keystate_keyboard_destroy(keyboard); } } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&keystate_manager->server_destroy.link); free(keystate_manager); } static void keyboard_lock_change(enum input_lock_key key) { uint32_t state = 0; REVBIT(keystate_manager->keyboard_lock, key); state = GETBIT(keystate_manager->keyboard_lock, key); if (state) { SETBIT(keystate_manager->release_record, key); state = ORG_KDE_KWIN_KEYSTATE_STATE_LOCKED; } struct wl_resource *resource; wl_resource_for_each(resource, &keystate_manager->resources) { org_kde_kwin_keystate_send_stateChanged(resource, key, state); } } static void keyboard_handle_lock(enum input_lock_key key, struct seat_keyboard_key_event *event) { if (GETBIT(keystate_manager->release_record, key)) { CLRBIT(keystate_manager->release_record, key); return; } if (GETBIT(keystate_manager->keyboard_lock, key) != event->pressed) { keyboard_lock_change(key); } } static void handle_key(struct wl_listener *listener, void *data) { struct keystate_keyboard *keyboard = wl_container_of(listener, keyboard, key); struct seat_keyboard_key_event *event = data; keyboard->keycode = event->pressed ? event->keycode : 0; switch (event->keycode) { case KEY_CAPSLOCK: keyboard_handle_lock(INPUT_KEY_CAPSLOCK, event); break; case KEY_NUMLOCK: keyboard_handle_lock(INPUT_KEY_NUMLOCK, event); break; case KEY_SCROLLLOCK: keyboard_handle_lock(INPUT_KEY_SCROLLLOCK, event); break; default: return; } } static void handle_modifiers(struct wl_listener *listener, void *data) { struct keystate_keyboard *keyboard = wl_container_of(listener, keyboard, modifiers); struct wlr_keyboard_modifiers *modifiers = data; uint32_t keycode = keyboard->keycode; uint32_t key = 0; switch (keycode) { case KEY_LEFTSHIFT: case KEY_RIGHTSHIFT: if (modifiers->depressed & WLR_MODIFIER_SHIFT) { key = ORG_KDE_KWIN_KEYSTATE_KEY_SHIFT; } break; case KEY_LEFTCTRL: case KEY_RIGHTCTRL: if (modifiers->depressed & WLR_MODIFIER_CTRL) { key = ORG_KDE_KWIN_KEYSTATE_KEY_CONTROL; } break; case KEY_LEFTMETA: case KEY_RIGHTMETA: if (modifiers->depressed & WLR_MODIFIER_LOGO) { key = ORG_KDE_KWIN_KEYSTATE_KEY_META; } break; case KEY_LEFTALT: if (modifiers->depressed & WLR_MODIFIER_ALT) { key = ORG_KDE_KWIN_KEYSTATE_KEY_ALT; } break; case KEY_RIGHTALT: if (modifiers->depressed & WLR_MODIFIER_ALT) { key = ORG_KDE_KWIN_KEYSTATE_KEY_ALTGR; } break; default: return; } if (!key) { return; } struct wl_resource *resource; wl_resource_for_each(resource, &keystate_manager->resources) { uint32_t version = wl_resource_get_version(resource); if (version >= ORG_KDE_KWIN_KEYSTATE_VERSION) { org_kde_kwin_keystate_send_stateChanged(resource, key, ORG_KDE_KWIN_KEYSTATE_STATE_PRESSED); } } } static void handle_destroy(struct wl_listener *listener, void *data) { struct keystate_keyboard *keyboard = wl_container_of(listener, keyboard, destroy); keystate_keyboard_destroy(keyboard); } static void handle_new_seat(struct wl_listener *listener, void *data) { struct seat *seat = data; struct keystate_keyboard *keystate_keyboard = calloc(1, sizeof(*keystate_keyboard)); if (!keystate_keyboard) { return; } wl_list_insert(&keystate_manager->keyboards, &keystate_keyboard->link); keystate_keyboard->key.notify = handle_key; wl_signal_add(&seat->events.keyboard_key, &keystate_keyboard->key); keystate_keyboard->modifiers.notify = handle_modifiers; wl_signal_add(&seat->events.keyboard_modifiers, &keystate_keyboard->modifiers); keystate_keyboard->destroy.notify = handle_destroy; wl_signal_add(&seat->events.destroy, &keystate_keyboard->destroy); } bool kde_keystate_manager_create(struct input_manager *input_manager) { keystate_manager = calloc(1, sizeof(*keystate_manager)); if (!keystate_manager) { return false; } wl_list_init(&keystate_manager->resources); wl_list_init(&keystate_manager->keyboards); keystate_manager->global = wl_global_create(input_manager->server->display, &org_kde_kwin_keystate_interface, ORG_KDE_KWIN_KEYSTATE_VERSION, keystate_manager, kde_keystate_bind); if (!keystate_manager->global) { kywc_log(KYWC_WARN, "Failed to create %s global", org_kde_kwin_keystate_interface.name); free(keystate_manager); return false; } keystate_manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(input_manager->server->display, &keystate_manager->display_destroy); keystate_manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(input_manager->server, &keystate_manager->server_destroy); keystate_manager->new_seat.notify = handle_new_seat; wl_signal_add(&input_manager->events.new_seat, &keystate_manager->new_seat); return true; } kylin-wayland-compositor/src/input/event.c0000664000175000017500000000445715160461067017712 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "input/event.h" static void input_event_node_destroy(struct wl_listener *listener, void *data) { struct input_event_node *inode = wl_container_of(listener, inode, node_destroy); wl_list_remove(&inode->node_destroy.link); free(inode); } struct input_event_node *input_event_node_create(struct ky_scene_node *node, const struct input_event_node_impl *impl, input_event_node_get_root get_root, input_event_node_get_toplevel get_toplevel, void *data) { struct input_event_node *inode = calloc(1, sizeof(*inode)); if (!inode) { return NULL; } inode->data = data; inode->impl = impl; inode->get_root = get_root; inode->get_toplevel = get_toplevel; node->data = inode; inode->node_destroy.notify = input_event_node_destroy; wl_signal_add(&node->events.destroy, &inode->node_destroy); return inode; } static const struct input_event_node_impl dumb_impl = { .click = NULL, .hover = NULL, .leave = NULL, }; static struct ky_scene_node *dumb_get_root(void *data) { return data; } struct input_event_node *input_event_dumb_node_create(struct ky_scene_node *node, struct ky_scene_node *root) { return input_event_node_create(node, &dumb_impl, dumb_get_root, NULL, root); } struct input_event_node *input_event_node_from_node(struct ky_scene_node *node) { struct ky_scene_node *n = node; struct ky_scene_tree *parent; while (n && !n->data) { parent = n->parent; n = parent ? &parent->node : NULL; } return n ? n->data : NULL; } struct ky_scene_node *input_event_node_root(struct input_event_node *event_node) { if (!event_node || !event_node->get_root) { return NULL; } return event_node->get_root(event_node->data); } struct wlr_surface *input_event_node_toplevel(struct input_event_node *event_node) { if (!event_node || !event_node->get_toplevel) { return NULL; } return event_node->get_toplevel(event_node->data); } kylin-wayland-compositor/src/input/tablet.c0000664000175000017500000005465115160461067020045 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include "backend/libinput.h" #include "input/seat.h" #include "input_p.h" #include "scene/surface.h" #include "server.h" #include "view/view.h" #include "xwayland.h" struct tablet_manager { struct wlr_tablet_manager_v2 *manager; struct wl_list tablets; struct wl_list tablet_pads; struct wl_listener new_input; struct wl_listener server_destroy; }; struct tablet { struct wlr_tablet *wlr_tablet; struct wlr_tablet_v2_tablet *tablet; struct wl_list link; struct input *input; struct wl_listener input_destroy; struct tablet_tool *tablet_tool; }; struct tablet_tool { struct wlr_tablet_v2_tablet_tool *tablet_tool; struct tablet *tablet; double tilt_x, tilt_y; struct wl_listener set_cursor; struct wl_listener tool_destroy; struct wlr_surface *current_surface; struct wl_listener surface_unmap; }; struct tablet_pad { struct wlr_tablet_pad *wlr_tablet_pad; struct wlr_tablet_v2_tablet_pad *tablet_pad; struct wl_list link; struct tablet *tablet; struct wl_listener tablet_destroy; struct input *input; struct wl_listener input_destroy; struct wl_listener attach; struct wl_listener button; struct wl_listener ring; struct wl_listener strip; struct wlr_surface *current_surface; struct wl_listener surface_destroy; }; static struct tablet_manager *manager = NULL; static struct tablet *tablet_from_wlr_tablet(struct wlr_tablet *wlr_tablet) { return wlr_tablet->data; } static struct tablet_tool *tablet_tool_from_wlr_tablet_tool(struct wlr_tablet_tool *wlr_tablet_tool) { return wlr_tablet_tool->data; } static void tablet_tool_handle_set_cursor(struct wl_listener *listener, void *data) { struct tablet_tool *tablet_tool = wl_container_of(listener, tablet_tool, set_cursor); struct wlr_tablet_v2_event_cursor *event = data; struct cursor *cursor = tablet_tool->tablet->input->seat->cursor; if (cursor->seat->pointer_grab || cursor->hidden) { return; } struct wl_client *focused_client = NULL; struct wlr_surface *focused_surface = tablet_tool->tablet_tool->focused_surface; if (focused_surface != NULL) { focused_client = wl_resource_get_client(focused_surface->resource); } if (focused_client == NULL || event->seat_client->client != focused_client) { kywc_log(KYWC_DEBUG, "Denying request to set cursor from unfocused client"); return; } cursor_set_surface(cursor, event->surface, event->hotspot_x, event->hotspot_y, event->seat_client->client); } static void tablet_tool_handle_tool_destroy(struct wl_listener *listener, void *data) { struct tablet_tool *tablet_tool = wl_container_of(listener, tablet_tool, tool_destroy); wl_list_remove(&tablet_tool->tool_destroy.link); wl_list_remove(&tablet_tool->set_cursor.link); wl_list_remove(&tablet_tool->surface_unmap.link); tablet_tool->tablet->tablet_tool = NULL; free(tablet_tool); } static void tablet_tool_handle_surface_unmap(struct wl_listener *listener, void *data) { struct tablet_tool *tablet_tool = wl_container_of(listener, tablet_tool, surface_unmap); wl_list_remove(&tablet_tool->surface_unmap.link); wl_list_init(&tablet_tool->surface_unmap.link); tablet_tool->current_surface = NULL; } static struct tablet_tool *tablet_tool_create(struct tablet *tablet, struct wlr_tablet_tool *wlr_tablet_tool) { struct tablet_tool *tablet_tool = calloc(1, sizeof(*tablet_tool)); if (!tablet_tool) { return NULL; } tablet_tool->tablet = tablet; wlr_tablet_tool->data = tablet_tool; tablet->tablet_tool = tablet_tool; wl_list_init(&tablet_tool->surface_unmap.link); tablet_tool->surface_unmap.notify = tablet_tool_handle_surface_unmap; tablet_tool->tablet_tool = wlr_tablet_tool_create(manager->manager, tablet->input->seat->wlr_seat, wlr_tablet_tool); tablet_tool->set_cursor.notify = tablet_tool_handle_set_cursor; wl_signal_add(&tablet_tool->tablet_tool->events.set_cursor, &tablet_tool->set_cursor); tablet_tool->tool_destroy.notify = tablet_tool_handle_tool_destroy; wl_signal_add(&wlr_tablet_tool->events.destroy, &tablet_tool->tool_destroy); return tablet_tool; } static void tablet_pad_handle_tablet_destroy(struct wl_listener *listener, void *data) { struct tablet_pad *tablet_pad = wl_container_of(listener, tablet_pad, tablet_destroy); tablet_pad->tablet = NULL; wl_list_remove(&tablet_pad->tablet_destroy.link); wl_list_init(&tablet_pad->tablet_destroy.link); } static void attach_tablet_pad(struct tablet_pad *tablet_pad, struct tablet *tablet) { kywc_log(KYWC_DEBUG, "Attaching tablet pad \"%s\" to tablet tool \"%s\"", tablet_pad->input->wlr_input->name, tablet->input->wlr_input->name); tablet_pad->tablet = tablet; wl_list_remove(&tablet_pad->tablet_destroy.link); tablet_pad->tablet_destroy.notify = tablet_pad_handle_tablet_destroy; wl_signal_add(&tablet->input->wlr_input->events.destroy, &tablet_pad->tablet_destroy); } static void tablet_handle_input_destroy(struct wl_listener *listener, void *data) { struct tablet *tablet = wl_container_of(listener, tablet, input_destroy); wl_list_remove(&tablet->input_destroy.link); wl_list_remove(&tablet->link); free(tablet); } static void tablet_create(struct tablet_manager *manager, struct input *input) { struct tablet *tablet = calloc(1, sizeof(*tablet)); if (!tablet) { return; } tablet->input = input; tablet->input_destroy.notify = tablet_handle_input_destroy; wl_signal_add(&input->events.destroy, &tablet->input_destroy); wl_list_insert(&manager->tablets, &tablet->link); tablet->wlr_tablet = wlr_tablet_from_input_device(input->wlr_input); tablet->wlr_tablet->data = tablet; tablet->wlr_tablet->usb_vendor_id = input->prop.vendor; tablet->wlr_tablet->usb_product_id = input->prop.product; tablet->tablet = wlr_tablet_create(manager->manager, input->seat->wlr_seat, input->wlr_input); if (!input->device) { return; } struct libinput_device_group *group = libinput_device_get_device_group(wlr_libinput_get_device_handle(input->wlr_input)); struct tablet_pad *tablet_pad; wl_list_for_each(tablet_pad, &manager->tablet_pads, link) { if (!tablet_pad->input->device) { continue; } struct libinput_device_group *tablet_pad_group = libinput_device_get_device_group( wlr_libinput_get_device_handle(tablet_pad->input->wlr_input)); if (tablet_pad_group == group) { attach_tablet_pad(tablet_pad, tablet); break; } } } static void tablet_pad_handle_input_destroy(struct wl_listener *listener, void *data) { struct tablet_pad *tablet_pad = wl_container_of(listener, tablet_pad, input_destroy); wl_list_remove(&tablet_pad->input_destroy.link); wl_list_remove(&tablet_pad->link); wl_list_remove(&tablet_pad->attach.link); wl_list_remove(&tablet_pad->button.link); wl_list_remove(&tablet_pad->strip.link); wl_list_remove(&tablet_pad->ring.link); wl_list_remove(&tablet_pad->tablet_destroy.link); wl_list_remove(&tablet_pad->surface_destroy.link); free(tablet_pad); } static void tablet_pad_handle_attach(struct wl_listener *listener, void *data) { struct tablet_pad *tablet_pad = wl_container_of(listener, tablet_pad, attach); struct wlr_tablet_tool *wlr_tablet_tool = data; struct tablet_tool *tablet_tool = tablet_tool_from_wlr_tablet_tool(wlr_tablet_tool); if (!tablet_tool) { return; } attach_tablet_pad(tablet_pad, tablet_tool->tablet); } static void tablet_pad_handle_button(struct wl_listener *listener, void *data) { struct tablet_pad *tablet_pad = wl_container_of(listener, tablet_pad, button); struct wlr_tablet_pad_button_event *event = data; if (input_event_filter(tablet_pad->input->seat, tablet_pad->input, INPUT_EVENT_TABLET_PAD_BUTTON, event)) { return; } if (!tablet_pad->current_surface) { return; } wlr_tablet_v2_tablet_pad_notify_mode(tablet_pad->tablet_pad, event->group, event->mode, event->time_msec); wlr_tablet_v2_tablet_pad_notify_button(tablet_pad->tablet_pad, event->button, event->time_msec, (enum zwp_tablet_pad_v2_button_state)event->state); } static void tablet_pad_handle_strip(struct wl_listener *listener, void *data) { struct tablet_pad *tablet_pad = wl_container_of(listener, tablet_pad, strip); struct wlr_tablet_pad_strip_event *event = data; if (input_event_filter(tablet_pad->input->seat, tablet_pad->input, INPUT_EVENT_TABLET_PAD_STRIP, event)) { return; } if (!tablet_pad->current_surface) { return; } wlr_tablet_v2_tablet_pad_notify_strip(tablet_pad->tablet_pad, event->strip, event->position, event->source == WLR_TABLET_PAD_STRIP_SOURCE_FINGER, event->time_msec); } static void tablet_pad_handle_ring(struct wl_listener *listener, void *data) { struct tablet_pad *tablet_pad = wl_container_of(listener, tablet_pad, ring); struct wlr_tablet_pad_ring_event *event = data; if (input_event_filter(tablet_pad->input->seat, tablet_pad->input, INPUT_EVENT_TABLET_PAD_RING, event)) { return; } if (!tablet_pad->current_surface) { return; } wlr_tablet_v2_tablet_pad_notify_ring(tablet_pad->tablet_pad, event->ring, event->position, event->source == WLR_TABLET_PAD_RING_SOURCE_FINGER, event->time_msec); } static void tablet_pad_create(struct tablet_manager *manager, struct input *input) { struct tablet_pad *tablet_pad = calloc(1, sizeof(*tablet_pad)); if (!tablet_pad) { return; } tablet_pad->input = input; tablet_pad->input_destroy.notify = tablet_pad_handle_input_destroy; wl_signal_add(&input->events.destroy, &tablet_pad->input_destroy); wl_list_insert(&manager->tablet_pads, &tablet_pad->link); tablet_pad->wlr_tablet_pad = wlr_tablet_pad_from_input_device(input->wlr_input); tablet_pad->wlr_tablet_pad->data = tablet_pad; tablet_pad->tablet_pad = wlr_tablet_pad_create(manager->manager, input->seat->wlr_seat, input->wlr_input); tablet_pad->attach.notify = tablet_pad_handle_attach; wl_signal_add(&tablet_pad->wlr_tablet_pad->events.attach_tablet, &tablet_pad->attach); tablet_pad->button.notify = tablet_pad_handle_button; wl_signal_add(&tablet_pad->wlr_tablet_pad->events.button, &tablet_pad->button); tablet_pad->strip.notify = tablet_pad_handle_strip; wl_signal_add(&tablet_pad->wlr_tablet_pad->events.strip, &tablet_pad->strip); tablet_pad->ring.notify = tablet_pad_handle_ring; wl_signal_add(&tablet_pad->wlr_tablet_pad->events.ring, &tablet_pad->ring); wl_list_init(&tablet_pad->tablet_destroy.link); wl_list_init(&tablet_pad->surface_destroy.link); if (!input->device) { return; } struct libinput_device_group *group = libinput_device_get_device_group(wlr_libinput_get_device_handle(input->wlr_input)); struct tablet *tablet; wl_list_for_each(tablet, &manager->tablets, link) { if (!tablet->input->device) { continue; } struct libinput_device_group *tablet_group = libinput_device_get_device_group( wlr_libinput_get_device_handle(tablet->input->wlr_input)); if (tablet_group == group) { attach_tablet_pad(tablet_pad, tablet); break; } } } static void handle_new_input(struct wl_listener *listener, void *data) { struct input *input = data; /* input has been configured, only care about tablet_tool and tablet_pad */ if (input->prop.type != WLR_INPUT_DEVICE_TABLET && input->prop.type != WLR_INPUT_DEVICE_TABLET_PAD) { return; } if (input->prop.type == WLR_INPUT_DEVICE_TABLET) { tablet_create(manager, input); } else { tablet_pad_create(manager, input); } } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->server_destroy.link); wl_list_remove(&manager->new_input.link); free(manager); manager = NULL; } bool tablet_manager_create(struct input_manager *input_manager) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->manager = wlr_tablet_v2_create(input_manager->server->display); if (!manager->manager) { kywc_log(KYWC_WARN, "Failed to create table manager"); free(manager); manager = NULL; return false; } wl_list_init(&manager->tablets); wl_list_init(&manager->tablet_pads); manager->new_input.notify = handle_new_input; input_add_new_listener(&manager->new_input); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(input_manager->server, &manager->server_destroy); return true; } static struct wlr_surface *tablet_get_surface(struct tablet *tablet, double *sx, double *sy, struct wlr_surface **toplevel) { struct seat *seat = tablet->input->seat; struct cursor *cursor = seat->cursor; struct ky_scene_node *node = ky_scene_node_at(&seat->scene->tree.node, cursor->lx, cursor->ly, sx, sy); if (!node) { return NULL; } if (toplevel) { *toplevel = input_event_node_toplevel(input_event_node_from_node(node)); } return wlr_surface_try_from_node(node); } static bool tablet_handle_tool_position(struct tablet_tool *tablet_tool) { struct cursor *cursor = tablet_tool->tablet->input->seat->cursor; struct wlr_surface *current_surface = tablet_tool->current_surface; double sx = 0, sy = 0; struct wlr_surface *surface = tablet_get_surface(tablet_tool->tablet, &sx, &sy, NULL); if (current_surface && surface != current_surface) { int lx, ly; struct ky_scene_buffer *scene_buffer = ky_scene_buffer_try_from_surface(current_surface); ky_scene_node_coords(&scene_buffer->node, &lx, &ly); sx = cursor->lx - lx; sy = cursor->ly - ly; } else if (surface && wlr_surface_accepts_tablet_v2(surface, tablet_tool->tablet->tablet)) { wlr_tablet_v2_tablet_tool_notify_proximity_in(tablet_tool->tablet_tool, tablet_tool->tablet->tablet, surface); } else { wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool->tablet_tool); cursor_set_image(cursor, CURSOR_DEFAULT); return false; } wlr_tablet_v2_tablet_tool_notify_motion(tablet_tool->tablet_tool, sx, sy); selection_handle_cursor_move(cursor->seat, cursor->lx, cursor->ly); return true; } bool tablet_handle_tool_proximity(struct wlr_tablet_tool_proximity_event *event) { struct tablet *tablet = tablet_from_wlr_tablet(event->tablet); if (!tablet) { return false; } /* create tablet_tool when proximity */ struct tablet_tool *tablet_tool = tablet_tool_from_wlr_tablet_tool(event->tool); if (!tablet_tool) { tablet_tool = tablet_tool_create(tablet, event->tool); } if (!tablet_tool) { return false; } if (event->state == WLR_TABLET_TOOL_PROXIMITY_OUT) { wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool->tablet_tool); return true; } struct cursor *cursor = tablet_tool->tablet->input->seat->cursor; cursor_move(cursor, &event->tablet->base, event->x, event->y, false, true); return tablet_handle_tool_position(tablet_tool); } void tablet_handle_tool_axis(struct wlr_tablet_tool_axis_event *event) { struct tablet_tool *tablet_tool = tablet_tool_from_wlr_tablet_tool(event->tool); if (!tablet_tool) { return; } bool change_x = event->updated_axes & WLR_TABLET_TOOL_AXIS_X; bool change_y = event->updated_axes & WLR_TABLET_TOOL_AXIS_Y; if (change_x || change_y) { struct cursor *cursor = tablet_tool->tablet->input->seat->cursor; switch (event->tool->type) { case WLR_TABLET_TOOL_TYPE_LENS: case WLR_TABLET_TOOL_TYPE_MOUSE: cursor_move(cursor, &event->tablet->base, event->dx, event->dy, true, false); break; default: cursor_move(cursor, &event->tablet->base, change_x ? event->x : NAN, change_y ? event->y : NAN, false, true); break; } // cursor_feed_motion(cursor, event->time_msec, &event->tablet->base, 0, 0, 0, 0); // wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); tablet_handle_tool_position(tablet_tool); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) { wlr_tablet_v2_tablet_tool_notify_pressure(tablet_tool->tablet_tool, event->pressure); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) { wlr_tablet_v2_tablet_tool_notify_distance(tablet_tool->tablet_tool, event->distance); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X) { tablet_tool->tilt_x = event->tilt_x; } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y) { tablet_tool->tilt_y = event->tilt_y; } if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) { wlr_tablet_v2_tablet_tool_notify_tilt(tablet_tool->tablet_tool, tablet_tool->tilt_x, tablet_tool->tilt_y); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) { wlr_tablet_v2_tablet_tool_notify_rotation(tablet_tool->tablet_tool, event->rotation); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) { wlr_tablet_v2_tablet_tool_notify_slider(tablet_tool->tablet_tool, event->slider); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) { wlr_tablet_v2_tablet_tool_notify_wheel(tablet_tool->tablet_tool, event->wheel_delta, 0); } } bool tablet_handle_tool_tip(struct wlr_tablet_tool_tip_event *event) { struct tablet_tool *tablet_tool = tablet_tool_from_wlr_tablet_tool(event->tool); if (!tablet_tool) { return false; } struct wlr_surface *toplevel = NULL; struct wlr_surface *surface = tablet_get_surface(tablet_tool->tablet, NULL, NULL, &toplevel); wl_list_remove(&tablet_tool->surface_unmap.link); wl_list_init(&tablet_tool->surface_unmap.link); tablet_tool->current_surface = NULL; if (!surface || !wlr_surface_accepts_tablet_v2(surface, tablet_tool->tablet->tablet)) { if (event->state == WLR_TABLET_TOOL_TIP_UP) { wlr_tablet_v2_tablet_tool_notify_up(tablet_tool->tablet_tool); } return false; } if (event->state == WLR_TABLET_TOOL_TIP_UP) { wlr_tablet_v2_tablet_tool_notify_up(tablet_tool->tablet_tool); return true; } tablet_tool->current_surface = surface; wl_signal_add(&surface->events.unmap, &tablet_tool->surface_unmap); wlr_tablet_v2_tablet_tool_notify_down(tablet_tool->tablet_tool); wlr_tablet_tool_v2_start_implicit_grab(tablet_tool->tablet_tool); /* activate and focus the toplevel surface */ if (toplevel) { struct view *view = view_try_from_wlr_surface(toplevel); if (view) { kywc_view_activate(&view->base); view_set_focus(view, tablet_tool->tablet->input->seat); } else { seat_focus_surface(tablet_tool->tablet->input->seat, toplevel); } } return true; } bool tablet_handle_tool_button(struct wlr_tablet_tool_button_event *event) { struct tablet_tool *tablet_tool = tablet_tool_from_wlr_tablet_tool(event->tool); if (!tablet_tool) { return false; } struct wlr_surface *surface = tablet_get_surface(tablet_tool->tablet, NULL, NULL, NULL); if (!surface || !wlr_surface_accepts_tablet_v2(surface, tablet_tool->tablet->tablet)) { return false; } wlr_tablet_v2_tablet_tool_notify_button(tablet_tool->tablet_tool, event->button, (enum zwp_tablet_pad_v2_button_state)event->state); return true; } static void tablet_pad_set_focus(struct tablet_pad *tablet_pad, struct wlr_surface *surface); static void tablet_pad_handle_surface_destroy(struct wl_listener *listener, void *data) { struct tablet_pad *tablet_pad = wl_container_of(listener, tablet_pad, surface_destroy); tablet_pad_set_focus(tablet_pad, NULL); } static void tablet_pad_set_focus(struct tablet_pad *tablet_pad, struct wlr_surface *surface) { if (!tablet_pad || !tablet_pad->tablet) { return; } if (surface == tablet_pad->current_surface) { return; } /* Leave current surface */ if (tablet_pad->current_surface) { wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad->tablet_pad, tablet_pad->current_surface); wl_list_remove(&tablet_pad->surface_destroy.link); wl_list_init(&tablet_pad->surface_destroy.link); tablet_pad->current_surface = NULL; } if (surface == NULL || !wlr_surface_accepts_tablet_v2(surface, tablet_pad->tablet->tablet)) { return; } wlr_tablet_v2_tablet_pad_notify_enter(tablet_pad->tablet_pad, tablet_pad->tablet->tablet, surface); tablet_pad->current_surface = surface; tablet_pad->surface_destroy.notify = tablet_pad_handle_surface_destroy; wl_signal_add(&surface->events.destroy, &tablet_pad->surface_destroy); } void tablet_set_focus(struct seat *seat, struct wlr_surface *surface) { if (!manager) { return; } struct tablet_pad *tablet_pad; wl_list_for_each(tablet_pad, &manager->tablet_pads, link) { tablet_pad_set_focus(tablet_pad, surface); } } bool tablet_has_implicit_grab(struct seat *seat) { struct tablet *tablet; wl_list_for_each(tablet, &manager->tablets, link) { if (tablet->input->seat == seat && tablet->tablet_tool && wlr_tablet_tool_v2_has_implicit_grab(tablet->tablet_tool->tablet_tool)) { return true; } } return false; } kylin-wayland-compositor/src/input/monitor.c0000664000175000017500000001540115160461067020247 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "input_p.h" #include "output.h" #include "server.h" struct cursor_output { struct wl_list link; struct input_monitor *monitor; struct kywc_output *output; struct wl_listener on; struct wl_listener destroy; }; struct input_monitor { struct input_manager *input_manager; struct wl_list outputs; struct wl_listener new_output; struct wl_listener configured; struct wl_listener layout_damage; struct wl_listener new_seat; struct wl_listener server_destroy; }; static void seat_rebase_cursor(struct seat *seat, bool initial) { // lx/ly might change when pick output double lx = seat->cursor->lx; double ly = seat->cursor->ly; struct output *output = input_current_output(seat); if (output && !output->base.destroying && (!seat->manager->server->start || initial || !kywc_output_contains_point(&output->base, lx, ly))) { struct kywc_box geo = output->geometry; geo.x += geo.width / 2; geo.y += geo.height / 2; cursor_move(seat->cursor, NULL, geo.x, geo.y, false, false); } cursor_rebase(seat->cursor); } static void handle_output_destroy(struct wl_listener *listener, void *data) { struct cursor_output *cursor_output = wl_container_of(listener, cursor_output, destroy); wl_list_remove(&cursor_output->link); wl_list_remove(&cursor_output->on.link); wl_list_remove(&cursor_output->destroy.link); free(cursor_output); } static void handle_output_on(struct wl_listener *listener, void *data) { struct cursor_output *cursor_output = wl_container_of(listener, cursor_output, on); struct kywc_output *kywc_output = cursor_output->output; if (kywc_output != kywc_output_get_primary()) { return; } struct input *input; struct input_manager *input_manager = cursor_output->monitor->input_manager; wl_list_for_each(input, &input_manager->inputs, link) { if (input->prop.type != WLR_INPUT_DEVICE_TOUCH || input->mapped_output) { continue; } // mapped to primary output struct input_state state = input->state; state.mapped_to_output = kywc_output->name; input_set_state(input, &state); } } static void handle_new_output(struct wl_listener *listener, void *data) { struct cursor_output *cursor_output = calloc(1, sizeof(*cursor_output)); if (!cursor_output) { return; } struct input_monitor *input_monitor = wl_container_of(listener, input_monitor, new_output); struct kywc_output *kywc_output = data; cursor_output->monitor = input_monitor; cursor_output->output = kywc_output; wl_list_insert(&input_monitor->outputs, &cursor_output->link); cursor_output->on.notify = handle_output_on; wl_signal_add(&kywc_output->events.on, &cursor_output->on); cursor_output->destroy.notify = handle_output_destroy; wl_signal_add(&kywc_output->events.destroy, &cursor_output->destroy); if (kywc_output->state.enabled) { handle_output_on(&cursor_output->on, NULL); } } static void handle_configured(struct wl_listener *listener, void *data) { struct configure_event *event = data; if (event->type == CONFIGURE_TYPE_NONE) { return; } struct input_monitor *input_monitor = wl_container_of(listener, input_monitor, configured); struct seat *seat; wl_list_for_each(seat, &input_monitor->input_manager->seats, link) { seat_rebase_cursor(seat, event->type == CONFIGURE_TYPE_INIT); } } static void handle_layout_damage(struct wl_listener *listener, void *data) { struct input_monitor *input_monitor = wl_container_of(listener, input_monitor, layout_damage); pixman_region32_t *damage_region = data; struct seat *seat; wl_list_for_each(seat, &input_monitor->input_manager->seats, link) { /* skip motion when has grab */ if (seat->pointer_grab && seat->pointer_grab->interface->motion) { continue; } if (seat_is_dragging(seat) || seat->cursor->hold_mode) { continue; } double lx = seat->cursor->lx; double ly = seat->cursor->ly; if (!pixman_region32_contains_point(damage_region, lx, ly, NULL)) { continue; } // check node in cursor position struct ky_scene_node *node = ky_scene_node_at(&seat->scene->tree.node, lx, ly, NULL, NULL); if (node != seat->cursor->hover.node) { cursor_rebase(seat->cursor); } } } static void handle_seat_idle(struct idle *idle, void *data) {}; static void handle_seat_resume(struct idle *idle, void *data) { output_manager_power_outputs(true); } static void handle_new_seat(struct wl_listener *listener, void *data) { struct input_monitor *input_monitor = wl_container_of(listener, input_monitor, new_seat); struct seat *seat = data; seat_rebase_cursor(seat, true); idle_manager_add_idle(seat, false, 0, handle_seat_idle, handle_seat_resume, NULL, NULL); } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct input_monitor *input_monitor = wl_container_of(listener, input_monitor, server_destroy); wl_list_remove(&input_monitor->server_destroy.link); wl_list_remove(&input_monitor->new_seat.link); wl_list_remove(&input_monitor->new_output.link); wl_list_remove(&input_monitor->configured.link); wl_list_remove(&input_monitor->layout_damage.link); struct cursor_output *cursor_output, *tmp; wl_list_for_each_safe(cursor_output, tmp, &input_monitor->outputs, link) { handle_output_destroy(&cursor_output->destroy, NULL); } free(input_monitor); } struct input_monitor *input_monitor_create(struct input_manager *input_manager) { struct input_monitor *input_monitor = calloc(1, sizeof(*input_monitor)); if (!input_monitor) { return NULL; } wl_list_init(&input_monitor->outputs); input_monitor->input_manager = input_manager; input_monitor->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(input_manager->server, &input_monitor->server_destroy); input_monitor->new_output.notify = handle_new_output; kywc_output_add_new_listener(&input_monitor->new_output); input_monitor->configured.notify = handle_configured; output_manager_add_configured_listener(&input_monitor->configured); input_monitor->layout_damage.notify = handle_layout_damage; // output_manager_add_layout_damage_listener(&input_monitor->layout_damage); wl_list_init(&input_monitor->layout_damage.link); input_monitor->new_seat.notify = handle_new_seat; wl_signal_add(&input_manager->events.new_seat, &input_monitor->new_seat); return input_monitor; } kylin-wayland-compositor/src/input/text_input.c0000664000175000017500000011317715160461067020774 0ustar fengfeng// SPDX-FileCopyrightText: 2016-2017 Drew DeVault // SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include "input/event.h" #include "input_p.h" #include "output.h" #include "scene/surface.h" #include "server.h" #include "text_input_v1.h" #include "text_input_v2.h" #include "view/view.h" /* most codes are copied from sway input/text_input.c */ struct input_method_manager { struct wlr_input_method_manager_v2 *input_method; struct text_input_manager_v1 *text_input_v1; struct wl_listener new_text_input_v1; struct wl_listener text_input_v1_destroy; struct text_input_manager_v2 *text_input_v2; struct wlr_text_input_manager_v3 *text_input_v3; struct wl_listener new_seat; struct wl_listener server_destroy; }; /* input method per seat */ struct input_method_relay { struct seat *seat; struct wl_listener seat_destroy; struct wl_list text_inputs; struct wl_listener new_text_input_v2; struct wl_listener text_input_v2_destroy; struct wl_listener new_text_input_v3; struct wl_listener text_input_v3_destroy; struct wlr_input_method_v2 *wlr_input_method; struct wl_listener input_method_v2_destroy; struct wl_listener new_input_method; struct wl_listener input_method_commit; struct wl_listener input_method_grab_keyboard; struct wl_listener input_method_keyboard_grab_destroy; struct wl_listener input_method_destroy; struct wl_list input_popups; struct wl_listener new_popup_surface; struct text_input *pending_enabled_text_input; }; struct text_input { struct text_input_v1 *text_input_v1; struct text_input_v2 *text_input_v2; struct wlr_text_input_v3 *text_input_v3; struct input_method_relay *relay; struct wl_list link; struct wl_listener text_input_enable; struct wl_listener text_input_commit; struct wl_listener text_input_disable; struct wl_listener text_input_destroy; struct wlr_surface *pending_focused_surface; struct wl_listener pending_focused_surface_destroy; }; struct input_popup { struct wlr_input_popup_surface_v2 *popup_surface; struct input_method_relay *relay; struct wl_list link; struct ky_scene_node *surface_node; /* not map/unmap handle in scene surface */ struct wl_listener surface_map; struct wl_listener surface_commit; struct wl_listener surface_unmap; struct wl_listener popup_surface_destroy; }; static struct wlr_surface *text_input_focused_surface(struct text_input *text_input) { if (text_input->text_input_v1) { return text_input->text_input_v1->surface; } else if (text_input->text_input_v2) { return text_input->text_input_v2->surface; } else { return text_input->text_input_v3->focused_surface; } } static void text_input_send_leave(struct text_input *text_input) { if (text_input->text_input_v3) { wlr_text_input_v3_send_leave(text_input->text_input_v3); } else if (text_input->text_input_v2) { text_input_v2_send_leave(text_input->text_input_v2); } else { text_input_v1_send_leave(text_input->text_input_v1); } } static void text_input_send_enter(struct text_input *text_input, struct wlr_surface *surface) { if (text_input->text_input_v3) { wlr_text_input_v3_send_enter(text_input->text_input_v3, surface); } else if (text_input->text_input_v2) { text_input_v2_send_enter(text_input->text_input_v2, surface); } else { text_input_v1_send_enter(text_input->text_input_v1, surface); } } static struct text_input *relay_get_focused_text_input(struct input_method_relay *relay) { struct text_input *text_input = NULL; wl_list_for_each(text_input, &relay->text_inputs, link) { if (text_input_focused_surface(text_input)) { return text_input; } } return NULL; } static void input_popup_update(struct input_popup *popup, struct seat *seat) { if (!popup->popup_surface->surface->mapped) { return; } struct text_input *text_input = relay_get_focused_text_input(popup->relay); if (!text_input) { return; } struct wlr_surface *focused_surface = text_input_focused_surface(text_input); /* workaround: view_destroy is emitted before surface_destroy */ if (!focused_surface || !focused_surface->mapped) { return; } struct ky_scene_buffer *scene_buffer = ky_scene_buffer_try_from_surface(focused_surface); assert(scene_buffer); /* surface primary output */ if (!scene_buffer->primary_output) { return; } struct kywc_box *output_box = &output_from_wlr_output(scene_buffer->primary_output->output)->geometry; /* focused surface geometry in layout coord */ struct kywc_box parent_box; ky_scene_node_coords(&scene_buffer->node, &parent_box.x, &parent_box.y); parent_box.width = focused_surface->current.width; parent_box.height = focused_surface->current.height; struct wlr_box cursor_box = { 0 }; if (text_input->text_input_v3) { cursor_box = text_input->text_input_v3->current.cursor_rectangle; } else if (text_input->text_input_v2) { cursor_box = text_input->text_input_v2->cursor_rectangle; } else { cursor_box = text_input->text_input_v1->cursor_rectangle; } int surface_width = popup->popup_surface->surface->current.width; int surface_height = popup->popup_surface->surface->current.height; /* constraints for output */ int dx = cursor_box.x + parent_box.x + surface_width - output_box->x - output_box->width; int dy = cursor_box.y + cursor_box.height + parent_box.y + surface_height - output_box->y - output_box->height; if (dx > 0) { cursor_box.x -= dx; } if (dy > 0) { cursor_box.y -= cursor_box.height + surface_height; } wlr_input_popup_surface_v2_send_text_input_rectangle(popup->popup_surface, &cursor_box); int x = cursor_box.x + parent_box.x; int y = cursor_box.y + cursor_box.height + parent_box.y; ky_scene_node_set_position(popup->surface_node, x, y); } static void relay_send_input_method_state(struct input_method_relay *relay, struct text_input *text_input) { struct wlr_input_method_v2 *input_method = relay->wlr_input_method; // TODO: only send each of those if they were modified if (text_input->text_input_v3) { if (text_input->text_input_v3->active_features & WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT) { wlr_input_method_v2_send_surrounding_text( input_method, text_input->text_input_v3->current.surrounding.text, text_input->text_input_v3->current.surrounding.cursor, text_input->text_input_v3->current.surrounding.anchor); } wlr_input_method_v2_send_text_change_cause( input_method, text_input->text_input_v3->current.text_change_cause); if (text_input->text_input_v3->active_features & WLR_TEXT_INPUT_V3_FEATURE_CONTENT_TYPE) { wlr_input_method_v2_send_content_type( input_method, text_input->text_input_v3->current.content_type.hint, text_input->text_input_v3->current.content_type.purpose); } } else if (text_input->text_input_v2) { if (text_input->text_input_v2->surrounding.pending) { wlr_input_method_v2_send_surrounding_text( input_method, text_input->text_input_v2->surrounding.text, text_input->text_input_v2->surrounding.cursor, text_input->text_input_v2->surrounding.anchor); } wlr_input_method_v2_send_text_change_cause(input_method, 0); if (text_input->text_input_v2->content_type.pending) { wlr_input_method_v2_send_content_type(input_method, text_input->text_input_v2->content_type.hint, text_input->text_input_v2->content_type.purpose); } } else { if (text_input->text_input_v1->surrounding.pending) { wlr_input_method_v2_send_surrounding_text( input_method, text_input->text_input_v1->surrounding.text, text_input->text_input_v1->surrounding.cursor, text_input->text_input_v1->surrounding.anchor); } wlr_input_method_v2_send_text_change_cause(input_method, 0); if (text_input->text_input_v1->content_type.pending) { wlr_input_method_v2_send_content_type(input_method, text_input->text_input_v1->content_type.hint, text_input->text_input_v1->content_type.purpose); } } struct input_popup *popup; wl_list_for_each(popup, &relay->input_popups, link) { input_popup_update(popup, relay->seat); } wlr_input_method_v2_send_done(input_method); } static void handle_text_input_enable(struct wl_listener *listener, void *data) { struct text_input *text_input = wl_container_of(listener, text_input, text_input_enable); #if 0 if (text_input->text_input_v1) { assert(!text_input->relay); assert(text_input->text_input_v1->seat); text_input->relay = seat_from_wlr_seat(text_input->text_input_v1->seat)->relay; wl_list_insert(&text_input->relay->text_inputs, &text_input->link); } #endif if (text_input->relay->wlr_input_method == NULL) { text_input->relay->pending_enabled_text_input = text_input; kywc_log(KYWC_INFO, "Enabling text input when input method is gone"); return; } wlr_input_method_v2_send_activate(text_input->relay->wlr_input_method); relay_send_input_method_state(text_input->relay, text_input); } static bool text_input_is_enabeld(struct text_input *text_input) { if (text_input->text_input_v3) { return text_input->text_input_v3->current_enabled; } else if (text_input->text_input_v2) { return text_input->text_input_v2->enabled; } else { return text_input->text_input_v1->activated; } } static void handle_text_input_commit(struct wl_listener *listener, void *data) { struct text_input *text_input = wl_container_of(listener, text_input, text_input_commit); if (!text_input_is_enabeld(text_input)) { kywc_log(KYWC_DEBUG, "Inactive text input tried to commit an update"); return; } // kywc_log(KYWC_DEBUG, "Text input committed update"); if (text_input->relay->wlr_input_method == NULL) { kywc_log(KYWC_INFO, "Text input committed, but input method is gone"); return; } relay_send_input_method_state(text_input->relay, text_input); } static void relay_disable_text_input(struct input_method_relay *relay, struct text_input *text_input) { if (relay->wlr_input_method == NULL) { kywc_log(KYWC_DEBUG, "Disabling text input, but input method is gone"); return; } wlr_input_method_v2_send_deactivate(relay->wlr_input_method); relay_send_input_method_state(relay, text_input); #if 0 /* clear seat relay and updated when enable */ if (text_input->text_input_v1) { wl_list_remove(&text_input->link); wl_list_init(&text_input->link); text_input->relay = NULL; } #endif } static void handle_text_input_disable(struct wl_listener *listener, void *data) { struct text_input *text_input = wl_container_of(listener, text_input, text_input_disable); if (text_input->relay->pending_enabled_text_input == text_input) { text_input->relay->pending_enabled_text_input = NULL; } if (!text_input_focused_surface(text_input)) { kywc_log(KYWC_DEBUG, "Disabling text input, but no longer focused"); return; } relay_disable_text_input(text_input->relay, text_input); } static void text_input_set_pending_focused_surface(struct text_input *text_input, struct wlr_surface *surface) { wl_list_remove(&text_input->pending_focused_surface_destroy.link); text_input->pending_focused_surface = surface; if (surface) { wl_signal_add(&surface->events.destroy, &text_input->pending_focused_surface_destroy); } else { wl_list_init(&text_input->pending_focused_surface_destroy.link); } } static void handle_text_input_destroy(struct wl_listener *listener, void *data) { struct text_input *text_input = wl_container_of(listener, text_input, text_input_destroy); if (text_input_is_enabeld(text_input)) { relay_disable_text_input(text_input->relay, text_input); } text_input_set_pending_focused_surface(text_input, NULL); if (text_input->relay->pending_enabled_text_input == text_input) { text_input->relay->pending_enabled_text_input = NULL; } wl_list_remove(&text_input->link); wl_list_remove(&text_input->text_input_commit.link); wl_list_remove(&text_input->text_input_destroy.link); wl_list_remove(&text_input->text_input_disable.link); wl_list_remove(&text_input->text_input_enable.link); free(text_input); } static void handle_pending_focused_surface_destroy(struct wl_listener *listener, void *data) { struct text_input *text_input = wl_container_of(listener, text_input, pending_focused_surface_destroy); struct wlr_surface *surface = data; assert(text_input->pending_focused_surface == surface); text_input->pending_focused_surface = NULL; wl_list_remove(&text_input->pending_focused_surface_destroy.link); wl_list_init(&text_input->pending_focused_surface_destroy.link); } static void handle_new_text_input_v3(struct wl_listener *listener, void *data) { struct input_method_relay *relay = wl_container_of(listener, relay, new_text_input_v3); struct wlr_text_input_v3 *wlr_text_input = data; if (relay->seat->wlr_seat != wlr_text_input->seat) { return; } struct text_input *text_input = calloc(1, sizeof(*text_input)); if (!text_input) { return; } text_input->text_input_v3 = wlr_text_input; text_input->relay = relay; wl_list_insert(&relay->text_inputs, &text_input->link); text_input->text_input_enable.notify = handle_text_input_enable; wl_signal_add(&wlr_text_input->events.enable, &text_input->text_input_enable); text_input->text_input_commit.notify = handle_text_input_commit; wl_signal_add(&wlr_text_input->events.commit, &text_input->text_input_commit); text_input->text_input_disable.notify = handle_text_input_disable; wl_signal_add(&wlr_text_input->events.disable, &text_input->text_input_disable); text_input->text_input_destroy.notify = handle_text_input_destroy; wl_signal_add(&wlr_text_input->events.destroy, &text_input->text_input_destroy); text_input->pending_focused_surface_destroy.notify = handle_pending_focused_surface_destroy; wl_list_init(&text_input->pending_focused_surface_destroy.link); } static void handle_new_text_input_v1(struct wl_listener *listener, void *data) { struct text_input *text_input = calloc(1, sizeof(*text_input)); if (!text_input) { return; } struct text_input_v1 *text_input_v1 = data; text_input->text_input_v1 = text_input_v1; /* no seat set */ // wl_list_init(&text_input->link); // TODO: multi-seat not support text_input->relay = input_manager_get_default_seat()->relay; wl_list_insert(&text_input->relay->text_inputs, &text_input->link); text_input->text_input_enable.notify = handle_text_input_enable; wl_signal_add(&text_input_v1->events.activate, &text_input->text_input_enable); text_input->text_input_commit.notify = handle_text_input_commit; wl_signal_add(&text_input_v1->events.commit, &text_input->text_input_commit); text_input->text_input_disable.notify = handle_text_input_disable; wl_signal_add(&text_input_v1->events.deactivate, &text_input->text_input_disable); text_input->text_input_destroy.notify = handle_text_input_destroy; wl_signal_add(&text_input_v1->events.destroy, &text_input->text_input_destroy); text_input->pending_focused_surface_destroy.notify = handle_pending_focused_surface_destroy; wl_list_init(&text_input->pending_focused_surface_destroy.link); } static void handle_new_text_input_v2(struct wl_listener *listener, void *data) { struct input_method_relay *relay = wl_container_of(listener, relay, new_text_input_v2); struct text_input_v2 *text_input_v2 = data; if (relay->seat->wlr_seat != text_input_v2->seat) { return; } struct text_input *text_input = calloc(1, sizeof(*text_input)); if (!text_input) { return; } text_input->text_input_v2 = text_input_v2; text_input->relay = relay; wl_list_insert(&relay->text_inputs, &text_input->link); text_input->text_input_enable.notify = handle_text_input_enable; wl_signal_add(&text_input_v2->events.enable, &text_input->text_input_enable); text_input->text_input_commit.notify = handle_text_input_commit; wl_signal_add(&text_input_v2->events.commit, &text_input->text_input_commit); text_input->text_input_disable.notify = handle_text_input_disable; wl_signal_add(&text_input_v2->events.disable, &text_input->text_input_disable); text_input->text_input_destroy.notify = handle_text_input_destroy; wl_signal_add(&text_input_v2->events.destroy, &text_input->text_input_destroy); text_input->pending_focused_surface_destroy.notify = handle_pending_focused_surface_destroy; wl_list_init(&text_input->pending_focused_surface_destroy.link); } static void handle_input_method_commit(struct wl_listener *listener, void *data) { struct input_method_relay *relay = wl_container_of(listener, relay, input_method_commit); struct text_input *text_input = relay_get_focused_text_input(relay); if (!text_input) { return; } struct wlr_input_method_v2 *context = data; assert(context == relay->wlr_input_method); if (text_input->text_input_v3) { if (context->current.preedit.text) { wlr_text_input_v3_send_preedit_string( text_input->text_input_v3, context->current.preedit.text, context->current.preedit.cursor_begin, context->current.preedit.cursor_end); } if (context->current.commit_text) { wlr_text_input_v3_send_commit_string(text_input->text_input_v3, context->current.commit_text); } if (context->current.delete.before_length || context->current.delete.after_length) { wlr_text_input_v3_send_delete_surrounding_text(text_input->text_input_v3, context->current.delete.before_length, context->current.delete.after_length); } wlr_text_input_v3_send_done(text_input->text_input_v3); } else if (text_input->text_input_v2) { text_input_v2_send_preedit_string(text_input->text_input_v2, context->current.preedit.text, context->current.preedit.cursor_begin); if (context->current.commit_text) { text_input_v2_send_commit_string(text_input->text_input_v2, context->current.commit_text); } if (context->current.delete.before_length || context->current.delete.after_length) { text_input_v2_send_delete_surrounding_text( text_input->text_input_v2, context->current.preedit.text, context->current.delete.before_length, context->current.delete.after_length); } } else { text_input_v1_send_preedit_string(text_input->text_input_v1, context->current.preedit.text, context->current.preedit.cursor_begin); if (context->current.commit_text) { text_input_v1_send_commit_string(text_input->text_input_v1, context->current.commit_text); } if (context->current.delete.before_length || context->current.delete.after_length) { text_input_v1_send_delete_surrounding_text( text_input->text_input_v1, context->current.preedit.text, context->current.delete.before_length, context->current.delete.after_length); } } } static void handle_input_method_keyboard_grab_destroy(struct wl_listener *listener, void *data) { struct input_method_relay *relay = wl_container_of(listener, relay, input_method_keyboard_grab_destroy); struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data; wl_list_remove(&relay->input_method_keyboard_grab_destroy.link); if (keyboard_grab->keyboard) { // send modifier state to original client wlr_seat_keyboard_notify_modifiers(keyboard_grab->input_method->seat, &keyboard_grab->keyboard->modifiers); } } static void handle_input_method_grab_keyboard(struct wl_listener *listener, void *data) { struct input_method_relay *relay = wl_container_of(listener, relay, input_method_grab_keyboard); struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data; // send modifier state to grab struct wlr_keyboard *active_keyboard = wlr_seat_get_keyboard(relay->seat->wlr_seat); wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab, active_keyboard); relay->input_method_keyboard_grab_destroy.notify = handle_input_method_keyboard_grab_destroy; wl_signal_add(&keyboard_grab->events.destroy, &relay->input_method_keyboard_grab_destroy); } static void handle_input_method_destroy(struct wl_listener *listener, void *data) { struct input_method_relay *relay = wl_container_of(listener, relay, input_method_destroy); struct wlr_input_method_v2 *context = data; assert(context == relay->wlr_input_method); relay->wlr_input_method = NULL; wl_list_remove(&relay->input_method_commit.link); wl_list_remove(&relay->input_method_grab_keyboard.link); wl_list_remove(&relay->input_method_destroy.link); wl_list_remove(&relay->new_popup_surface.link); struct text_input *text_input = relay_get_focused_text_input(relay); if (text_input) { // keyboard focus is still there, so keep the surface at hand in case // the input method returns struct wlr_surface *focused_surface = text_input_focused_surface(text_input); text_input_set_pending_focused_surface(text_input, focused_surface); text_input_send_leave(text_input); } } static void handle_input_surface_map(struct wl_listener *listener, void *data) { struct input_popup *popup = wl_container_of(listener, popup, surface_map); input_popup_update(popup, popup->relay->seat); ky_scene_node_set_enabled(popup->surface_node, true); } static void handle_input_surface_unmap(struct wl_listener *listener, void *data) { struct input_popup *popup = wl_container_of(listener, popup, surface_unmap); ky_scene_node_set_enabled(popup->surface_node, false); } static void handle_input_surface_commit(struct wl_listener *listener, void *data) { struct input_popup *popup = wl_container_of(listener, popup, surface_commit); input_popup_update(popup, popup->relay->seat); } static void handle_input_popup_destroy(struct wl_listener *listener, void *data) { struct input_popup *popup = wl_container_of(listener, popup, popup_surface_destroy); wl_list_remove(&popup->popup_surface_destroy.link); wl_list_remove(&popup->surface_commit.link); wl_list_remove(&popup->surface_unmap.link); wl_list_remove(&popup->surface_map.link); wl_list_remove(&popup->link); /* surface destroy signal is bebind this, destroy scene here */ ky_scene_node_destroy(popup->surface_node); free(popup); } static bool input_popup_hover(struct seat *seat, struct ky_scene_node *node, double x, double y, uint32_t time, bool first, bool hold, void *data) { struct wlr_surface *surface = wlr_surface_try_from_node(node); seat_notify_motion(seat, surface, time, x, y, first); return false; } static void input_popup_click(struct seat *seat, struct ky_scene_node *node, uint32_t button, bool pressed, uint32_t time, enum click_state state, void *data) { seat_notify_button(seat, time, button, pressed); } static void input_popup_leave(struct seat *seat, struct ky_scene_node *node, bool last, void *data) { struct wlr_surface *surface = wlr_surface_try_from_node(node); seat_notify_leave(seat, surface); } static const struct input_event_node_impl input_popup_event_node_impl = { .hover = input_popup_hover, .click = input_popup_click, .leave = input_popup_leave, }; static struct ky_scene_node *input_popup_get_root(void *data) { struct input_popup *popup = data; return popup->surface_node; } static void handle_new_popup_surface(struct wl_listener *listener, void *data) { struct input_method_relay *relay = wl_container_of(listener, relay, new_popup_surface); struct wlr_input_popup_surface_v2 *popup_surface = data; struct input_popup *popup = calloc(1, sizeof(*popup)); if (!popup) { return; } popup->relay = relay; popup->popup_surface = popup_surface; wl_list_insert(&relay->input_popups, &popup->link); /* create a scene node to show the popup */ struct view_layer *layer = view_manager_get_layer(LAYER_POPUP, false); struct ky_scene_surface *scene_surface = ky_scene_surface_create(layer->tree, popup_surface->surface); popup->surface_node = &scene_surface->buffer->node; input_event_node_create(popup->surface_node, &input_popup_event_node_impl, input_popup_get_root, NULL, popup); ky_scene_node_set_enabled(popup->surface_node, popup_surface->surface->mapped); popup->surface_map.notify = handle_input_surface_map; wl_signal_add(&popup->popup_surface->surface->events.map, &popup->surface_map); popup->surface_unmap.notify = handle_input_surface_unmap; wl_signal_add(&popup->popup_surface->surface->events.unmap, &popup->surface_unmap); popup->surface_commit.notify = handle_input_surface_commit; wl_signal_add(&popup->popup_surface->surface->events.commit, &popup->surface_commit); popup->popup_surface_destroy.notify = handle_input_popup_destroy; wl_signal_add(&popup->popup_surface->events.destroy, &popup->popup_surface_destroy); /* update popup position when surface is mapped */ input_popup_update(popup, relay->seat); } static void handle_new_input_method(struct wl_listener *listener, void *data) { struct input_method_relay *relay = wl_container_of(listener, relay, new_input_method); struct wlr_input_method_v2 *input_method = data; if (relay->seat->wlr_seat != input_method->seat) { return; } if (relay->wlr_input_method) { kywc_log(KYWC_WARN, "Attempted to connect second input method to a seat"); wlr_input_method_v2_send_unavailable(input_method); return; } relay->wlr_input_method = input_method; relay->input_method_commit.notify = handle_input_method_commit; wl_signal_add(&relay->wlr_input_method->events.commit, &relay->input_method_commit); relay->input_method_grab_keyboard.notify = handle_input_method_grab_keyboard; wl_signal_add(&relay->wlr_input_method->events.grab_keyboard, &relay->input_method_grab_keyboard); relay->input_method_destroy.notify = handle_input_method_destroy; wl_signal_add(&relay->wlr_input_method->events.destroy, &relay->input_method_destroy); relay->new_popup_surface.notify = handle_new_popup_surface; wl_signal_add(&relay->wlr_input_method->events.new_popup_surface, &relay->new_popup_surface); /* if has a pending focused text_inputs */ struct text_input *text_input; wl_list_for_each(text_input, &relay->text_inputs, link) { if (!text_input->pending_focused_surface) { continue; } text_input_send_enter(text_input, text_input->pending_focused_surface); text_input_set_pending_focused_surface(text_input, NULL); break; } if (relay->pending_enabled_text_input) { wlr_input_method_v2_send_activate(input_method); relay_send_input_method_state(relay, relay->pending_enabled_text_input); relay->pending_enabled_text_input = NULL; } } static void handle_input_method_v2_destroy(struct wl_listener *listener, void *data) { struct input_method_relay *relay = wl_container_of(listener, relay, input_method_v2_destroy); wl_list_remove(&relay->input_method_v2_destroy.link); wl_list_remove(&relay->new_input_method.link); wl_list_init(&relay->input_method_v2_destroy.link); wl_list_init(&relay->new_input_method.link); } static void handle_text_input_v2_destroy(struct wl_listener *listener, void *data) { struct input_method_relay *relay = wl_container_of(listener, relay, text_input_v2_destroy); wl_list_remove(&relay->text_input_v2_destroy.link); wl_list_remove(&relay->new_text_input_v2.link); wl_list_init(&relay->text_input_v2_destroy.link); wl_list_init(&relay->new_text_input_v2.link); } static void handle_text_input_v3_destroy(struct wl_listener *listener, void *data) { struct input_method_relay *relay = wl_container_of(listener, relay, text_input_v3_destroy); wl_list_remove(&relay->text_input_v3_destroy.link); wl_list_remove(&relay->new_text_input_v3.link); wl_list_init(&relay->text_input_v3_destroy.link); wl_list_init(&relay->new_text_input_v3.link); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { struct input_method_relay *relay = wl_container_of(listener, relay, seat_destroy); wl_list_remove(&relay->seat_destroy.link); handle_input_method_v2_destroy(&relay->input_method_v2_destroy, NULL); handle_text_input_v2_destroy(&relay->text_input_v2_destroy, NULL); handle_text_input_v3_destroy(&relay->text_input_v3_destroy, NULL); if (relay->wlr_input_method) { handle_input_method_destroy(&relay->input_method_destroy, relay->wlr_input_method); } struct text_input *input, *tmp_input; wl_list_for_each_safe(input, tmp_input, &relay->text_inputs, link) { wl_list_remove(&input->link); wl_list_init(&input->link); } struct input_popup *popup, *tmp_popup; wl_list_for_each_safe(popup, tmp_popup, &relay->input_popups, link) { wl_list_remove(&popup->link); wl_list_init(&popup->link); } free(relay); } static void handle_new_seat(struct wl_listener *listener, void *data) { struct input_method_manager *manager = wl_container_of(listener, manager, new_seat); struct seat *seat = data; /* create input_manager for this seat */ struct input_method_relay *relay = calloc(1, sizeof(*relay)); if (!relay) { return; } wl_list_init(&relay->text_inputs); wl_list_init(&relay->input_popups); seat->relay = relay; relay->seat = seat; relay->seat_destroy.notify = handle_seat_destroy; wl_signal_add(&seat->events.destroy, &relay->seat_destroy); relay->new_input_method.notify = handle_new_input_method; wl_signal_add(&manager->input_method->events.input_method, &relay->new_input_method); relay->input_method_v2_destroy.notify = handle_input_method_v2_destroy; wl_signal_add(&manager->input_method->events.destroy, &relay->input_method_v2_destroy); relay->new_text_input_v2.notify = handle_new_text_input_v2; wl_signal_add(&manager->text_input_v2->events.text_input, &relay->new_text_input_v2); relay->text_input_v2_destroy.notify = handle_text_input_v2_destroy; wl_signal_add(&manager->text_input_v2->events.destroy, &relay->text_input_v2_destroy); relay->new_text_input_v3.notify = handle_new_text_input_v3; wl_signal_add(&manager->text_input_v3->events.text_input, &relay->new_text_input_v3); relay->text_input_v3_destroy.notify = handle_text_input_v3_destroy; wl_signal_add(&manager->text_input_v3->events.destroy, &relay->text_input_v3_destroy); } static void handle_text_input_v1_destroy(struct wl_listener *listener, void *data) { struct input_method_manager *manager = wl_container_of(listener, manager, text_input_v1_destroy); wl_list_remove(&manager->text_input_v1_destroy.link); wl_list_remove(&manager->new_text_input_v1.link); } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct input_method_manager *manager = wl_container_of(listener, manager, server_destroy); wl_list_remove(&manager->server_destroy.link); wl_list_remove(&manager->new_seat.link); free(manager); } bool input_method_manager_create(struct input_manager *input_manager) { struct input_method_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->input_method = wlr_input_method_manager_v2_create(input_manager->server->display); manager->text_input_v3 = wlr_text_input_manager_v3_create(input_manager->server->display); manager->text_input_v2 = text_input_manager_v2_create(input_manager->server->display); /* no seat in text_input_v1 create_text_input request */ manager->text_input_v1 = text_input_manager_v1_create(input_manager->server->display); manager->new_text_input_v1.notify = handle_new_text_input_v1; wl_signal_add(&manager->text_input_v1->events.text_input, &manager->new_text_input_v1); manager->text_input_v1_destroy.notify = handle_text_input_v1_destroy; wl_signal_add(&manager->text_input_v1->events.destroy, &manager->text_input_v1_destroy); manager->new_seat.notify = handle_new_seat; wl_signal_add(&input_manager->events.new_seat, &manager->new_seat); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(input_manager->server, &manager->server_destroy); return true; } static bool text_input_match_surface(struct text_input *text_input, struct wlr_surface *surface) { if (text_input->text_input_v3) { return wl_resource_get_client(text_input->text_input_v3->resource) == wl_resource_get_client(surface->resource); } else if (text_input->text_input_v2) { return wl_resource_get_client(text_input->text_input_v2->resource) == wl_resource_get_client(surface->resource); } else { return wl_resource_get_client(text_input->text_input_v1->resource) == wl_resource_get_client(surface->resource) && text_input->text_input_v1->surface == surface; } } void input_method_set_focus(struct seat *seat, struct wlr_surface *surface) { struct input_method_relay *relay = seat->relay; struct wlr_surface *focused_surface; struct text_input *text_input; wl_list_for_each(text_input, &relay->text_inputs, link) { focused_surface = text_input_focused_surface(text_input); if (text_input->pending_focused_surface) { if (surface != text_input->pending_focused_surface) { text_input_set_pending_focused_surface(text_input, NULL); } } else if (focused_surface) { if (surface != focused_surface) { relay_disable_text_input(relay, text_input); text_input_send_leave(text_input); } else { kywc_log(KYWC_DEBUG, "IM relay set_focus already focused"); continue; } } if (surface && text_input_match_surface(text_input, surface)) { if (relay->wlr_input_method) { text_input_send_enter(text_input, surface); } else { text_input_set_pending_focused_surface(text_input, surface); } } } } static struct wlr_input_method_keyboard_grab_v2 * keyboard_get_input_method_grab(struct keyboard *keyboard) { struct wlr_input_method_v2 *input_method = keyboard->seat->relay->wlr_input_method; struct wlr_virtual_keyboard_v1 *virtual_keyboard = wlr_input_device_get_virtual_keyboard(&keyboard->wlr_keyboard->base); if (!input_method || !input_method->keyboard_grab || (virtual_keyboard && wl_resource_get_client(virtual_keyboard->resource) == wl_resource_get_client(input_method->keyboard_grab->resource))) { return NULL; } return input_method->keyboard_grab; } bool input_method_handle_key(struct keyboard *keyboard, uint32_t time, uint32_t key, uint32_t state) { struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = keyboard_get_input_method_grab(keyboard); if (!keyboard_grab) { return false; } wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab, keyboard->wlr_keyboard); wlr_input_method_keyboard_grab_v2_send_key(keyboard_grab, time, key, state); return true; } bool input_method_handle_modifiers(struct keyboard *keyboard) { struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = keyboard_get_input_method_grab(keyboard); if (!keyboard_grab) { return false; } wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab, keyboard->wlr_keyboard); wlr_input_method_keyboard_grab_v2_send_modifiers(keyboard_grab, &keyboard->wlr_keyboard->modifiers); return true; } bool keyboard_is_from_input_method(struct keyboard *keyboard) { struct wlr_input_method_v2 *input_method = keyboard->seat->relay->wlr_input_method; struct wlr_virtual_keyboard_v1 *virtual_keyboard = wlr_input_device_get_virtual_keyboard(&keyboard->wlr_keyboard->base); return input_method && virtual_keyboard && wl_resource_get_client(virtual_keyboard->resource) == wl_resource_get_client(input_method->resource); } kylin-wayland-compositor/src/input/touch.c0000664000175000017500000005356715160461067017721 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include "input/seat.h" #include "input_p.h" #include "scene/surface.h" #include "server.h" #include "util/macros.h" #include "view/view.h" #include "xwayland.h" #define TOUCH_HOLD_TIMEOUT (50) #define TOUCH_FILTER_TIMEOUT (100) struct touch_manager { struct wl_listener new_input; struct wl_listener server_destroy; }; struct touch { struct wlr_touch *wlr_touch; struct input *input; struct wl_listener input_destroy; struct wl_list points; uint32_t points_count; /* timer for gesture detect filter */ struct wl_event_source *filter; bool filter_enabled; /* current gesture state per touch */ struct gesture_state gestures; uint32_t hold_points; /* hold canceled status */ uint32_t hold_canceled; /* pinch angle */ double angle; }; struct touch_point { struct touch *touch; struct wl_list link; struct wlr_surface *surface; struct wl_listener surface_unmap; /* timer for hold gesture */ struct wl_event_source *timer; bool hold, moved; double abs_x, abs_y; double last_x, last_y; double dx, dy; int32_t touch_id; uint32_t directions; }; static struct touch *touch_from_wlr_touch(struct wlr_touch *wlr_touch) { return wlr_touch->data; } static void touch_handle_input_destroy(struct wl_listener *listener, void *data) { struct touch *touch = wl_container_of(listener, touch, input_destroy); wl_list_remove(&touch->input_destroy.link); gesture_state_finish(&touch->gestures); wl_event_source_remove(touch->filter); struct touch_point *point, *tmp; wl_list_for_each_safe(point, tmp, &touch->points, link) { wl_list_remove(&point->link); if (point->surface) { wl_list_remove(&point->surface_unmap.link); } if (point->timer) { wl_event_source_remove(point->timer); } free(point); } free(touch); } static enum gesture_edge touch_calc_edge(struct touch *touch) { double abs_avg_x = 0.0; double abs_avg_y = 0.0; struct touch_point *point; wl_list_for_each(point, &touch->points, link) { /* meet the first free point */ if (point->touch_id < 0) { break; } abs_avg_x += point->abs_x; abs_avg_y += point->abs_y; } abs_avg_x /= touch->points_count; abs_avg_y /= touch->points_count; // TODO: edge normalize by physical size of touch enum gesture_edge edge = GESTURE_EDGE_NONE; if (abs_avg_y <= 0.05) { edge = GESTURE_EDGE_TOP; } if (abs_avg_x <= 0.05) { edge = GESTURE_EDGE_LEFT; } if (abs_avg_x >= 0.95) { edge = GESTURE_EDGE_RIGHT; } if (abs_avg_y >= 0.95) { edge = GESTURE_EDGE_BOTTOM; } return edge; } static bool touch_gesture_begin(struct touch *touch, enum gesture_type gesture, uint8_t fingers) { struct gesture_state *state = &touch->gestures; if (state->type == gesture && state->fingers == fingers) { return false; } /* cancel current gesture and enter the new hold */ if (state->type != GESTURE_TYPE_NONE) { gesture_state_end(state, state->type, state->device, true); } if (gesture == GESTURE_TYPE_NONE) { return false; } enum gesture_edge edge = touch_calc_edge(touch); gesture_state_begin(state, gesture, GESTURE_DEVICE_TOUCHSCREEN, edge, fingers); return true; } static void touch_gesture_detect(struct touch *touch) { if (touch->points_count == 0) { return; } bool all_points_moved = true; struct touch_point *point; wl_list_for_each(point, &touch->points, link) { /* meet the first free point */ if (point->touch_id < 0) { break; } if (!point->moved) { all_points_moved = false; break; } } if (!all_points_moved) { /* hold gesture if all hold points not moved */ if (touch->hold_points && !touch->hold_canceled) { if (!touch_gesture_begin(touch, GESTURE_TYPE_HOLD, touch->hold_points)) { /* cancel the hold gesture when touch point is moved, * except for there is only one touch point */ if (touch->hold_points != 1) { gesture_state_end(&touch->gestures, GESTURE_TYPE_HOLD, GESTURE_DEVICE_TOUCHSCREEN, true); } touch->hold_canceled = true; } } return; } /* moved when one touch point swipe from edge */ if (touch->points_count == 1) { touch_gesture_begin(touch, GESTURE_TYPE_SWIPE, 1); return; } /* swipre or pinch check */ wl_list_for_each(point, &touch->points, link) { /* meet the first free point */ if (point->touch_id < 0) { break; } if (!point->moved) { all_points_moved = false; break; } } bool one_direction = true; uint32_t directions = GESTURE_DIRECTION_NONE; wl_list_for_each(point, &touch->points, link) { /* meet the first free point */ if (point->touch_id < 0) { break; } if (directions == GESTURE_DIRECTION_NONE) { directions = point->directions; } else if (directions != point->directions) { one_direction = false; break; } } touch_gesture_begin(touch, one_direction ? GESTURE_TYPE_SWIPE : GESTURE_TYPE_PINCH, touch->points_count); } static void touch_filter_enable(struct touch *touch, bool enabled) { wl_event_source_timer_update(touch->filter, enabled ? TOUCH_FILTER_TIMEOUT : 0); touch->filter_enabled = enabled; } static int touch_handle_timer(void *data) { struct touch *touch = data; touch->filter_enabled = false; touch_gesture_detect(touch); return 0; } static void handle_new_input(struct wl_listener *listener, void *data) { struct touch_manager *manager = wl_container_of(listener, manager, new_input); struct input *input = data; /* input has been configured, only care about touch */ if (input->prop.type != WLR_INPUT_DEVICE_TOUCH) { return; } struct touch *touch = calloc(1, sizeof(*touch)); if (!touch) { return; } struct wl_display *display = input->seat->wlr_seat->display; struct wl_event_loop *loop = wl_display_get_event_loop(display); touch->filter = wl_event_loop_add_timer(loop, touch_handle_timer, touch); if (!touch->filter) { free(touch); return; } touch->input = input; touch->input_destroy.notify = touch_handle_input_destroy; wl_signal_add(&input->events.destroy, &touch->input_destroy); touch->wlr_touch = wlr_touch_from_input_device(input->wlr_input); touch->wlr_touch->data = touch; wl_list_init(&touch->points); gesture_state_init(&touch->gestures, display); } void touch_reset_gesture(struct input_manager *input_manager) { struct input *input = NULL; wl_list_for_each(input, &input_manager->inputs, link) { if (input->prop.type != WLR_INPUT_DEVICE_TOUCH) { continue; } struct wlr_touch *wlr_touch = wlr_touch_from_input_device(input->wlr_input); struct touch *touch = wlr_touch->data; touch_filter_enable(touch, false); struct touch_point *point; wl_list_for_each(point, &touch->points, link) { /* meet the first free point */ if (point->touch_id < 0) { break; } if (point->timer) { wl_event_source_timer_update(point->timer, 0); } } gesture_state_end(&touch->gestures, touch->gestures.type, GESTURE_DEVICE_TOUCHSCREEN, true); } } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct touch_manager *manager = wl_container_of(listener, manager, server_destroy); wl_list_remove(&manager->server_destroy.link); wl_list_remove(&manager->new_input.link); free(manager); } bool touch_manager_create(struct input_manager *input_manager) { struct touch_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->new_input.notify = handle_new_input; input_add_new_listener(&manager->new_input); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(input_manager->server, &manager->server_destroy); return true; } static struct wlr_surface *touch_get_surface(struct touch *touch, double *sx, double *sy, struct wlr_surface **toplevel) { struct seat *seat = touch->input->seat; struct cursor *cursor = seat->cursor; struct ky_scene_node *node = ky_scene_node_at(&seat->scene->tree.node, cursor->lx, cursor->ly, sx, sy); if (!node) { return NULL; } if (toplevel) { *toplevel = input_event_node_toplevel(input_event_node_from_node(node)); } return wlr_surface_try_from_node(node); } static int touch_point_handle_timer(void *data) { struct touch_point *point = data; point->hold = true; point->touch->hold_points++; if (point->touch->filter_enabled) { return 0; } if (point->touch->gestures.type == GESTURE_TYPE_NONE) { touch_filter_enable(point->touch, true); return 0; } /* keep hold gesture */ if (point->touch->gestures.type == GESTURE_TYPE_HOLD) { touch_gesture_begin(point->touch, GESTURE_TYPE_HOLD, point->touch->hold_points); } return 0; } static void touch_point_handle_surface_unmap(struct wl_listener *listener, void *data) { struct touch_point *point = wl_container_of(listener, point, surface_unmap); wl_list_remove(&point->surface_unmap.link); point->surface = NULL; } static struct touch_point *touch_point_create(struct touch *touch, int32_t touch_id) { struct touch_point *point, *free_point = NULL; wl_list_for_each(point, &touch->points, link) { if (point->touch_id == touch_id) { return point; } if (!free_point && point->touch_id < 0) { free_point = point; } } /* not found, reuse the first free one */ if (free_point) { free_point->touch_id = touch_id; return free_point; } /* alloc one if all in used */ point = calloc(1, sizeof(*point)); if (!point) { return NULL; } point->touch_id = touch_id; point->touch = touch; point->surface_unmap.notify = touch_point_handle_surface_unmap; wl_list_insert(touch->points.prev, &point->link); struct wl_display *display = touch->input->seat->wlr_seat->display; struct wl_event_loop *loop = wl_display_get_event_loop(display); point->timer = wl_event_loop_add_timer(loop, touch_point_handle_timer, point); return point; } static struct touch_point *touch_point_from_id(struct touch *touch, int32_t touch_id) { struct touch_point *point; wl_list_for_each(point, &touch->points, link) { if (point->touch_id == touch_id) { return point; } } return NULL; } static void touch_point_reset(struct touch_point *point, bool cancelled) { struct gesture_state *state = &point->touch->gestures; if (state->type != GESTURE_TYPE_NONE) { /* touch point up to set hold canceled in hold state, * and cancels gesture when touch point count is more than 1 */ if (state->type == GESTURE_TYPE_HOLD) { point->touch->hold_canceled = true; if (!cancelled && point->touch->points_count > 1) { cancelled = true; } } gesture_state_end(state, state->type, state->device, cancelled); } point->touch_id = -1; point->touch->points_count--; if (point->surface) { point->surface = NULL; wl_list_remove(&point->surface_unmap.link); } if (point->timer) { wl_event_source_timer_update(point->timer, 0); } if (point->hold) { point->touch->hold_points--; point->hold = false; } /* last touch point up reset the hold canceled state */ if (!point->touch->points_count) { point->touch->hold_canceled = false; } /* reinsert to tail */ wl_list_remove(&point->link); wl_list_insert(point->touch->points.prev, &point->link); } bool touch_handle_down(struct wlr_touch_down_event *event) { struct touch *touch = touch_from_wlr_touch(event->touch); if (!touch) { return false; } struct seat *seat = touch->input->seat; cursor_move(seat->cursor, &event->touch->base, event->x, event->y, false, true); if (seat->touch_grab && seat->touch_grab->interface->touch && seat->touch_grab->interface->touch(seat->touch_grab, event->time_msec, true)) { return true; } struct touch_point *point = touch_point_create(touch, event->touch_id); if (point->timer) { wl_event_source_timer_update(point->timer, TOUCH_HOLD_TIMEOUT); } touch->points_count++; point->abs_x = point->last_x = event->x; point->abs_y = point->last_y = event->y; point->moved = false; touch->angle = 0.0; struct wlr_surface *toplevel = NULL; double sx, sy; struct wlr_surface *surface = touch_get_surface(touch, &sx, &sy, &toplevel); if (!surface || !wlr_surface_accepts_touch(surface, seat->wlr_seat)) { return false; } point->surface = surface; wl_signal_add(&surface->events.unmap, &point->surface_unmap); if (xwayland_check_client(wl_resource_get_client(point->surface->resource))) { sx = xwayland_scale(sx); sy = xwayland_scale(sy); } wlr_seat_touch_notify_down(seat->wlr_seat, surface, event->time_msec, event->touch_id, sx, sy); /* activate and focus the toplevel surface */ if (toplevel) { struct view *view = view_try_from_wlr_surface(toplevel); if (view) { kywc_view_activate(&view->base); view_set_focus(view, seat); } else { seat_focus_surface(seat, toplevel); } } return true; } static uint32_t touch_point_calc_directions(struct touch_point *point, double dx, double dy) { uint32_t directions = GESTURE_DIRECTION_NONE; if (fabs(dx) > fabs(dy)) { if (dx > 0) { directions |= GESTURE_DIRECTION_RIGHT; } else { directions |= GESTURE_DIRECTION_LEFT; } } else { if (dy > 0) { directions |= GESTURE_DIRECTION_DOWN; } else { directions |= GESTURE_DIRECTION_UP; } } return directions; } static void touch_calc_average_delta(struct touch *touch, double *dx, double *dy) { double total_dx = 0, total_dy = 0; struct touch_point *point; wl_list_for_each(point, &touch->points, link) { /* meet the first free point */ if (point->touch_id < 0) { break; } total_dx += point->dx; total_dy += point->dy; } *dx = total_dx / touch->points_count; *dy = total_dy / touch->points_count; } static double touch_calc_average_scale(struct touch *touch) { double start_min = 1.0, current_min = 1.0; double start_max = 0.0, current_max = 0.0; struct touch_point *point; wl_list_for_each(point, &touch->points, link) { /* meet the first free point */ if (point->touch_id < 0) { break; } if (start_min == 1.0 && start_max == 0.0) { start_min = start_max = point->abs_x; current_min = current_max = point->last_x; } else { start_min = MIN(start_min, point->abs_x); start_max = MAX(start_max, point->abs_x); current_min = MIN(current_min, point->last_x); current_max = MAX(current_max, point->last_x); } } double start_width = start_max - start_min; double current_width = current_max - current_min; if (start_width == 0.0) { kywc_log(KYWC_WARN, "Start(max = %f, min = %f), Current(max = %f, min = %f)", start_max, start_min, current_max, current_min); return 1.0; } return current_width / start_width; } static double touch_calc_average_angle_delta(struct touch *touch) { double dx = 0.0; double dy = 0.0; struct touch_point *point; wl_list_for_each(point, &touch->points, link) { /* meet the first free point */ if (point->touch_id < 0) { break; } dx = point->last_x - dx; dy = point->last_y - dy; } double tangle = atan2(dy, dx) * 180.0 / 3.14; if (touch->angle == 0.0) { touch->angle = tangle; } double angle_delta = tangle - touch->angle; if (angle_delta > 180.0) { angle_delta -= 360.0; } else if (angle_delta < -180.0) { angle_delta += 360.0; } touch->angle = tangle; return angle_delta; } void touch_handle_motion(struct wlr_touch_motion_event *event, bool handle) { struct touch *touch = touch_from_wlr_touch(event->touch); if (!touch) { return; } struct seat *seat = touch->input->seat; cursor_move(seat->cursor, &event->touch->base, event->x, event->y, false, true); if (seat->touch_grab && seat->touch_grab->interface->motion && seat->touch_grab->interface->motion(seat->touch_grab, event->time_msec, seat->cursor->lx, seat->cursor->ly)) { return; } struct touch_point *point = touch_point_from_id(touch, event->touch_id); if (!point) { return; } point->dx = event->x - point->last_x; point->dy = event->y - point->last_y; point->last_x = event->x; point->last_y = event->y; double dx = event->x - point->abs_x; double dy = event->y - point->abs_y; point->moved = fabs(dx) > 0.01f || fabs(dy) > 0.01f; /* calc point motion directions */ point->directions = touch_point_calc_directions(point, dx, dy); if (!touch->filter_enabled) { if (!touch->gestures.handled) { touch_gesture_detect(touch); } if (touch->gestures.type == GESTURE_TYPE_SWIPE) { double avg_dx = 0, avg_dy = 0; touch_calc_average_delta(touch, &avg_dx, &avg_dy); gesture_state_update(&touch->gestures, GESTURE_TYPE_SWIPE, GESTURE_DEVICE_TOUCHSCREEN, avg_dx, avg_dy, NAN, NAN); } else if (touch->gestures.type == GESTURE_TYPE_PINCH) { double avg_dx = 0, avg_dy = 0; touch_calc_average_delta(touch, &avg_dx, &avg_dy); double scale = touch_calc_average_scale(touch); double angle_delta = touch_calc_average_angle_delta(touch); gesture_state_update(&touch->gestures, GESTURE_TYPE_PINCH, GESTURE_DEVICE_TOUCHSCREEN, avg_dx, avg_dy, scale, angle_delta); } } if (!handle || !point->surface) { return; } double sx, sy; struct wlr_surface *surface = touch_get_surface(touch, &sx, &sy, NULL); /* if motion out of point->surface */ if (surface != point->surface) { if (!seat_is_dragging(seat)) { int lx, ly; struct ky_scene_buffer *scene_buffer = ky_scene_buffer_try_from_surface(point->surface); ky_scene_node_coords(&scene_buffer->node, &lx, &ly); sx = seat->cursor->lx - lx; sy = seat->cursor->ly - ly; } else if (surface) { wlr_seat_touch_point_focus(seat->wlr_seat, surface, event->time_msec, event->touch_id, sx, sy); } } if (xwayland_check_client(wl_resource_get_client(point->surface->resource))) { sx = xwayland_scale(sx); sy = xwayland_scale(sy); } selection_handle_cursor_move(seat, seat->cursor->lx, seat->cursor->ly); wlr_seat_touch_notify_motion(seat->wlr_seat, event->time_msec, event->touch_id, sx, sy); } void touch_handle_up(struct wlr_touch_up_event *event, bool handle) { struct touch *touch = touch_from_wlr_touch(event->touch); if (!touch) { return; } struct touch_point *point = touch_point_from_id(touch, event->touch_id); if (point) { touch_point_reset(point, false); } struct seat *seat = touch->input->seat; if (seat->touch_grab && seat->touch_grab->interface->touch && seat->touch_grab->interface->touch(seat->touch_grab, event->time_msec, false)) { return; } if (!point) { return; } /* workaround: ukui-panel need to send motion event when drop */ if (seat_is_dragging(seat)) { cursor_feed_motion(seat->cursor, event->time_msec, &event->touch->base, 0, 0, 0, 0); wlr_seat_pointer_notify_frame(seat->wlr_seat); } if (handle) { wlr_seat_touch_notify_up(seat->wlr_seat, event->time_msec, event->touch_id); } } void touch_handle_cancel(struct wlr_touch_cancel_event *event, bool handle) { struct touch *touch = touch_from_wlr_touch(event->touch); if (!touch) { return; } struct touch_point *point = touch_point_from_id(touch, event->touch_id); if (point) { touch_point_reset(point, true); } struct seat *seat = touch->input->seat; if (seat->touch_grab && seat->touch_grab->interface->touch && seat->touch_grab->interface->touch(seat->touch_grab, event->time_msec, false)) { return; } if (point && handle && point->surface) { struct seat *seat = touch->input->seat; struct wl_client *client = wl_resource_get_client(point->surface->resource); struct wlr_seat_client *seat_client = wlr_seat_client_for_wl_client(seat->wlr_seat, client); if (seat_client) { wlr_seat_touch_notify_cancel(seat->wlr_seat, seat_client); } } } kylin-wayland-compositor/src/input/action.c0000664000175000017500000010711515160461067020041 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include "config.h" #include "input_p.h" #include "server.h" #include "util/dbus.h" #include "util/macros.h" #include "util/spawn.h" #include "util/string.h" enum action_type { ACTION_TYPE_NONE = 0, ACTION_TYPE_DBUS_ACTION, ACTION_TYPE_RUN_COMMAND, ACTION_TYPE_SEND_BUTTON, ACTION_TYPE_SEND_KEY, }; enum key_action { KEY_ACTION_PRESS = 1 << 0, KEY_ACTION_RELEASE = 1 << 1, KEY_ACTION_CLICK = (1 << 2) - 1, }; enum input_type { INPUT_TYPE_NONE = 0, INPUT_TYPE_KEYBOARD, INPUT_TYPE_GESTURE }; enum dbus_type { DBUS_TYPE_NONE = 0, DBUS_TYPE_SESSION, DBUS_TYPE_SYSTEM }; enum control_type { CONTROL_TYPE_DELETE, CONTROL_TYPE_ENABLE, CONTROL_TYPE_DISABLE }; struct action_dbus_data { enum dbus_type type; char *service; char *path; char *interface; char *method; char *param_type; char *param_value; }; struct action_command_data { char *cmd; }; struct action_button_data { uint32_t val; }; struct keycodes { enum key_action action; uint32_t *code; uint32_t len; }; struct action_key_data { struct keycodes *modifiers; struct keycodes *keys; }; struct action_data { enum action_type type; enum key_binding_type binding_type; bool enable; union { struct action_dbus_data dbus; struct action_button_data button; struct action_key_data key; struct action_command_data command; } data; const char *desc; }; struct input_action { enum input_type type; char *bindings; struct action_data *action; void *data; /* key or gesture binding */ struct wl_list link; }; static struct input_action_manager { struct config *config; struct server *server; struct wl_listener server_destroy; struct wl_list actions; } *manager = NULL; static struct keycode_map { const char *key; uint32_t code; } keycode_maps[] = { { "a", KEY_A }, { "b", KEY_B }, { "c", KEY_C }, { "d", KEY_D }, { "e", KEY_E }, { "f", KEY_F }, { "g", KEY_G }, { "h", KEY_H }, { "i", KEY_I }, { "g", KEY_G }, { "k", KEY_K }, { "l", KEY_L }, { "m", KEY_M }, { "n", KEY_N }, { "o", KEY_O }, { "p", KEY_P }, { "q", KEY_Q }, { "r", KEY_R }, { "s", KEY_S }, { "t", KEY_T }, { "u", KEY_U }, { "v", KEY_V }, { "w", KEY_W }, { "x", KEY_X }, { "y", KEY_Y }, { "z", KEY_Z }, { "Tab", KEY_TAB }, { "Super_L", KEY_LEFTMETA }, { "Alt_L", KEY_LEFTALT }, { "Left", KEY_LEFT }, { "Right", KEY_RIGHT }, { "Down", KEY_DOWN }, { "Up", KEY_UP }, { "Shift_L", KEY_LEFTSHIFT }, { "Control_R", KEY_RIGHTCTRL }, { "Control_L", KEY_LEFTCTRL }, { "Alt_R", KEY_RIGHTALT }, { "Super_R", KEY_RIGHTMETA }, { "Shift_R", KEY_RIGHTSHIFT }, }; static struct btncode_map { const char *btn; uint32_t code; } btncode_maps[] = { { "left", BTN_LEFT }, { "right", BTN_RIGHT }, { "middle", BTN_MIDDLE }, { "back", BTN_BACK }, { "forward", BTN_FORWARD }, }; static const char *service_path = "/com/kylin/Wlcom/InputAction"; static const char *service_interface = "com.kylin.Wlcom.InputAction"; static uint32_t keycode_map(const char *keystr) { for (size_t i = 0; i < ARRAY_SIZE(keycode_maps); i++) { if (strcasecmp(keystr, keycode_maps[i].key) == 0) { return keycode_maps[i].code; } } return 0; } static const char *keycode_to_str(uint32_t code) { for (size_t i = 0; i < ARRAY_SIZE(keycode_maps); i++) { if (keycode_maps[i].code == code) { return keycode_maps[i].key; } } return NULL; } static char *keycodes_to_str(struct keycodes *keycodes) { char *str = NULL; for (uint32_t i = 0; i < keycodes->len; ++i) { const char *keystr = keycode_to_str(keycodes->code[i]); if (!keystr) { continue; } if (!str) { size_t len = strlen(keystr); str = malloc(len + 1); if (!str) { return NULL; } memcpy(str, keystr, len); str[len] = '\0'; } else { int new_size = strlen(str) + strlen(keystr) + 2; char *new_str = malloc(new_size); if (!new_str) { free(str); return NULL; } snprintf(new_str, new_size, "%s+%s", str, keystr); free(str); str = new_str; } } return str; } static uint32_t btncode_map(const char *btnstr) { for (size_t i = 0; i < ARRAY_SIZE(btncode_maps); i++) { if (strcasecmp(btnstr, btncode_maps[i].btn) == 0) { return btncode_maps[i].code; } } return 0; } static const char *btncode_to_str(uint32_t code) { for (size_t i = 0; i < ARRAY_SIZE(btncode_maps); i++) { if (code == btncode_maps[i].code) { return btncode_maps[i].btn; } } return NULL; } static struct keycodes *keycodes_create(const char *str) { struct keycodes *keycodes = calloc(1, sizeof(*keycodes)); if (!keycodes) { return NULL; } size_t action_len = 0; char **split_action = string_split(str, ":", &action_len); if (action_len > 2) { kywc_log(KYWC_ERROR, "Split key action error"); } size_t len = 0; char **split_str = string_split(split_action[0], "+", &len); for (size_t i = 0, j = 0; i < len; i++) { uint32_t keycode = keycode_map(split_str[i]); if (!keycode) { continue; } keycodes->code = realloc(keycodes->code, (keycodes->len + 1) * sizeof(uint32_t)); keycodes->code[j++] = keycode; keycodes->len++; } string_free_split(split_str); keycodes->action = KEY_ACTION_CLICK; if (action_len == 2) { if (strcmp(split_action[1], "press") == 0) { keycodes->action = KEY_ACTION_PRESS; } else if (strcmp(split_action[1], "release") == 0) { keycodes->action = KEY_ACTION_RELEASE; } } string_free_split(split_action); return keycodes; } static int list_input_actions(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct input_action_manager *manager = userdata; sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(m, &reply)); CK(sd_bus_message_open_container(reply, 'a', "(ss)")); struct input_action *action; wl_list_for_each(action, &manager->actions, link) { json_object *itype_obj = json_object_object_get( manager->config->json, action->type == INPUT_TYPE_KEYBOARD ? "keyboard" : "gesture"); json_object *config = json_object_object_get(itype_obj, action->bindings); const char *cfg = json_object_to_json_string(config); sd_bus_message_append(reply, "(ss)", action->bindings, cfg); } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } static struct action_data *action_data_create_from_bus_string(const char *bus_str) { struct action_data *action_data = NULL; size_t len = 0; char **split_str = string_split(bus_str, ",", &len); if (len < 2) { goto err; } action_data = calloc(1, sizeof(*action_data)); if (!action_data) { goto err; } if (strcmp(split_str[0], "dbus") == 0) { action_data->type = ACTION_TYPE_DBUS_ACTION; } else if (strcmp(split_str[0], "command") == 0) { action_data->type = ACTION_TYPE_RUN_COMMAND; } else if (strcmp(split_str[0], "button") == 0) { action_data->type = ACTION_TYPE_SEND_BUTTON; } else if (strcmp(split_str[0], "key") == 0) { action_data->type = ACTION_TYPE_SEND_KEY; } else { action_data->type = ACTION_TYPE_NONE; } kywc_log(KYWC_DEBUG, "Action type: %s, len: %ld", split_str[0], len); if (action_data->type == ACTION_TYPE_DBUS_ACTION && len == 6) { if (strcmp(split_str[1], "session") == 0) { action_data->data.dbus.type = DBUS_TYPE_SESSION; } else if (strcmp(split_str[1], "system") == 0) { action_data->data.dbus.type = DBUS_TYPE_SYSTEM; } else { action_data->data.dbus.type = DBUS_TYPE_NONE; } action_data->data.dbus.service = strdup(split_str[2]); action_data->data.dbus.path = strdup(split_str[3]); action_data->data.dbus.interface = strdup(split_str[4]); action_data->data.dbus.method = strdup(split_str[5]); } else if (action_data->type == ACTION_TYPE_RUN_COMMAND && len == 2) { action_data->data.command.cmd = strdup(split_str[1]); } else if (action_data->type == ACTION_TYPE_SEND_BUTTON && len == 2) { action_data->data.button.val = btncode_map(split_str[1]); } else if (action_data->type == ACTION_TYPE_SEND_KEY && len == 3) { action_data->data.key.modifiers = keycodes_create(split_str[1]); action_data->data.key.keys = keycodes_create(split_str[2]); } else { free(action_data); action_data = NULL; goto err; } action_data->enable = true; err: string_free_split(split_str); return action_data; } static bool dbus_method_paramter_handle(enum dbus_type type, const char *service, const char *path, const char *interface, const char *method, const char *param_type, const char *param_value) { if (!param_type || !param_value) { return type == DBUS_TYPE_SESSION ? dbus_call_method(service, path, interface, method, NULL, NULL) : dbus_call_system_method(service, path, interface, method, NULL, NULL); } if (!strcmp(param_type, "s")) { const char *value = param_value; return type == DBUS_TYPE_SESSION ? dbus_call_methodv(service, path, interface, method, NULL, NULL, param_type, value) : dbus_call_system_methodv(service, path, interface, method, NULL, NULL, param_type, value); } else if (!strcmp(param_type, "b")) { bool value = strcasecmp(param_value, "true") == 0; return type == DBUS_TYPE_SESSION ? dbus_call_methodv(service, path, interface, method, NULL, NULL, param_type, value) : dbus_call_system_methodv(service, path, interface, method, NULL, NULL, param_type, value); } else if (!strcmp(param_type, "d")) { double value = atof(param_value); return type == DBUS_TYPE_SESSION ? dbus_call_methodv(service, path, interface, method, NULL, NULL, param_type, value) : dbus_call_system_methodv(service, path, interface, method, NULL, NULL, param_type, value); } else if (!strcmp(param_type, "i")) { int value = atoi(param_value); return type == DBUS_TYPE_SESSION ? dbus_call_methodv(service, path, interface, method, NULL, NULL, param_type, value) : dbus_call_system_methodv(service, path, interface, method, NULL, NULL, param_type, value); } return false; } static void action_call_dbus_method(struct action_dbus_data *dbus_data) { bool ret = dbus_method_paramter_handle(dbus_data->type, dbus_data->service, dbus_data->path, dbus_data->interface, dbus_data->method, dbus_data->param_type, dbus_data->param_value); if (!ret) { kywc_log(KYWC_ERROR, "Dbus call failed: %s %s %s %s", dbus_data->service, dbus_data->path, dbus_data->interface, dbus_data->method); } } static void action_call_send_button(struct action_button_data *data) { struct seat *seat = input_manager_get_default_seat(); struct cursor *cursor = seat->cursor; if (cursor->touch_simulation_pointer && cursor->last_click_button == BTN_LEFT && data->val == BTN_RIGHT) { seat_feed_pointer_button(seat, BTN_LEFT, false); cursor->touch_simulation_pointer = false; } seat_feed_pointer_button(seat, data->val, true); seat_feed_pointer_button(seat, data->val, false); } static void action_call_send_key(struct action_key_data *data) { struct seat *seat = input_manager_get_default_seat(); for (uint32_t i = 0; data->modifiers && i < data->modifiers->len; ++i) { if (data->modifiers->action & KEY_ACTION_PRESS) { seat_feed_keyboard_key(seat, data->modifiers->code[i], true); } } for (uint32_t i = 0; data->keys && i < data->keys->len; ++i) { if (data->keys->action & KEY_ACTION_PRESS) { seat_feed_keyboard_key(seat, data->keys->code[i], true); } } for (uint32_t i = 0; data->modifiers && i < data->modifiers->len; ++i) { if (data->modifiers->action & KEY_ACTION_RELEASE) { seat_feed_keyboard_key(seat, data->modifiers->code[i], false); } } for (uint32_t i = 0; data->keys && i < data->keys->len; ++i) { if (data->keys->action & KEY_ACTION_RELEASE) { seat_feed_keyboard_key(seat, data->keys->code[i], false); } } } static void handle_input_action(struct input_action *input_action) { if (!input_action) { return; } struct action_data *action_data = input_action->action; switch (action_data->type) { case ACTION_TYPE_DBUS_ACTION: action_call_dbus_method(&action_data->data.dbus); break; case ACTION_TYPE_RUN_COMMAND: spawn_invoke(action_data->data.command.cmd); break; case ACTION_TYPE_SEND_BUTTON: action_call_send_button(&action_data->data.button); break; case ACTION_TYPE_SEND_KEY: action_call_send_key(&action_data->data.key); break; default: break; } kywc_log(KYWC_DEBUG, "Input_action: %s, type: %d", input_action->bindings, action_data->type); } static void input_manager_keybinding_action(struct key_binding *binding, void *data) { handle_input_action(data); } static void input_manager_gesturebinding_action(struct gesture_binding *binding, void *data, double dx, double dy) { handle_input_action(data); } static void input_action_destroy(struct input_action *input_action) { if (input_action->action->type == ACTION_TYPE_DBUS_ACTION) { free(input_action->action->data.dbus.service); free(input_action->action->data.dbus.path); free(input_action->action->data.dbus.interface); free(input_action->action->data.dbus.method); free(input_action->action->data.dbus.param_type); free(input_action->action->data.dbus.param_value); } else if (input_action->action->type == ACTION_TYPE_RUN_COMMAND) { free(input_action->action->data.command.cmd); } else if (input_action->action->type == ACTION_TYPE_SEND_KEY) { struct keycodes *modifiers = input_action->action->data.key.modifiers; struct keycodes *keys = input_action->action->data.key.keys; if (modifiers) { free(modifiers->code); } if (keys) { free(keys->code); } free(modifiers); free(keys); } wl_list_remove(&input_action->link); free(input_action->bindings); free(input_action->action); free(input_action); } static bool input_action_manager_delete_config(struct input_action_manager *manager, struct input_action *input_action) { if (!manager->config || !manager->config->json || !input_action) { return false; } char *input_type = input_action->type == INPUT_TYPE_KEYBOARD ? "keyboard" : "gesture"; json_object *config = json_object_object_get(manager->config->json, input_type); if (!config) { return false; } json_object_object_del(config, input_action->bindings); return true; } static bool input_action_manager_write_config(struct input_action_manager *manager, struct input_action *input_action) { if (!manager->config || !manager->config->json || !input_action) { return false; } char *input_type = input_action->type == INPUT_TYPE_KEYBOARD ? "keyboard" : "gesture"; json_object *config = json_object_object_get(manager->config->json, input_type); if (!config) { config = json_object_new_object(); json_object_object_add(manager->config->json, input_type, config); } json_object *action_config = json_object_object_get(config, input_action->bindings); if (!action_config) { action_config = json_object_new_object(); json_object_object_add(config, input_action->bindings, action_config); } struct action_data *action_data = input_action->action; json_object_object_add(action_config, "enable", json_object_new_boolean(action_data->enable)); switch (action_data->type) { case ACTION_TYPE_DBUS_ACTION: json_object_object_add(action_config, "actiontype", json_object_new_string("dbus")); const char *bustype = action_data->data.dbus.type == DBUS_TYPE_SESSION ? "session" : "system"; json_object_object_add(action_config, "bustype", json_object_new_string(bustype)); json_object_object_add(action_config, "service", json_object_new_string(action_data->data.dbus.service)); json_object_object_add(action_config, "path", json_object_new_string(action_data->data.dbus.path)); json_object_object_add(action_config, "interface", json_object_new_string(action_data->data.dbus.interface)); json_object_object_add(action_config, "method", json_object_new_string(action_data->data.dbus.method)); break; case ACTION_TYPE_RUN_COMMAND: json_object_object_add(action_config, "actiontype", json_object_new_string("command")); json_object_object_add(action_config, "command", json_object_new_string(action_data->data.command.cmd)); break; case ACTION_TYPE_SEND_BUTTON: json_object_object_add(action_config, "actiontype", json_object_new_string("button")); const char *btnstr = btncode_to_str(action_data->data.button.val); if (btnstr) { json_object_object_add(action_config, "button", json_object_new_string(btnstr)); } break; case ACTION_TYPE_SEND_KEY: json_object_object_add(action_config, "actiontype", json_object_new_string("key")); char *keystr = keycodes_to_str(action_data->data.key.modifiers); if (keystr) { json_object_object_add(action_config, "modifiers", json_object_new_string(keystr)); free(keystr); } keystr = keycodes_to_str(action_data->data.key.keys); if (keystr) { json_object_object_add(action_config, "keys", json_object_new_string(keystr)); free(keystr); } break; default: break; } if (action_data->desc) { json_object_object_add(action_config, "desc", json_object_new_string(action_data->desc)); } return true; } static struct action_data *action_data_create_from_config(json_object *action_config) { struct action_data *action_data = calloc(1, sizeof(*action_data)); if (!action_data) { return NULL; } json_object *data; if (json_object_object_get_ex(action_config, "desc", &data)) { action_data->desc = json_object_get_string(data); } enum action_type type = ACTION_TYPE_NONE; if (json_object_object_get_ex(action_config, "actiontype", &data)) { const char *type_str = json_object_get_string(data); if (strcmp(type_str, "dbus") == 0) { type = ACTION_TYPE_DBUS_ACTION; } else if (strcmp(type_str, "command") == 0) { type = ACTION_TYPE_RUN_COMMAND; } else if (strcmp(type_str, "button") == 0) { type = ACTION_TYPE_SEND_BUTTON; } else if (strcmp(type_str, "key") == 0) { type = ACTION_TYPE_SEND_KEY; } action_data->type = type; } if (json_object_object_get_ex(action_config, "type", &data)) { const char *type_str = json_object_get_string(data); action_data->binding_type = kywc_key_binding_type_by_name(type_str, NULL); } switch (type) { case ACTION_TYPE_DBUS_ACTION: if (json_object_object_get_ex(action_config, "bustype", &data)) { const char *bustype = json_object_get_string(data); if (strcmp(bustype, "session") == 0) { action_data->data.dbus.type = DBUS_TYPE_SESSION; } else { action_data->data.dbus.type = DBUS_TYPE_SYSTEM; } } if (json_object_object_get_ex(action_config, "service", &data)) { action_data->data.dbus.service = strdup(json_object_get_string(data)); } if (json_object_object_get_ex(action_config, "path", &data)) { action_data->data.dbus.path = strdup(json_object_get_string(data)); } if (json_object_object_get_ex(action_config, "interface", &data)) { action_data->data.dbus.interface = strdup(json_object_get_string(data)); } if (json_object_object_get_ex(action_config, "method", &data)) { action_data->data.dbus.method = strdup(json_object_get_string(data)); } if (json_object_object_get_ex(action_config, "param_type", &data)) { action_data->data.dbus.param_type = strdup(json_object_get_string(data)); } if (json_object_object_get_ex(action_config, "param_value", &data)) { action_data->data.dbus.param_value = strdup(json_object_get_string(data)); } break; case ACTION_TYPE_RUN_COMMAND: if (json_object_object_get_ex(action_config, "command", &data)) { action_data->data.command.cmd = strdup(json_object_get_string(data)); } break; case ACTION_TYPE_SEND_BUTTON: if (json_object_object_get_ex(action_config, "button", &data)) { const char *button = json_object_get_string(data); action_data->data.button.val = btncode_map(button); } break; case ACTION_TYPE_SEND_KEY: if (json_object_object_get_ex(action_config, "modifiers", &data)) { const char *modifiers = json_object_get_string(data); action_data->data.key.modifiers = keycodes_create(modifiers); } if (json_object_object_get_ex(action_config, "keys", &data)) { const char *keys = json_object_get_string(data); action_data->data.key.keys = keycodes_create(keys); } break; default: break; } if (json_object_object_get_ex(action_config, "enable", &data)) { action_data->enable = json_object_get_boolean(data); } return action_data; } static int add_input_action(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct input_action_manager *manager = userdata; const char *input_type = NULL, *input_bindings = NULL; const char *action_desc = NULL, *action_dat = NULL; const char *binding_type = NULL; CK(sd_bus_message_read(m, "sssss", &input_type, &input_bindings, &action_desc, &action_dat, &binding_type)); enum input_type itype; if (strcmp(input_type, "keyboard") == 0) { itype = INPUT_TYPE_KEYBOARD; } else if (strcmp(input_type, "gesture") == 0) { itype = INPUT_TYPE_GESTURE; } else { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input_type."); return sd_bus_reply_method_error(m, &error); } void *binding = itype == INPUT_TYPE_KEYBOARD ? (void *)kywc_key_binding_create(input_bindings, action_desc) : (void *)kywc_gesture_binding_create_by_string(input_bindings, action_desc); if (!binding) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input_bindings."); return sd_bus_reply_method_error(m, &error); } struct action_data *action_data = action_data_create_from_bus_string(action_dat); if (!action_data) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid action_data."); return sd_bus_reply_method_error(m, &error); } struct input_action *input_action = calloc(1, sizeof(*input_action)); input_action->type = itype; input_action->bindings = strdup(input_bindings); input_action->action = action_data; action_data->desc = strdup(action_desc); if (itype == INPUT_TYPE_KEYBOARD) { action_data->binding_type = kywc_key_binding_type_by_name(binding_type, NULL); } if (action_data->enable) { bool ret = itype == INPUT_TYPE_KEYBOARD ? kywc_key_binding_register(binding, action_data->binding_type, input_manager_keybinding_action, input_action) : kywc_gesture_binding_register(binding, input_manager_gesturebinding_action, input_action); if (!ret) { itype == INPUT_TYPE_KEYBOARD ? kywc_key_binding_destroy(binding) : kywc_gesture_binding_destroy(binding); wl_list_init(&input_action->link); input_action_destroy(input_action); const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Failed register input_bindings."); return sd_bus_reply_method_error(m, &error); } } input_action->data = binding; struct input_action *old, *temp; wl_list_for_each_safe(old, temp, &manager->actions, link) { if (strcmp(old->bindings, input_action->bindings) == 0) { input_action_destroy(old); } } input_action_manager_write_config(manager, input_action); wl_list_insert(&manager->actions, &input_action->link); return sd_bus_reply_method_return(m, NULL); } static int control_input_action(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct input_action_manager *manager = userdata; const char *control_type = NULL, *input_bindings = NULL; CK(sd_bus_message_read(m, "ss", &control_type, &input_bindings)); enum control_type ctype; if (strcmp(control_type, "delete") == 0) { ctype = CONTROL_TYPE_DELETE; } else if (strcmp(control_type, "disable") == 0) { ctype = CONTROL_TYPE_DISABLE; } else if (strcmp(control_type, "enable") == 0) { ctype = CONTROL_TYPE_ENABLE; } else { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid control_type."); return sd_bus_reply_method_error(m, &error); } bool found = false; struct input_action *input_action = NULL; wl_list_for_each(input_action, &manager->actions, link) { if (strcmp(input_bindings, input_action->bindings)) { continue; } found = true; break; } if (!found) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input_bindings."); return sd_bus_reply_method_error(m, &error); } switch (ctype) { case CONTROL_TYPE_DELETE: if (input_action->type == INPUT_TYPE_KEYBOARD && input_action->data) { kywc_key_binding_destroy(input_action->data); } else if (input_action->type == INPUT_TYPE_GESTURE && input_action->data) { kywc_gesture_binding_destroy(input_action->data); } input_action_manager_delete_config(manager, input_action); input_action_destroy(input_action); break; case CONTROL_TYPE_DISABLE: if (!input_action->action->enable) { break; } if (input_action->type == INPUT_TYPE_KEYBOARD && input_action->data) { kywc_key_binding_destroy(input_action->data); } else if (input_action->type == INPUT_TYPE_GESTURE && input_action->data) { kywc_gesture_binding_destroy(input_action->data); } input_action->data = NULL; input_action->action->enable = false; input_action_manager_write_config(manager, input_action); break; case CONTROL_TYPE_ENABLE: if (input_action->action->enable) { break; } if (input_action->type == INPUT_TYPE_KEYBOARD) { if (!input_action->data) { input_action->data = kywc_key_binding_create(input_action->bindings, input_action->action->desc); } if (input_action->data) { if (!kywc_key_binding_register(input_action->data, input_action->action->binding_type, input_manager_keybinding_action, input_action)) { kywc_key_binding_destroy(input_action->data); input_action->data = NULL; } } } else if (input_action->type == INPUT_TYPE_GESTURE) { if (!input_action->data) { input_action->data = kywc_gesture_binding_create_by_string( input_action->bindings, input_action->action->desc); } if (input_action->data) { if (!kywc_gesture_binding_register( input_action->data, input_manager_gesturebinding_action, input_action)) { kywc_key_binding_destroy(input_action->data); input_action->data = NULL; } } } input_action->action->enable = true; input_action_manager_write_config(manager, input_action); break; default: break; } return sd_bus_reply_method_return(m, NULL); } static const sd_bus_vtable service_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("ListAllActions", "", "a(ss)", list_input_actions, 0), SD_BUS_METHOD("AddAction", "sssss", "", add_input_action, 0), SD_BUS_METHOD("ControlAction", "ss", "", control_input_action, 0), SD_BUS_VTABLE_END, }; static void handle_server_destroy(struct wl_listener *listener, void *data) { struct input_action_manager *manager = wl_container_of(listener, manager, server_destroy); struct input_action *action, *temp; wl_list_for_each_safe(action, temp, &manager->actions, link) { input_action_destroy(action); } wl_list_remove(&manager->server_destroy.link); wl_list_remove(&manager->actions); free(manager); } static void input_action_create_with_keyboard(struct input_action_manager *manager, json_object *keyboard_obj, bool user) { json_object_object_foreach(keyboard_obj, keybind, action_config) { struct action_data *action_data = action_data_create_from_config(action_config); if (!action_data) { continue; } struct input_action *input_action = calloc(1, sizeof(*input_action)); input_action->bindings = strdup(keybind); input_action->type = INPUT_TYPE_KEYBOARD; input_action->action = action_data; kywc_log(KYWC_DEBUG, "Input_action keybind: %s", keybind); wl_list_insert(&manager->actions, &input_action->link); if (!action_data->enable) { continue; } struct key_binding *binding = kywc_key_binding_create(keybind, action_data->desc); if (!binding) { continue; } if (!kywc_key_binding_register(binding, action_data->binding_type, input_manager_keybinding_action, input_action)) { kywc_log(KYWC_DEBUG, "Key_binding registion failed"); if (user) { json_object_object_del(keyboard_obj, keybind); } kywc_key_binding_destroy(binding); binding = NULL; } input_action->data = binding; } } static void input_action_create_with_gesture(struct input_action_manager *manager, json_object *gesture_obj, bool user) { json_object_object_foreach(gesture_obj, gesture, action_config) { struct action_data *action_data = action_data_create_from_config(action_config); if (!action_data) { continue; } struct input_action *input_action = calloc(1, sizeof(*input_action)); input_action->bindings = strdup(gesture); input_action->type = INPUT_TYPE_GESTURE; input_action->action = action_data; kywc_log(KYWC_DEBUG, "Input_action gesture bind: %s", gesture); wl_list_insert(&manager->actions, &input_action->link); if (!action_data->enable) { continue; } struct gesture_binding *binding = kywc_gesture_binding_create_by_string(gesture, action_data->desc); if (!binding) { continue; } if (!kywc_gesture_binding_register(binding, input_manager_gesturebinding_action, input_action)) { kywc_log(KYWC_DEBUG, "Gesture_binding registion failed"); if (user) { json_object_object_del(gesture_obj, gesture); } kywc_gesture_binding_destroy(binding); binding = NULL; } input_action->data = binding; } } static bool input_action_manager_read_config(struct input_action_manager *manager) { if (!manager->config || !manager->config->json) { return false; } json_object *data; /* get system default config */ if (manager->config->sys_json && json_object_object_get_ex(manager->config->sys_json, "keyboard", &data)) { input_action_create_with_keyboard(manager, data, false); } if (manager->config->sys_json && json_object_object_get_ex(manager->config->sys_json, "gesture", &data)) { input_action_create_with_gesture(manager, data, false); } /* get user config */ if (manager->config->json && json_object_object_get_ex(manager->config->json, "keyboard", &data)) { input_action_create_with_keyboard(manager, data, true); } if (json_object_object_get_ex(manager->config->json, "gesture", &data)) { input_action_create_with_gesture(manager, data, true); } return true; } static bool input_action_manager_config_init(struct input_action_manager *manager) { manager->config = config_manager_add_config("InputAction"); if (!manager->config) { return false; } return dbus_register_object(NULL, service_path, service_interface, service_vtable, manager); } bool input_action_manager_create(struct server *server) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->server = server; wl_list_init(&manager->actions); input_action_manager_config_init(manager); input_action_manager_read_config(manager); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); return true; } kylin-wayland-compositor/src/input/idle.c0000664000175000017500000002750515160461067017505 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "ext-idle-notify-v1-protocol.h" #include "idle-protocol.h" #include "input/seat.h" #include "input_p.h" #include "server.h" #define IDLE_NOTIFIER_VERSION 1 #define ORG_KDE_KWIN_IDLE_VERSION 1 struct idle_manager { struct wl_display *display; struct wl_global *kde_idle_global; struct wl_global *idle_notifier_global; struct wl_list idles; bool inhibited; struct wl_listener display_destroy; struct wl_listener server_destroy; }; enum idle_type { IDLE_FROM_KDE_IDLE, IDLE_FROM_IDLE_NOTIFIER, IDLE_FROM_SERVER, }; struct idle { enum idle_type type; struct wl_list link; uint32_t timeout_ms; struct wl_event_source *timer; bool idle_state; bool support_inhibit; struct seat *seat; struct wl_listener seat_destroy; void (*idle_func)(struct idle *idle, void *data); void (*resume_func)(struct idle *idle, void *data); void (*destroy_func)(struct idle *idle, void *data); void *data; // resource }; static struct idle_manager *idle_manager = NULL; static void idle_set_idle(struct idle *idle, bool idle_state) { if (idle->idle_state == idle_state) { return; } if (idle_state) { idle->idle_func(idle, idle->data); } else { idle->resume_func(idle, idle->data); } idle->idle_state = idle_state; } static void idle_notifier_resource_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static int idle_handle_timer(void *data) { struct idle *idle = data; idle_set_idle(idle, true); return 0; } void idle_destroy(struct idle *idle) { if (!idle) { return; } if (idle->destroy_func) { idle->destroy_func(idle, idle->data); } wl_list_remove(&idle->link); wl_list_remove(&idle->seat_destroy.link); if (idle->timer) { wl_event_source_remove(idle->timer); } if (idle->type != IDLE_FROM_SERVER) { wl_resource_set_user_data(idle->data, NULL); } free(idle); } static void idle_reset_timer(struct idle *idle) { if (idle_manager->inhibited && idle->support_inhibit) { idle_set_idle(idle, false); if (idle->timer) { wl_event_source_timer_update(idle->timer, 0); } return; } if (idle->timer) { wl_event_source_timer_update(idle->timer, idle->timeout_ms); } else { idle_set_idle(idle, true); } } static void idle_handle_seat_destroy(struct wl_listener *listener, void *data) { struct idle *idle = wl_container_of(listener, idle, seat_destroy); idle_destroy(idle); } static struct idle *idle_create(enum idle_type type, struct seat *seat, bool support_inhibit, uint32_t timeout, void (*idle_func)(struct idle *idle, void *data), void (*resume_func)(struct idle *idle, void *data), void (*destroy_func)(struct idle *idle, void *data), void *data) { struct idle *idle = calloc(1, sizeof(*idle)); if (!idle) { return NULL; } idle->type = type; idle->timeout_ms = timeout; idle->support_inhibit = support_inhibit; idle->seat = seat; idle->idle_func = idle_func; idle->resume_func = resume_func; idle->destroy_func = destroy_func; idle->data = data; if (timeout > 0) { struct wl_event_loop *loop = wl_display_get_event_loop(idle_manager->display); idle->timer = wl_event_loop_add_timer(loop, idle_handle_timer, idle); if (!idle->timer) { free(idle); return NULL; } } wl_list_insert(&idle_manager->idles, &idle->link); idle->seat_destroy.notify = idle_handle_seat_destroy; wl_signal_add(&seat->events.destroy, &idle->seat_destroy); return idle; } static void idle_notification_destroy(struct wl_resource *resource) { struct idle *idle = wl_resource_get_user_data(resource); idle_destroy(idle); } static const struct ext_idle_notification_v1_interface notification_impl = { .destroy = idle_notifier_resource_destroy, }; static void idle_notification_idle(struct idle *idle, void *data) { struct wl_resource *resource = data; ext_idle_notification_v1_send_idled(resource); } static void idle_notification_resume(struct idle *idle, void *data) { struct wl_resource *resource = data; ext_idle_notification_v1_send_resumed(resource); } static void idle_notifier_get_idle_notification(struct wl_client *client, struct wl_resource *notifier_resource, uint32_t id, uint32_t timeout, struct wl_resource *seat_resource) { uint32_t version = wl_resource_get_version(notifier_resource); struct wl_resource *resource = wl_resource_create(client, &ext_idle_notification_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, ¬ification_impl, NULL, idle_notification_destroy); struct seat *seat = seat_from_resource(seat_resource); if (!seat) { return; // leave the resource inert } struct idle *idle = idle_create(IDLE_FROM_IDLE_NOTIFIER, seat, true, timeout, idle_notification_idle, idle_notification_resume, NULL, resource); if (!idle) { wl_client_post_no_memory(client); return; } wl_resource_set_user_data(resource, idle); idle_reset_timer(idle); } static const struct ext_idle_notifier_v1_interface idle_notifier_impl = { .destroy = idle_notifier_resource_destroy, .get_idle_notification = idle_notifier_get_idle_notification, }; static void idle_notifier_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &ext_idle_notifier_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &idle_notifier_impl, NULL, NULL); } static void idle_timeout_destroy(struct wl_resource *resource) { struct idle *idle = wl_resource_get_user_data(resource); idle_destroy(idle); } static void idle_timeout_release(struct wl_client *client, struct wl_resource *resource) { idle_timeout_destroy(resource); } static void simulate_activity(struct wl_client *client, struct wl_resource *resource) { struct idle *idle = wl_resource_get_user_data(resource); if (idle->idle_state) { idle->idle_state = false; org_kde_kwin_idle_timeout_send_resumed(idle->data); } if (idle->timer) { wl_event_source_timer_update(idle->timer, idle->timeout_ms); } else { idle_set_idle(idle, true); } } static const struct org_kde_kwin_idle_timeout_interface idle_timeout_impl = { .release = idle_timeout_release, .simulate_user_activity = simulate_activity, }; static void kde_idle_idle(struct idle *idle, void *data) { struct wl_resource *resource = data; org_kde_kwin_idle_timeout_send_idle(resource); } static void kde_idle_resume(struct idle *idle, void *data) { struct wl_resource *resource = data; org_kde_kwin_idle_timeout_send_resumed(resource); } static void kde_idle_get_idle_timeout(struct wl_client *client, struct wl_resource *idle_resource, uint32_t id, struct wl_resource *seat_resource, uint32_t timeout) { uint32_t version = wl_resource_get_version(idle_resource); struct wl_resource *resource = wl_resource_create(client, &org_kde_kwin_idle_timeout_interface, version, id); if (!resource) { wl_resource_post_no_memory(idle_resource); return; } wl_resource_set_implementation(resource, &idle_timeout_impl, NULL, idle_timeout_destroy); struct seat *seat = seat_from_resource(seat_resource); if (!seat) { return; } struct idle *idle = idle_create(IDLE_FROM_KDE_IDLE, seat, true, timeout, kde_idle_idle, kde_idle_resume, NULL, resource); if (!idle) { wl_client_post_no_memory(client); return; } wl_resource_set_user_data(resource, idle); idle_reset_timer(idle); } static const struct org_kde_kwin_idle_interface kde_idle_impl = { .get_idle_timeout = kde_idle_get_idle_timeout, }; static void kde_idle_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &org_kde_kwin_idle_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &kde_idle_impl, NULL, NULL); } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&idle_manager->display_destroy.link); if (idle_manager->idle_notifier_global) { wl_global_destroy(idle_manager->idle_notifier_global); } if (idle_manager->kde_idle_global) { wl_global_destroy(idle_manager->kde_idle_global); } } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&idle_manager->server_destroy.link); free(idle_manager); } bool idle_manager_create(struct server *server) { idle_manager = calloc(1, sizeof(*idle_manager)); if (!idle_manager) { return false; } idle_manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &idle_manager->display_destroy); idle_manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &idle_manager->server_destroy); idle_manager->idle_notifier_global = wl_global_create(server->display, &ext_idle_notifier_v1_interface, IDLE_NOTIFIER_VERSION, idle_manager, idle_notifier_bind); if (!idle_manager->idle_notifier_global) { kywc_log(KYWC_WARN, "Failed to create %s global", ext_idle_notifier_v1_interface.name); } idle_manager->idle_notifier_global = wl_global_create(server->display, &org_kde_kwin_idle_interface, ORG_KDE_KWIN_IDLE_VERSION, idle_manager, kde_idle_bind); if (!idle_manager->idle_notifier_global) { kywc_log(KYWC_WARN, "Failed to create %s global", org_kde_kwin_idle_interface.name); } idle_manager->display = server->display; wl_list_init(&idle_manager->idles); return true; } void idle_manager_set_inhibited(bool inhibited) { if (idle_manager->inhibited == inhibited) { return; } idle_manager->inhibited = inhibited; struct idle *idle; wl_list_for_each(idle, &idle_manager->idles, link) { if (idle->support_inhibit) { idle_reset_timer(idle); } } } void idle_manager_notify_activity(struct seat *seat) { struct idle *idle; wl_list_for_each(idle, &idle_manager->idles, link) { if (idle->support_inhibit && idle_manager->inhibited) { continue; } if (idle->seat == seat) { idle_set_idle(idle, false); idle_reset_timer(idle); } } } struct idle *idle_manager_add_idle(struct seat *seat, bool support_inhibit, uint32_t timeout, void (*idle_func)(struct idle *idle, void *data), void (*resume_func)(struct idle *idle, void *data), void (*destroy_func)(struct idle *idle, void *data), void *data) { struct idle *idle = idle_create(IDLE_FROM_SERVER, seat, support_inhibit, timeout, idle_func, resume_func, destroy_func, data); if (!idle) { return NULL; } idle_reset_timer(idle); return idle; } kylin-wayland-compositor/src/input/keyboard_group.c0000664000175000017500000002425515160460057021601 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include "input/keyboard.h" #include "input/keyboard_group.h" struct keyboard_group_device { struct wlr_keyboard *keyboard; struct wl_listener key; struct wl_listener modifiers; struct wl_listener keymap; struct wl_listener repeat_info; struct wl_listener destroy; struct wl_list link; // keyboard_group.devices }; struct keyboard_group_key { uint32_t keycode; size_t count; struct wl_list link; // keyboard_group.keys }; static void keyboard_group_set_leds(struct wlr_keyboard *kb, uint32_t leds) { struct keyboard_group *group = keyboard_group_from_wlr_keyboard(kb); uint32_t fixed_leds = leds; if (group->scroll_lock > 0) { fixed_leds |= WLR_LED_SCROLL_LOCK; } else { fixed_leds &= ~WLR_LED_SCROLL_LOCK; } struct keyboard_group_device *device; wl_list_for_each(device, &group->devices, link) { if (group->scroll_lock_led_on) { device->keyboard->leds |= WLR_LED_SCROLL_LOCK; } wlr_keyboard_led_update(device->keyboard, fixed_leds); device->keyboard->leds &= ~WLR_LED_SCROLL_LOCK; } group->scroll_lock_led_on = group->scroll_lock > 0; } static const struct wlr_keyboard_impl impl = { .name = "keyboard-group", .led_update = keyboard_group_set_leds, }; struct keyboard_group *keyboard_group_create(void) { struct keyboard_group *group = calloc(1, sizeof(*group)); if (!group) { kywc_log(KYWC_ERROR, "Failed to allocate keyboard_group"); return NULL; } wlr_keyboard_init(&group->keyboard, &impl, "keyboard_group"); wl_list_init(&group->devices); wl_list_init(&group->keys); return group; } struct keyboard_group *keyboard_group_from_wlr_keyboard(struct wlr_keyboard *keyboard) { if (keyboard->impl != &impl) { return NULL; } struct keyboard_group *group = wl_container_of(keyboard, group, keyboard); return group; } static bool process_key(struct keyboard_group_device *group_device, struct wlr_keyboard_key_event *event) { struct keyboard_group *group = (struct keyboard_group *)group_device->keyboard->group; struct keyboard_group_key *key, *tmp; wl_list_for_each_safe(key, tmp, &group->keys, link) { if (key->keycode != event->keycode) { continue; } if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { key->count++; return false; } if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { key->count--; if (key->count > 0) { return false; } wl_list_remove(&key->link); free(key); } break; } if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { struct keyboard_group_key *key = calloc(1, sizeof(*key)); if (!key) { kywc_log(KYWC_ERROR, "Failed to allocate keyboard_group_key"); return false; } key->keycode = event->keycode; key->count = 1; wl_list_insert(&group->keys, &key->link); } return true; } static void handle_keyboard_key(struct wl_listener *listener, void *data) { struct keyboard_group_device *group_device = wl_container_of(listener, group_device, key); struct keyboard_group *group = (struct keyboard_group *)group_device->keyboard->group; if (process_key(group_device, data)) { wlr_keyboard_notify_key(&group->keyboard, data); } } static void handle_keyboard_modifiers(struct wl_listener *listener, void *data) { // Sync the effective layout (group modifier) to all keyboards. The rest of // the modifiers will be derived from the keyboard_group's key state struct keyboard_group_device *group_device = wl_container_of(listener, group_device, modifiers); struct keyboard_group *group = (struct keyboard_group *)group_device->keyboard->group; struct wlr_keyboard_modifiers mods = group_device->keyboard->modifiers; struct keyboard_group_device *device; wl_list_for_each(device, &group->devices, link) { if (mods.depressed != device->keyboard->modifiers.depressed || mods.latched != device->keyboard->modifiers.latched || mods.locked != device->keyboard->modifiers.locked || mods.group != device->keyboard->modifiers.group) { wlr_keyboard_notify_modifiers(device->keyboard, mods.depressed, mods.latched, mods.locked, mods.group); return; } } wlr_keyboard_notify_modifiers(&group->keyboard, mods.depressed, mods.latched, mods.locked, mods.group); } static void handle_keyboard_keymap(struct wl_listener *listener, void *data) { struct keyboard_group_device *group_device = wl_container_of(listener, group_device, keymap); struct wlr_keyboard *keyboard = group_device->keyboard; struct keyboard_group *group = (struct keyboard_group *)keyboard->group; if (!keyboard_keymaps_match(&group->keyboard, keyboard)) { struct keyboard_group_device *device; wl_list_for_each(device, &group->devices, link) { if (!keyboard_keymaps_match(keyboard, device->keyboard)) { wlr_keyboard_set_keymap(device->keyboard, keyboard->keymap); return; } } } wlr_keyboard_set_keymap(&group->keyboard, keyboard->keymap); } static void handle_keyboard_repeat_info(struct wl_listener *listener, void *data) { struct keyboard_group_device *group_device = wl_container_of(listener, group_device, repeat_info); struct wlr_keyboard *keyboard = group_device->keyboard; struct keyboard_group *group = (struct keyboard_group *)keyboard->group; struct keyboard_group_device *device; wl_list_for_each(device, &group->devices, link) { struct wlr_keyboard *devkb = device->keyboard; if (devkb->repeat_info.rate != keyboard->repeat_info.rate || devkb->repeat_info.delay != keyboard->repeat_info.delay) { wlr_keyboard_set_repeat_info(devkb, keyboard->repeat_info.rate, keyboard->repeat_info.delay); return; } } wlr_keyboard_set_repeat_info(&group->keyboard, keyboard->repeat_info.rate, keyboard->repeat_info.delay); } static void remove_keyboard_group_device(struct keyboard_group_device *device) { device->keyboard->group = NULL; wl_list_remove(&device->link); wl_list_remove(&device->key.link); wl_list_remove(&device->modifiers.link); wl_list_remove(&device->keymap.link); wl_list_remove(&device->repeat_info.link); wl_list_remove(&device->destroy.link); free(device); } static void handle_keyboard_destroy(struct wl_listener *listener, void *data) { struct keyboard_group_device *device = wl_container_of(listener, device, destroy); remove_keyboard_group_device(device); } bool keyboard_group_add_keyboard(struct keyboard_group *group, struct wlr_keyboard *keyboard) { if (keyboard->group) { kywc_log(KYWC_ERROR, "A wlr_keyboard can only belong to one group"); return false; } if (keyboard->impl == &impl) { kywc_log(KYWC_ERROR, "Cannot add a group's keyboard to a group"); return false; } if (!keyboard_keymaps_match(&group->keyboard, keyboard)) { kywc_log(KYWC_ERROR, "Device keymap does not match keyboard group's"); return false; } struct keyboard_group_device *device = calloc(1, sizeof(*device)); if (!device) { kywc_log(KYWC_ERROR, "Failed to allocate keyboard_group_device"); return false; } device->keyboard = keyboard; keyboard->group = (struct wlr_keyboard_group *)group; wl_list_insert(&group->devices, &device->link); wl_signal_add(&keyboard->events.key, &device->key); device->key.notify = handle_keyboard_key; wl_signal_add(&keyboard->events.modifiers, &device->modifiers); device->modifiers.notify = handle_keyboard_modifiers; wl_signal_add(&keyboard->events.keymap, &device->keymap); device->keymap.notify = handle_keyboard_keymap; wl_signal_add(&keyboard->events.repeat_info, &device->repeat_info); device->repeat_info.notify = handle_keyboard_repeat_info; wl_signal_add(&keyboard->base.events.destroy, &device->destroy); device->destroy.notify = handle_keyboard_destroy; struct wlr_keyboard *group_kb = &group->keyboard; wlr_keyboard_set_repeat_info(keyboard, group_kb->repeat_info.rate, group_kb->repeat_info.delay); /* sync the group modifiers to keyboard */ wlr_keyboard_notify_modifiers(keyboard, group_kb->modifiers.depressed, group_kb->modifiers.latched, group_kb->modifiers.locked, group_kb->modifiers.group); /* force sync leds to keyboard */ group->scroll_lock_led_on = false; keyboard_group_set_leds(group_kb, group_kb->leds); return true; } void keyboard_group_remove_keyboard(struct keyboard_group *group, struct wlr_keyboard *keyboard) { struct keyboard_group_device *device, *tmp; wl_list_for_each_safe(device, tmp, &group->devices, link) { if (device->keyboard == keyboard) { remove_keyboard_group_device(device); return; } } kywc_log(KYWC_ERROR, "keyboard not found in group"); } void keyboard_group_destroy(struct keyboard_group *group) { struct keyboard_group_device *device, *tmp; wl_list_for_each_safe(device, tmp, &group->devices, link) { keyboard_group_remove_keyboard(group, device->keyboard); } wlr_keyboard_finish(&group->keyboard); free(group); } struct wlr_keyboard *keyboard_group_pick_keyboard(struct keyboard_group *group) { if (wl_list_empty(&group->devices)) { return &group->keyboard; } struct keyboard_group_device *device = wl_container_of(group->devices.next, device, link); return device->keyboard; } kylin-wayland-compositor/src/input/transient_seat.c0000664000175000017500000001221015160460057021574 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "ext-transient-seat-v1-protocol.h" #include "input_p.h" #include "server.h" struct transient_seat_manager { struct wl_global *global; struct wl_listener display_destroy; struct wl_listener server_destroy; struct input_manager *input_manager; }; struct transient_seat { struct wl_resource *resource; struct seat *seat; struct wl_listener seat_destroy; }; static void transient_seat_destroy(struct transient_seat *seat) { wl_list_remove(&seat->seat_destroy.link); if (seat->seat) { seat_destroy(seat->seat); } free(seat); } static void transient_seat_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct ext_transient_seat_v1_interface transient_seat_impl = { .destroy = transient_seat_handle_destroy, }; static void transient_seat_handle_resource_destroy(struct wl_resource *resource) { struct transient_seat *seat = wl_resource_get_user_data(resource); if (seat) { transient_seat_destroy(seat); } } static void transient_seat_handle_seat_destroy(struct wl_listener *listener, void *data) { struct transient_seat *seat = wl_container_of(listener, seat, seat_destroy); wl_resource_set_user_data(seat->resource, NULL); seat->seat = NULL; transient_seat_destroy(seat); } static void manager_handle_create(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id) { struct transient_seat *seat = calloc(1, sizeof(*seat)); if (!seat) { wl_client_post_no_memory(client); return; } int version = wl_resource_get_version(manager_resource); seat->resource = wl_resource_create(client, &ext_transient_seat_v1_interface, version, id); if (!seat->resource) { free(seat); wl_client_post_no_memory(client); return; } wl_resource_set_implementation(seat->resource, &transient_seat_impl, seat, transient_seat_handle_resource_destroy); // create seat and send ready or denied static uint64_t i = 0; char name[256]; snprintf(name, sizeof(name), "transient-%" PRIx64, i++); struct transient_seat_manager *manager = wl_resource_get_user_data(manager_resource); seat->seat = seat_create(manager->input_manager, name); if (!seat->seat) { wl_list_init(&seat->seat_destroy.link); ext_transient_seat_v1_send_denied(seat->resource); return; } seat->seat_destroy.notify = transient_seat_handle_seat_destroy; wl_signal_add(&seat->seat->events.destroy, &seat->seat_destroy); uint32_t global_name = wl_global_get_name(seat->seat->wlr_seat->global, client); ext_transient_seat_v1_send_ready(seat->resource, global_name); } static void manager_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct ext_transient_seat_manager_v1_interface transient_seat_manager_impl = { .create = manager_handle_create, .destroy = manager_handle_destroy, }; static void transient_seat_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &ext_transient_seat_manager_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } struct transient_seat_manager *manager = data; wl_resource_set_implementation(resource, &transient_seat_manager_impl, manager, NULL); } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct transient_seat_manager *manager = wl_container_of(listener, manager, server_destroy); wl_list_remove(&manager->server_destroy.link); free(manager); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct transient_seat_manager *manager = wl_container_of(listener, manager, display_destroy); wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); } bool transient_seat_manager_create(struct input_manager *input_manager) { struct transient_seat_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } struct server *server = input_manager->server; manager->input_manager = input_manager; manager->global = wl_global_create(server->display, &ext_transient_seat_manager_v1_interface, 1, manager, transient_seat_manager_bind); if (!manager->global) { kywc_log(KYWC_WARN, "Failed to create ext_transient_seat_manager_v1"); free(manager); return false; } manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &manager->display_destroy); return true; } kylin-wayland-compositor/src/input/config.c0000664000175000017500000012031615160460057020025 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include "config.h" #include "input_p.h" #include "util/dbus.h" static const char *service_input_path = "/com/kylin/Wlcom/Input"; static const char *service_input_interface = "com.kylin.Wlcom.Input"; static const char *service_seat_path = "/com/kylin/Wlcom/Seat"; static const char *service_seat_interface = "com.kylin.Wlcom.Seat"; static const char *input_type_map[] = { [WLR_INPUT_DEVICE_KEYBOARD] = "keyboard", [WLR_INPUT_DEVICE_POINTER] = "pointer", [WLR_INPUT_DEVICE_TOUCH] = "touch", [WLR_INPUT_DEVICE_TABLET] = "table-tool", [WLR_INPUT_DEVICE_TABLET_PAD] = "table-pad", [WLR_INPUT_DEVICE_SWITCH] = "switch", }; void input_prop_and_state_debug(struct input *input) { struct input_prop *prop = &input->prop; struct input_state *default_state = &input->default_state; struct input_state *state = &input->state; kywc_log(KYWC_DEBUG, "Input %s(%s) prop debug", input->name, input_type_map[prop->type]); kywc_log(KYWC_DEBUG, "\tsend_event_modes = %d", prop->send_events_modes); kywc_log(KYWC_DEBUG, "\t\tsend_events_mode = %d (%d)", state->send_events_mode, default_state->send_events_mode); if (prop->click_methods != 0) { kywc_log(KYWC_DEBUG, "\tclick_methods = %d", prop->click_methods); kywc_log(KYWC_DEBUG, "\t\tclick_method = %d (%d)", state->click_method, default_state->click_method); } if (prop->tap_finger_count > 0) { kywc_log(KYWC_DEBUG, "\ttap_finger_count = %d", prop->tap_finger_count); kywc_log(KYWC_DEBUG, "\t\ttap_to_click = %d (%d)", state->tap_to_click, default_state->tap_to_click); kywc_log(KYWC_DEBUG, "\t\ttap_button_map = %d (%d)", state->tap_button_map, default_state->tap_button_map); kywc_log(KYWC_DEBUG, "\t\ttap_and_drag = %d (%d)", state->tap_and_drag, default_state->tap_and_drag); kywc_log(KYWC_DEBUG, "\t\ttap_drag_lock = %d (%d)", state->tap_drag_lock, default_state->tap_drag_lock); } if (prop->scroll_methods != 0) { kywc_log(KYWC_DEBUG, "\tscroll_methods = %d", prop->scroll_methods); kywc_log(KYWC_DEBUG, "\t\tscroll_method = %d (%d)", state->scroll_method, default_state->scroll_method); if (prop->scroll_methods & 0x4) { kywc_log(KYWC_DEBUG, "\t\tscroll_button = %d (%d)", state->scroll_button, default_state->scroll_button); kywc_log(KYWC_DEBUG, "\t\tscroll_button_lock = %d (%d)", state->scroll_button_lock, default_state->scroll_button_lock); } } if (prop->has_pointer_accel) { kywc_log(KYWC_DEBUG, "\thas_pointer_accel = %d", prop->has_pointer_accel); kywc_log(KYWC_DEBUG, "\t\tpointer_accel_speed = %f (%f)", state->pointer_accel_speed, default_state->pointer_accel_speed); kywc_log(KYWC_DEBUG, "\taccel_profiles = %d", prop->accel_profiles); kywc_log(KYWC_DEBUG, "\t\taccel_profile = %d (%d)", state->accel_profile, default_state->accel_profile); } if (prop->has_calibration_matrix) { kywc_log(KYWC_DEBUG, "\thas_calibration_matrix = %d", prop->has_calibration_matrix); kywc_log(KYWC_DEBUG, "\t\tcalibration_set_matrix(%f, %f, %f, %f, %f, %f) (%f, %f, %f, %f, %f, %f)", state->calibration_matrix[0], state->calibration_matrix[1], state->calibration_matrix[2], state->calibration_matrix[3], state->calibration_matrix[4], state->calibration_matrix[5], default_state->calibration_matrix[0], default_state->calibration_matrix[1], default_state->calibration_matrix[2], default_state->calibration_matrix[3], default_state->calibration_matrix[4], default_state->calibration_matrix[5]); } if (prop->has_natural_scroll) { kywc_log(KYWC_DEBUG, "\thas_natural_scroll = %d", prop->has_natural_scroll); kywc_log(KYWC_DEBUG, "\t\tnatural_scroll = %d (%d)", state->natural_scroll, default_state->natural_scroll); } if (prop->has_left_handed) { kywc_log(KYWC_DEBUG, "\thas_left_handed = %d", prop->has_left_handed); kywc_log(KYWC_DEBUG, "\t\tleft_handed = %d (%d)", state->left_handed, default_state->left_handed); } if (prop->has_middle_emulation) { kywc_log(KYWC_DEBUG, "\thas_middle_emulation = %d", prop->has_middle_emulation); kywc_log(KYWC_DEBUG, "\t\tmiddle_emulation = %d (%d)", state->middle_emulation, state->middle_emulation); } if (prop->has_dwt) { kywc_log(KYWC_DEBUG, "\thas_dwt = %d", prop->has_dwt); kywc_log(KYWC_DEBUG, "\t\tdwt = %d (%d)", state->dwt, default_state->dwt); } if (prop->has_dwtp) { kywc_log(KYWC_DEBUG, "\thas_dwtp = %d", prop->has_dwtp); kywc_log(KYWC_DEBUG, "\t\tdwtp = %d (%d)", state->dwtp, default_state->dwtp); } if (prop->has_rotation) { kywc_log(KYWC_DEBUG, "\thas_rotation = %d", prop->has_rotation); kywc_log(KYWC_DEBUG, "\t\trotation_angle = %d (%d)", state->rotation_angle, default_state->rotation_angle); } } static int list_inputs(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct input_manager *manager = userdata; sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(m, &reply)); CK(sd_bus_message_open_container(reply, 'a', "(su)")); struct input *input; wl_list_for_each(input, &manager->inputs, link) { if (input->prop.is_virtual) { continue; } sd_bus_message_append(reply, "(su)", input->name, input->prop.prop); } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } static int map_to_output(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL, *output_name = NULL; CK(sd_bus_message_read(m, "ss", &input_name, &output_name)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } bool none_output = !strcmp(output_name, "none"); if (!none_output) { struct kywc_output *kywc_output = kywc_output_by_name(output_name); if (!kywc_output || !kywc_output->state.enabled) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid output or disabled."); return sd_bus_reply_method_error(m, &error); } } const char *current = input->mapped_output ? input->state.mapped_to_output : NULL; if (input->prop.support_mapped_to_output && (!current || strcmp(current, output_name))) { struct input_state state = input->state; state.mapped_to_output = none_output ? NULL : output_name; input_set_state(input, &state); } return sd_bus_reply_method_return(m, NULL); } static int change_seat(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL, *seat_name = NULL; CK(sd_bus_message_read(m, "ss", &input_name, &seat_name)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } if (strncmp(seat_name, "seat", 4)) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid seat."); return sd_bus_reply_method_error(m, &error); } if (strcmp(input->state.seat, seat_name)) { struct input_state state = input->state; state.seat = seat_name; input_set_state(input, &state); } return sd_bus_reply_method_return(m, NULL); } static int get_send_events(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; CK(sd_bus_message_read(m, "s", &input_name)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } return sd_bus_reply_method_return(m, "uu", input->state.send_events_mode, input->default_state.send_events_mode); } static int set_send_events(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; uint32_t mode = 0; CK(sd_bus_message_read(m, "su", &input_name, &mode)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } if (input->prop.send_events_modes < mode) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid mode."); return sd_bus_reply_method_error(m, &error); } if (input->state.send_events_mode != mode) { struct input_state state = input->state; state.send_events_mode = mode; input_set_state(input, &state); } return sd_bus_reply_method_return(m, NULL); } static int get_tap_to_click(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; CK(sd_bus_message_read(m, "s", &input_name)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } return sd_bus_reply_method_return(m, "bb", input->state.tap_to_click, input->default_state.tap_to_click); } static int enable_tap_to_click(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; int32_t enabled = 0; CK(sd_bus_message_read(m, "sb", &input_name, &enabled)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } if (input->prop.tap_finger_count && input->state.tap_to_click != enabled) { struct input_state state = input->state; state.tap_to_click = enabled; input_set_state(input, &state); } return sd_bus_reply_method_return(m, NULL); } static int get_tap_and_drag(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; CK(sd_bus_message_read(m, "s", &input_name)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } return sd_bus_reply_method_return(m, "bb", input->state.tap_and_drag, input->default_state.tap_and_drag); } static int enable_tap_and_drag(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; int32_t enabled = 0; CK(sd_bus_message_read(m, "sb", &input_name, &enabled)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } if (input->prop.tap_finger_count && input->state.tap_and_drag != enabled) { struct input_state state = input->state; state.tap_and_drag = enabled; input_set_state(input, &state); } return sd_bus_reply_method_return(m, NULL); } static int get_pointer_speed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; CK(sd_bus_message_read(m, "s", &input_name)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } return sd_bus_reply_method_return(m, "dd", input->state.pointer_accel_speed, input->default_state.pointer_accel_speed); } static int set_pointer_speed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; double speed = 0.0f; CK(sd_bus_message_read(m, "sd", &input_name, &speed)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } if (speed < -1.0f || speed > 1.0f) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid speed."); return sd_bus_reply_method_error(m, &error); } if (input->prop.has_pointer_accel && input->state.pointer_accel_speed != speed) { struct input_state state = input->state; state.pointer_accel_speed = speed; input_set_state(input, &state); } return sd_bus_reply_method_return(m, NULL); } static int get_accel_profile(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; CK(sd_bus_message_read(m, "s", &input_name)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } return sd_bus_reply_method_return(m, "uu", input->state.accel_profile, input->default_state.accel_profile); } static int set_accel_profile(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; uint32_t accel_profile; CK(sd_bus_message_read(m, "su", &input_name, &accel_profile)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } if (input->prop.has_pointer_accel && input->prop.accel_profiles && input->state.accel_profile != accel_profile) { struct input_state state = input->state; state.accel_profile = accel_profile; input_set_state(input, &state); } return sd_bus_reply_method_return(m, NULL); } static int get_scroll_method(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; CK(sd_bus_message_read(m, "s", &input_name)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } return sd_bus_reply_method_return(m, "uu", input->state.scroll_method, input->default_state.scroll_method); } static int set_scroll_method(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; uint32_t scroll_method; CK(sd_bus_message_read(m, "su", &input_name, &scroll_method)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } if (input->prop.scroll_methods && input->state.scroll_method != scroll_method) { struct input_state state = input->state; state.scroll_method = scroll_method; input_set_state(input, &state); } return sd_bus_reply_method_return(m, NULL); } static int get_disable_while_typing(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; CK(sd_bus_message_read(m, "s", &input_name)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } return sd_bus_reply_method_return(m, "bb", input->state.dwt, input->default_state.dwt); } static int set_disable_while_typing(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; uint32_t dwt; CK(sd_bus_message_read(m, "sb", &input_name, &dwt)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } if (input->prop.has_dwt && input->state.dwt != dwt) { struct input_state state = input->state; state.dwt = dwt; input_set_state(input, &state); } return sd_bus_reply_method_return(m, NULL); } static int get_natural_scroll(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; CK(sd_bus_message_read(m, "s", &input_name)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } return sd_bus_reply_method_return(m, "bb", input->state.natural_scroll, input->default_state.natural_scroll); } static int enable_natural_scroll(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; int32_t enabled = 0; CK(sd_bus_message_read(m, "sb", &input_name, &enabled)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } if (input->prop.has_natural_scroll && input->state.natural_scroll != enabled) { struct input_state state = input->state; state.natural_scroll = enabled; input_set_state(input, &state); } return sd_bus_reply_method_return(m, NULL); } static int get_left_handed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; CK(sd_bus_message_read(m, "s", &input_name)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } return sd_bus_reply_method_return(m, "bb", input->state.left_handed, input->default_state.left_handed); } static int enable_left_handed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; int32_t enabled = 0; CK(sd_bus_message_read(m, "sb", &input_name, &enabled)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } if (input->prop.has_left_handed && input->state.left_handed != enabled) { struct input_state state = input->state; state.left_handed = enabled; input_set_state(input, &state); } return sd_bus_reply_method_return(m, NULL); } static int get_repeat_info(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; CK(sd_bus_message_read(m, "s", &input_name)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } return sd_bus_reply_method_return(m, "iiii", input->state.repeat_rate, input->state.repeat_delay, input->default_state.repeat_rate, input->default_state.repeat_delay); } static int set_repeat_info(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; int32_t rate, delay; CK(sd_bus_message_read(m, "sii", &input_name, &rate, &delay)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } if (input->prop.type == WLR_INPUT_DEVICE_KEYBOARD && (input->state.repeat_rate != rate || input->state.repeat_delay != delay)) { struct input_state state = input->state; state.repeat_rate = rate; state.repeat_delay = delay; input_set_state(input, &state); } return sd_bus_reply_method_return(m, NULL); } static int get_scroll_factor(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; CK(sd_bus_message_read(m, "s", &input_name)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } return sd_bus_reply_method_return(m, "dd", input->state.scroll_factor, input->default_state.scroll_factor); } static int set_scroll_factor(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; double scroll_factor; CK(sd_bus_message_read(m, "sd", &input_name, &scroll_factor)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } if (input->state.scroll_factor != scroll_factor) { struct input_state state = input->state; state.scroll_factor = scroll_factor; input_set_state(input, &state); } return sd_bus_reply_method_return(m, NULL); } static int get_double_click_time(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; CK(sd_bus_message_read(m, "s", &input_name)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } return sd_bus_reply_method_return(m, "uu", input->state.double_click_time, input->default_state.double_click_time); } static int set_double_click_time(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *input_name = NULL; uint32_t double_click_time; CK(sd_bus_message_read(m, "su", &input_name, &double_click_time)); struct input *input = input_by_name(input_name); if (!input) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid input."); return sd_bus_reply_method_error(m, &error); } if (input->state.double_click_time != double_click_time) { struct input_state state = input->state; state.double_click_time = double_click_time; input_set_state(input, &state); } return sd_bus_reply_method_return(m, NULL); } static const sd_bus_vtable service_input_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("ListAllInputs", "", "a(su)", list_inputs, 0), SD_BUS_METHOD("MapToOutput", "ss", "", map_to_output, 0), SD_BUS_METHOD("ChangeSeat", "ss", "", change_seat, 0), SD_BUS_METHOD("GetSendEventsMode", "s", "uu", get_send_events, 0), SD_BUS_METHOD("SetSendEventsMode", "su", "", set_send_events, 0), SD_BUS_METHOD("GetTapToClick", "s", "bb", get_tap_to_click, 0), SD_BUS_METHOD("EnableTapToClick", "sb", "", enable_tap_to_click, 0), SD_BUS_METHOD("GetTapAndDrag", "s", "bb", get_tap_and_drag, 0), SD_BUS_METHOD("EnableTapAndDrag", "sb", "", enable_tap_and_drag, 0), SD_BUS_METHOD("GetPointerSpeed", "s", "dd", get_pointer_speed, 0), SD_BUS_METHOD("SetPointerSpeed", "sd", "", set_pointer_speed, 0), SD_BUS_METHOD("GetAccelProfile", "s", "uu", get_accel_profile, 0), SD_BUS_METHOD("SetAccelProfile", "su", "", set_accel_profile, 0), SD_BUS_METHOD("GetScrollMethod", "s", "uu", get_scroll_method, 0), SD_BUS_METHOD("SetScrollMethod", "su", "", set_scroll_method, 0), SD_BUS_METHOD("GetDisableWhileTyping", "s", "bb", get_disable_while_typing, 0), SD_BUS_METHOD("SetDisableWhileTyping", "sb", "", set_disable_while_typing, 0), SD_BUS_METHOD("GetNaturalScroll", "s", "bb", get_natural_scroll, 0), SD_BUS_METHOD("EnableNaturalScroll", "sb", "", enable_natural_scroll, 0), SD_BUS_METHOD("GetLeftHand", "s", "bb", get_left_handed, 0), SD_BUS_METHOD("EnableLeftHand", "sb", "", enable_left_handed, 0), SD_BUS_METHOD("GetRepeatInfo", "s", "iiii", get_repeat_info, 0), SD_BUS_METHOD("SetRepeatInfo", "sii", "", set_repeat_info, 0), SD_BUS_METHOD("GetScrollFactor", "s", "dd", get_scroll_factor, 0), SD_BUS_METHOD("SetScrollFactor", "sd", "", set_scroll_factor, 0), SD_BUS_METHOD("GetDoubleClickTime", "s", "uu", get_double_click_time, 0), SD_BUS_METHOD("SetDoubleClickTime", "su", "", set_double_click_time, 0), SD_BUS_VTABLE_END, }; static int list_seats(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct input_manager *manager = userdata; sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(m, &reply)); CK(sd_bus_message_open_container(reply, 'a', "(ss)")); struct seat *seat; wl_list_for_each(seat, &manager->seats, link) { json_object *config = json_object_object_get(manager->seat_config->json, seat->name); const char *cfg = json_object_to_json_string(config); sd_bus_message_append(reply, "(ss)", seat->name, cfg); } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } static int set_cursor(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *seat_name, *cursor_theme; uint32_t cursor_size; CK(sd_bus_message_read(m, "ssu", &seat_name, &cursor_theme, &cursor_size)); struct seat *seat = seat_by_name(seat_name); if (!seat) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid seat."); return sd_bus_reply_method_error(m, &error); } seat_set_cursor(seat, cursor_theme, cursor_size); return sd_bus_reply_method_return(m, NULL); } static int set_lock_keys_mode(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *seat_name; uint32_t mode; CK(sd_bus_message_read(m, "su", &seat_name, &mode)); struct seat *seat = seat_by_name(seat_name); if (!seat) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid seat."); return sd_bus_reply_method_error(m, &error); } seat->state.keyboard_lock_mode = mode; seat_write_config(seat); return sd_bus_reply_method_return(m, NULL); } static int get_focused_client(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *seat_name; CK(sd_bus_message_read(m, "s", &seat_name)); struct seat *seat = seat_by_name(seat_name); if (!seat) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid seat."); return sd_bus_reply_method_error(m, &error); } struct wlr_seat_client *client = seat->wlr_seat->keyboard_state.focused_client; pid_t pid = 0; // no focused client if (client) { wl_client_get_credentials(client->client, &pid, NULL, NULL); } return sd_bus_reply_method_return(m, "u", pid); } static const sd_bus_vtable service_seat_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("ListAllSeats", "", "a(ss)", list_seats, 0), SD_BUS_METHOD("SetCursor", "ssu", "", set_cursor, 0), SD_BUS_METHOD("SetLockKeysMode", "su", "", set_lock_keys_mode, 0), SD_BUS_METHOD("GetFocusedClient", "s", "u", get_focused_client, 0), SD_BUS_VTABLE_END, }; void input_notify_destroy(struct input *input) { if (!input->device) { return; } struct input_manager *manager = input->manager; if (!manager->config) { return; } dbus_emit_signal(service_input_path, service_input_interface, "input_destroy", "s", input->name); } void input_notify_create(struct input *input) { if (!input->device) { return; } struct input_manager *manager = input->manager; if (!manager->config) { return; } dbus_emit_signal(service_input_path, service_input_interface, "input_create", "su", input->name, input->prop.prop); } bool input_manager_config_init(struct input_manager *input_manager) { input_manager->config = config_manager_add_config("Inputs"); if (!input_manager->config) { return false; } dbus_register_object(NULL, service_input_path, service_input_interface, service_input_vtable, input_manager); input_manager->seat_config = config_manager_add_config("Seats"); if (!input_manager->seat_config) { return false; } dbus_register_object(NULL, service_seat_path, service_seat_interface, service_seat_vtable, input_manager); return true; } bool input_read_config(struct input *input, struct input_state *state) { struct input_manager *manager = input->manager; if (!manager->config || !manager->config->json) { return false; } json_object *config = json_object_object_get(manager->config->json, input->name); if (!config) { return false; } json_object *data; if (json_object_object_get_ex(config, "mapped_to_output", &data)) { state->mapped_to_output = json_object_get_string(data); } if (json_object_object_get_ex(config, "seat", &data)) { state->seat = json_object_get_string(data); } if (json_object_object_get_ex(config, "send_events_mode", &data)) { state->send_events_mode = json_object_get_int(data); } if (input->prop.tap_finger_count > 0) { if (json_object_object_get_ex(config, "tap_to_click", &data)) { state->tap_to_click = json_object_get_boolean(data); } if (json_object_object_get_ex(config, "tap_button_map", &data)) { state->tap_button_map = json_object_get_int(data); } if (json_object_object_get_ex(config, "tap_and_drag", &data)) { state->tap_and_drag = json_object_get_boolean(data); } if (json_object_object_get_ex(config, "tap_drag_lock", &data)) { state->tap_drag_lock = json_object_get_boolean(data); } } if (input->prop.has_natural_scroll) { if (json_object_object_get_ex(config, "natural_scroll", &data)) { state->natural_scroll = json_object_get_boolean(data); } } if (input->prop.has_middle_emulation) { if (json_object_object_get_ex(config, "middle_emulation", &data)) { state->middle_emulation = json_object_get_boolean(data); } } if (input->prop.has_left_handed) { if (json_object_object_get_ex(config, "left_handed", &data)) { state->left_handed = json_object_get_boolean(data); } } if (input->prop.has_dwt) { if (json_object_object_get_ex(config, "dwt", &data)) { state->dwt = json_object_get_boolean(data); } } if (input->prop.has_dwtp) { if (json_object_object_get_ex(config, "dwtp", &data)) { state->dwtp = json_object_get_boolean(data); } } if (input->prop.scroll_methods) { if (json_object_object_get_ex(config, "scroll_method", &data)) { state->scroll_method = json_object_get_int(data); } if (input->prop.scroll_methods & 0x4) { if (json_object_object_get_ex(config, "scroll_button", &data)) { state->scroll_button = json_object_get_int(data); } if (json_object_object_get_ex(config, "scroll_button_lock", &data)) { state->scroll_button_lock = json_object_get_boolean(data); } } } if (input->prop.click_methods) { if (json_object_object_get_ex(config, "click_method", &data)) { state->click_method = json_object_get_int(data); } } if (input->prop.has_pointer_accel) { if (json_object_object_get_ex(config, "pointer_accel_speed", &data)) { state->pointer_accel_speed = json_object_get_double(data); } if (input->prop.accel_profiles) { if (json_object_object_get_ex(config, "accel_profile", &data)) { state->accel_profile = json_object_get_int(data); } } } if (input->prop.has_calibration_matrix) { if (json_object_object_get_ex(config, "calibration_matrix", &data)) { for (int i = 0; i < 6; i++) { state->calibration_matrix[i] = json_object_get_double(json_object_array_get_idx(data, i)); } } } if (json_object_object_get_ex(config, "scroll_factor", &data)) { state->scroll_factor = json_object_get_double(data); } if (json_object_object_get_ex(config, "double_click_time", &data)) { state->double_click_time = json_object_get_int(data); } if (input->prop.type == WLR_INPUT_DEVICE_KEYBOARD) { if (json_object_object_get_ex(config, "repeat_delay", &data)) { state->repeat_delay = json_object_get_int(data); } if (json_object_object_get_ex(config, "repeat_rate", &data)) { state->repeat_rate = json_object_get_int(data); } } return true; } #define WRITE_CONFIG(entry, type) \ if (state->entry == default_state->entry) { \ json_object_object_del(config, #entry); \ } else { \ json_object_object_add(config, #entry, json_object_new_##type(state->entry)); \ } void input_write_config(struct input *input) { struct input_manager *manager = input->manager; if (!manager->config || !manager->config->json) { return; } struct input_state *state = &input->state; json_object *config = json_object_object_get(manager->config->json, input->name); if (!config) { config = json_object_new_object(); json_object_object_add(manager->config->json, input->name, config); } if (state->mapped_to_output) { json_object_object_add(config, "mapped_to_output", json_object_new_string(state->mapped_to_output)); } else { json_object_object_del(config, "mapped_to_output"); } if (state->seat && strcmp(state->seat, "seat0")) { json_object_object_add(config, "seat", json_object_new_string(state->seat)); } else { json_object_object_del(config, "seat"); } /* filter by default state */ struct input_state *default_state = &input->default_state; WRITE_CONFIG(send_events_mode, int); if (input->prop.tap_finger_count > 0) { WRITE_CONFIG(tap_to_click, boolean); WRITE_CONFIG(tap_button_map, int); WRITE_CONFIG(tap_and_drag, boolean); WRITE_CONFIG(tap_drag_lock, boolean); } if (input->prop.has_natural_scroll) { WRITE_CONFIG(natural_scroll, boolean); } if (input->prop.has_middle_emulation) { WRITE_CONFIG(middle_emulation, boolean); } if (input->prop.has_left_handed) { WRITE_CONFIG(left_handed, boolean); } if (input->prop.has_dwt) { WRITE_CONFIG(dwt, boolean); } if (input->prop.has_dwtp) { WRITE_CONFIG(dwtp, boolean); } if (input->prop.scroll_methods) { WRITE_CONFIG(scroll_method, int); if (input->prop.scroll_methods & 0x4) { WRITE_CONFIG(scroll_button, int); WRITE_CONFIG(scroll_button_lock, boolean); } } if (input->prop.click_methods) { WRITE_CONFIG(click_method, int); } if (input->prop.has_pointer_accel) { WRITE_CONFIG(pointer_accel_speed, double); if (input->prop.accel_profiles) { WRITE_CONFIG(accel_profile, int); } } if (input->prop.has_calibration_matrix) { if (memcmp(state->calibration_matrix, default_state->calibration_matrix, sizeof(state->calibration_matrix)) == 0) { json_object_object_del(config, "calibration_matrix"); } else { json_object *matrix = json_object_new_array_ext(6); for (int i = 0; i < 6; i++) { json_object_array_add(matrix, json_object_new_double(state->calibration_matrix[i])); } json_object_object_add(config, "calibration_matrix", matrix); } } // TODO: rotation angle WRITE_CONFIG(scroll_factor, double); WRITE_CONFIG(double_click_time, int); if (input->prop.type == WLR_INPUT_DEVICE_KEYBOARD) { WRITE_CONFIG(repeat_delay, int); WRITE_CONFIG(repeat_rate, int); } } #undef WRITE_CONFIG bool seat_read_config(struct seat *seat) { if (!seat->manager->seat_config || !seat->manager->seat_config->json) { return false; } json_object *config = json_object_object_get(seat->manager->seat_config->json, seat->name); if (!config) { return true; } json_object *data; if (json_object_object_get_ex(config, "cursor_theme", &data)) { seat->state.cursor_theme = strdup(json_object_get_string(data)); } if (json_object_object_get_ex(config, "cursor_size", &data)) { seat->state.cursor_size = json_object_get_int(data); } if (json_object_object_get_ex(config, "keyboard_lock_mode", &data)) { seat->state.keyboard_lock_mode = json_object_get_int(data); } if (seat->state.keyboard_lock_mode == 0) { seat->state.keyboard_lock = 0; } else if (seat->state.keyboard_lock_mode == 1) { seat->state.keyboard_lock = 7; } else { if (json_object_object_get_ex(config, "keyboard_lock", &data)) { seat->state.keyboard_lock = json_object_get_int(data); } } return true; } void seat_write_config(struct seat *seat) { if (!seat->manager->seat_config) { return; } json_object *config = json_object_object_get(seat->manager->seat_config->json, seat->name); if (!config) { config = json_object_new_object(); json_object_object_add(seat->manager->seat_config->json, seat->name, config); } if (seat->state.cursor_theme && strcmp(seat->state.cursor_theme, "default")) { json_object_object_add(config, "cursor_theme", json_object_new_string(seat->state.cursor_theme)); } else { json_object_object_del(config, "cursor_theme"); } if (seat->state.cursor_size != 24) { json_object_object_add(config, "cursor_size", json_object_new_int(seat->state.cursor_size)); } else { json_object_object_del(config, "cursor_size"); } json_object_object_add(config, "keyboard_lock_mode", json_object_new_int(seat->state.keyboard_lock_mode)); json_object_object_add(config, "keyboard_lock", json_object_new_int(seat->state.keyboard_lock)); } kylin-wayland-compositor/src/input/text_input_v2.c0000664000175000017500000002702415160461067021376 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include "text-input-unstable-v2-protocol.h" #include "text_input_v2.h" static void text_input_destroy(struct text_input_v2 *text_input) { wl_signal_emit_mutable(&text_input->events.destroy, NULL); assert(wl_list_empty(&text_input->events.enable.listener_list)); assert(wl_list_empty(&text_input->events.disable.listener_list)); assert(wl_list_empty(&text_input->events.commit.listener_list)); assert(wl_list_empty(&text_input->events.destroy.listener_list)); wl_resource_set_user_data(text_input->resource, NULL); wl_list_remove(&text_input->surface_destroy.link); wl_list_remove(&text_input->seat_destroy.link); wl_list_remove(&text_input->link); free(text_input->surrounding.text); free(text_input); } static void text_input_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void text_input_enable(struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface) { struct text_input_v2 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } text_input->surface = wlr_surface_from_resource(surface); wl_signal_add(&text_input->surface->events.destroy, &text_input->surface_destroy); text_input->enabled = true; wl_signal_emit_mutable(&text_input->events.enable, NULL); } static void text_input_disable(struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface) { struct text_input_v2 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } text_input->enabled = false; wl_signal_emit_mutable(&text_input->events.disable, NULL); text_input->surface = NULL; wl_list_remove(&text_input->surface_destroy.link); wl_list_init(&text_input->surface_destroy.link); } static void text_input_show_input_panel(struct wl_client *client, struct wl_resource *resource) { // Not implemented yet } static void text_input_hide_input_panel(struct wl_client *client, struct wl_resource *resource) { // Not implemented yet } static void text_input_set_surrounding_text(struct wl_client *client, struct wl_resource *resource, const char *text, int32_t cursor, int32_t anchor) { struct text_input_v2 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } free(text_input->surrounding.text); text_input->surrounding.text = strdup(text); if (!text_input->surrounding.text) { wl_client_post_no_memory(client); } text_input->surrounding.cursor = cursor; text_input->surrounding.anchor = anchor; text_input->surrounding.pending = true; } static void text_input_set_content_type(struct wl_client *client, struct wl_resource *resource, uint32_t hint, uint32_t purpose) { struct text_input_v2 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } /* convert text_input_v2 to text_input_v3 */ text_input->content_type.hint = hint; text_input->content_type.purpose = purpose > ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_PASSWORD ? purpose + 1 : purpose; text_input->content_type.pending = true; } static void text_input_set_cursor_rectangle(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) { struct text_input_v2 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } text_input->cursor_rectangle.x = x; text_input->cursor_rectangle.y = y; text_input->cursor_rectangle.width = width; text_input->cursor_rectangle.height = height; } static void text_input_set_preferred_language(struct wl_client *client, struct wl_resource *resource, const char *language) { // Not implemented yet } static void text_input_update_state(struct wl_client *client, struct wl_resource *resource, uint32_t serial, uint32_t reason) { struct text_input_v2 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } if (text_input->surface == NULL) { kywc_log(KYWC_DEBUG, "Text input commit when surface destroyed"); } text_input->serial = serial; wl_signal_emit_mutable(&text_input->events.commit, NULL); text_input->surrounding.pending = false; text_input->content_type.pending = false; } static const struct zwp_text_input_v2_interface text_input_impl = { .destroy = text_input_handle_destroy, .enable = text_input_enable, .disable = text_input_disable, .show_input_panel = text_input_show_input_panel, .hide_input_panel = text_input_hide_input_panel, .set_surrounding_text = text_input_set_surrounding_text, .set_content_type = text_input_set_content_type, .set_cursor_rectangle = text_input_set_cursor_rectangle, .set_preferred_language = text_input_set_preferred_language, .update_state = text_input_update_state, }; static void text_input_resource_destroy(struct wl_resource *resource) { struct text_input_v2 *text_input = wl_resource_get_user_data(resource); if (!text_input) { return; } text_input_destroy(text_input); } static void text_input_handle_surface_destroy(struct wl_listener *listener, void *data) { struct text_input_v2 *text_input = wl_container_of(listener, text_input, surface_destroy); wl_list_remove(&text_input->surface_destroy.link); wl_list_init(&text_input->surface_destroy.link); text_input->surface = NULL; } static void text_input_handle_seat_destroy(struct wl_listener *listener, void *data) { struct text_input_v2 *text_input = wl_container_of(listener, text_input, seat_destroy); text_input_destroy(text_input); } static void text_input_manager_get_text_input(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *seat) { int version = wl_resource_get_version(resource); struct wl_resource *text_input_resource = wl_resource_create(client, &zwp_text_input_v2_interface, version, id); if (text_input_resource == NULL) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(text_input_resource, &text_input_impl, NULL, text_input_resource_destroy); struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat); if (seat_client == NULL) { return; } struct text_input_v2 *text_input = calloc(1, sizeof(*text_input)); if (!text_input) { wl_client_post_no_memory(client); return; } wl_signal_init(&text_input->events.enable); wl_signal_init(&text_input->events.commit); wl_signal_init(&text_input->events.disable); wl_signal_init(&text_input->events.destroy); text_input->resource = text_input_resource; wl_resource_set_user_data(text_input_resource, text_input); text_input->seat = seat_client->seat; wl_signal_add(&seat_client->events.destroy, &text_input->seat_destroy); text_input->seat_destroy.notify = text_input_handle_seat_destroy; text_input->surface_destroy.notify = text_input_handle_surface_destroy; wl_list_init(&text_input->surface_destroy.link); struct text_input_manager_v2 *manager = wl_resource_get_user_data(resource); wl_list_insert(&manager->text_inputs, &text_input->link); wl_signal_emit_mutable(&manager->events.text_input, text_input); } static void text_input_manager_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct zwp_text_input_manager_v2_interface text_input_manager_impl = { .destroy = text_input_manager_destroy, .get_text_input = text_input_manager_get_text_input, }; static void text_input_manager_bind(struct wl_client *wl_client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(wl_client, &zwp_text_input_manager_v2_interface, version, id); if (resource == NULL) { wl_client_post_no_memory(wl_client); return; } struct text_input_manager_v2 *manager = data; wl_resource_set_implementation(resource, &text_input_manager_impl, manager, NULL); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct text_input_manager_v2 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); assert(wl_list_empty(&manager->events.text_input.listener_list)); assert(wl_list_empty(&manager->events.destroy.listener_list)); wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); } struct text_input_manager_v2 *text_input_manager_v2_create(struct wl_display *display) { struct text_input_manager_v2 *manager = calloc(1, sizeof(*manager)); if (!manager) { return NULL; } wl_list_init(&manager->text_inputs); wl_signal_init(&manager->events.text_input); wl_signal_init(&manager->events.destroy); manager->global = wl_global_create(display, &zwp_text_input_manager_v2_interface, 1, manager, text_input_manager_bind); if (!manager->global) { free(manager); return NULL; } manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(display, &manager->display_destroy); return manager; } void text_input_v2_send_enter(struct text_input_v2 *text_input, struct wlr_surface *surface) { zwp_text_input_v2_send_enter(text_input->resource, text_input->serial, surface->resource); } void text_input_v2_send_leave(struct text_input_v2 *text_input) { assert(text_input->surface != NULL); zwp_text_input_v2_send_leave(text_input->resource, text_input->serial, text_input->surface->resource); text_input->surface = NULL; wl_list_remove(&text_input->surface_destroy.link); wl_list_init(&text_input->surface_destroy.link); } void text_input_v2_send_preedit_string(struct text_input_v2 *text_input, const char *text, int32_t cursor_begin) { zwp_text_input_v2_send_preedit_cursor(text_input->resource, cursor_begin); zwp_text_input_v2_send_preedit_styling(text_input->resource, 0, text ? strlen(text) : 0, ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_DEFAULT); zwp_text_input_v2_send_preedit_string(text_input->resource, text ? text : "", ""); } void text_input_v2_send_commit_string(struct text_input_v2 *text_input, const char *text) { zwp_text_input_v2_send_commit_string(text_input->resource, text); } void text_input_v2_send_delete_surrounding_text(struct text_input_v2 *text_input, const char *text, uint32_t before_length, uint32_t after_length) { zwp_text_input_v2_send_delete_surrounding_text( text_input->resource, strlen(text) - before_length, after_length + before_length); text_input_v2_send_commit_string(text_input, text); } kylin-wayland-compositor/src/input/text_input_v2.h0000664000175000017500000000350315160460057021375 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _TEXT_INPUT_V2_H_ #define _TEXT_INPUT_V2_H_ #include #include struct text_input_v2 { struct wl_resource *resource; struct wl_list link; struct wlr_seat *seat; struct wl_listener seat_destroy; struct wlr_surface *surface; struct wl_listener surface_destroy; struct wlr_box cursor_rectangle; struct { bool pending; char *text; uint32_t cursor; uint32_t anchor; } surrounding; struct { bool pending; uint32_t hint; uint32_t purpose; } content_type; uint32_t serial; bool enabled; struct { struct wl_signal enable; struct wl_signal disable; struct wl_signal commit; struct wl_signal destroy; } events; }; struct text_input_manager_v2 { struct wl_global *global; struct wl_list text_inputs; struct wl_listener display_destroy; struct { struct wl_signal text_input; struct wl_signal destroy; } events; }; struct text_input_manager_v2 *text_input_manager_v2_create(struct wl_display *display); void text_input_v2_send_enter(struct text_input_v2 *text_input, struct wlr_surface *surface); void text_input_v2_send_leave(struct text_input_v2 *text_input); void text_input_v2_send_preedit_string(struct text_input_v2 *text_input, const char *text, int32_t cursor_begin); void text_input_v2_send_commit_string(struct text_input_v2 *text_input, const char *text); void text_input_v2_send_delete_surrounding_text(struct text_input_v2 *text_input, const char *text, uint32_t before_length, uint32_t after_length); #endif /* _TEXT_INPUT_V2_H_ */ kylin-wayland-compositor/src/input/keyboard.c0000664000175000017500000005214415160461067020365 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include "input/keyboard.h" #include "input/keyboard_group.h" #include "input/seat.h" #include "input_p.h" #include "util/macros.h" #include "util/time.h" static struct modifier { char *name; uint32_t mod; } modifiers[] = { { XKB_MOD_NAME_SHIFT, WLR_MODIFIER_SHIFT }, { XKB_MOD_NAME_CAPS, WLR_MODIFIER_CAPS }, { "Ctrl", WLR_MODIFIER_CTRL }, { XKB_MOD_NAME_CTRL, WLR_MODIFIER_CTRL }, { "Alt", WLR_MODIFIER_ALT }, { XKB_MOD_NAME_ALT, WLR_MODIFIER_ALT }, { XKB_MOD_NAME_NUM, WLR_MODIFIER_MOD2 }, { "Mod3", WLR_MODIFIER_MOD3 }, { "Win", WLR_MODIFIER_LOGO }, { XKB_MOD_NAME_LOGO, WLR_MODIFIER_LOGO }, { "Mod5", WLR_MODIFIER_MOD5 }, }; uint32_t keyboard_get_modifier_mask_by_name(const char *name) { for (size_t i = 0; i < ARRAY_SIZE(modifiers); i++) { if (strcasecmp(modifiers[i].name, name) == 0) { return modifiers[i].mod; } } return 0; } const char *keyboard_get_modifier_name_by_mask(uint32_t modifier) { for (size_t i = 0; i < ARRAY_SIZE(modifiers); i++) { if (modifiers[i].mod == modifier) { return modifiers[i].name; } } return NULL; } const char *keyboard_get_modifier_names(uint32_t modifier_masks, char split) { static char names[128] = { 0 }; char *p = names; for (size_t i = 0; i < ARRAY_SIZE(modifiers); i++) { if ((modifier_masks & modifiers[i].mod) != 0) { p += sprintf(p, "%s%c", modifiers[i].name, split); modifier_masks ^= modifiers[i].mod; } } p != names ? (*--p = '\0') : (*p = '\0'); return names; } static void modifiers_mask_debug(uint32_t mask, const char *mask_name) { const char *names = keyboard_get_modifier_names(mask, ' '); kywc_log(KYWC_DEBUG, "\t%s: %s", mask_name, names); } // TODO: compose and dead key support static void keyboard_state_erase_key(struct keyboard_state *keyboard_state, uint32_t keysym) { uint32_t idx = 0; for (size_t i = 0; i < keyboard_state->npressed; i++) { if (i > idx) { keyboard_state->pressed_keysyms[idx] = keyboard_state->pressed_keysyms[i]; } if (keyboard_state->pressed_keysyms[i] != keysym) { idx++; } } while (keyboard_state->npressed > idx) { keyboard_state->npressed--; keyboard_state->pressed_keysyms[keyboard_state->npressed] = 0; } if (kywc_log_get_level() == KYWC_DEBUG) { kywc_log(KYWC_DEBUG, "Erase key 0x%x, %lu", keysym, keyboard_state->npressed); for (size_t i = 0; i < keyboard_state->npressed; i++) { kywc_log(KYWC_DEBUG, "\tcurrent keysym %lu: 0x%x", i, keyboard_state->pressed_keysyms[i]); } } } static void keyboard_state_add_key(struct keyboard_state *keyboard_state, uint32_t keysym) { if (keyboard_state->npressed >= MAX_PRESSED_KEY) { return; } if (keyboard_state->npressed > 0 && keyboard_state->pressed_keysyms[keyboard_state->npressed - 1] == keysym) { return; } keyboard_state->pressed_keysyms[keyboard_state->npressed] = keysym; keyboard_state->npressed++; if (kywc_log_get_level() == KYWC_DEBUG) { kywc_log(KYWC_DEBUG, "Add key 0x%x, %lu", keysym, keyboard_state->npressed); for (size_t i = 0; i < keyboard_state->npressed; i++) { kywc_log(KYWC_DEBUG, "\tcurrent keysym %lu: 0x%x", i, keyboard_state->pressed_keysyms[i]); } } } static void keyboard_state_clear(struct keyboard_state *keyboard_state) { if (keyboard_state->npressed > 0) { *keyboard_state = (struct keyboard_state){ 0 }; } } static void handle_keyboard_state(struct keyboard_state *keyboard_state, uint32_t modifiers, uint32_t keysym, bool pressed) { bool last_key_is_modifiers = modifiers != keyboard_state->last_modifiers; keyboard_state->only_one_modifier = modifiers && keyboard_state->last_modifiers == 0 && !pressed && keyboard_state->npressed == 1; keyboard_state->last_modifiers = modifiers; if (last_key_is_modifiers && keyboard_state->last_keysym) { // a modifiier key preesed before this key, erase it keyboard_state_erase_key(keyboard_state, keyboard_state->last_keysym); keyboard_state->last_keysym = 0; } if (pressed) { keyboard_state_add_key(keyboard_state, keysym); keyboard_state->last_keysym = keysym; } else { keyboard_state_erase_key(keyboard_state, xkb_keysym_to_upper(keysym)); keyboard_state_erase_key(keyboard_state, xkb_keysym_to_lower(keysym)); } } static bool keyboard_update_keyboard_state(struct keyboard *keyboard, uint32_t key, uint32_t modifiers, bool pressed) { struct keyboard_state *keyboard_state = &keyboard->state; /* Translate libinput keycode -> xkbcommon keysym */ const xkb_keysym_t *keysyms; size_t len = 0; // xkb_state may be null if (keyboard->wlr_keyboard->xkb_state) { len = xkb_state_key_get_syms(keyboard->wlr_keyboard->xkb_state, key + 8, &keysyms); } for (size_t i = 0; i < len; ++i) { handle_keyboard_state(keyboard_state, modifiers, keysyms[i], pressed); } for (size_t i = 0; i < len; ++i) { xkb_keysym_t keysym = keysyms[i]; if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { input_manager_switch_vt(keysym - XKB_KEY_XF86Switch_VT_1 + 1); return false; } } return true; } static struct key_binding *keyboard_get_key_binding(struct keyboard *keyboard, bool pressed) { struct keyboard_state *keyboard_state = &keyboard->state; struct seat *seat = keyboard->seat; if (seat_is_keyboard_shortcuts_inhibited(seat)) { return NULL; } if (!pressed && !keyboard->state.only_one_modifier) { return NULL; } return bindings_get_key_binding(keyboard_state); } static void keyboard_repeat_stop(struct keyboard *keyboard) { if (!keyboard->repeat.timer) { return; } if (keyboard->repeat.key == 0) { return; } keyboard->repeat.key = 0; wl_event_source_timer_update(keyboard->repeat.timer, 0); } static void keyboard_repeat_start(struct keyboard *keyboard, uint32_t key, bool pressed) { if (!keyboard->repeat.timer) { return; } if (keyboard->repeat.key > 0) { if (keyboard->repeat.key == key && !pressed) { keyboard_repeat_stop(keyboard); } return; } /* only enable key repeat when pressed state */ if (!pressed) { return; } int32_t delay = keyboard->wlr_keyboard->repeat_info.delay; if (delay > 0) { keyboard->repeat.key = key; if (wl_event_source_timer_update(keyboard->repeat.timer, delay) < 0) { kywc_log(KYWC_DEBUG, "Failed to set key repeat timer"); } } else if (keyboard->repeat.key > 0) { keyboard_repeat_stop(keyboard); } } static void keyboard_update_lock(struct keyboard *keyboard, uint32_t key, bool pressed) { if (key != KEY_SCROLLLOCK) { return; } struct keyboard_group *group = keyboard_group_from_wlr_keyboard(keyboard->wlr_keyboard); if (!group) { return; } if (group->scroll_lock == 0 && pressed) { group->scroll_lock = 2; group->keyboard.leds |= WLR_LED_SCROLL_LOCK; } else if (group->scroll_lock > 0 && !pressed) { if (--group->scroll_lock == 0) { // only used to bypass check in wlr_keyboard_led_update group->keyboard.leds |= WLR_LED_SCROLL_LOCK; } } } static void keyboard_feed_key(struct keyboard *keyboard, uint32_t key, uint32_t state, uint32_t time, uint32_t modifiers) { if (kywc_log_get_level() == KYWC_DEBUG) { modifiers_mask_debug(modifiers, "modifiers"); } struct seat *seat = keyboard->seat; bool pressed = state == WL_KEYBOARD_KEY_STATE_PRESSED; keyboard_update_lock(keyboard, key, pressed); /* early return if key is sent by input method */ if (keyboard_is_from_input_method(keyboard)) { wlr_seat_set_keyboard(seat->wlr_seat, keyboard->wlr_keyboard); wlr_seat_keyboard_notify_key(seat->wlr_seat, time, key, state); return; } struct seat_keyboard_key_event event = { .device = input_from_wlr_input(&keyboard->wlr_keyboard->base), .time_msec = time, .keycode = key, .pressed = pressed, }; wl_signal_emit_mutable(&seat->events.keyboard_key, &event); if (!keyboard_update_keyboard_state(keyboard, key, modifiers, pressed)) { return; } struct key_binding *binding = keyboard_get_key_binding(keyboard, pressed); bool bypass_grab = bindings_get_key_binding_bypass_grab(binding); if (!bypass_grab && seat->keyboard_grab && seat->keyboard_grab->interface->key && seat->keyboard_grab->interface->key(seat->keyboard_grab, keyboard, time, key, pressed, modifiers)) { keyboard_state_clear(&keyboard->state); if (key != keyboard->repeat.key) { keyboard_repeat_stop(keyboard); } if (!bindings_get_key_binding_no_repeat(binding)) { keyboard_repeat_start(keyboard, key, pressed); } return; } /* don't auto repeat for some bindings, like vt switch */ bool need_repeat = false; bool handled = bindings_handle_key_binding(binding, &need_repeat); if (handled) { keyboard_update_keyboard_state(keyboard, key, modifiers, false); need_repeat ? keyboard_repeat_start(keyboard, key, pressed) : keyboard_repeat_stop(keyboard); return; } keyboard_repeat_stop(keyboard); handled = input_method_handle_key(keyboard, time, key, state); if (handled) { return; } wlr_seat_set_keyboard(seat->wlr_seat, keyboard->wlr_keyboard); wlr_seat_keyboard_notify_key(seat->wlr_seat, time, key, state); } static void keyboard_feed_modifiers(struct keyboard *keyboard, struct wlr_keyboard_modifiers *modifiers) { if (kywc_log_get_level() == KYWC_DEBUG) { kywc_log(KYWC_DEBUG, "Keyboard modifiers update"); modifiers_mask_debug(modifiers->depressed, "depressed"); modifiers_mask_debug(modifiers->latched, "latched"); modifiers_mask_debug(modifiers->locked, "locked"); modifiers_mask_debug(modifiers->group, "group"); } if (input_method_handle_modifiers(keyboard)) { return; } wl_signal_emit_mutable(&keyboard->seat->events.keyboard_modifiers, modifiers); struct wlr_seat *wlr_seat = keyboard->seat->wlr_seat; wlr_seat_set_keyboard(wlr_seat, keyboard->wlr_keyboard); wlr_seat_keyboard_notify_modifiers(wlr_seat, modifiers); } static bool keyboard_sync_physical_key(struct keyboard *keyboard, uint32_t key, bool state) { if (!keyboard->is_virtual || keyboard_is_from_input_method(keyboard)) { return false; } if (key == KEY_CAPSLOCK) { struct seat *seat = keyboard->seat; struct keyboard *kb; wl_list_for_each(kb, &seat->keyboards, link) { if (keyboard_has_no_input(kb) || kb->is_virtual) { continue; } keyboard_send_key(kb, key, state); return true; } } return false; } static void keyboard_handle_key(struct wl_listener *listener, void *data) { struct keyboard *keyboard = wl_container_of(listener, keyboard, key); struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard; struct wlr_keyboard_key_event *event = data; if (input_event_filter(keyboard->seat, NULL, INPUT_EVENT_KEYBOARD_KEY, event)) { return; } struct keyboard_group *group = keyboard_group_from_wlr_keyboard(wlr_keyboard); if (group) { keyboard->seat->keyboard = keyboard; } uint32_t modifiers = wlr_keyboard_get_modifiers(wlr_keyboard); if (!keyboard_sync_physical_key(keyboard, event->keycode, event->state)) { keyboard_feed_key(keyboard, event->keycode, event->state, event->time_msec, modifiers); } } static void keyboard_handle_modifiers(struct wl_listener *listener, void *data) { struct keyboard *keyboard = wl_container_of(listener, keyboard, modifiers); struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard; if (input_event_filter(keyboard->seat, NULL, INPUT_EVENT_KEYBOARD_MODIFIERS, data)) { return; } keyboard_feed_modifiers(keyboard, &wlr_keyboard->modifiers); } static int keyboard_handle_repeat(void *data) { struct keyboard *keyboard = data; if (keyboard->repeat.key > 0) { if (keyboard->wlr_keyboard->repeat_info.rate > 0) { // We queue the next event first, as the command might cancel it if (wl_event_source_timer_update(keyboard->repeat.timer, 1000 / keyboard->wlr_keyboard->repeat_info.rate) < 0) { kywc_log(KYWC_DEBUG, "Failed to update key repeat timer"); } } keyboard_feed_key(keyboard, keyboard->repeat.key, true, current_time_msec(), wlr_keyboard_get_modifiers(keyboard->wlr_keyboard)); } return 0; } struct keyboard *keyboard_create(struct seat *seat, struct wlr_keyboard *wlr_keyboard) { struct keyboard *keyboard = calloc(1, sizeof(*keyboard)); if (!keyboard) { return NULL; } if (!wlr_keyboard) { struct keyboard_group *group = keyboard_group_create(); keyboard->wlr_keyboard = &group->keyboard; } else { keyboard->wlr_keyboard = wlr_keyboard; } keyboard->is_virtual = !!wlr_keyboard; keyboard->wlr_keyboard->data = keyboard; /* insert new keyboard to seat keyboard list */ keyboard->seat = seat; wl_list_insert(&seat->keyboards, &keyboard->link); /* create timer for internal key repeat */ if (!keyboard->is_virtual) { struct wl_event_loop *loop = wl_display_get_event_loop(seat->wlr_seat->display); keyboard->repeat.timer = wl_event_loop_add_timer(loop, keyboard_handle_repeat, keyboard); } keyboard->key.notify = keyboard_handle_key; wl_signal_add(&keyboard->wlr_keyboard->events.key, &keyboard->key); keyboard->modifiers.notify = keyboard_handle_modifiers; wl_signal_add(&keyboard->wlr_keyboard->events.modifiers, &keyboard->modifiers); return keyboard; } void keyboard_add_input(struct seat *seat, struct input *input) { struct wlr_input_device *wlr_input = input->wlr_input; struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(wlr_input); /* virtual keyboard is not managed by group */ if (input->prop.is_virtual) { keyboard_create(seat, wlr_keyboard); return; } /* find a suitable group */ struct keyboard *keyboard, *empty_keyboard = NULL; wl_list_for_each(keyboard, &seat->keyboards, link) { if (keyboard->is_virtual) { continue; } struct wlr_keyboard *dst_keyboard = keyboard->wlr_keyboard; struct keyboard_group *group = keyboard_group_from_wlr_keyboard(dst_keyboard); if (wl_list_empty(&group->devices)) { empty_keyboard = keyboard; continue; } if (keyboard_keymaps_match(wlr_keyboard, dst_keyboard) && wlr_keyboard->repeat_info.rate == dst_keyboard->repeat_info.rate && wlr_keyboard->repeat_info.delay == dst_keyboard->repeat_info.delay) { keyboard_group_add_keyboard(group, wlr_keyboard); return; } } if (empty_keyboard) { struct wlr_keyboard *dst_keyboard = empty_keyboard->wlr_keyboard; if (!dst_keyboard->keymap || !keyboard_keymaps_match(wlr_keyboard, dst_keyboard)) { wlr_keyboard_set_keymap(dst_keyboard, wlr_keyboard->keymap); } wlr_keyboard_set_repeat_info(dst_keyboard, wlr_keyboard->repeat_info.rate, wlr_keyboard->repeat_info.delay); struct keyboard_group *group = keyboard_group_from_wlr_keyboard(dst_keyboard); keyboard_group_add_keyboard(group, wlr_keyboard); return; } /* create a new keyboard group with keyboard configuration */ keyboard = keyboard_create(seat, NULL); if (!keyboard) { return; } struct keyboard_group *group = keyboard_group_from_wlr_keyboard(keyboard->wlr_keyboard); wlr_keyboard_set_keymap(keyboard->wlr_keyboard, wlr_keyboard->keymap); wlr_keyboard_set_repeat_info(keyboard->wlr_keyboard, wlr_keyboard->repeat_info.rate, wlr_keyboard->repeat_info.delay); keyboard_group_add_keyboard(group, wlr_keyboard); } void keyboard_destroy(struct keyboard *keyboard) { struct wlr_seat *wlr_seat = keyboard->seat->wlr_seat; struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard; if (wlr_seat_get_keyboard(wlr_seat) == wlr_keyboard) { wlr_seat_set_keyboard(wlr_seat, NULL); } wl_list_remove(&keyboard->link); wl_list_remove(&keyboard->key.link); wl_list_remove(&keyboard->modifiers.link); if (!keyboard->is_virtual) { struct keyboard_group *group = keyboard_group_from_wlr_keyboard(wlr_keyboard); keyboard_group_destroy(group); } if (keyboard->repeat.timer) { wl_event_source_remove(keyboard->repeat.timer); } if (keyboard->seat->keyboard == keyboard) { keyboard->seat->keyboard = NULL; } free(keyboard); } void keyboard_remove_input(struct input *input) { struct wlr_input_device *wlr_input = input->wlr_input; struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(wlr_input); struct keyboard *keyboard; if (input->prop.is_virtual) { keyboard = wlr_keyboard->data; keyboard_destroy(keyboard); return; } struct keyboard_group *group = (struct keyboard_group *)wlr_keyboard->group; /* may removed when input destroy at keyboard group */ if (group) { keyboard_group_remove_keyboard(group, wlr_keyboard); } } void keyboard_send_key(struct keyboard *keyboard, uint32_t key, bool pressed) { struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard; struct keyboard_group *group = keyboard_group_from_wlr_keyboard(wlr_keyboard); if (!group) { return; } struct wlr_keyboard_key_event wlr_event = { .time_msec = current_time_msec(), .keycode = key, .update_state = true, .state = pressed ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED, }; wlr_keyboard = keyboard_group_pick_keyboard(group); wlr_keyboard_notify_key(wlr_keyboard, &wlr_event); } uint32_t keyboard_get_locks(struct keyboard *keyboard) { struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard; uint32_t locks = 0; if (wlr_keyboard->modifiers.locked & WLR_MODIFIER_CAPS) { locks |= 1 << INPUT_KEY_CAPSLOCK; } if (wlr_keyboard->modifiers.locked & WLR_MODIFIER_MOD2) { locks |= 1 << INPUT_KEY_NUMLOCK; } struct keyboard_group *group = keyboard_group_from_wlr_keyboard(wlr_keyboard); if (group && group->scroll_lock) { locks |= 1 << INPUT_KEY_SCROLLLOCK; } return locks; } bool keyboard_has_no_input(struct keyboard *keyboard) { if (keyboard->is_virtual) { return true; } struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard; struct keyboard_group *group = keyboard_group_from_wlr_keyboard(wlr_keyboard); return wl_list_empty(&group->devices); } bool keyboard_keymaps_match(struct wlr_keyboard *kb1, struct wlr_keyboard *kb2) { const char *km1 = kb1->keymap_string; const char *km2 = kb2->keymap_string; if (!km1 && !km2) { return true; } if (!km1 || !km2) { return false; } return strcmp(km1, km2) == 0; } static bool compare_string(const char *a, const char *b) { if (!a && !b) { return false; } if (!a || !b) { return true; } return strcmp(a, b) != 0; } bool keyboard_check_keymap_rules(struct keymap_rules *old, struct keymap_rules *new) { return compare_string(old->xkb_rules, new->xkb_rules) || compare_string(old->xkb_model, new->xkb_model) || compare_string(old->xkb_layout, new->xkb_layout) || compare_string(old->xkb_variant, new->xkb_variant) || compare_string(old->xkb_options, new->xkb_options); } struct xkb_keymap *keyboard_compile_keymap(struct keymap_rules *rules) { struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_SECURE_GETENV); struct xkb_rule_names names = { .layout = rules->xkb_layout, .model = rules->xkb_model, .options = rules->xkb_options, .rules = rules->xkb_rules, .variant = rules->xkb_variant, }; struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); xkb_context_unref(context); return keymap; } kylin-wayland-compositor/src/input/selection.c0000664000175000017500000004222015160461067020544 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #if HAVE_XWAYLAND #include #endif #include #include "config.h" #include "input/cursor.h" #include "input_p.h" #include "scene/surface.h" #include "server.h" #include "util/dbus.h" #include "view/view.h" struct selection_manager { struct wl_listener new_seat; struct wl_listener server_destroy; }; /* selection per seat */ struct selection { struct selection_manager *manager; struct seat *seat; struct wl_listener seat_destroy; struct wl_listener request_start_drag; struct wl_listener start_drag; struct wl_listener destroy_drag; struct wl_listener request_set_selection; struct wl_listener request_set_primary_selection; struct wlr_drag *drag; /* only one drag_icon in seat at the same time */ struct wlr_drag_icon *drag_icon; struct ky_scene_node *icon_node; struct ky_scene_node *surface_node; struct wl_listener drag_icon_map; struct wl_listener drag_icon_unmap; struct wl_listener drag_icon_commit; struct wl_listener drag_icon_destroy; struct wl_listener source_accepted; struct wl_listener source_dnd_action; #if HAVE_KDE_CLIPBOARD struct wl_listener set_selection; struct wl_listener set_primary_selection; int clipboard_selection_pid; int primary_selection_pid; #endif }; #if HAVE_KDE_CLIPBOARD static const char *service_bus = "org.kde.KWin"; static const char *service_path = "/Clipboard"; static const char *service_interface = "org.kde.KWin.Clipboard"; // SD_BUS_METHOD("getClipboardSelectionPid", "", "i", get_clipboard_selection_pid, 0), static int get_clipboard_selection_pid(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { struct seat *seat = input_manager_get_default_seat(); struct selection *selection = seat->selection; return sd_bus_reply_method_return(msg, "i", selection->clipboard_selection_pid); } // SD_BUS_METHOD("getPrimarySelectionPid", "", "i", get_primary_selection_pid, 0), static int get_primary_selection_pid(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { struct seat *seat = input_manager_get_default_seat(); struct selection *selection = seat->selection; return sd_bus_reply_method_return(msg, "i", selection->primary_selection_pid); } // SD_BUS_PROPERTY("GetClipboardSelectionPid", "i", get_selection_pid, 0, // SD_BUS_VTABLE_PROPERTY_CONST), static int get_selection_pid(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct seat *seat = input_manager_get_default_seat(); struct selection *selection = seat->selection; return sd_bus_message_append_basic(reply, 'i', &selection->clipboard_selection_pid); } // SD_BUS_PROPERTY("GetPrimarySelectionPid", "i", get_primary_pid, 0, SD_BUS_VTABLE_PROPERTY_CONST), static int get_primary_pid(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { struct seat *seat = input_manager_get_default_seat(); struct selection *selection = seat->selection; return sd_bus_message_append_basic(reply, 'i', &selection->primary_selection_pid); } static const sd_bus_vtable clipboard_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("getClipboardSelectionPid", "", "i", get_clipboard_selection_pid, 0), SD_BUS_METHOD("getPrimarySelectionPid", "", "i", get_primary_selection_pid, 0), SD_BUS_PROPERTY("GetClipboardSelectionPid", "i", get_selection_pid, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("GetPrimarySelectionPid", "i", get_primary_pid, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_SIGNAL("clipboardSelectionPidChanged", "i", 0), SD_BUS_SIGNAL("primarySelectionPidChanged", "i", 0), SD_BUS_VTABLE_END, }; static pid_t get_pid_by_wlr_surface(struct wlr_surface *surface) { pid_t pid = 0; if (!surface) { return pid; } #if HAVE_XWAYLAND struct wlr_xwayland_surface *xwayland = wlr_xwayland_surface_try_from_wlr_surface(surface); if (xwayland) { return xwayland->pid; } #endif struct wl_client *client = wl_resource_get_client(surface->resource); wl_client_get_credentials(client, &pid, NULL, NULL); return pid; } static void send_selection_pid_changed(struct selection *selection, struct wlr_seat *seat, bool is_primary) { pid_t pid = 0, current_pid = 0; char *signal_name = NULL; struct wlr_surface *focused_surface = seat->keyboard_state.focused_surface; pid = get_pid_by_wlr_surface(focused_surface); if (!is_primary && pid != selection->clipboard_selection_pid) { current_pid = pid; selection->clipboard_selection_pid = pid; signal_name = "clipboardSelectionPidChanged"; } else if (is_primary && pid != selection->primary_selection_pid) { current_pid = pid; selection->primary_selection_pid = pid; signal_name = "primarySelectionPidChanged"; } if (signal_name) { dbus_emit_signal(service_path, service_interface, signal_name, "i", current_pid); } } static void handle_set_selection(struct wl_listener *listener, void *data) { struct selection *selection = wl_container_of(listener, selection, set_selection); struct wlr_seat *seat = data; send_selection_pid_changed(selection, seat, false); } static void handle_set_primary_selection(struct wl_listener *listener, void *data) { struct selection *selection = wl_container_of(listener, selection, set_primary_selection); struct wlr_seat *seat = data; send_selection_pid_changed(selection, seat, true); } #endif static void handle_drag_icon_map(struct wl_listener *listener, void *data) { struct selection *selection = wl_container_of(listener, selection, drag_icon_map); ky_scene_node_set_enabled(selection->icon_node, true); } static void handle_drag_icon_unmap(struct wl_listener *listener, void *data) { struct selection *selection = wl_container_of(listener, selection, drag_icon_unmap); ky_scene_node_set_enabled(selection->icon_node, false); } static void handle_drag_icon_commit(struct wl_listener *listener, void *data) { struct selection *selection = wl_container_of(listener, selection, drag_icon_commit); struct wlr_surface *surface = selection->drag_icon->surface; ky_scene_node_set_position(selection->surface_node, selection->surface_node->x + surface->current.dx, selection->surface_node->y + surface->current.dy); } static void handle_drag_icon_destroy(struct wl_listener *listener, void *data) { struct selection *selection = wl_container_of(listener, selection, drag_icon_destroy); wl_list_remove(&selection->drag_icon_map.link); wl_list_remove(&selection->drag_icon_unmap.link); wl_list_remove(&selection->drag_icon_commit.link); wl_list_remove(&selection->drag_icon_destroy.link); ky_scene_node_destroy(selection->icon_node); selection->icon_node = NULL; } static void handle_request_set_selection(struct wl_listener *listener, void *data) { struct selection *selection = wl_container_of(listener, selection, request_set_selection); struct wlr_seat *seat = selection->seat->wlr_seat; struct wlr_seat_request_set_selection_event *event = data; wlr_seat_set_selection(seat, event->source, event->serial); } static void handle_request_start_drag(struct wl_listener *listener, void *data) { struct selection *selection = wl_container_of(listener, selection, request_start_drag); struct wlr_seat *wlr_seat = selection->seat->wlr_seat; struct wlr_seat_request_start_drag_event *event = data; if (wlr_seat_validate_pointer_grab_serial(wlr_seat, event->origin, event->serial)) { wlr_seat_start_pointer_drag(wlr_seat, event->drag, event->serial); return; } struct wlr_touch_point *point; if (wlr_seat_validate_touch_grab_serial(wlr_seat, event->origin, event->serial, &point)) { wlr_seat_start_touch_drag(wlr_seat, event->drag, event->serial, point); return; } /* workaround to simulate pointer drag */ if (tablet_has_implicit_grab(selection->seat)) { wlr_seat->pointer_state.grab_button = BTN_LEFT; selection->seat->cursor->tablet_tool_tip_simulation_pointer = true; wlr_seat_start_pointer_drag(wlr_seat, event->drag, event->serial); return; } wlr_data_source_destroy(event->drag->source); } static void update_drag_cursor(struct selection *selection) { struct wlr_data_source *source = selection->drag->source; struct cursor *cursor = selection->seat->cursor; cursor_lock_image(cursor, false); if (source && source->accepted) { switch (source->current_dnd_action) { case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: cursor_set_image(cursor, CURSOR_GRABBING); break; case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: cursor_set_image(cursor, CURSOR_COPY); break; case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: cursor_set_image(cursor, CURSOR_MOVE); break; case WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK: cursor_set_image(cursor, CURSOR_GRABBING); break; } } else { cursor_set_image(cursor, CURSOR_NOT_ALLOWED); } cursor_lock_image(cursor, true); } static void handle_source_accepted(struct wl_listener *listener, void *data) { struct selection *selection = wl_container_of(listener, selection, source_accepted); update_drag_cursor(selection); } static void handle_source_dnd_action(struct wl_listener *listener, void *data) { struct selection *selection = wl_container_of(listener, selection, source_dnd_action); update_drag_cursor(selection); } static void handle_start_drag(struct wl_listener *listener, void *data) { struct selection *selection = wl_container_of(listener, selection, start_drag); struct wlr_drag *wlr_drag = data; struct wlr_drag_icon *drag_icon = wlr_drag->icon; selection->drag = wlr_drag; selection->drag_icon = drag_icon; wl_signal_add(&wlr_drag->events.destroy, &selection->destroy_drag); cursor_set_image(selection->seat->cursor, CURSOR_DEFAULT); cursor_lock_image(selection->seat->cursor, true); // selection->source_accepted.notify = handle_source_accepted; // wl_signal_add(&selection->drag->source->events.accepted, &selection->source_accepted); // selection->source_dnd_action.notify = handle_source_dnd_action; // wl_signal_add(&selection->drag->source->events.dnd_action, // &selection->source_dnd_action); /* drag icon may be NULL */ if (!drag_icon) { kywc_log(KYWC_DEBUG, "Started drag but not set a drag icon"); return; } struct view_layer *layer = view_manager_get_layer(LAYER_ON_SCREEN_DISPLAY, false); struct ky_scene_tree *tree = ky_scene_tree_create(layer->tree); struct ky_scene_tree *surface_tree = ky_scene_subsurface_tree_create(tree, drag_icon->surface); selection->icon_node = &tree->node; selection->surface_node = &surface_tree->node; ky_scene_node_set_position(selection->icon_node, selection->seat->cursor->lx, selection->seat->cursor->ly); ky_scene_node_set_enabled(selection->icon_node, drag_icon->surface->mapped); selection->drag_icon_map.notify = handle_drag_icon_map; wl_signal_add(&drag_icon->surface->events.map, &selection->drag_icon_map); selection->drag_icon_unmap.notify = handle_drag_icon_unmap; wl_signal_add(&drag_icon->surface->events.unmap, &selection->drag_icon_unmap); selection->drag_icon_commit.notify = handle_drag_icon_commit; wl_signal_add(&drag_icon->surface->events.commit, &selection->drag_icon_commit); selection->drag_icon_destroy.notify = handle_drag_icon_destroy; wl_signal_add(&drag_icon->events.destroy, &selection->drag_icon_destroy); } static void handle_destroy_drag(struct wl_listener *listener, void *data) { struct selection *selection = wl_container_of(listener, selection, destroy_drag); wl_list_remove(&selection->destroy_drag.link); // wl_list_remove(&selection->source_accepted.link); // wl_list_remove(&selection->source_dnd_action.link); selection->drag = NULL; cursor_lock_image(selection->seat->cursor, false); cursor_set_image(selection->seat->cursor, CURSOR_DEFAULT); } static void handle_request_set_primary_selection(struct wl_listener *listener, void *data) { struct selection *selection = wl_container_of(listener, selection, request_set_primary_selection); struct wlr_seat *seat = selection->seat->wlr_seat; struct wlr_seat_request_set_primary_selection_event *event = data; wlr_seat_set_primary_selection(seat, event->source, event->serial); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { struct selection *selection = wl_container_of(listener, selection, seat_destroy); wl_list_remove(&selection->request_start_drag.link); wl_list_remove(&selection->start_drag.link); #if HAVE_KDE_CLIPBOARD wl_list_remove(&selection->set_selection.link); wl_list_remove(&selection->set_primary_selection.link); #endif wl_list_remove(&selection->request_set_selection.link); wl_list_remove(&selection->request_set_primary_selection.link); wl_list_remove(&selection->seat_destroy.link); free(selection); } static void handle_new_seat(struct wl_listener *listener, void *data) { struct selection *selection = calloc(1, sizeof(*selection)); if (!selection) { return; } struct selection_manager *manager = wl_container_of(listener, manager, new_seat); selection->manager = manager; struct seat *seat = data; selection->seat = seat; seat->selection = selection; selection->seat_destroy.notify = handle_seat_destroy; wl_signal_add(&seat->events.destroy, &selection->seat_destroy); selection->request_start_drag.notify = handle_request_start_drag; wl_signal_add(&seat->wlr_seat->events.request_start_drag, &selection->request_start_drag); selection->start_drag.notify = handle_start_drag; wl_signal_add(&seat->wlr_seat->events.start_drag, &selection->start_drag); selection->destroy_drag.notify = handle_destroy_drag; wl_list_init(&selection->destroy_drag.link); #if HAVE_KDE_CLIPBOARD selection->set_selection.notify = handle_set_selection; wl_signal_add(&seat->wlr_seat->events.set_selection, &selection->set_selection); selection->set_primary_selection.notify = handle_set_primary_selection; wl_signal_add(&seat->wlr_seat->events.set_primary_selection, &selection->set_primary_selection); #endif selection->request_set_selection.notify = handle_request_set_selection; wl_signal_add(&seat->wlr_seat->events.request_set_selection, &selection->request_set_selection); selection->request_set_primary_selection.notify = handle_request_set_primary_selection; wl_signal_add(&seat->wlr_seat->events.request_set_primary_selection, &selection->request_set_primary_selection); } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct selection_manager *manager = wl_container_of(listener, manager, server_destroy); wl_list_remove(&manager->server_destroy.link); wl_list_remove(&manager->new_seat.link); free(manager); } bool selection_manager_create(struct input_manager *input_manager) { struct selection_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } #if HAVE_KDE_CLIPBOARD if (!dbus_register_object(service_bus, service_path, service_interface, clipboard_vtable, manager)) { kywc_log(KYWC_WARN, "Failed to register org.kde.KWin.Clipboard"); } #endif wlr_data_device_manager_create(input_manager->server->display); wlr_data_control_manager_v1_create(input_manager->server->display); wlr_primary_selection_v1_device_manager_create(input_manager->server->display); toplevel_drag_manager_create(input_manager->server); manager->new_seat.notify = handle_new_seat; wl_signal_add(&input_manager->events.new_seat, &manager->new_seat); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(input_manager->server, &manager->server_destroy); return true; } void selection_handle_cursor_move(struct seat *seat, int lx, int ly) { if (!seat->selection || !seat->selection->drag) { return; } if (toplevel_drag_move(seat->selection->drag->source, lx, ly)) { return; } /* update dnd icon if support */ if (seat->selection->icon_node) { ky_scene_node_set_position(seat->selection->icon_node, lx, ly); } } bool selection_is_dragging(struct seat *seat) { return seat->selection && seat->selection->drag; } kylin-wayland-compositor/src/input/cursor.c0000664000175000017500000014747215160461067020113 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "input/event.h" #include "input_p.h" #include "scene/surface.h" #include "server.h" #include "util/time.h" #include "xwayland.h" /* cursor images used in compositor */ static char *cursor_image[] = { "none", "default", "context-menu", "help", "pointer", "progress", "wait", "cell", "crosshair", "text", "vertical-text", "alias", "copy", "move", "no-drop", "not-allowed", "grab", "grabbing", "e-resize", "n-resize", "ne-resize", "nw-resize", "s-resize", "se-resize", "sw-resize", "w-resize", "ew-resize", "ns-resize", "nesw-resize", "nwse-resize", "col-resize", "row-resize", "all-scroll", "zoom-in", "zoom-out", }; static bool cursor_set_node(struct cursor_node *cursor_node, struct ky_scene_node *hover) { if (hover == cursor_node->node) { return false; } if (cursor_node->node) { wl_list_remove(&cursor_node->destroy.link); } if (hover) { wl_signal_add(&hover->events.destroy, &cursor_node->destroy); } cursor_node->node = hover; return true; } static bool cursor_update_node(struct cursor *cursor, bool click) { struct seat *seat = cursor->seat; /* find node below the cursor */ struct ky_scene_node *hover = ky_scene_node_at(&seat->scene->tree.node, cursor->lx, cursor->ly, &cursor->sx, &cursor->sy); /* update cursor hover node */ if (!click) { return cursor_set_node(&cursor->hover, hover); } /* update cursor focus node */ return cursor_set_node(&cursor->focus, hover); } static void _cursor_feed_motion(struct cursor *cursor, uint32_t time) { struct seat *seat = cursor->seat; struct ky_scene_node *old_hover = cursor->hover.node; bool changed = cursor_update_node(cursor, false); bool left_button_pressed = LEFT_BUTTON_PRESSED(cursor->last_click_button, cursor->last_click_pressed); /* if hold press moving but not dragging */ if (left_button_pressed && cursor->focus.node && cursor->focus.node != cursor->hover.node && !selection_is_dragging(seat)) { struct input_event_node *inode = input_event_node_from_node(cursor->focus.node); if (inode && inode->impl->hover) { cursor->hold_mode = inode->impl->hover(seat, cursor->focus.node, cursor->lx, cursor->ly, time, false, true, inode->data); } if (cursor->hold_mode) { return; } } /* mark hold_mode to false, hover to node again */ cursor->hold_mode = false; /* cursor has moved to another node */ struct input_event_node *inode = input_event_node_from_node(cursor->hover.node); if (changed && old_hover) { struct input_event_node *old_inode = input_event_node_from_node(old_hover); if (old_inode && old_inode->impl->leave) { bool leave = input_event_node_root(old_inode) != input_event_node_root(inode); old_inode->impl->leave(seat, old_hover, leave, old_inode->data); } } /* hover current node */ if (inode && inode->impl->hover) { inode->impl->hover(seat, cursor->hover.node, cursor->sx, cursor->sy, time, changed, false, inode->data); } selection_handle_cursor_move(seat, cursor->lx, cursor->ly); if (!cursor->hover.node) { /* once no node found under the cursor, restore cursor to default */ cursor_set_image(cursor, CURSOR_DEFAULT); /* clear pointer focus if hover changed to null */ if (changed) { seat_notify_leave(seat, NULL); } } } /* return true if pointer is locked */ static bool cursor_apply_constraint(struct cursor *cursor, struct wlr_input_device *device, double *dx, double *dy); void cursor_feed_motion(struct cursor *cursor, uint32_t time, struct wlr_input_device *device, double dx, double dy, double dx_unaccel, double dy_unaccel) { wlr_relative_pointer_manager_v1_send_relative_motion( cursor->seat->manager->relative_pointer, cursor->seat->wlr_seat, (uint64_t)time * 1000, dx, dy, dx_unaccel, dy_unaccel); if (cursor_apply_constraint(cursor, device, &dx, &dy)) { return; } cursor_move(cursor, device, dx, dy, true, false); struct seat_pointer_grab *pointer_grab = cursor->seat->pointer_grab; if (pointer_grab && pointer_grab->interface->motion && pointer_grab->interface->motion(pointer_grab, time, cursor->lx, cursor->ly)) { return; } _cursor_feed_motion(cursor, time); } static void cursor_motion_absolute(struct cursor *cursor, uint32_t time, struct wlr_input_device *dev, double x, double y) { double lx, ly; wlr_cursor_absolute_to_layout_coords(cursor->wlr_cursor, dev, x, y, &lx, &ly); double dx = lx - cursor->lx; double dy = ly - cursor->ly; cursor_feed_motion(cursor, time, dev, dx, dy, dx, dy); } static void cursor_feed_fake_motion(struct cursor *cursor, bool leave) { /* force leave current hover node, then re-hover it */ if (leave && cursor->hover.node) { struct input_event_node *inode = input_event_node_from_node(cursor->hover.node); if (inode && inode->impl->leave) { inode->impl->leave(cursor->seat, cursor->hover.node, false, inode->data); } } /* skip motion when has grab */ if (cursor->seat->pointer_grab && cursor->seat->pointer_grab->interface->motion) { return; } _cursor_feed_motion(cursor, current_time_msec()); if (leave) { wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); } } void cursor_feed_button(struct cursor *cursor, uint32_t button, bool pressed, uint32_t time, uint32_t double_click_time) { struct seat *seat = cursor->seat; if (seat->pointer_grab && seat->pointer_grab->interface->button && seat->pointer_grab->interface->button(seat->pointer_grab, time, button, pressed)) { return; } bool last_is_pressed = cursor->last_click_pressed; uint32_t last_button = cursor->last_click_button; cursor->last_click_pressed = pressed; /* current focus node */ struct ky_scene_node *old_focus = cursor->focus.node; bool changed = cursor_update_node(cursor, true); if (wlr_seat_pointer_has_grab(seat->wlr_seat)) { seat_notify_button(seat, time, button, pressed); return; } /* old focus node and view */ struct input_event_node *old_inode = input_event_node_from_node(old_focus); struct input_event_node *inode = input_event_node_from_node(cursor->focus.node); /* exit hold mode if any button clicked */ if (cursor->hold_mode) { /* send button release to last focus node */ if (old_inode && old_inode->impl->click) { old_inode->impl->click(seat, old_focus, BTN_LEFT, false, time, CLICK_STATE_NONE, old_inode->data); } /* leave focus node, otherwise wrong cursor image */ if (old_inode && old_inode->impl->leave) { bool leave = input_event_node_root(old_inode) != input_event_node_root(inode); old_inode->impl->leave(seat, old_focus, leave, old_inode->data); } if (inode && inode->impl->hover) { inode->impl->hover(seat, cursor->focus.node, cursor->sx, cursor->sy, time, true, false, inode->data); } else { cursor_set_image(cursor, CURSOR_DEFAULT); } cursor->hold_mode = false; return; } /* no pointer motion or hover node was destroyed */ if (!cursor->hover.node && cursor->focus.node && inode) { inode->impl->hover(seat, cursor->focus.node, cursor->sx, cursor->sy, time, true, false, inode->data); } /* send a button released event to old focus node */ if (old_focus && changed && !pressed && last_is_pressed) { kywc_log(KYWC_DEBUG, "Release button %d in %p", last_button, old_focus); if (old_inode && old_inode->impl->click) { old_inode->impl->click(seat, old_focus, last_button, false, time, CLICK_STATE_FOCUS_LOST, old_inode->data); } /* fix cursor image sometimes */ if (!seat_is_dragging(seat)) { cursor_feed_fake_motion(cursor, false); } return; } bool double_click = false; if (pressed) { if (!changed && button == cursor->last_click_button && time - cursor->last_click_time < double_click_time) { double_click = true; } /* reset after a double click */ cursor->last_click_time = double_click ? 0 : time; cursor->last_click_button = button; } if (inode && inode->impl->click) { inode->impl->click(seat, cursor->focus.node, button, pressed, time, double_click ? CLICK_STATE_DOUBLE : CLICK_STATE_NONE, inode->data); } /* update surface coord if surface size changed when click, like maximize */ if (cursor->hover.node == cursor->focus.node && !seat_is_dragging(seat)) { cursor_feed_fake_motion(cursor, false); } if (!cursor->focus.node) { /* clear keyboard focus */ seat_focus_surface(seat, NULL); } } static void cursor_handle_motion(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, motion); struct wlr_pointer_motion_event *event = data; struct input *input = input_from_wlr_input(&event->pointer->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_POINTER_MOTION, event)) { return; } cursor_set_hidden(cursor, false); cursor_feed_motion(cursor, event->time_msec, &event->pointer->base, event->delta_x, event->delta_y, event->unaccel_dx, event->unaccel_dy); } static void cursor_handle_motion_absolute(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, motion_absolute); struct wlr_pointer_motion_absolute_event *event = data; struct input *input = input_from_wlr_input(&event->pointer->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_POINTER_MOTION_ABSOLUTE, event)) { return; } cursor_set_hidden(cursor, false); cursor_motion_absolute(cursor, event->time_msec, &event->pointer->base, event->x, event->y); } static void cursor_handle_button(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, button); struct wlr_pointer_button_event *event = data; struct input *input = input_from_wlr_input(&event->pointer->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_POINTER_BUTTON, event)) { return; } cursor_set_hidden(cursor, false); cursor_feed_button(cursor, event->button, event->state == WL_POINTER_BUTTON_STATE_PRESSED, event->time_msec, input->state.double_click_time); } void cursor_feed_axis(struct cursor *cursor, uint32_t orientation, uint32_t source, double delta, int32_t delta_discrete, int32_t relative_direction, uint32_t time) { struct seat *seat = cursor->seat; if (seat->pointer_grab && seat->pointer_grab->interface->axis && seat->pointer_grab->interface->axis( seat->pointer_grab, time, orientation == WL_POINTER_AXIS_VERTICAL_SCROLL, delta)) { return; } /* Notify the client with pointer focus of the axis event. */ wlr_seat_pointer_notify_axis(seat->wlr_seat, time, orientation, delta, delta_discrete, source, relative_direction); } static void cursor_handle_axis(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, axis); struct wlr_pointer_axis_event *event = data; struct input *input = input_from_wlr_input(&event->pointer->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_POINTER_AXIS, event)) { return; } /* workaround to fix zwcada2023 crash */ static uint32_t time_tmp = 0; if (time_tmp == event->time_msec) { wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); } time_tmp = event->time_msec; cursor_set_hidden(cursor, false); cursor_feed_axis(cursor, event->orientation, event->source, input->state.scroll_factor * event->delta, roundf(input->state.scroll_factor * event->delta_discrete), event->relative_direction, event->time_msec); } static void cursor_handle_frame(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, frame); if (input_event_filter(cursor->seat, NULL, INPUT_EVENT_POINTER_FRAME, data)) { return; } /* only send to client when pointer grab frame return false */ if (cursor->seat->pointer_grab && (!cursor->seat->pointer_grab->interface->frame || cursor->seat->pointer_grab->interface->frame(cursor->seat->pointer_grab))) { return; } /* Notify the client with pointer focus of the frame event. */ wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); } static void cursor_handle_tablet_tool_axis(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, tablet_tool_axis); struct wlr_tablet_tool_axis_event *event = data; struct input *input = input_from_wlr_input(&event->tablet->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_TABLET_TOOL_AXIS, event)) { return; } cursor_set_hidden(cursor, false); /* force to pointer when move and resize */ if (cursor->seat->pointer_grab) { cursor->tablet_tool_tip_simulation_pointer = true; } bool change_x = event->updated_axes & WLR_TABLET_TOOL_AXIS_X; bool change_y = event->updated_axes & WLR_TABLET_TOOL_AXIS_Y; if (cursor->tablet_tool_tip_simulation_pointer && (change_x || change_y)) { if (event->tool->type == WLR_TABLET_TOOL_TYPE_LENS || event->tool->type == WLR_TABLET_TOOL_TYPE_MOUSE) { cursor_feed_motion(cursor, event->time_msec, &event->tablet->base, event->dx, event->dy, event->dx, event->dy); } else { cursor_motion_absolute(cursor, event->time_msec, &event->tablet->base, change_x ? event->x : NAN, change_y ? event->y : NAN); } wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); return; } if (!cursor->tablet_tool_tip_simulation_pointer) { tablet_handle_tool_axis(event); } } static void cursor_handle_tablet_tool_proximity(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, tablet_tool_proximity); struct wlr_tablet_tool_proximity_event *event = data; struct input *input = input_from_wlr_input(&event->tablet->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_TABLET_TOOL_PROXIMITY, event)) { return; } cursor_set_hidden(cursor, false); if (!cursor->tablet_tool_tip_simulation_pointer && tablet_handle_tool_proximity(event)) { return; } if (event->state == WLR_TABLET_TOOL_PROXIMITY_OUT) { return; } cursor_motion_absolute(cursor, event->time_msec, &event->tablet->base, event->x, event->y); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); } static void cursor_handle_tablet_tool_tip(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, tablet_tool_tip); struct wlr_tablet_tool_tip_event *event = data; struct input *input = input_from_wlr_input(&event->tablet->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_TABLET_TOOL_TIP, event)) { return; } cursor->tablet_tool_tip_down = event->state == WLR_TABLET_TOOL_TIP_DOWN; cursor_set_hidden(cursor, false); /* workaround: tablet simulate pointer drag to fix peony drag */ cursor_motion_absolute(cursor, event->time_msec, &event->tablet->base, event->x, event->y); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); if (cursor->tablet_tool_tip_simulation_pointer && event->state == WLR_TABLET_TOOL_TIP_UP) { cursor->tablet_tool_tip_simulation_pointer = false; cursor_feed_button(cursor, BTN_LEFT, false, event->time_msec, input->state.double_click_time); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); /* workaround to send a tool-tip up */ tablet_handle_tool_tip(event); return; } if (seat_is_dragging(cursor->seat)) { return; } if (tablet_handle_tool_tip(event)) { return; } if (event->state == WLR_TABLET_TOOL_TIP_UP) { return; } cursor->tablet_tool_tip_simulation_pointer = true; cursor_feed_button(cursor, BTN_LEFT, true, event->time_msec, input->state.double_click_time); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); } static void cursor_handle_tablet_tool_button(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, tablet_tool_button); struct wlr_tablet_tool_button_event *event = data; struct input *input = input_from_wlr_input(&event->tablet->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_TABLET_TOOL_BUTTON, event)) { return; } if (seat_is_dragging(cursor->seat)) { return; } cursor_set_hidden(cursor, false); if (cursor->tablet_tool_buttons > 0 && cursor->tablet_tool_button_simulation_pointer) { struct input *input = input_from_wlr_input(&event->tablet->base); cursor_feed_button(cursor, BTN_RIGHT, event->state == WLR_BUTTON_PRESSED, event->time_msec, input->state.double_click_time); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); } else if (tablet_handle_tool_button(event)) { cursor->tablet_tool_button_simulation_pointer = false; } else { cursor->tablet_tool_button_simulation_pointer = true; } switch (event->state) { case WLR_BUTTON_PRESSED: cursor->tablet_tool_buttons++; break; case WLR_BUTTON_RELEASED: if (cursor->tablet_tool_buttons == 0) { kywc_log(KYWC_ERROR, "Inconsistent tablet tool button events"); } else { cursor->tablet_tool_buttons--; } break; } } static void cursor_handle_touch_up(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, touch_up); struct wlr_touch_up_event *event = data; struct input *input = input_from_wlr_input(&event->touch->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_TOUCH_UP, event)) { return; } touch_handle_up(event, !cursor->touch_simulation_pointer); if (cursor->touch_simulation_pointer && cursor->pointer_touch_id == event->touch_id) { cursor->pointer_touch_up = true; struct input *input = input_from_wlr_input(&event->touch->base); cursor_feed_button(cursor, BTN_LEFT, false, event->time_msec, input->state.double_click_time); } cursor->pointer_touch_id = -1; } static void cursor_handle_touch_down(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, touch_down); struct wlr_touch_down_event *event = data; struct input *input = input_from_wlr_input(&event->touch->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_TOUCH_DOWN, event)) { return; } cursor_set_hidden(cursor, true); /* workaround to fix peony drag icon position */ cursor_motion_absolute(cursor, event->time_msec, &event->touch->base, event->x, event->y); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); cursor->pointer_touch_id = event->touch_id; if (touch_handle_down(event)) { return; } cursor->touch_simulation_pointer = true; cursor_feed_button(cursor, BTN_LEFT, true, event->time_msec, input->state.double_click_time); } static void cursor_handle_touch_motion(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, touch_motion); struct wlr_touch_motion_event *event = data; struct input *input = input_from_wlr_input(&event->touch->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_TOUCH_MOTION, event)) { return; } touch_handle_motion(event, !cursor->touch_simulation_pointer); if ((cursor->seat->pointer_grab || cursor->touch_simulation_pointer) && cursor->pointer_touch_id == event->touch_id) { cursor_motion_absolute(cursor, event->time_msec, &event->touch->base, event->x, event->y); } } static void cursor_handle_touch_cancel(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, touch_cancel); struct wlr_touch_cancel_event *event = data; struct input *input = input_from_wlr_input(&event->touch->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_TOUCH_CANCEL, event)) { return; } touch_handle_cancel(event, !cursor->touch_simulation_pointer); if (cursor->touch_simulation_pointer && cursor->pointer_touch_id == event->touch_id) { cursor->pointer_touch_up = true; struct input *input = input_from_wlr_input(&event->touch->base); cursor_feed_button(cursor, BTN_LEFT, false, event->time_msec, input->state.double_click_time); } cursor->pointer_touch_id = -1; } static void cursor_handle_touch_frame(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, touch_frame); if (input_event_filter(cursor->seat, NULL, INPUT_EVENT_TOUCH_FRAME, data)) { return; } if (cursor->touch_simulation_pointer) { wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); if (cursor->pointer_touch_up) { cursor->pointer_touch_up = false; cursor->touch_simulation_pointer = false; } return; } wlr_seat_touch_notify_frame(cursor->seat->wlr_seat); } static void cursor_handle_swipe_begin(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, swipe_begin); struct wlr_pointer_swipe_begin_event *event = data; struct input *input = input_from_wlr_input(&event->pointer->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_GESTURE_SWIPE_BEGIN, event)) { return; } gesture_state_begin(&cursor->gestures, GESTURE_TYPE_SWIPE, GESTURE_DEVICE_TOUCHPAD, GESTURE_EDGE_NONE, event->fingers); wlr_pointer_gestures_v1_send_swipe_begin(cursor->seat->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->fingers); } static void cursor_handle_swipe_update(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, swipe_update); struct wlr_pointer_swipe_update_event *event = data; struct input *input = input_from_wlr_input(&event->pointer->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_GESTURE_SWIPE_UPDATE, event)) { return; } gesture_state_update(&cursor->gestures, GESTURE_TYPE_SWIPE, GESTURE_DEVICE_TOUCHPAD, event->dx, event->dy, NAN, NAN); wlr_pointer_gestures_v1_send_swipe_update(cursor->seat->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->dx, event->dy); } static void cursor_handle_swipe_end(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, swipe_end); struct wlr_pointer_swipe_end_event *event = data; struct input *input = input_from_wlr_input(&event->pointer->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_GESTURE_SWIPE_END, event)) { return; } bool handled = gesture_state_end(&cursor->gestures, GESTURE_TYPE_SWIPE, GESTURE_DEVICE_TOUCHPAD, event->cancelled); /* tell client the gesture is cancelled if gesture handled by compositor */ wlr_pointer_gestures_v1_send_swipe_end(cursor->seat->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->cancelled || handled); } static void cursor_handle_pinch_begin(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, pinch_begin); struct wlr_pointer_pinch_begin_event *event = data; struct input *input = input_from_wlr_input(&event->pointer->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_GESTURE_PINCH_BEGIN, event)) { return; } gesture_state_begin(&cursor->gestures, GESTURE_TYPE_PINCH, GESTURE_DEVICE_TOUCHPAD, GESTURE_EDGE_NONE, event->fingers); wlr_pointer_gestures_v1_send_pinch_begin(cursor->seat->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->fingers); } static void cursor_handle_pinch_update(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, pinch_update); struct wlr_pointer_pinch_update_event *event = data; struct input *input = input_from_wlr_input(&event->pointer->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_GESTURE_PINCH_UPDATE, event)) { return; } gesture_state_update(&cursor->gestures, GESTURE_TYPE_PINCH, GESTURE_DEVICE_TOUCHPAD, event->dx, event->dy, event->scale, event->rotation); wlr_pointer_gestures_v1_send_pinch_update(cursor->seat->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->dx, event->dy, event->scale, event->rotation); } static void cursor_handle_pinch_end(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, pinch_end); struct wlr_pointer_pinch_end_event *event = data; struct input *input = input_from_wlr_input(&event->pointer->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_GESTURE_PINCH_END, event)) { return; } bool handled = gesture_state_end(&cursor->gestures, GESTURE_TYPE_PINCH, GESTURE_DEVICE_TOUCHPAD, event->cancelled); wlr_pointer_gestures_v1_send_pinch_end(cursor->seat->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->cancelled || handled); } static void cursor_handle_hold_begin(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, hold_begin); struct wlr_pointer_hold_begin_event *event = data; struct input *input = input_from_wlr_input(&event->pointer->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_GESTURE_HOLD_BEGIN, event)) { return; } gesture_state_begin(&cursor->gestures, GESTURE_TYPE_HOLD, GESTURE_DEVICE_TOUCHPAD, GESTURE_EDGE_NONE, event->fingers); wlr_pointer_gestures_v1_send_hold_begin(cursor->seat->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->fingers); } static void cursor_handle_hold_end(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, hold_end); struct wlr_pointer_hold_end_event *event = data; struct input *input = input_from_wlr_input(&event->pointer->base); if (input_event_filter(cursor->seat, input, INPUT_EVENT_GESTURE_HOLD_END, event)) { return; } bool handled = gesture_state_end(&cursor->gestures, GESTURE_TYPE_HOLD, GESTURE_DEVICE_TOUCHPAD, event->cancelled); wlr_pointer_gestures_v1_send_hold_end(cursor->seat->pointer_gestures, cursor->seat->wlr_seat, event->time_msec, event->cancelled || handled); } static void cursor_handle_surface_client_commit(struct wl_listener *listener, void *data) { float scale = xwayland_get_scale(); if (scale == 1.0) { return; } struct cursor *cursor = wl_container_of(listener, cursor, surface_client_commit); struct wlr_surface_state *pending = &cursor->surface->pending; pending->width = xwayland_unscale(pending->width); pending->height = xwayland_unscale(pending->height); if (pending->committed & WLR_SURFACE_STATE_SURFACE_DAMAGE) { wlr_region_scale(&pending->surface_damage, &pending->surface_damage, 1.0 / scale); } if (pending->committed & WLR_SURFACE_STATE_OFFSET) { pending->dx = xwayland_unscale(pending->dx); pending->dy = xwayland_unscale(pending->dy); } } static void cursor_handle_surface_destroy(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, surface_destroy); wl_list_remove(&cursor->surface_client_commit.link); wl_list_remove(&cursor->surface_destroy.link); wl_list_init(&cursor->surface_client_commit.link); wl_list_init(&cursor->surface_destroy.link); cursor->surface = NULL; } void cursor_set_surface(struct cursor *cursor, struct wlr_surface *surface, int32_t hotspot_x, int32_t hotspot_y, struct wl_client *client) { /* unscale cursor surface from xwayland */ if (xwayland_check_client(client)) { if (surface) { hotspot_x = xwayland_unscale(hotspot_x); hotspot_y = xwayland_unscale(hotspot_y); } if (cursor->surface != surface) { if (cursor->surface) { cursor_handle_surface_destroy(&cursor->surface_destroy, NULL); } if (surface) { cursor->surface = surface; wl_signal_add(&surface->events.client_commit, &cursor->surface_client_commit); wl_signal_add(&surface->events.destroy, &cursor->surface_destroy); } } } /* use this to filter cursor image */ cursor->client_requested = true; wlr_cursor_set_surface(cursor->wlr_cursor, surface, hotspot_x, hotspot_y); } static void cursor_handle_request_set_cursor(struct wl_listener *listener, void *data) { struct cursor *cursor = wl_container_of(listener, cursor, request_set_cursor); struct wlr_seat_pointer_request_set_cursor_event *event = data; struct wlr_seat_client *focused_client = cursor->seat->wlr_seat->pointer_state.focused_client; struct seat_pointer_grab *grab = cursor->seat->pointer_grab; if (cursor->image_locks > 0 || cursor->hidden) { return; } if (grab || focused_client != event->seat_client) { return; } cursor_set_surface(cursor, event->surface, event->hotspot_x, event->hotspot_y, event->seat_client->client); } static void cursor_node_handle_destroy(struct wl_listener *listener, void *data) { struct cursor_node *cursor_node = wl_container_of(listener, cursor_node, destroy); wl_list_remove(&cursor_node->destroy.link); cursor_node->node = NULL; } void cursor_set_xcursor_manager(struct cursor *cursor, const char *theme, uint32_t size, bool saved) { bool need_set = !cursor->xcursor_manager; if (!need_set) { bool same_theme = (!cursor->xcursor_manager->name && !theme) || (theme && cursor->xcursor_manager->name && strcmp(theme, cursor->xcursor_manager->name) == 0); need_set = !same_theme || cursor->xcursor_manager->size != size; } if (!need_set) { return; } /* clear wlr_cursor state */ wlr_cursor_unset_image(cursor->wlr_cursor); /* destroy the prev one, NULL is ok */ wlr_xcursor_manager_destroy(cursor->xcursor_manager); cursor->xcursor_manager = wlr_xcursor_manager_create(theme, size); /* apply the new configuration */ cursor_rebase(cursor); if (!saved) { return; } free((void *)cursor->seat->state.cursor_theme); cursor->seat->state.cursor_theme = strdup(cursor->xcursor_manager->name); cursor->seat->state.cursor_size = cursor->xcursor_manager->size; wl_signal_emit_mutable(&cursor->seat->events.cursor_configure, NULL); } struct cursor *cursor_create(struct seat *seat) { struct cursor *cursor = calloc(1, sizeof(*cursor)); if (!cursor) { return NULL; } struct wlr_cursor *wlr_cursor = wlr_cursor_create(); if (!wlr_cursor) { free(cursor); return NULL; } cursor->seat = seat; seat->cursor = cursor; cursor->wlr_cursor = wlr_cursor; cursor->pointer_touch_id = -1; // TODO: multi-layout for multi-seat wlr_cursor_attach_output_layout(wlr_cursor, seat->layout); const char *xcursor_theme = getenv("XCURSOR_THEME"); const char *xcursor_size = getenv("XCURSOR_SIZE"); /* xcursor manager per seat for cursor theme */ cursor_set_xcursor_manager( cursor, xcursor_theme ? xcursor_theme : seat->state.cursor_theme, xcursor_size ? (uint32_t)atoi(xcursor_size) : seat->state.cursor_size, false); cursor->motion.notify = cursor_handle_motion; wl_signal_add(&wlr_cursor->events.motion, &cursor->motion); cursor->motion_absolute.notify = cursor_handle_motion_absolute; wl_signal_add(&wlr_cursor->events.motion_absolute, &cursor->motion_absolute); cursor->button.notify = cursor_handle_button; wl_signal_add(&wlr_cursor->events.button, &cursor->button); cursor->axis.notify = cursor_handle_axis; wl_signal_add(&wlr_cursor->events.axis, &cursor->axis); cursor->frame.notify = cursor_handle_frame; wl_signal_add(&wlr_cursor->events.frame, &cursor->frame); cursor->swipe_begin.notify = cursor_handle_swipe_begin; wl_signal_add(&wlr_cursor->events.swipe_begin, &cursor->swipe_begin); cursor->swipe_update.notify = cursor_handle_swipe_update; wl_signal_add(&wlr_cursor->events.swipe_update, &cursor->swipe_update); cursor->swipe_end.notify = cursor_handle_swipe_end; wl_signal_add(&wlr_cursor->events.swipe_end, &cursor->swipe_end); cursor->pinch_begin.notify = cursor_handle_pinch_begin; wl_signal_add(&wlr_cursor->events.pinch_begin, &cursor->pinch_begin); cursor->pinch_update.notify = cursor_handle_pinch_update; wl_signal_add(&wlr_cursor->events.pinch_update, &cursor->pinch_update); cursor->pinch_end.notify = cursor_handle_pinch_end; wl_signal_add(&wlr_cursor->events.pinch_end, &cursor->pinch_end); cursor->hold_begin.notify = cursor_handle_hold_begin; wl_signal_add(&wlr_cursor->events.hold_begin, &cursor->hold_begin); cursor->hold_end.notify = cursor_handle_hold_end; wl_signal_add(&wlr_cursor->events.hold_end, &cursor->hold_end); cursor->touch_up.notify = cursor_handle_touch_up; wl_signal_add(&wlr_cursor->events.touch_up, &cursor->touch_up); cursor->touch_down.notify = cursor_handle_touch_down; wl_signal_add(&wlr_cursor->events.touch_down, &cursor->touch_down); cursor->touch_motion.notify = cursor_handle_touch_motion; wl_signal_add(&wlr_cursor->events.touch_motion, &cursor->touch_motion); cursor->touch_cancel.notify = cursor_handle_touch_cancel; wl_signal_add(&wlr_cursor->events.touch_cancel, &cursor->touch_cancel); cursor->touch_frame.notify = cursor_handle_touch_frame; wl_signal_add(&wlr_cursor->events.touch_frame, &cursor->touch_frame); cursor->tablet_tool_axis.notify = cursor_handle_tablet_tool_axis; wl_signal_add(&wlr_cursor->events.tablet_tool_axis, &cursor->tablet_tool_axis); cursor->tablet_tool_proximity.notify = cursor_handle_tablet_tool_proximity; wl_signal_add(&wlr_cursor->events.tablet_tool_proximity, &cursor->tablet_tool_proximity); cursor->tablet_tool_tip.notify = cursor_handle_tablet_tool_tip; wl_signal_add(&wlr_cursor->events.tablet_tool_tip, &cursor->tablet_tool_tip); cursor->tablet_tool_button.notify = cursor_handle_tablet_tool_button; wl_signal_add(&wlr_cursor->events.tablet_tool_button, &cursor->tablet_tool_button); cursor->request_set_cursor.notify = cursor_handle_request_set_cursor; wl_signal_add(&seat->wlr_seat->events.request_set_cursor, &cursor->request_set_cursor); cursor->surface_client_commit.notify = cursor_handle_surface_client_commit; wl_list_init(&cursor->surface_client_commit.link); cursor->surface_destroy.notify = cursor_handle_surface_destroy; wl_list_init(&cursor->surface_destroy.link); cursor->hover.destroy.notify = cursor_node_handle_destroy; cursor->focus.destroy.notify = cursor_node_handle_destroy; gesture_state_init(&cursor->gestures, seat->wlr_seat->display); return cursor; } void cursor_destroy(struct cursor *cursor) { wl_list_remove(&cursor->motion.link); wl_list_remove(&cursor->motion_absolute.link); wl_list_remove(&cursor->button.link); wl_list_remove(&cursor->axis.link); wl_list_remove(&cursor->frame.link); wl_list_remove(&cursor->request_set_cursor.link); wl_list_remove(&cursor->swipe_begin.link); wl_list_remove(&cursor->swipe_update.link); wl_list_remove(&cursor->swipe_end.link); wl_list_remove(&cursor->pinch_begin.link); wl_list_remove(&cursor->pinch_update.link); wl_list_remove(&cursor->pinch_end.link); wl_list_remove(&cursor->hold_begin.link); wl_list_remove(&cursor->hold_end.link); wl_list_remove(&cursor->touch_up.link); wl_list_remove(&cursor->touch_down.link); wl_list_remove(&cursor->touch_motion.link); wl_list_remove(&cursor->touch_cancel.link); wl_list_remove(&cursor->touch_frame.link); wl_list_remove(&cursor->tablet_tool_axis.link); wl_list_remove(&cursor->tablet_tool_proximity.link); wl_list_remove(&cursor->tablet_tool_tip.link); wl_list_remove(&cursor->tablet_tool_button.link); wl_list_remove(&cursor->surface_client_commit.link); wl_list_remove(&cursor->surface_destroy.link); if (cursor->hover.node) { wl_list_remove(&cursor->hover.destroy.link); } if (cursor->focus.node) { wl_list_remove(&cursor->focus.destroy.link); } wlr_xcursor_manager_destroy(cursor->xcursor_manager); wlr_cursor_destroy(cursor->wlr_cursor); gesture_state_finish(&cursor->gestures); cursor->seat->cursor = NULL; free(cursor); } void curosr_add_input(struct seat *seat, struct input *input) { struct wlr_cursor *wlr_cursor = seat->cursor->wlr_cursor; wlr_cursor_attach_input_device(wlr_cursor, input->wlr_input); } void cursor_remove_input(struct input *input) { struct wlr_cursor *wlr_cursor = input->seat->cursor->wlr_cursor; wlr_cursor_detach_input_device(wlr_cursor, input->wlr_input); } static void _cursor_set_image(struct cursor *cursor, enum cursor_name name, bool force) { struct server *server = cursor->seat->manager->server; if (server->terminate) { return; } if (cursor->image_locks > 0 || cursor->hidden) { return; } /* early return if cursor not changed when client not requested */ if (!force && name == cursor->name && !cursor->client_requested) { return; } if (name == CURSOR_NONE) { wlr_cursor_unset_image(cursor->wlr_cursor); } else { wlr_cursor_set_xcursor(cursor->wlr_cursor, cursor->xcursor_manager, cursor_image[name]); } cursor->client_requested = false; cursor->name = name; kywc_log(KYWC_DEBUG, "Set %s cursor to %s", cursor->seat->name, cursor_image[name]); } void cursor_set_image(struct cursor *cursor, enum cursor_name name) { _cursor_set_image(cursor, name, false); } void cursor_set_resize_image(struct cursor *cursor, uint32_t edges) { enum cursor_name name = CURSOR_DEFAULT; if (edges == (KYWC_EDGE_TOP | KYWC_EDGE_LEFT)) { name = CURSOR_RESIZE_TOP_LEFT; } else if (edges == KYWC_EDGE_TOP) { name = CURSOR_RESIZE_TOP; } else if (edges == (KYWC_EDGE_TOP | KYWC_EDGE_RIGHT)) { name = CURSOR_RESIZE_TOP_RIGHT; } else if (edges == KYWC_EDGE_RIGHT) { name = CURSOR_RESIZE_RIGHT; } else if (edges == (KYWC_EDGE_BOTTOM | KYWC_EDGE_RIGHT)) { name = CURSOR_RESIZE_BOTTOM_RIGHT; } else if (edges == KYWC_EDGE_BOTTOM) { name = CURSOR_RESIZE_BOTTOM; } else if (edges == (KYWC_EDGE_BOTTOM | KYWC_EDGE_LEFT)) { name = CURSOR_RESIZE_BOTTOM_LEFT; } else if (edges == KYWC_EDGE_LEFT) { name = CURSOR_RESIZE_LEFT; } _cursor_set_image(cursor, name, false); } void cursor_rebase(struct cursor *cursor) { cursor_move(cursor, NULL, 0, 0, true, false); _cursor_set_image(cursor, CURSOR_DEFAULT, true); cursor_feed_fake_motion(cursor, true); } static bool seat_rebase_cursor(struct seat *seat, int index, void *data) { struct cursor *cursor = seat->cursor; bool need_rebase = *(bool *)data; if (!need_rebase) { struct ky_scene_node *node = ky_scene_node_at(&seat->scene->tree.node, cursor->lx, cursor->ly, NULL, NULL); need_rebase = node != cursor->hover.node; } if (need_rebase) { cursor_rebase(cursor); } return false; } void cursor_rebase_all(bool force) { input_manager_for_each_seat(seat_rebase_cursor, &force); } void cursor_move(struct cursor *cursor, struct wlr_input_device *dev, double x, double y, bool delta, bool absolute) { struct wlr_cursor *wlr_cursor = cursor->wlr_cursor; if (delta) { wlr_cursor_move(wlr_cursor, dev, x, y); } else if (absolute) { wlr_cursor_warp_absolute(wlr_cursor, dev, x, y); } else { wlr_cursor_warp(wlr_cursor, dev, x, y); } cursor->lx = wlr_cursor->x; cursor->ly = wlr_cursor->y; struct seat_cursor_motion_event event = { .device = dev ? input_from_wlr_input(dev) : NULL, .time_msec = current_time_msec(), .lx = cursor->lx, .ly = cursor->ly, }; wl_signal_emit_mutable(&cursor->seat->events.cursor_motion, &event); } void cursor_set_hidden(struct cursor *cursor, bool hidden) { if (cursor->hidden == hidden) { return; } if (hidden) { cursor_set_image(cursor, CURSOR_NONE); cursor->hidden = true; } else { cursor->hidden = false; cursor_rebase(cursor); } } void cursor_lock_image(struct cursor *cursor, bool lock) { if (lock) { ++cursor->image_locks; } else { assert(cursor->image_locks > 0); --cursor->image_locks; } } static void cursor_constraint_warp_to_hint(struct cursor_constraint *constraint) { struct wlr_pointer_constraint_v1_state *current = &constraint->constraint->current; if (!current->cursor_hint.enabled) { return; } struct wlr_surface *surface = constraint->constraint->surface; struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(surface); if (!buffer) { return; } int lx, ly; ky_scene_node_coords(&buffer->node, &lx, &ly); double sx = current->cursor_hint.x; double sy = current->cursor_hint.y; cursor_move(constraint->cursor, NULL, lx + sx, ly + sy, false, false); wlr_seat_pointer_warp(constraint->cursor->seat->wlr_seat, sx, sy); } static void cursor_constraint_deactivate(void *data) { struct cursor_constraint *constraint = data; wlr_pointer_constraint_v1_send_deactivated(constraint->constraint); } static void cursor_active_constraint(struct cursor *cursor, struct cursor_constraint *constraint, bool deactivate_later) { struct cursor_constraint *old_constraint = cursor->active_constraint; if (old_constraint == constraint) { return; } cursor->active_constraint = constraint; if (old_constraint) { wl_list_remove(&old_constraint->surface_unmap.link); wl_list_init(&old_constraint->surface_unmap.link); wl_list_remove(&old_constraint->set_region.link); wl_list_init(&old_constraint->set_region.link); if (!constraint) { cursor_constraint_warp_to_hint(old_constraint); } if (deactivate_later) { struct wl_event_loop *loop = wl_display_get_event_loop(cursor->seat->wlr_seat->display); wl_event_loop_add_idle(loop, cursor_constraint_deactivate, old_constraint); } else { wlr_pointer_constraint_v1_send_deactivated(old_constraint->constraint); } } if (!constraint) { return; } cursor->pending_constraint = NULL; wlr_pointer_constraint_v1_send_activated(constraint->constraint); struct wlr_surface *surface = constraint->constraint->surface; wl_signal_add(&surface->events.unmap, &constraint->surface_unmap); } static bool cursor_constraint_check_region(struct cursor_constraint *constraint) { struct wlr_surface *surface = constraint->constraint->surface; struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(surface); if (!buffer) { return false; } int lx, ly; ky_scene_node_coords(&buffer->node, &lx, &ly); int sx = constraint->cursor->lx - lx; int sy = constraint->cursor->ly - ly; return pixman_region32_contains_point(&constraint->constraint->region, sx, sy, NULL); } static void cursor_set_constraint(struct cursor *cursor, struct cursor_constraint *constraint); static void cursor_constraint_handle_set_region(struct wl_listener *listener, void *data) { struct cursor_constraint *constraint = wl_container_of(listener, constraint, set_region); struct wlr_pointer_constraint_v1 *wlr_constraint = constraint->constraint; struct cursor *cursor = constraint->cursor; /* check if the cursor is in the region */ bool in_region = cursor_constraint_check_region(constraint); /* type always be WLR_POINTER_CONSTRAINT_V1_CONFINED */ if (cursor->pending_constraint == constraint && in_region) { cursor_active_constraint(cursor, cursor->pending_constraint, false); } else if (cursor->active_constraint == constraint && !in_region) { /* deactivate the constraint and set pending if not oneshot */ bool oneshot = wlr_constraint->lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT; cursor_active_constraint(cursor, NULL, oneshot); if (!oneshot) { cursor_set_constraint(cursor, constraint); } } } static void cursor_set_constraint(struct cursor *cursor, struct cursor_constraint *constraint) { if (cursor->active_constraint) { if (cursor->active_constraint == constraint) { return; } /* clear activated constraint always */ cursor_active_constraint(cursor, NULL, false); } if (cursor->pending_constraint == constraint) { return; } if (cursor->pending_constraint) { wl_list_remove(&cursor->pending_constraint->set_region.link); wl_list_init(&cursor->pending_constraint->set_region.link); } cursor->pending_constraint = constraint; if (!constraint) { return; } if (constraint->constraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED) { wl_signal_add(&constraint->constraint->events.set_region, &constraint->set_region); } /* activate this constraint if cursor is in the region */ if (cursor_constraint_check_region(constraint)) { cursor_active_constraint(cursor, constraint, false); } } static bool cursor_apply_constraint(struct cursor *cursor, struct wlr_input_device *device, double *dx, double *dy) { struct cursor_constraint *constraint = cursor->active_constraint; if (!constraint) { constraint = cursor->pending_constraint; } bool has_constraint = constraint && (!device || device->type == WLR_INPUT_DEVICE_POINTER); if (!has_constraint) { return false; } /* no need to check the region once locked */ if (cursor->active_constraint && cursor->active_constraint->constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) { return true; } struct wlr_surface *surface = constraint->constraint->surface; struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(surface); if (!buffer) { return false; } int lx, ly; ky_scene_node_coords(&buffer->node, &lx, &ly); double sx = constraint->cursor->lx - lx, sy = constraint->cursor->ly - ly; double sx_confined, sy_confined; if (!wlr_region_confine(&constraint->constraint->region, sx, sy, sx + *dx, sy + *dy, &sx_confined, &sy_confined)) { return false; } /* activate the pending constraint */ if (constraint == cursor->pending_constraint) { cursor_active_constraint(cursor, constraint, false); } if (!cursor->active_constraint) { return false; } if (cursor->active_constraint->constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) { return true; } *dx = sx_confined - sx; *dy = sy_confined - sy; return false; } static void cursor_constraint_handle_surface_unmap(struct wl_listener *listener, void *data) { struct cursor_constraint *constraint = wl_container_of(listener, constraint, surface_unmap); cursor_active_constraint(constraint->cursor, NULL, false); } static void cursor_constraint_handle_destroy(struct wl_listener *listener, void *data) { struct cursor_constraint *constraint = wl_container_of(listener, constraint, destroy); struct cursor *cursor = constraint->cursor; wl_list_remove(&constraint->destroy.link); wl_list_remove(&constraint->set_region.link); wl_list_remove(&constraint->surface_unmap.link); if (cursor->active_constraint == constraint) { cursor_constraint_warp_to_hint(constraint); cursor->active_constraint = NULL; } else if (cursor->pending_constraint == constraint) { cursor->pending_constraint = NULL; } free(constraint); } struct cursor_constraint *cursor_constraint_create(struct cursor *cursor, struct wlr_pointer_constraint_v1 *constraint) { struct cursor_constraint *cursor_constraint = calloc(1, sizeof(*cursor_constraint)); if (!cursor_constraint) { return NULL; } cursor_constraint->cursor = cursor; cursor_constraint->constraint = constraint; constraint->data = cursor_constraint; cursor_constraint->set_region.notify = cursor_constraint_handle_set_region; wl_list_init(&cursor_constraint->set_region.link); cursor_constraint->destroy.notify = cursor_constraint_handle_destroy; wl_signal_add(&constraint->events.destroy, &cursor_constraint->destroy); cursor_constraint->surface_unmap.notify = cursor_constraint_handle_surface_unmap; wl_list_init(&cursor_constraint->surface_unmap.link); struct wlr_surface *surface = cursor->seat->wlr_seat->keyboard_state.focused_surface; if (surface && surface == constraint->surface) { cursor_set_constraint(cursor, cursor_constraint); } return cursor_constraint; } void cursor_constraint_set_focus(struct seat *seat, struct wlr_surface *surface) { struct wlr_pointer_constraint_v1 *constraint = wlr_pointer_constraints_v1_constraint_for_surface(seat->manager->pointer_constraints, surface, seat->wlr_seat); struct cursor_constraint *cursor_constraint = constraint ? constraint->data : NULL; cursor_set_constraint(seat->cursor, cursor_constraint); } kylin-wayland-compositor/src/meson.build0000664000175000017500000000054715160460057017422 0ustar fengfengwlcom_sources += files( 'main.c', 'server.c', 'plugin/config.c', 'plugin/plugin.c', ) subdir('backend') subdir('config') subdir('effect') subdir('input') subdir('output') subdir('painter') subdir('render') subdir('scene') subdir('security') subdir('theme') subdir('util') subdir('view') subdir('widget') if have_xwayland subdir('xwayland') endif kylin-wayland-compositor/src/view/0000775000175000017500000000000015160461067016226 5ustar fengfengkylin-wayland-compositor/src/view/kde_blur.c0000664000175000017500000002056315160461067020167 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "blur-protocol.h" #include "scene/surface.h" #include "view_p.h" #define KDE_KWIN_BLUR_MANAGER_VERSION 1 enum KDE_BLUR_STATE_MASK { KDE_BLUR_STATE_NONE = 0, KDE_BLUR_STATE_REGION = 1 << 0, }; struct kde_blur_manager { struct wl_global *global; struct wl_list kde_blurs; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct kde_blur { struct wl_list link; struct wl_list resources; struct wlr_surface *wlr_surface; struct wl_listener surface_map; struct wl_listener surface_destroy; struct ky_scene_buffer *scene_buffer; struct wl_listener node_destroy; pixman_region32_t region, pending_region; uint32_t pending_mask; }; static struct kde_blur_manager *manager = NULL; static void kde_blur_apply_state(struct kde_blur *blur) { if (blur->pending_mask & KDE_BLUR_STATE_REGION) { ky_scene_node_set_blur_region(&blur->scene_buffer->node, &blur->region); } blur->pending_mask = KDE_BLUR_STATE_NONE; } static struct kde_blur *kde_blur_from_wlr_surface(struct wlr_surface *wlr_surface) { struct kde_blur *blur; wl_list_for_each(blur, &manager->kde_blurs, link) { if (blur->wlr_surface == wlr_surface) { return blur; } } return NULL; } static void kde_blur_handle_commit(struct wl_client *client, struct wl_resource *resource) { struct kde_blur *blur = wl_resource_get_user_data(resource); if (!blur) { return; } pixman_region32_copy(&blur->region, &blur->pending_region); if (!blur->wlr_surface->mapped) { return; } kde_blur_apply_state(blur); } static void kde_blur_handle_set_region(struct wl_client *client, struct wl_resource *resource, struct wl_resource *region_resource) { struct kde_blur *blur = wl_resource_get_user_data(resource); if (!blur) { return; } if (region_resource) { const pixman_region32_t *region = wlr_region_from_resource(region_resource); pixman_region32_copy(&blur->pending_region, region); } else { pixman_region32_clear(&blur->pending_region); } blur->pending_mask |= KDE_BLUR_STATE_REGION; } static void kde_blur_handle_release(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct org_kde_kwin_blur_interface kde_blur_impl = { .commit = kde_blur_handle_commit, .set_region = kde_blur_handle_set_region, .release = kde_blur_handle_release, }; static void kde_blur_destroy(struct kde_blur *blur) { /* remove blur if surface is not destroyed */ if (blur->scene_buffer) { ky_scene_node_set_blur_region(&blur->scene_buffer->node, NULL); } /* clear destructor when surface destroyed before blur resources */ struct wl_resource *resource, *tmp; wl_resource_for_each_safe(resource, tmp, &blur->resources) { wl_resource_set_user_data(resource, NULL); wl_resource_set_destructor(resource, NULL); wl_list_remove(wl_resource_get_link(resource)); wl_list_init(wl_resource_get_link(resource)); } wl_list_remove(&blur->link); wl_list_remove(&blur->surface_map.link); wl_list_remove(&blur->surface_destroy.link); wl_list_remove(&blur->node_destroy.link); pixman_region32_fini(&blur->region); pixman_region32_fini(&blur->pending_region); free(blur); } static void kde_blur_handle_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); /* destroy blur if no blur resource */ struct kde_blur *blur = wl_resource_get_user_data(resource); if (blur && wl_list_empty(&blur->resources)) { kde_blur_destroy(blur); } } static void blur_handle_node_destroy(struct wl_listener *listener, void *data) { struct kde_blur *blur = wl_container_of(listener, blur, node_destroy); blur->scene_buffer = NULL; kde_blur_destroy(blur); } static void blur_handle_surface_map(struct wl_listener *listener, void *data) { struct kde_blur *blur = wl_container_of(listener, blur, surface_map); wl_list_remove(&blur->surface_map.link); wl_list_init(&blur->surface_map.link); blur->scene_buffer = ky_scene_buffer_try_from_surface(blur->wlr_surface); wl_signal_add(&blur->scene_buffer->node.events.destroy, &blur->node_destroy); kde_blur_apply_state(blur); } static void blur_handle_surface_destroy(struct wl_listener *listener, void *data) { struct kde_blur *blur = wl_container_of(listener, blur, surface_destroy); kde_blur_destroy(blur); } static void handle_create(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id, struct wl_resource *surface_resource) { struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface_resource); struct kde_blur *blur = kde_blur_from_wlr_surface(wlr_surface); if (!blur) { blur = calloc(1, sizeof(*blur)); if (!blur) { wl_client_post_no_memory(client); return; } wl_list_init(&blur->resources); wl_list_insert(&manager->kde_blurs, &blur->link); pixman_region32_init(&blur->region); pixman_region32_init(&blur->pending_region); blur->wlr_surface = wlr_surface; blur->surface_map.notify = blur_handle_surface_map; wl_signal_add(&wlr_surface->events.map, &blur->surface_map); blur->surface_destroy.notify = blur_handle_surface_destroy; wl_signal_add(&wlr_surface->events.destroy, &blur->surface_destroy); blur->node_destroy.notify = blur_handle_node_destroy; wl_list_init(&blur->node_destroy.link); if (wlr_surface->mapped) { blur_handle_surface_map(&blur->surface_map, NULL); } } int version = wl_resource_get_version(manager_resource); struct wl_resource *resource = wl_resource_create(client, &org_kde_kwin_blur_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_list_insert(&blur->resources, wl_resource_get_link(resource)); wl_resource_set_implementation(resource, &kde_blur_impl, blur, kde_blur_handle_resource_destroy); } static void handle_unset(struct wl_client *client, struct wl_resource *manager_resource, struct wl_resource *surface_resource) { struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface_resource); struct kde_blur *blur = kde_blur_from_wlr_surface(wlr_surface); if (blur) { kde_blur_destroy(blur); } } static const struct org_kde_kwin_blur_manager_interface kde_blur_manager_impl = { .create = handle_create, .unset = handle_unset, }; static void kde_blur_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &org_kde_kwin_blur_manager_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &kde_blur_manager_impl, manager, NULL); } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->server_destroy.link); free(manager); manager = NULL; } bool kde_blur_manager_create(struct server *server) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->global = wl_global_create(server->display, &org_kde_kwin_blur_manager_interface, KDE_KWIN_BLUR_MANAGER_VERSION, manager, kde_blur_manager_bind); if (!manager->global) { kywc_log(KYWC_WARN, "Kde blur manager create failed"); free(manager); return false; } wl_list_init(&manager->kde_blurs); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &manager->display_destroy); return true; } kylin-wayland-compositor/src/view/stack_mode.c0000664000175000017500000001751115160461067020510 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include "view_p.h" struct stack_mode_view { struct wl_list link; char *uuid; struct { bool maximized, fullscreen; enum kywc_tile tiled; struct kywc_box geometry; } saved; }; static struct stack_mode_manager { struct wl_list stack_views; struct view_manager *view_manager; } *manager = NULL; static struct stack_mode_view *stack_mode_view_from_uuid(const char *uuid) { if (!uuid) { return NULL; } struct stack_mode_view *stack_view; wl_list_for_each(stack_view, &manager->stack_views, link) { if (strcmp(stack_view->uuid, uuid) == 0) { return stack_view; } } return NULL; } static void stack_mode_view_destroy(struct stack_mode_view *stack_view) { wl_list_remove(&stack_view->link); free(stack_view->uuid); free(stack_view); } static void stack_mode_view_save(struct view *view) { struct stack_mode_view *stack_view = calloc(1, sizeof(*stack_view)); if (!stack_view) { return; } wl_list_insert(&manager->stack_views, &stack_view->link); stack_view->uuid = strdup(view->base.uuid); stack_view->saved.maximized = view->base.maximized; stack_view->saved.fullscreen = view->base.fullscreen; stack_view->saved.tiled = view->base.tiled; stack_view->saved.geometry = view->base.geometry; } static void stack_mode_view_restore(struct view *view) { struct stack_mode_view *stack_view = stack_mode_view_from_uuid(view->base.uuid); if (!stack_view) { return; } if (stack_view->saved.fullscreen) { view_do_fullscreen(view, true, view->output); } else if (stack_view->saved.maximized) { if (view->base.fullscreen) { view_do_fullscreen(view, false, view->output); } view_do_maximized(view, true, view->output); } else if (stack_view->saved.tiled) { if (view->base.fullscreen) { view_do_fullscreen(view, false, view->output); } if (view->base.maximized) { view_do_maximized(view, false, view->output); } if (manager->view_manager->impl.set_tiled) { manager->view_manager->impl.set_tiled(view, stack_view->saved.tiled, view->output); } } else { if (view->base.fullscreen) { view_do_fullscreen(view, false, view->output); } if (view->base.maximized) { view_do_maximized(view, false, view->output); } view_do_resize(view, &stack_view->saved.geometry); } stack_mode_view_destroy(stack_view); } static void stack_mode_view_map(struct view *view) { /* init view position */ positioner_add_new_view(view); } static void stack_mode_view_move(struct view *view, int x, int y) { if (!KYWC_VIEW_IS_MOVABLE(&view->base)) { return; } view_do_move(view, x, y); } static void stack_mode_view_resize(struct view *view, struct kywc_box *geometry) { if (!KYWC_VIEW_IS_RESIZABLE(&view->base)) { return; } view_do_resize(view, geometry); } static void stack_mode_view_minimized(struct view *view, bool minimized) { if (!KYWC_VIEW_IS_MINIMIZABLE(&view->base)) { return; } view_do_minimized(view, minimized); } static void stack_mode_view_maximized(struct view *view, bool maximized, struct kywc_output *kywc_output) { if (!KYWC_VIEW_IS_MAXIMIZABLE(&view->base)) { return; } view_do_maximized(view, maximized, kywc_output); } static void stack_mode_view_fullscreen(struct view *view, bool fullscreen, struct kywc_output *kywc_output) { if (!KYWC_VIEW_IS_FULLSCREENABLE(&view->base)) { return; } view_do_fullscreen(view, fullscreen, kywc_output); } static void stack_mode_view_tiled(struct view *view, enum kywc_tile tile, struct kywc_output *kywc_output) { if (!KYWC_VIEW_IS_RESIZABLE(&view->base)) { return; } if (manager->view_manager->impl.set_tiled) { manager->view_manager->impl.set_tiled(view, tile, kywc_output); } } static void stack_mode_view_activate(struct view *view) { if (!KYWC_VIEW_IS_ACTIVATABLE(&view->base)) { return; } struct view *descendant = view_find_descendant_modal(view); view_do_activate(descendant ? descendant : view); view_raise_to_top(view, true); } static void stack_mode_view_show_menu(struct view *view, struct seat *seat, int x, int y) { if (!view->current_proxy) { return; } window_menu_show(view, seat, x, y); } static void stack_mode_view_show_tile_flyout(struct view *view, struct seat *seat, struct kywc_box *box) { if (!view->current_proxy) { return; } tile_flyout_show(view, seat, box); } static void stack_mode_view_show_linkage_bar(struct view *view, uint32_t edges) { if (!view->current_proxy) { return; } tile_linkage_bar_show(view, edges); } static void stack_mode_view_click(struct seat *seat, struct view *view, uint32_t button, bool pressed, enum click_state state) { struct kywc_view *kywc_view = &view->base; /* active current view */ kywc_view_activate(kywc_view); view_set_focus(view, seat); } static void stack_mode_enter(void) { struct view *view; struct view_manager *view_manager = manager->view_manager; wl_list_for_each(view, &view_manager->views, link) { if (!view->base.mapped || view->base.minimized || view->base.role != KYWC_VIEW_ROLE_NORMAL) { continue; } stack_mode_view_restore(view); } struct stack_mode_view *stack_view, *tmp; wl_list_for_each_safe(stack_view, tmp, &manager->stack_views, link) { stack_mode_view_destroy(stack_view); } } static void stack_mode_leave(void) { struct view_manager *view_manager = manager->view_manager; struct view *view; wl_list_for_each(view, &view_manager->views, link) { if (!view->base.mapped || view->base.role != KYWC_VIEW_ROLE_NORMAL) { continue; } stack_mode_view_save(view); } } static void stack_mode_destroy(void) { if (!manager) { return; } struct stack_mode_view *stack_view, *tmp; wl_list_for_each_safe(stack_view, tmp, &manager->stack_views, link) { stack_mode_view_destroy(stack_view); } free(manager); manager = NULL; } static const struct view_mode_interface stack_mode_impl = { .name = "stack_mode", .view_map = stack_mode_view_map, .view_unmap = NULL, .view_request_move = stack_mode_view_move, .view_request_resize = stack_mode_view_resize, .view_request_minimized = stack_mode_view_minimized, .view_request_maximized = stack_mode_view_maximized, .view_request_fullscreen = stack_mode_view_fullscreen, .view_request_tiled = stack_mode_view_tiled, .view_request_activate = stack_mode_view_activate, .view_request_show_menu = stack_mode_view_show_menu, .view_request_show_tile_flyout = stack_mode_view_show_tile_flyout, .view_request_show_tile_linkage_bar = stack_mode_view_show_linkage_bar, .view_click = stack_mode_view_click, .view_hover = NULL, .view_mode_enter = stack_mode_enter, .view_mode_leave = stack_mode_leave, .mode_destroy = stack_mode_destroy, }; void stack_mode_register(struct view_manager *view_manager) { struct view_mode *mode = view_manager_mode_register(&stack_mode_impl); if (!mode) { return; } manager = calloc(1, sizeof(*manager)); if (!manager) { view_manager_mode_unregister(mode); return; } wl_list_init(&manager->stack_views); manager->view_manager = view_manager; } kylin-wayland-compositor/src/view/interactive.c0000664000175000017500000012607115160461067020716 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "effect/move.h" #include "effect/node_transform.h" #include "input/cursor.h" #include "input/seat.h" #include "output.h" #include "scene/surface.h" #include "theme.h" #include "util/color.h" #include "util/macros.h" #include "util/time.h" #include "view/action.h" #include "view/workspace.h" #include "view_p.h" #define VIEW_EDGE_GAP 20 #define VIEW_TOP_GAP 5 #define VIEW_BOTTOM_GAP 100 #define VIEW_LEFT_GAP 100 #define VIEW_RIGHT_GAP 100 #define VIEW_MIN_WIDTH 200 #define VIEW_MIN_HEIGHT 100 #define VIEW_MOVE_STEP 10 #define VIEW_RESIZE_STEP 10 #define SNAP_BOX_FILTER 200 #define SNAP_BORDER_CORNER_RATIO 0.25 #define EDGE_OFFSET 10 enum interactive_mode { INTERACTIVE_MODE_NONE = 0, INTERACTIVE_MODE_MOVE, INTERACTIVE_MODE_RESIZE, INTERACTIVE_MODE_TILE, INTERACTIVE_MODE_TILE_HALF_SCREEN, }; enum tile_state { TILE_NONE = 0, TILE_TOP, TILE_BOTTOM, TILE_LEFT, TILE_RIGHT, TILE_TOP_LEFT, TILE_BOTTOM_LEFT, TILE_TOP_RIGHT, TILE_BOTTOM_RIGHT, TILE_MINIMIZE, TILE_MAXIMIZE, }; struct interactive_tile_state { int key_up; int key_down; int key_left; int key_right; }; const struct interactive_tile_state tile_states[] = { { TILE_MAXIMIZE, TILE_MINIMIZE, TILE_LEFT, TILE_RIGHT }, // TILE_NONE { TILE_TOP, TILE_NONE, TILE_TOP_LEFT, TILE_TOP_RIGHT }, // TILE_TOP { TILE_NONE, TILE_MINIMIZE, TILE_BOTTOM_LEFT, TILE_BOTTOM_RIGHT }, // TILE_BOTTOM { TILE_TOP_LEFT, TILE_BOTTOM_LEFT, TILE_RIGHT, TILE_NONE }, // TILE_LEFT { TILE_TOP_RIGHT, TILE_BOTTOM_RIGHT, TILE_NONE, TILE_LEFT }, // TILE_RIGHT { TILE_MAXIMIZE, TILE_LEFT, TILE_TOP_RIGHT, TILE_TOP_RIGHT }, // TILE_TOP_LEFT { TILE_LEFT, TILE_MINIMIZE, TILE_BOTTOM_RIGHT, TILE_BOTTOM_RIGHT }, // TILE_BOTTOM_LEFT { TILE_MAXIMIZE, TILE_RIGHT, TILE_TOP_LEFT, TILE_TOP_LEFT }, // TILE_TOP_RIGHT { TILE_RIGHT, TILE_MINIMIZE, TILE_BOTTOM_LEFT, TILE_BOTTOM_LEFT }, // TILE_BOTTOM_RIGHT { TILE_NONE, TILE_MINIMIZE, TILE_MINIMIZE, TILE_MINIMIZE }, // TILE_MINIMIZE { TILE_TOP, TILE_NONE, TILE_LEFT, TILE_RIGHT }, // TILE_MAXIMIZE }; struct interactive_grab { struct seat_pointer_grab pointer_grab; struct seat_keyboard_grab keyboard_grab; struct seat_touch_grab touch_grab; struct seat *seat; // struct wl_listener *seat_destroy; struct output *output; /* view being moved or resized */ struct view *view; struct wl_listener view_unmap; /* move or resize */ enum interactive_mode mode; /* moved or resized actually */ bool ongoing; /* tile view resize linkage */ bool has_linkage; /* cursor position before move or resize */ double cursor_x, cursor_y; /* view position and size before move or resize */ struct kywc_box geo; /* resize edges */ uint32_t resize_edges; uint32_t last_time; /* snap_box */ struct ky_scene_node *snap_node; struct ky_scene_rect *snap_rect; struct output *snap_output; enum kywc_tile snap_mode; struct wl_event_source *filter; bool filter_enabled; /* move effect */ struct move_proxy *proxy; struct wl_listener proxy_destroy; struct kywc_box saved_usable_area; bool update_saved_geo; }; static enum kywc_tile get_kywc_tile_mode(struct interactive_grab *grab) { double cur_x = grab->seat->cursor->lx; double cur_y = grab->seat->cursor->ly; enum kywc_tile mode = KYWC_TILE_NONE; /* current output usable area */ struct kywc_box *usable = &grab->output->usable_area; int32_t y1 = cur_y - usable->y; /* left */ if (cur_x - usable->x < VIEW_EDGE_GAP) { /* top left*/ if (y1 < usable->height * SNAP_BORDER_CORNER_RATIO) { mode = KYWC_TILE_TOP_LEFT; /* bottom left */ } else if (y1 > usable->height - usable->height * SNAP_BORDER_CORNER_RATIO) { mode = KYWC_TILE_BOTTOM_LEFT; /* left */ } else { mode = KYWC_TILE_LEFT; } /* right */ } else if (usable->x + usable->width - cur_x < VIEW_EDGE_GAP) { /* top right*/ if (y1 < usable->height * SNAP_BORDER_CORNER_RATIO) { mode = KYWC_TILE_TOP_RIGHT; /* bottom right */ } else if (y1 > usable->height - usable->height * SNAP_BORDER_CORNER_RATIO) { mode = KYWC_TILE_BOTTOM_RIGHT; /* right */ } else { mode = KYWC_TILE_RIGHT; } /* all */ } else if (y1 < VIEW_TOP_GAP) { if (KYWC_VIEW_IS_MAXIMIZABLE(&grab->view->base)) { mode = KYWC_TILE_ALL; } } return mode; } static void snap_box_update(struct interactive_grab *grab, enum kywc_tile mode) { if (grab->snap_mode == mode && grab->snap_output == grab->output) { return; } struct view *view = grab->view; bool need_source_box = grab->snap_mode == KYWC_TILE_NONE; if (!grab->snap_rect) { struct ky_scene_node *sibling = &view->tree->node; float color[4]; struct theme *theme = theme_manager_get_theme(); color_float_pax(color, theme->active_bg_color, theme->opacity / 100.0); grab->snap_rect = ky_scene_rect_create(sibling->parent, 0, 0, color); grab->snap_node = &grab->snap_rect->node; /* add blur */ pixman_region32_t region; pixman_region32_init(®ion); ky_scene_node_set_blur_region(grab->snap_node, theme->opacity != 100 ? ®ion : NULL); pixman_region32_fini(®ion); ky_scene_node_place_below(grab->snap_node, sibling); need_source_box = true; } grab->snap_mode = mode; grab->snap_output = grab->output; ky_scene_node_set_enabled(grab->snap_node, mode != KYWC_TILE_NONE); if (mode == KYWC_TILE_NONE) { return; } struct kywc_box geo = { 0 }; view_get_tiled_geometry(view, &geo, &grab->output->base, mode); geo.x -= view->base.margin.off_x; geo.y -= view->base.margin.off_y; geo.width += view->base.margin.off_width; geo.height += view->base.margin.off_height; if (need_source_box) { struct wlr_box output_box = { geo.x, geo.y, geo.width, geo.height }; struct wlr_box view_box = { view->base.geometry.x - view->base.margin.off_x, view->base.geometry.y - view->base.margin.off_y, view->base.geometry.width + view->base.margin.off_x, view->base.geometry.height + view->base.margin.off_y }; struct wlr_box rect_box; wlr_box_intersection(&rect_box, &output_box, &view_box); ky_scene_rect_set_size(grab->snap_rect, rect_box.width, rect_box.height); ky_scene_node_set_position(grab->snap_node, rect_box.x, rect_box.y); } struct kywc_box start_geo = { grab->snap_rect->node.x, grab->snap_rect->node.y, grab->snap_rect->width, grab->snap_rect->height }; struct transform_options options = { .start_time = current_time_msec(), .duration = 200, .below_view = true, .start = { .geometry = start_geo, .alpha = 1.0 }, .end = { .geometry = geo, .alpha = 1.0 }, .animations = { .geometry = animation_manager_get(ANIMATION_TYPE_EASE_OUT) }, }; node_add_transform_effect(grab->snap_node, &options); ky_scene_node_set_position(grab->snap_node, geo.x, geo.y); ky_scene_rect_set_size(grab->snap_rect, geo.width, geo.height); } static void snap_box_enable_filter(struct interactive_grab *grab, bool enabled) { if (grab->filter_enabled == enabled) { return; } grab->filter_enabled = enabled; wl_event_source_timer_update(grab->filter, enabled ? SNAP_BOX_FILTER : 0); } static int handle_snap_box(void *data) { struct interactive_grab *grab = data; grab->output = input_current_output(grab->seat); enum kywc_tile mode = get_kywc_tile_mode(grab); grab->filter_enabled = false; snap_box_update(grab, mode); return 0; } static void interactive_move_show_snap_box(struct interactive_grab *grab, int cur_x, int cur_y) { struct view *view = grab->view; if (!KYWC_VIEW_IS_RESIZABLE(&view->base)) { return; } struct kywc_box *usable = &grab->output->usable_area; /* left */ if (cur_x - usable->x < VIEW_EDGE_GAP) { /* trigger timer to show snap box if not the leftmost output */ if (!output_at_layout_edge(grab->output, LAYOUT_EDGE_LEFT) && grab->snap_mode != KYWC_TILE_TOP_LEFT && grab->snap_mode != KYWC_TILE_LEFT && grab->snap_mode != KYWC_TILE_BOTTOM_LEFT) { snap_box_update(grab, KYWC_TILE_NONE); snap_box_enable_filter(grab, true); return; } /* right */ } else if (usable->x + usable->width - cur_x < VIEW_EDGE_GAP) { /* trigger timer to show snap box if not the rightmost output */ if (!output_at_layout_edge(grab->output, LAYOUT_EDGE_RIGHT) && grab->snap_mode != KYWC_TILE_RIGHT && grab->snap_mode != KYWC_TILE_TOP_RIGHT && grab->snap_mode != KYWC_TILE_BOTTOM_RIGHT) { snap_box_update(grab, KYWC_TILE_NONE); snap_box_enable_filter(grab, true); return; } /* all */ } else if (cur_y - usable->y < VIEW_TOP_GAP) { /* trigger timer to show snap box if not the topmost output */ if (KYWC_VIEW_IS_MAXIMIZABLE(&view->base) && !output_at_layout_edge(grab->output, LAYOUT_EDGE_TOP) && grab->snap_mode != KYWC_TILE_ALL) { snap_box_update(grab, KYWC_TILE_NONE); snap_box_enable_filter(grab, true); return; } } enum kywc_tile mode = get_kywc_tile_mode(grab); snap_box_enable_filter(grab, false); snap_box_update(grab, mode); } static void interactive_grab_destroy(struct interactive_grab *grab) { grab->view->interactive_moving = false; grab->view->current_resize_edges = KYWC_EDGE_NONE; wl_list_remove(&grab->view_unmap.link); if (grab->proxy) { wl_list_remove(&grab->proxy_destroy.link); move_proxy_destroy(grab->proxy); } if (grab->has_linkage) { tile_linkage_resize_done(grab->view, false); grab->has_linkage = false; } cursor_set_image(grab->seat->cursor, CURSOR_DEFAULT); seat_end_pointer_grab(grab->seat, &grab->pointer_grab); seat_end_keyboard_grab(grab->seat, &grab->keyboard_grab); seat_end_touch_grab(grab->seat, &grab->touch_grab); ky_scene_node_destroy(grab->snap_node); /* sync the position to client */ int lx = grab->view->base.geometry.x; int ly = grab->view->base.geometry.y; kywc_view_move(&grab->view->base, lx, ly); wl_event_source_remove(grab->filter); free(grab); } static void interactivate_done_move(struct interactive_grab *grab) { grab->output = input_current_output(grab->seat); snap_box_update(grab, KYWC_TILE_NONE); enum kywc_tile mode = get_kywc_tile_mode(grab); if (mode == KYWC_TILE_NONE) { return; } if (mode == KYWC_TILE_ALL) { /* current cursor focused output, not the view most at output */ kywc_view_set_maximized(&grab->view->base, true, &grab->output->base); } else { kywc_view_set_tiled(&grab->view->base, mode, &grab->output->base); } if (grab->update_saved_geo) { grab->view->saved.geometry = grab->geo; if (!kywc_box_equal(&grab->saved_usable_area, &grab->output->usable_area)) { view_fix_geometry(grab->view, &grab->view->saved.geometry, &grab->saved_usable_area, &grab->output->usable_area); } } } static void interactive_done(struct interactive_grab *grab) { bool need_assist = false; /* for snap to edge */ if (grab->view->base.mapped && grab->ongoing && grab->mode == INTERACTIVE_MODE_MOVE) { interactivate_done_move(grab); if (grab->view->base.tiled != KYWC_TILE_NONE && grab->view->base.tiled != KYWC_TILE_ALL) { need_assist = true; } } if (grab->mode == INTERACTIVE_MODE_TILE || grab->mode == INTERACTIVE_MODE_TILE_HALF_SCREEN) { if (!grab->view->base.minimized && !grab->view->base.maximized && grab->view->base.tiled) { need_assist = true; } } struct view *view = grab->view; struct output *output = grab->output; struct seat *seat = grab->seat; interactive_grab_destroy(grab); if (need_assist) { view_manager_show_tile_assist(view, seat, &output->base); } } static void window_adsorption_top_or_bottom(struct kywc_box *s_box, const struct kywc_box *l_box, int *offset, enum interactive_mode mode) { int sx1 = s_box->x, sy1 = s_box->y, sx2 = s_box->x + s_box->width, sy2 = s_box->y + s_box->height; int lx1 = l_box->x, ly1 = l_box->y, lx2 = l_box->x + l_box->width, ly2 = l_box->y + l_box->height; if (sx1 > lx2 || sx2 < lx1) { return; } /* top adsorb bottom */ int temp = abs(ly2 - sy1); if (temp < *offset) { *offset = temp; s_box->y = ly2; if (mode == INTERACTIVE_MODE_RESIZE) { s_box->height = sy2 - ly2; } return; } /* bottom adsorb top */ temp = abs(ly1 - sy2); if (temp < *offset) { *offset = temp; if (mode == INTERACTIVE_MODE_MOVE) { s_box->y = ly1 - (sy2 - sy1); } else if (mode == INTERACTIVE_MODE_RESIZE) { s_box->height += temp; } } } static void window_adsorption_left_or_right(struct kywc_box *s_box, const struct kywc_box *l_box, int *offset, enum interactive_mode mode) { int sx1 = s_box->x, sy1 = s_box->y, sx2 = s_box->x + s_box->width, sy2 = s_box->y + s_box->height; int lx1 = l_box->x, ly1 = l_box->y, lx2 = l_box->x + l_box->width, ly2 = l_box->y + l_box->height; if (sy1 > ly2 || sy2 < ly1) { return; } /* left adsorb right */ int temp = abs(lx2 - sx1); if (temp < *offset) { *offset = temp; s_box->x = lx2; if (mode == INTERACTIVE_MODE_RESIZE) { s_box->width = sx2 - lx2; } return; } /* right adsorb left */ temp = abs(lx1 - sx2); if (temp < *offset) { *offset = temp; if (mode == INTERACTIVE_MODE_MOVE) { s_box->x = lx1 - (sx2 - sx1); } else if (mode == INTERACTIVE_MODE_RESIZE) { s_box->width += temp; } } } static void window_adsorb_window_constraints(struct kywc_view *kywc_view, struct kywc_box *pending, int *gap_x, int *gap_y, uint32_t edges, enum interactive_mode mode) { if ((view_manager_get_adsorption() & VIEW_ADSORPTION_WINDOW_EDGES) == 0) { return; } /* actual window box */ struct kywc_box s_box = { .x = pending->x - kywc_view->margin.off_x, .y = pending->y - kywc_view->margin.off_y, .width = pending->width + kywc_view->margin.off_width, .height = pending->height + kywc_view->margin.off_height, }; struct view_proxy *view_proxy; struct workspace *workspace = workspace_manager_get_current(); wl_list_for_each(view_proxy, &workspace->view_proxies, workspace_link) { if (!view_proxy->view->base.mapped || view_proxy->view == view_from_kywc_view(kywc_view) || view_proxy->view->base.minimized || view_proxy->view->base.maximized || view_proxy->view->base.fullscreen || !view_proxy->view->tree->node.enabled) { continue; } /* be adsorbed window box */ struct kywc_box l_box = { .x = view_proxy->view->base.geometry.x - view_proxy->view->base.margin.off_x, .y = view_proxy->view->base.geometry.y - view_proxy->view->base.margin.off_y, .width = view_proxy->view->base.geometry.width + view_proxy->view->base.margin.off_width, .height = view_proxy->view->base.geometry.height + view_proxy->view->base.margin.off_height, }; if (edges & KYWC_EDGE_LEFT || edges & KYWC_EDGE_RIGHT) { window_adsorption_left_or_right(&s_box, &l_box, gap_x, mode); } if (edges & KYWC_EDGE_TOP || edges & KYWC_EDGE_BOTTOM) { window_adsorption_top_or_bottom(&s_box, &l_box, gap_y, mode); } } pending->x = s_box.x + kywc_view->margin.off_x; pending->y = s_box.y + kywc_view->margin.off_y; if (mode == INTERACTIVE_MODE_RESIZE) { pending->width = s_box.width - kywc_view->margin.off_width; pending->height = s_box.height - kywc_view->margin.off_height; } } static void window_adsorb_edges_constraints(struct kywc_view *kywc_view, struct kywc_box *pending, struct output *output, int *gap_x, int *gap_y) { if ((view_manager_get_adsorption() & VIEW_ADSORPTION_SCREEN_EDGES) == 0) { return; } /* actual window box */ struct kywc_box s_box = { .x = pending->x - kywc_view->margin.off_x, .y = pending->y - kywc_view->margin.off_y, .width = pending->width + kywc_view->margin.off_width, .height = pending->height + kywc_view->margin.off_height, }; /* actual view coord */ int x1 = s_box.x; int y1 = s_box.y; int x2 = s_box.x + s_box.width; int y2 = s_box.y + s_box.height; /* usable coord */ struct kywc_box *usable = &output->usable_area; int ux1 = usable->x; int uy1 = usable->y; int ux2 = usable->x + usable->width; int uy2 = usable->y + usable->height; /* window edge adsorption in left, right */ if (abs(ux1 - x1) < *gap_x) { *gap_x = abs(ux1 - x1); pending->x = ux1 + kywc_view->margin.off_x; } else if (abs(x2 - ux2) < *gap_x) { *gap_x = abs(x2 - ux2); pending->x = ux2 - pending->width - kywc_view->margin.off_width + kywc_view->margin.off_x; } /* top and bottom */ if (abs(uy1 - y1) < *gap_y) { *gap_y = abs(uy1 - y1); pending->y = uy1 + kywc_view->margin.off_y; } else if (abs(y2 - uy2) < *gap_y) { pending->y = uy2 - pending->height - kywc_view->margin.off_height + kywc_view->margin.off_y; } } void window_move_constraints(struct kywc_view *kywc_view, struct output *output, int *x, int *y, int width, int height) { if (kywc_view->unconstrained) { return; } struct kywc_box pending = { *x, *y, width, height }; int gap_x = EDGE_OFFSET, gap_y = EDGE_OFFSET; uint32_t edges = KYWC_EDGE_NONE; /* window edge adsorption in left, right */ edges |= *x != kywc_view->geometry.x ? KYWC_EDGE_LEFT | KYWC_EDGE_RIGHT : KYWC_EDGE_NONE; /* top and bottom */ edges |= *y != kywc_view->geometry.y ? KYWC_EDGE_TOP | KYWC_EDGE_BOTTOM : KYWC_EDGE_NONE; window_adsorb_window_constraints(kywc_view, &pending, &gap_x, &gap_y, edges, INTERACTIVE_MODE_MOVE); gap_x = gap_x < EDGE_OFFSET ? gap_x : VIEW_EDGE_GAP; gap_y = gap_y < EDGE_OFFSET ? gap_y : VIEW_EDGE_GAP; window_adsorb_edges_constraints(kywc_view, &pending, output, &gap_x, &gap_y); *x = pending.x; *y = pending.y; /* actual view coord */ int x1 = *x - kywc_view->margin.off_x; int y1 = *y - kywc_view->margin.off_y; int x2 = x1 + width + kywc_view->margin.off_width; int y2 = y1 + height + kywc_view->margin.off_height; /* get current seat constraints output */ struct kywc_box *usable = &output->usable_area; int ux1 = usable->x, uy1 = usable->y; int ux2 = usable->x + usable->width, uy2 = usable->y + usable->height; struct kywc_box *geo = &output->geometry; int top = uy1 - geo->y, bottom = geo->y + geo->height - uy2; int left = ux1 - geo->x, right = geo->x + geo->width - ux2; int bottom_gap = MIN(VIEW_BOTTOM_GAP, height); int left_gap = MIN(VIEW_LEFT_GAP, width); int right_gap = MIN(VIEW_RIGHT_GAP, width); /* constraints when moving to top and bottom */ if (output_at_layout_edge(output, LAYOUT_EDGE_TOP) && height > top && y1 < uy1) { *y = uy1 + kywc_view->margin.off_y; } else if (output_at_layout_edge(output, LAYOUT_EDGE_BOTTOM) && height > bottom && y2 > uy2 && uy2 - y1 < bottom_gap) { *y = uy2 - bottom_gap + kywc_view->margin.off_y; } /* constraints when moving to left and right */ if (output_at_layout_edge(output, LAYOUT_EDGE_LEFT) && width > left && x1 < ux1 && x2 - ux1 < left_gap) { *x = left_gap - width - (kywc_view->margin.off_width - kywc_view->margin.off_x); } else if (output_at_layout_edge(output, LAYOUT_EDGE_RIGHT) && width > right && x2 > ux2 && ux2 - x1 < right_gap) { *x = ux2 - right_gap + kywc_view->margin.off_y; } } static void interactive_process_move(struct interactive_grab *grab, double x, double y) { struct kywc_view *kywc_view = &grab->view->base; struct kywc_box *geometry = &kywc_view->geometry; if (kywc_view->maximized || kywc_view->tiled) { struct kywc_box *saved = &grab->view->saved.geometry; double frac = (x - geometry->x) / geometry->width; int position_x = x - frac * saved->width; if (position_x < geometry->x) { position_x = geometry->x; } grab->geo.x = position_x; if (kywc_view->maximized) { kywc_view_set_maximized(kywc_view, false, NULL); } else { kywc_view_set_tiled(kywc_view, KYWC_TILE_NONE, NULL); } if (grab->proxy) { int width = kywc_view->margin.off_width + saved->width; int height = kywc_view->margin.off_height + saved->height; move_proxy_resize(grab->proxy, width, height); } } int nx = grab->geo.x + x - grab->cursor_x; int ny = grab->geo.y + y - grab->cursor_y; window_move_constraints(&grab->view->base, grab->output, &nx, &ny, geometry->width, geometry->height); if (grab->proxy) { move_proxy_move(grab->proxy, nx, ny); } else { kywc_view_move(kywc_view, nx, ny); } interactive_move_show_snap_box(grab, x, y); } void interactive_resize_constraints(struct view *view, struct output *output, struct kywc_box *box, uint32_t edges) { struct kywc_view *kywc_view = &view->base; if (kywc_view->unconstrained) { return; } /* get current seat constraints output */ struct kywc_box *usable = &output->usable_area; struct kywc_box *current = &kywc_view->geometry; /* pending view coord */ int x1 = box->x - kywc_view->margin.off_x; int y1 = box->y - kywc_view->margin.off_y; int x2 = x1 + box->width + kywc_view->margin.off_width; int y2 = y1 + box->height + kywc_view->margin.off_height; int gap_x = EDGE_OFFSET, gap_y = EDGE_OFFSET; window_adsorb_window_constraints(kywc_view, box, &gap_x, &gap_y, edges, INTERACTIVE_MODE_RESIZE); if (x1 > usable->x + usable->width || x2 < usable->x || y1 > usable->y + usable->height || y2 < usable->y) { usable = &output_from_kywc_output(view->output)->usable_area; } int ux2 = usable->x + usable->width; int uy2 = usable->y + usable->height; /* constraints when resize to top and bottom */ if (edges & KYWC_EDGE_TOP) { /* top */ if (output_at_layout_edge(output, LAYOUT_EDGE_TOP) && y1 < usable->y) { box->y = usable->y + kywc_view->margin.off_y; box->height = current->height + current->y - box->y; /* bottom */ } else if (output_at_layout_edge(output, LAYOUT_EDGE_BOTTOM) && y1 > uy2 - kywc_view->margin.off_y - VIEW_EDGE_GAP) { box->y = uy2 - VIEW_EDGE_GAP; box->height = y2 - box->y - (kywc_view->margin.off_height - kywc_view->margin.off_y); } } else if (edges & KYWC_EDGE_BOTTOM) { /* top */ if (output_at_layout_edge(output, LAYOUT_EDGE_TOP) && y2 < usable->y + VIEW_EDGE_GAP) { box->height = usable->y + VIEW_EDGE_GAP - y1 - kywc_view->margin.off_height; /* bottom */ } else if (output_at_layout_edge(output, LAYOUT_EDGE_BOTTOM) && y2 > uy2) { box->height = uy2 - y1 - kywc_view->margin.off_height; } } /* constraints when resize to left and right */ if (edges & KYWC_EDGE_LEFT) { /* left */ if (output_at_layout_edge(output, LAYOUT_EDGE_LEFT) && x1 < usable->x) { box->x = usable->x + kywc_view->margin.off_x; box->width = current->width + current->x - box->x; /* right */ } else if (output_at_layout_edge(output, LAYOUT_EDGE_RIGHT) && x1 > ux2 - VIEW_EDGE_GAP) { box->x = ux2 - VIEW_EDGE_GAP + kywc_view->margin.off_x; box->width = x2 - box->x - (kywc_view->margin.off_width - kywc_view->margin.off_x); } } else if (edges & KYWC_EDGE_RIGHT) { /* left */ if (output_at_layout_edge(output, LAYOUT_EDGE_LEFT) && x2 < usable->x + VIEW_EDGE_GAP) { box->width = usable->x + VIEW_EDGE_GAP - x1 - kywc_view->margin.off_width; /* right */ } else if (output_at_layout_edge(output, LAYOUT_EDGE_RIGHT) && x2 > ux2) { box->width = ux2 - x1 - kywc_view->margin.off_width; } } } static void interactive_process_resize(struct interactive_grab *grab, double x, double y) { struct kywc_view *kywc_view = &grab->view->base; int min_width = kywc_view->min_width; int min_height = kywc_view->min_height; if (!kywc_view->unconstrained) { min_width = MAX(kywc_view->min_width, VIEW_MIN_WIDTH); min_height = MAX(kywc_view->min_height, VIEW_MIN_HEIGHT); } int max_width = kywc_view->max_width; int max_height = kywc_view->max_height; int width = 0, height = 0; output_layout_get_size(&width, &height); if (max_width <= 0 || max_width > width - kywc_view->margin.off_width) { max_width = width - kywc_view->margin.off_width; } if (max_height <= 0 || max_height > height - kywc_view->margin.off_height) { max_height = height - kywc_view->margin.off_height; } struct kywc_box pending = kywc_view->geometry; double dx = x - grab->cursor_x; double dy = y - grab->cursor_y; if (grab->resize_edges & KYWC_EDGE_TOP) { pending.height = grab->geo.height - dy; } else if (grab->resize_edges & KYWC_EDGE_BOTTOM) { pending.height = grab->geo.height + dy; } pending.height = CLAMP(pending.height, min_height, max_height); if (grab->resize_edges & KYWC_EDGE_LEFT) { pending.width = grab->geo.width - dx; } else if (grab->resize_edges & KYWC_EDGE_RIGHT) { pending.width = grab->geo.width + dx; } pending.width = CLAMP(pending.width, min_width, max_width); if (grab->resize_edges & KYWC_EDGE_TOP) { /* anchor bottom edge */ pending.y = grab->geo.y + grab->geo.height - pending.height; } if (grab->resize_edges & KYWC_EDGE_LEFT) { /* anchor right edge */ pending.x = grab->geo.x + grab->geo.width - pending.width; } interactive_resize_constraints(grab->view, grab->output, &pending, grab->resize_edges); kywc_view_resize(kywc_view, &pending); } static void interactive_tile_output_update(struct interactive_grab *grab, int key, enum tile_state current) { grab->output = output_from_kywc_output(grab->view->output); enum layout_edge layout_edge = LAYOUT_EDGE_TOP; if (key == KEY_LEFT) { if (current == TILE_LEFT || current == TILE_TOP_LEFT || current == TILE_BOTTOM_LEFT) { layout_edge = LAYOUT_EDGE_LEFT; } } else if (key == KEY_RIGHT) { if (current == TILE_RIGHT || current == TILE_TOP_RIGHT || current == TILE_BOTTOM_RIGHT) { layout_edge = LAYOUT_EDGE_RIGHT; } } if (layout_edge == LAYOUT_EDGE_TOP) { return; } if (!output_state_is_mirror_mode()) { struct output *output = output_find_specified_output(grab->output, layout_edge); if (!output) { return; } view_fix_geometry(grab->view, &grab->view->saved.geometry, &grab->output->usable_area, &output->usable_area); grab->output = output; } } static void interactive_tile_update(struct interactive_grab *grab, struct output *output, int state) { if (state == TILE_MINIMIZE) { kywc_view_set_minimized(&grab->view->base, true); return; } if (state == TILE_MAXIMIZE) { kywc_view_set_maximized(&grab->view->base, true, NULL); return; } if (grab->view->base.maximized && !state) { kywc_view_set_maximized(&grab->view->base, false, NULL); return; } if (grab->view->base.minimized) { /* kywc_view_activate will call the minimize restore interface. */ kywc_view_activate(&grab->view->base); view_set_focus(grab->view, grab->view->base.focused_seat); return; } kywc_view_set_tiled(&grab->view->base, state, &output->base); } static int interactive_get_tile_state(struct view *view) { if (view->base.maximized) { return TILE_MAXIMIZE; } else if (view->base.minimized) { return TILE_MINIMIZE; } return view->base.tiled < KYWC_TILE_CENTER ? (enum tile_state)view->base.tiled : TILE_NONE; } static void interactive_process_tile(struct interactive_grab *grab, int key) { if (key != KEY_UP && key != KEY_DOWN && key != KEY_LEFT && key != KEY_RIGHT) { return; } enum tile_state pending_state = TILE_NONE; enum tile_state current = interactive_get_tile_state(grab->view); if (key == KEY_UP) { pending_state = tile_states[current].key_up; } else if (key == KEY_DOWN) { pending_state = tile_states[current].key_down; } else if (key == KEY_LEFT) { pending_state = tile_states[current].key_left; } else if (key == KEY_RIGHT) { pending_state = tile_states[current].key_right; } interactive_tile_output_update(grab, key, current); interactive_tile_update(grab, grab->output, pending_state); } static void interactive_process_tile_half_screen(struct interactive_grab *grab, int key) { if (key != KEY_UP && key != KEY_DOWN && key != KEY_LEFT && key != KEY_RIGHT) { return; } enum tile_state current = interactive_get_tile_state(grab->view); if (current == TILE_MINIMIZE) { return; } grab->output = output_from_kywc_output(grab->view->output); enum kywc_tile tile = KYWC_TILE_NONE; if (key == KEY_UP) { tile = KYWC_TILE_TOP; } else if (key == KEY_DOWN) { tile = KYWC_TILE_BOTTOM; } else if (key == KEY_LEFT) { tile = KYWC_TILE_LEFT; } else if (key == KEY_RIGHT) { tile = KYWC_TILE_RIGHT; } if (grab->view->base.tiled != tile) { kywc_view_set_tiled(&grab->view->base, tile, grab->view->output); return; } if (tile == KYWC_TILE_TOP || tile == KYWC_TILE_BOTTOM) { return; } if (!output_state_is_mirror_mode()) { enum layout_edge edge = tile == KYWC_TILE_LEFT ? LAYOUT_EDGE_LEFT : LAYOUT_EDGE_RIGHT; struct output *output = output_find_specified_output(grab->output, edge); if (!output) { return; } view_fix_geometry(grab->view, &grab->view->saved.geometry, &grab->output->usable_area, &output->usable_area); grab->output = output; } tile = tile == KYWC_TILE_LEFT ? KYWC_TILE_RIGHT : KYWC_TILE_LEFT; kywc_view_set_tiled(&grab->view->base, tile, &grab->output->base); } static bool interactive_move_filter(struct interactive_grab *grab, double x, double y) { if (grab->ongoing) { return false; } struct kywc_view *kywc_view = &grab->view->base; if (kywc_view->maximized || kywc_view->tiled) { /* add move filter */ if (fabs(grab->cursor_x - x) < VIEW_MOVE_STEP && fabs(grab->cursor_y - y) < VIEW_MOVE_STEP) { return true; } } return false; } static bool interactive_resize_filter(struct interactive_grab *grab, uint32_t time) { if (time - grab->last_time < view_manager_get_resize_filter(grab->view)) { return true; } grab->last_time = time; return false; } static bool pointer_grab_motion(struct seat_pointer_grab *pointer_grab, uint32_t time, double lx, double ly) { struct interactive_grab *grab = pointer_grab->data; grab->output = input_current_output(grab->seat); if (grab->mode == INTERACTIVE_MODE_MOVE) { if (interactive_move_filter(grab, lx, ly)) { return true; } /* set moving cursor image if moving */ if (!grab->ongoing) { cursor_set_image(grab->seat->cursor, CURSOR_MOVE); grab->view->interactive_moving = true; grab->ongoing = true; } interactive_process_move(grab, lx, ly); } else if (grab->mode == INTERACTIVE_MODE_RESIZE) { if (interactive_resize_filter(grab, time)) { return true; } if (!grab->ongoing) { grab->view->current_resize_edges = grab->resize_edges; grab->ongoing = true; } if (grab->has_linkage) { tile_linkage_view_resize(grab->view, KYWC_EDGE_NONE, lx, ly); } else { interactive_process_resize(grab, lx, ly); } } return true; } static bool pointer_grab_button(struct seat_pointer_grab *pointer_grab, uint32_t time, uint32_t button, bool pressed) { struct interactive_grab *grab = pointer_grab->data; if (!pressed && button == BTN_LEFT) { bool on_surface = grab->seat->cursor->focus.node && wlr_surface_try_from_node(grab->seat->cursor->focus.node); interactive_done(grab); return !on_surface; } return true; } static bool pointer_grab_axis(struct seat_pointer_grab *pointer_grab, uint32_t time, bool vertical, double value) { return true; } static void pointer_grab_cancel(struct seat_pointer_grab *pointer_grab) { struct interactive_grab *grab = pointer_grab->data; interactive_grab_destroy(grab); } static const struct seat_pointer_grab_interface pointer_grab_impl = { .motion = pointer_grab_motion, .button = pointer_grab_button, .axis = pointer_grab_axis, .cancel = pointer_grab_cancel, }; static bool keyboard_grab_key(struct seat_keyboard_grab *keyboard_grab, struct keyboard *keyboard, uint32_t time, uint32_t key, bool pressed, uint32_t modifiers) { struct interactive_grab *grab = keyboard_grab->data; if (!pressed) { if (key != KEY_LEFTMETA && key != KEY_RIGHTMETA) { return true; } if (grab->mode == INTERACTIVE_MODE_TILE || grab->mode == INTERACTIVE_MODE_TILE_HALF_SCREEN) { interactive_done(grab); return true; } } if (grab->mode == INTERACTIVE_MODE_TILE || grab->mode == INTERACTIVE_MODE_TILE_HALF_SCREEN) { if (!((WLR_MODIFIER_LOGO | WLR_MODIFIER_ALT) ^ modifiers)) { interactive_process_tile_half_screen(grab, key); } else if (!(WLR_MODIFIER_LOGO ^ modifiers)) { interactive_process_tile(grab, key); } return true; } int step = grab->mode == INTERACTIVE_MODE_MOVE ? VIEW_MOVE_STEP : VIEW_RESIZE_STEP; int dx = key == KEY_RIGHT ? step : (key == KEY_LEFT ? -step : 0); int dy = key == KEY_DOWN ? step : (key == KEY_UP ? -step : 0); /* restore to the orig geometry */ if (key == KEY_ESC) { struct kywc_view *kywc_view = &grab->view->base; struct kywc_box geo = grab->geo; if (grab->has_linkage) { tile_linkage_resize_done(grab->view, true); grab->has_linkage = false; } interactive_done(grab); kywc_view_resize(kywc_view, &geo); return false; } if (key == KEY_ENTER) { interactive_done(grab); return false; } struct cursor *cursor = grab->seat->cursor; cursor_move(cursor, NULL, dx, dy, true, false); pointer_grab_motion(&grab->pointer_grab, time, cursor->lx, cursor->ly); return true; } static void keyboard_grab_cancel(struct seat_keyboard_grab *keyboard_grab) { struct interactive_grab *grab = keyboard_grab->data; interactive_grab_destroy(grab); } static const struct seat_keyboard_grab_interface keyboard_grab_impl = { .key = keyboard_grab_key, .cancel = keyboard_grab_cancel, }; static bool touch_grab_touch(struct seat_touch_grab *touch_grab, uint32_t time, bool down) { if (down) { return true; } struct interactive_grab *grab = touch_grab->data; /** * When touching on a surface, the pointer event is not simulated and the focus is not updated. * It will call the motion of the pointer and update the hover. * Use hover to determine whether it is on the surface. * */ bool on_surface = grab->seat->cursor->hover.node && wlr_surface_try_from_node(grab->seat->cursor->hover.node); interactive_done(grab); return !on_surface; } static bool touch_grab_motion(struct seat_touch_grab *touch_grab, uint32_t time, double lx, double ly) { struct interactive_grab *grab = touch_grab->data; bool first = !grab->ongoing; pointer_grab_motion(&grab->pointer_grab, time, lx, ly); if (first && grab->ongoing) { seat_reset_input_gesture(grab->seat); } return true; } static void touch_grab_cancel(struct seat_touch_grab *touch_grab) { struct interactive_grab *grab = touch_grab->data; interactive_grab_destroy(grab); } static const struct seat_touch_grab_interface touch_grab_impl = { .touch = touch_grab_touch, .motion = touch_grab_motion, .cancel = touch_grab_cancel, }; static void handle_view_unmap(struct wl_listener *listener, void *data) { struct interactive_grab *grab = wl_container_of(listener, grab, view_unmap); interactive_done(grab); } static void handle_move_proxy_destroy(struct wl_listener *listener, void *data) { struct interactive_grab *grab = wl_container_of(listener, grab, proxy_destroy); wl_list_remove(&grab->proxy_destroy.link); grab->proxy = NULL; } static int interactive_tiled_key_by_action(int action) { int key = 0; if (action == WINDOW_ACTION_TILE_TOP || action == WINDOW_ACTION_TILE_TOP_HALF_SCREEN) { key = KEY_UP; } else if (action == WINDOW_ACTION_TILE_BOTTOM || action == WINDOW_ACTION_TILE_BOTTOM_HALF_SCREEN) { key = KEY_DOWN; } else if (action == WINDOW_ACTION_TILE_LEFT || action == WINDOW_ACTION_TILE_LEFT_HALF_SCREEN) { key = KEY_LEFT; } else if (action == WINDOW_ACTION_TILE_RIGHT || action == WINDOW_ACTION_TILE_RIGHT_HALF_SCREEN) { key = KEY_RIGHT; } return key; } static void interactive_grab_add(struct view *view, enum interactive_mode mode, uint32_t edges, struct seat *seat) { struct interactive_grab *grab = calloc(1, sizeof(*grab)); if (!grab) { return; } grab->pointer_grab = (struct seat_pointer_grab){ &pointer_grab_impl, seat, grab }; seat_start_pointer_grab(seat, &grab->pointer_grab); grab->keyboard_grab = (struct seat_keyboard_grab){ &keyboard_grab_impl, seat, grab }; seat_start_keyboard_grab(seat, &grab->keyboard_grab); grab->touch_grab = (struct seat_touch_grab){ &touch_grab_impl, seat, grab }; seat_start_touch_grab(seat, &grab->touch_grab); grab->seat = seat; grab->mode = mode; grab->cursor_x = seat->cursor->lx; grab->cursor_y = seat->cursor->ly; grab->geo = view_action_change_size(view->pending.configure_action) ? view->pending.configure_geometry : view->base.geometry; grab->resize_edges = edges; grab->ongoing = false; grab->view = view; grab->view_unmap.notify = handle_view_unmap; wl_signal_add(&view->base.events.unmap, &grab->view_unmap); grab->proxy_destroy.notify = handle_move_proxy_destroy; wl_list_init(&grab->proxy_destroy.link); /* set the default cursor */ if (mode == INTERACTIVE_MODE_MOVE) { grab->saved_usable_area = output_from_kywc_output(view->output)->usable_area; grab->update_saved_geo = !view->base.tiled && !view->base.maximized; // In order not to rebase after the configure_action if (view_action_change_size(view->pending.configure_action)) { grab->view->interactive_moving = true; // recalculate the coordinates of the window under the cursor grab->geo.x = grab->cursor_x - ((grab->cursor_x - view->base.geometry.x) / view->base.geometry.width) * grab->geo.width; grab->geo.y = grab->cursor_y - ((grab->cursor_y - view->base.geometry.y) / view->base.geometry.height) * grab->geo.height; } cursor_set_image(seat->cursor, CURSOR_DEFAULT); /* create a move proxy for move effect */ int width = view->base.geometry.width + view->base.margin.off_width; int height = view->base.geometry.height + view->base.margin.off_height; grab->proxy = move_proxy_create(view, width, height); if (grab->proxy) { move_proxy_add_destroy_listener(grab->proxy, &grab->proxy_destroy); } } else if (mode == INTERACTIVE_MODE_RESIZE) { grab->has_linkage = tile_linkage_view_resize(view, edges, seat->cursor->lx, seat->cursor->ly); cursor_set_resize_image(seat->cursor, edges); } else if (mode == INTERACTIVE_MODE_TILE) { int key = interactive_tiled_key_by_action(edges); interactive_process_tile(grab, key); } else if (mode == INTERACTIVE_MODE_TILE_HALF_SCREEN) { int key = interactive_tiled_key_by_action(edges); interactive_process_tile_half_screen(grab, key); } struct wl_event_loop *loop = wl_display_get_event_loop(seat->wlr_seat->display); grab->filter = wl_event_loop_add_timer(loop, handle_snap_box, grab); } void window_begin_move(struct view *view, struct seat *seat) { if (!KYWC_VIEW_IS_MOVABLE(&view->base)) { return; } interactive_grab_add(view, INTERACTIVE_MODE_MOVE, 0, seat); } void window_begin_resize(struct view *view, uint32_t edges, struct seat *seat) { if (view->base.maximized || !KYWC_VIEW_IS_RESIZABLE(&view->base)) { return; } interactive_grab_add(view, INTERACTIVE_MODE_RESIZE, edges, seat); } void window_begin_tile(struct view *view, uint32_t key, struct seat *seat) { if (view->base.fullscreen || view_manager_get_highlight()) { return; } interactive_grab_add(view, INTERACTIVE_MODE_TILE, key, seat); } void window_begin_tile_half_screen(struct view *view, uint32_t key, struct seat *seat) { if (view->base.fullscreen || view_manager_get_highlight()) { return; } interactive_grab_add(view, INTERACTIVE_MODE_TILE_HALF_SCREEN, key, seat); } kylin-wayland-compositor/src/view/kde_virtual_desktop.c0000664000175000017500000002560415160461067022443 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "plasma-virtual-desktop-protocol.h" #include "view/workspace.h" #include "view_p.h" #define VIRTUAL_DESKTOP_VERSION 1 #define VIRTUAL_DESKTOP_MANAGEMENT_VERSION 2 struct kde_virtual_desktop_management { struct wl_global *global; struct wl_list resources; struct wl_list virtual_desktops; struct wl_listener new_workspace; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct kde_virtual_desktop { struct wl_list link; /* request get_virtual_desktop */ struct wl_list resources; struct workspace *workspace; struct kde_virtual_desktop_management *management; struct wl_listener activate; struct wl_listener destroy; }; static void handle_server_destroy(struct wl_listener *listener, void *data) { struct kde_virtual_desktop_management *management = wl_container_of(listener, management, server_destroy); wl_list_remove(&management->server_destroy.link); free(management); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct kde_virtual_desktop_management *management = wl_container_of(listener, management, display_destroy); wl_list_remove(&management->new_workspace.link); wl_list_remove(&management->display_destroy.link); wl_global_destroy(management->global); } static void handle_workspace_destroy(struct wl_listener *listener, void *data) { struct kde_virtual_desktop *virtual_desktop = wl_container_of(listener, virtual_desktop, destroy); wl_list_remove(&virtual_desktop->destroy.link); wl_list_remove(&virtual_desktop->activate.link); wl_list_remove(&virtual_desktop->link); struct kde_virtual_desktop_management *management = virtual_desktop->management; struct wl_resource *resource; wl_resource_for_each(resource, &virtual_desktop->resources) { org_kde_plasma_virtual_desktop_send_removed(resource); } wl_resource_for_each(resource, &management->resources) { org_kde_plasma_virtual_desktop_management_send_desktop_removed( resource, virtual_desktop->workspace->uuid); } free(virtual_desktop); } static void handle_workspace_activate(struct wl_listener *listener, void *data) { struct kde_virtual_desktop *virtual_desktop = wl_container_of(listener, virtual_desktop, activate); struct wl_resource *resource; wl_resource_for_each(resource, &virtual_desktop->resources) { if (virtual_desktop->workspace->activated) { org_kde_plasma_virtual_desktop_send_activated(resource); } else { org_kde_plasma_virtual_desktop_send_deactivated(resource); } org_kde_plasma_virtual_desktop_send_done(resource); } } static void handle_new_workspace(struct wl_listener *listener, void *data) { struct kde_virtual_desktop *virtual_desktop = calloc(1, sizeof(*virtual_desktop)); if (!virtual_desktop) { return; } struct workspace *workspace = data; virtual_desktop->workspace = workspace; virtual_desktop->activate.notify = handle_workspace_activate; wl_signal_add(&workspace->events.activate, &virtual_desktop->activate); virtual_desktop->destroy.notify = handle_workspace_destroy; wl_signal_add(&workspace->events.destroy, &virtual_desktop->destroy); struct kde_virtual_desktop_management *management = wl_container_of(listener, management, new_workspace); virtual_desktop->management = management; wl_list_init(&virtual_desktop->resources); wl_list_insert(&management->virtual_desktops, &virtual_desktop->link); struct wl_resource *resource; wl_resource_for_each(resource, &management->resources) { org_kde_plasma_virtual_desktop_management_send_desktop_created( resource, workspace->uuid, virtual_desktop->workspace->position); } } static struct kde_virtual_desktop * get_virtual_desktop(struct kde_virtual_desktop_management *management, const char *uuid) { struct kde_virtual_desktop *virtual_desktop; wl_list_for_each(virtual_desktop, &management->virtual_desktops, link) { if (!strcmp(uuid, virtual_desktop->workspace->uuid)) { return virtual_desktop; } } return NULL; } static void handle_request_activate(struct wl_client *client, struct wl_resource *resource) { struct kde_virtual_desktop *virtual_desktop = wl_resource_get_user_data(resource); if (virtual_desktop) { workspace_activate(virtual_desktop->workspace); } } static const struct org_kde_plasma_virtual_desktop_interface kde_virtual_desktop_impl = { .request_activate = handle_request_activate, }; static void kde_virtual_desktop_handle_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static void handle_get_virtual_desktop(struct wl_client *client, struct wl_resource *management_resource, uint32_t id, const char *desktop_id) { struct wl_resource *resource = wl_resource_create( client, &org_kde_plasma_virtual_desktop_interface, VIRTUAL_DESKTOP_VERSION, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &kde_virtual_desktop_impl, NULL, kde_virtual_desktop_handle_resource_destroy); wl_list_init(wl_resource_get_link(resource)); struct kde_virtual_desktop_management *management = wl_resource_get_user_data(management_resource); struct kde_virtual_desktop *virtual_desktop = get_virtual_desktop(management, desktop_id); if (!virtual_desktop) { /* there is no error code defined in protocol */ org_kde_plasma_virtual_desktop_send_removed(resource); return; } wl_resource_set_user_data(resource, virtual_desktop); wl_list_insert(&virtual_desktop->resources, wl_resource_get_link(resource)); /* send all desktop info to client */ org_kde_plasma_virtual_desktop_send_desktop_id(resource, virtual_desktop->workspace->uuid); org_kde_plasma_virtual_desktop_send_name(resource, virtual_desktop->workspace->name); if (virtual_desktop->workspace->activated) { org_kde_plasma_virtual_desktop_send_activated(resource); } else { org_kde_plasma_virtual_desktop_send_deactivated(resource); } org_kde_plasma_virtual_desktop_send_done(resource); } static void handle_request_create_virtual_desktop(struct wl_client *client, struct wl_resource *resource, const char *name, uint32_t position) { workspace_create(name, position); } static void handle_request_remove_virtual_desktop(struct wl_client *client, struct wl_resource *resource, const char *desktop_id) { struct kde_virtual_desktop_management *management = wl_resource_get_user_data(resource); struct kde_virtual_desktop *virtual_desktop = get_virtual_desktop(management, desktop_id); if (!virtual_desktop) { /* there is no error code defined in protocol */ return; } if (wl_list_length(&management->virtual_desktops) == 1) { kywc_log(KYWC_WARN, "Reject to destroy the last virtual desktop"); return; } workspace_destroy(virtual_desktop->workspace); } static const struct org_kde_plasma_virtual_desktop_management_interface kde_virtual_desktop_management_impl = { .get_virtual_desktop = handle_get_virtual_desktop, .request_create_virtual_desktop = handle_request_create_virtual_desktop, .request_remove_virtual_desktop = handle_request_remove_virtual_desktop, }; static void kde_virtual_desktop_management_handle_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static struct kde_virtual_desktop * get_virtual_desktop_by_workspace(struct kde_virtual_desktop_management *management, struct workspace *workspace) { struct kde_virtual_desktop *virtual_desktop; wl_list_for_each(virtual_desktop, &management->virtual_desktops, link) { if (virtual_desktop->workspace == workspace) { return virtual_desktop; } } return NULL; } static void kde_virtual_desktop_management_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create( client, &org_kde_plasma_virtual_desktop_management_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } struct kde_virtual_desktop_management *management = data; wl_resource_set_implementation(resource, &kde_virtual_desktop_management_impl, management, kde_virtual_desktop_management_handle_resource_destroy); wl_list_insert(&management->resources, wl_resource_get_link(resource)); /* send all desktops to client when bind */ struct kde_virtual_desktop *virtual_desktop; for (uint32_t i = 0; i < workspace_manager_get_count(); i++) { virtual_desktop = get_virtual_desktop_by_workspace(management, workspace_by_position(i)); org_kde_plasma_virtual_desktop_management_send_desktop_created( resource, virtual_desktop->workspace->uuid, virtual_desktop->workspace->position); } if (version >= ORG_KDE_PLASMA_VIRTUAL_DESKTOP_MANAGEMENT_ROWS_SINCE_VERSION) { org_kde_plasma_virtual_desktop_management_send_rows(resource, workspace_manager_get_rows()); } org_kde_plasma_virtual_desktop_management_send_done(resource); } bool kde_virtual_desktop_management_create(struct server *server) { struct kde_virtual_desktop_management *management = calloc(1, sizeof(*management)); if (!management) { return false; } wl_list_init(&management->resources); wl_list_init(&management->virtual_desktops); management->global = wl_global_create( server->display, &org_kde_plasma_virtual_desktop_management_interface, VIRTUAL_DESKTOP_MANAGEMENT_VERSION, management, kde_virtual_desktop_management_bind); if (!management->global) { kywc_log(KYWC_WARN, "Kde plasma virtual desktop create failed"); free(management); return false; } management->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &management->server_destroy); management->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &management->display_destroy); management->new_workspace.notify = handle_new_workspace; workspace_manager_add_new_listener(&management->new_workspace); return true; } kylin-wayland-compositor/src/view/xdg_toplevel_icon.c0000664000175000017500000002667515160461067022116 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include "output.h" #include "theme.h" #include "util/macros.h" #include "view_p.h" #include "xdg-toplevel-icon-v1-protocol.h" #define XDG_TOPLEVEL_ICON_MANAGER_VERSION 1 struct xdg_toplevel_icon_manager { struct wl_global *global; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct xdg_toplevel_icon_buffer { struct wl_list link; int scale; struct wlr_buffer *wlr_buffer; }; struct xdg_toplevel_icon { char *name; // may be NULL struct wl_list buffers; // xdg_toplevel_icon_buffer.link int n_refs; bool immutable; }; struct xdg_icon_toplevel { struct xdg_toplevel_icon *icon; struct wlr_xdg_toplevel *toplevel; struct wl_listener surface_commit; struct wl_listener toplevel_destroy; }; static const struct xdg_toplevel_icon_v1_interface icon_impl; static struct xdg_toplevel_icon *icon_from_resource(struct wl_resource *resource) { assert(wl_resource_instance_of(resource, &xdg_toplevel_icon_v1_interface, &icon_impl)); return wl_resource_get_user_data(resource); } static void icon_destroy(struct xdg_toplevel_icon *icon) { struct xdg_toplevel_icon_buffer *icon_buffer, *tmp; wl_list_for_each_safe(icon_buffer, tmp, &icon->buffers, link) { wlr_buffer_unlock(icon_buffer->wlr_buffer); wl_list_remove(&icon_buffer->link); free(icon_buffer); } free(icon->name); free(icon); } static void icon_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void icon_handle_set_name(struct wl_client *client, struct wl_resource *resource, const char *name) { struct xdg_toplevel_icon *icon = icon_from_resource(resource); if (!icon) { return; } if (icon->immutable) { wl_resource_post_error( resource, XDG_TOPLEVEL_ICON_V1_ERROR_IMMUTABLE, "the icon has already been assigned to a toplevel and must not be changed"); return; } char *tmp = strdup(name); if (!tmp) { wl_resource_post_no_memory(resource); return; } free(icon->name); icon->name = tmp; } static void icon_handle_add_buffer(struct wl_client *client, struct wl_resource *resource, struct wl_resource *buffer_resource, int32_t scale) { struct xdg_toplevel_icon *icon = icon_from_resource(resource); if (!icon) { return; } if (icon->immutable) { wl_resource_post_error( resource, XDG_TOPLEVEL_ICON_V1_ERROR_IMMUTABLE, "the icon has already been assigned to a toplevel and must not be changed"); return; } struct wlr_buffer *wlr_buffer = wlr_buffer_try_from_resource(buffer_resource); const char *bad_buffer_msg = NULL; struct wlr_shm_attributes shm_attribs; if (!wlr_buffer_get_shm(wlr_buffer, &shm_attribs)) { bad_buffer_msg = "not backed by wl_shm"; } else if (wlr_buffer->width != wlr_buffer->height) { bad_buffer_msg = "not square"; } if (bad_buffer_msg) { wl_resource_post_error(resource, XDG_TOPLEVEL_ICON_V1_ERROR_INVALID_BUFFER, "the provided buffer does not satisfy requirements: %s", bad_buffer_msg); wlr_buffer_unlock(wlr_buffer); return; } struct xdg_toplevel_icon_buffer *icon_buffer; wl_list_for_each(icon_buffer, &icon->buffers, link) { if (icon_buffer->wlr_buffer->width == wlr_buffer->width && icon_buffer->scale == scale) { wlr_buffer_unlock(icon_buffer->wlr_buffer); icon_buffer->wlr_buffer = wlr_buffer; return; } } icon_buffer = calloc(1, sizeof(*icon_buffer)); if (!icon_buffer) { wl_resource_post_no_memory(resource); } icon_buffer->wlr_buffer = wlr_buffer; icon_buffer->scale = scale; wl_list_insert(&icon->buffers, &icon_buffer->link); } static const struct xdg_toplevel_icon_v1_interface icon_impl = { .destroy = icon_handle_destroy, .set_name = icon_handle_set_name, .add_buffer = icon_handle_add_buffer, }; static struct xdg_toplevel_icon *xdg_toplevel_icon_ref(struct xdg_toplevel_icon *icon) { ++icon->n_refs; return icon; } static void xdg_toplevel_icon_unref(struct xdg_toplevel_icon *icon) { if (icon == NULL) { return; } assert(icon->n_refs > 0); --icon->n_refs; if (icon->n_refs == 0) { icon_destroy(icon); } } static void icon_handle_resource_destroy(struct wl_resource *resource) { xdg_toplevel_icon_unref(icon_from_resource(resource)); } static void manager_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void manager_handle_create_icon(struct wl_client *client, struct wl_resource *resource, uint32_t id) { struct xdg_toplevel_icon *icon = calloc(1, sizeof(*icon)); if (!icon) { wl_client_post_no_memory(client); return; } struct wl_resource *icon_resource = wl_resource_create(client, &xdg_toplevel_icon_v1_interface, wl_resource_get_version(resource), id); if (!icon_resource) { wl_client_post_no_memory(client); free(icon); return; } icon->n_refs = 1; wl_list_init(&icon->buffers); wl_resource_set_implementation(icon_resource, &icon_impl, icon, icon_handle_resource_destroy); } static void icon_toplevel_handle_surface_commit(struct wl_listener *listener, void *data) { struct xdg_icon_toplevel *icon_toplevel = wl_container_of(listener, icon_toplevel, surface_commit); struct xdg_toplevel_icon *icon = icon_toplevel->icon; struct wlr_xdg_toplevel *toplevel = icon_toplevel->toplevel; // surface commit, the view must have a value struct view *view = view_try_from_wlr_surface(toplevel->base->surface); if (icon) { view_set_icon_name(view, icon->name); struct xdg_toplevel_icon_buffer *icon_buffer; wl_list_for_each(icon_buffer, &icon->buffers, link) { xdg_view_add_icon_buffer(toplevel->base, icon_buffer->scale, icon_buffer->wlr_buffer); } xdg_toplevel_icon_unref(icon); } else { view_set_icon_name(view, NULL); xdg_view_clear_icon_buffer(toplevel->base); } view_set_icon(view, true); wl_list_remove(&icon_toplevel->surface_commit.link); wl_list_remove(&icon_toplevel->toplevel_destroy.link); free(icon_toplevel); } static void icon_toplevel_handle_toplevel_destroy(struct wl_listener *listener, void *data) { struct xdg_icon_toplevel *icon_toplevel = wl_container_of(listener, icon_toplevel, toplevel_destroy); if (icon_toplevel->icon) { xdg_toplevel_icon_unref(icon_toplevel->icon); } wl_list_remove(&icon_toplevel->surface_commit.link); wl_list_remove(&icon_toplevel->toplevel_destroy.link); free(icon_toplevel); } static void manager_handle_set_icon(struct wl_client *client, struct wl_resource *resource, struct wl_resource *toplevel_resource, struct wl_resource *icon_resource) { struct wlr_xdg_toplevel *toplevel = wlr_xdg_toplevel_from_resource(toplevel_resource); if (!toplevel) { return; } struct xdg_icon_toplevel *icon_toplevel = calloc(1, sizeof(*icon_toplevel)); if (!icon_toplevel) { wl_client_post_no_memory(client); return; } struct xdg_toplevel_icon *icon = icon_resource ? icon_from_resource(icon_resource) : NULL; if (icon) { icon->immutable = true; if (!icon->name && wl_list_empty(&icon->buffers)) { // Same as supplying null icon icon = NULL; } } if (icon) { icon_toplevel->icon = xdg_toplevel_icon_ref(icon); } icon_toplevel->toplevel = toplevel; icon_toplevel->surface_commit.notify = icon_toplevel_handle_surface_commit; wl_signal_add(&toplevel->base->surface->events.commit, &icon_toplevel->surface_commit); icon_toplevel->toplevel_destroy.notify = icon_toplevel_handle_toplevel_destroy; wl_signal_add(&toplevel->events.destroy, &icon_toplevel->toplevel_destroy); } static const struct xdg_toplevel_icon_manager_v1_interface xdg_toplevel_icon_manager_impl = { .destroy = manager_handle_destroy, .create_icon = manager_handle_create_icon, .set_icon = manager_handle_set_icon, }; static bool get_output_scale_icon_size(struct kywc_output *output, int index, void *data) { struct wl_array *sizes = data; struct theme *theme = theme_manager_get_theme(); float theme_icon_size = theme->icon_size * output->state.scale; uint32_t *size; wl_array_for_each(size, sizes) { if (FLOAT_EQUAL(*size, theme_icon_size)) { return false; } } uint32_t *icon_size = wl_array_add(sizes, sizeof(*icon_size)); if (icon_size) { *icon_size = theme_icon_size; } return false; } static void xdg_toplevel_icon_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct xdg_toplevel_icon_manager *manager = data; struct wl_resource *resource = wl_resource_create(client, &xdg_toplevel_icon_manager_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &xdg_toplevel_icon_manager_impl, manager, NULL); struct wl_array sizes; wl_array_init(&sizes); output_manager_for_each_output(get_output_scale_icon_size, true, &sizes); uint32_t *size; wl_array_for_each(size, &sizes) { xdg_toplevel_icon_manager_v1_send_icon_size(resource, *size); } wl_array_release(&sizes); xdg_toplevel_icon_manager_v1_send_done(resource); } static void manager_handle_display_destroy(struct wl_listener *listener, void *data) { struct xdg_toplevel_icon_manager *manager = wl_container_of(listener, manager, display_destroy); wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); } static void manager_handle_server_destroy(struct wl_listener *listener, void *data) { struct xdg_toplevel_icon_manager *manager = wl_container_of(listener, manager, server_destroy); wl_list_remove(&manager->server_destroy.link); free(manager); } bool xdg_toplevel_icon_manager_create(struct server *server) { struct xdg_toplevel_icon_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->global = wl_global_create(server->display, &xdg_toplevel_icon_manager_v1_interface, XDG_TOPLEVEL_ICON_MANAGER_VERSION, manager, xdg_toplevel_icon_manager_bind); if (!manager->global) { kywc_log(KYWC_WARN, "Xdg toplevel icon manager create failed"); free(manager); return false; } manager->display_destroy.notify = manager_handle_display_destroy; wl_display_add_destroy_listener(server->display, &manager->display_destroy); manager->server_destroy.notify = manager_handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); return true; } kylin-wayland-compositor/src/view/wlr_layer_shell.c0000664000175000017500000005236515160461067021574 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "input/event.h" #include "input/seat.h" #include "output.h" #include "scene/surface.h" #include "view_p.h" struct wlr_layer_shell_manager { struct wl_list outputs; /* layers for layer shell */ struct view_layer layers[4]; struct wl_listener new_output; struct wl_listener new_surface; struct wl_listener destroy; }; struct layer_output { struct wl_list link; struct output *output; /* layer shells in 4 layers */ struct wl_list shells[4]; /* if have exclusive_zone < 0 */ struct wl_listener geometry; /* if have exclusive_zone == 0 */ struct wl_listener usable_area; /* if have exclusive_zone > 0 */ struct wl_listener update_usable_area; /* destroy wlr layer-shell if output off and destroy*/ struct wl_listener off; struct wl_listener destroy; }; struct layer_shell { struct wl_list link; struct ky_scene_tree *tree; struct wlr_layer_surface_v1 *layer_surface; struct wlr_seat_keyboard_grab keyboard_grab; struct wl_listener commit; struct wl_listener map; struct wl_listener unmap; struct wl_listener new_popup; struct wl_listener destroy; }; static struct wlr_layer_shell_manager *manager = NULL; static void layer_shell_keyboard_enter(struct wlr_seat_keyboard_grab *grab, struct wlr_surface *surface, const uint32_t keycodes[], size_t num_keycodes, const struct wlr_keyboard_modifiers *modifiers) { // keyboard focus should remain on the layer shell } static void layer_shell_keyboard_clear_focus(struct wlr_seat_keyboard_grab *grab) { // keyboard focus should remain on the layer shell } static void layer_shell_keyboard_key(struct wlr_seat_keyboard_grab *grab, uint32_t time, uint32_t key, uint32_t state) { wlr_seat_keyboard_send_key(grab->seat, time, key, state); } static void layer_shell_keyboard_modifiers(struct wlr_seat_keyboard_grab *grab, const struct wlr_keyboard_modifiers *modifiers) { wlr_seat_keyboard_send_modifiers(grab->seat, modifiers); } static void layer_shell_keyboard_cancel(struct wlr_seat_keyboard_grab *grab) { grab->seat = NULL; } static const struct wlr_keyboard_grab_interface layer_shell_keyboard_grab = { .enter = layer_shell_keyboard_enter, .clear_focus = layer_shell_keyboard_clear_focus, .key = layer_shell_keyboard_key, .modifiers = layer_shell_keyboard_modifiers, .cancel = layer_shell_keyboard_cancel, }; static void layer_shell_keyboard_interactivity(struct layer_shell *layer_shell, struct seat *seat) { struct wlr_layer_surface_v1 *layer_surface = layer_shell->layer_surface; struct wlr_seat *wlr_seat = layer_shell->keyboard_grab.seat; if (layer_surface->surface->mapped) { if (layer_surface->current.keyboard_interactive) { seat_focus_surface(seat, layer_surface->surface); if (wlr_seat && wlr_seat != seat->wlr_seat) { wlr_seat_keyboard_end_grab(wlr_seat); } /* start a seat keyboard grab */ wlr_seat = layer_shell->keyboard_grab.seat; if (!wlr_seat && layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { layer_shell->keyboard_grab.interface = &layer_shell_keyboard_grab; layer_shell->keyboard_grab.data = layer_shell; wlr_seat_keyboard_start_grab(seat->wlr_seat, &layer_shell->keyboard_grab); } } } else { if (wlr_seat && wlr_seat->keyboard_state.grab == &layer_shell->keyboard_grab) { wlr_seat_keyboard_end_grab(wlr_seat); } // XXX: auto focus layer_shell if (seat->wlr_seat->keyboard_state.focused_surface == layer_surface->surface) { view_activate_topmost(false); } } } static struct layer_output *layer_output_from_wlr_output(struct wlr_output *wlr_output) { struct layer_output *layer_output; wl_list_for_each(layer_output, &manager->outputs, link) { if (layer_output->output->wlr_output == wlr_output) { return layer_output; } } return NULL; } static void layer_surface_exclusive_zone(struct wlr_layer_surface_v1_state *state, struct kywc_box *usable_area) { switch (state->anchor) { case ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP: case (ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT): // Anchor top usable_area->y += state->exclusive_zone + state->margin.top; usable_area->height -= state->exclusive_zone + state->margin.top; break; case ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM: case (ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT): // Anchor bottom usable_area->height -= state->exclusive_zone + state->margin.bottom; break; case ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT: case (ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT): // Anchor left usable_area->x += state->exclusive_zone + state->margin.left; usable_area->width -= state->exclusive_zone + state->margin.left; break; case ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT: case (ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT): // Anchor right usable_area->width -= state->exclusive_zone + state->margin.right; break; } } static void layer_shell_configure_surface(struct layer_shell *layer_shell, const struct kywc_box *full_area, struct kywc_box *usable_area) { struct wlr_layer_surface_v1 *layer_surface = layer_shell->layer_surface; struct wlr_layer_surface_v1_state *state = &layer_surface->current; // If the exclusive zone is set to -1, the layer surface will use the // full area of the output, otherwise it is constrained to the // remaining usable area. struct kywc_box bounds; if (state->exclusive_zone == -1) { bounds = *full_area; } else { bounds = *usable_area; } struct kywc_box box = { .width = state->desired_width, .height = state->desired_height, }; // Horizontal positioning if (box.width == 0) { box.x = bounds.x + state->margin.left; box.width = bounds.width - (state->margin.left + state->margin.right); } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT && state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) { box.x = bounds.x + bounds.width / 2 - box.width / 2; } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) { box.x = bounds.x + state->margin.left; } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) { box.x = bounds.x + bounds.width - box.width - state->margin.right; } else { box.x = bounds.x + bounds.width / 2 - box.width / 2; } // Vertical positioning if (box.height == 0) { box.y = bounds.y + state->margin.top; box.height = bounds.height - (state->margin.top + state->margin.bottom); } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP && state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM) { box.y = bounds.y + bounds.height / 2 - box.height / 2; } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) { box.y = bounds.y + state->margin.top; } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM) { box.y = bounds.y + bounds.height - box.height - state->margin.bottom; } else { box.y = bounds.y + bounds.height / 2 - box.height / 2; } ky_scene_node_set_position(&layer_shell->tree->node, box.x, box.y); wlr_layer_surface_v1_configure(layer_surface, box.width, box.height); if (layer_surface->surface->mapped && state->exclusive_zone > 0) { layer_surface_exclusive_zone(state, usable_area); } } static void layer_shell_handle_commit(struct wl_listener *listener, void *data) { struct layer_shell *layer_shell = wl_container_of(listener, layer_shell, commit); struct wlr_layer_surface_v1 *layer_surface = layer_shell->layer_surface; if (!layer_surface->output) { return; } struct output *output = output_from_wlr_output(layer_surface->output); if (!layer_surface->surface->mapped) { /* is not mapped, usable area will not be changed */ layer_shell_configure_surface(layer_shell, &output->geometry, &output->usable_area); return; } uint32_t committed = layer_surface->current.committed; if (committed & WLR_LAYER_SURFACE_V1_STATE_LAYER) { ky_scene_node_reparent(&layer_shell->tree->node, manager->layers[layer_surface->current.layer].tree); committed &= ~WLR_LAYER_SURFACE_V1_STATE_LAYER; } if (committed & WLR_LAYER_SURFACE_V1_STATE_KEYBOARD_INTERACTIVITY) { layer_shell_keyboard_interactivity(layer_shell, input_manager_get_default_seat()); committed &= ~WLR_LAYER_SURFACE_V1_STATE_KEYBOARD_INTERACTIVITY; } if (committed) { layer_shell_configure_surface(layer_shell, &output->geometry, &output->usable_area); output_update_usable_area(&output->base); } } static void layer_shell_handle_map(struct wl_listener *listener, void *data) { struct layer_shell *layer_shell = wl_container_of(listener, layer_shell, map); struct wlr_layer_surface_v1 *layer_surface = layer_shell->layer_surface; /* layer-shell first configure is done in commit */ ky_scene_node_set_enabled(&layer_shell->tree->node, true); if (layer_surface->current.exclusive_zone > 0) { struct output *output = output_from_wlr_output(layer_surface->output); output_update_usable_area(&output->base); } layer_shell_keyboard_interactivity(layer_shell, input_manager_get_default_seat()); } /* called when precommit */ static void layer_shell_handle_unmap(struct wl_listener *listener, void *data) { struct layer_shell *layer_shell = wl_container_of(listener, layer_shell, unmap); struct wlr_layer_surface_v1 *layer_surface = layer_shell->layer_surface; ky_scene_node_set_enabled(&layer_shell->tree->node, false); layer_shell_keyboard_interactivity(layer_shell, input_manager_get_default_seat()); if (layer_surface->output && layer_surface->current.exclusive_zone > 0) { output_update_usable_area(&output_from_wlr_output(layer_surface->output)->base); } } static void layer_shell_handle_destroy(struct wl_listener *listener, void *data) { struct layer_shell *layer_shell = wl_container_of(listener, layer_shell, destroy); wl_list_remove(&layer_shell->destroy.link); wl_list_remove(&layer_shell->commit.link); wl_list_remove(&layer_shell->map.link); wl_list_remove(&layer_shell->unmap.link); wl_list_remove(&layer_shell->new_popup.link); wl_list_remove(&layer_shell->link); ky_scene_node_destroy(&layer_shell->tree->node); free(layer_shell); } static void layer_shell_handle_new_popup(struct wl_listener *listener, void *data) { struct layer_shell *layer_shell = wl_container_of(listener, layer_shell, new_popup); struct wlr_xdg_popup *wlr_xdg_popup = data; struct view_layer *popup_layer = view_manager_get_layer(LAYER_POPUP, false); xdg_popup_create(wlr_xdg_popup, layer_shell->tree, popup_layer, false); } static bool layer_shell_hover(struct seat *seat, struct ky_scene_node *node, double x, double y, uint32_t time, bool first, bool hold, void *data) { struct wlr_surface *surface = wlr_surface_try_from_node(node); if (first) { kywc_log(KYWC_DEBUG, "First hover surface %p (%f %f)", surface, x, y); } seat_notify_motion(seat, surface, time, x, y, first); return false; } static void layer_shell_click(struct seat *seat, struct ky_scene_node *node, uint32_t button, bool pressed, uint32_t time, enum click_state state, void *data) { struct layer_shell *layer_shell = data; seat_notify_button(seat, time, button, pressed); layer_shell_keyboard_interactivity(layer_shell, seat); } static void layer_shell_leave(struct seat *seat, struct ky_scene_node *node, bool last, void *data) { /* so surface will call set_cursor when enter again */ struct wlr_surface *surface = wlr_surface_try_from_node(node); seat_notify_leave(seat, surface); } static struct ky_scene_node *layer_shell_get_root(void *data) { struct layer_shell *layer_shell = data; return &layer_shell->tree->node; } static struct wlr_surface *layer_shell_get_toplevel(void *data) { struct layer_shell *layer_shell = data; return layer_shell->layer_surface->surface; } static const struct input_event_node_impl layer_shell_event_node_impl = { .hover = layer_shell_hover, .click = layer_shell_click, .leave = layer_shell_leave, }; static void handle_new_layer_surface(struct wl_listener *listener, void *data) { struct wlr_layer_surface_v1 *layer_surface = data; /* the user most recently interacted with. */ if (!layer_surface->output) { struct output *output = input_current_output(input_manager_get_default_seat()); if (!output) { kywc_log(KYWC_WARN, "Cannot find output for layer shell"); wlr_layer_surface_v1_destroy(layer_surface); return; } layer_surface->output = output->wlr_output; } struct layer_shell *layer_shell = calloc(1, sizeof(*layer_shell)); if (!layer_shell) { return; } struct layer_output *layer_output = layer_output_from_wlr_output(layer_surface->output); wl_list_insert(&layer_output->shells[layer_surface->current.layer], &layer_shell->link); layer_shell->layer_surface = layer_surface; layer_shell->tree = ky_scene_tree_create(manager->layers[layer_surface->current.layer].tree); ky_scene_subsurface_tree_create(layer_shell->tree, layer_surface->surface); input_event_node_create(&layer_shell->tree->node, &layer_shell_event_node_impl, layer_shell_get_root, layer_shell_get_toplevel, layer_shell); layer_shell->commit.notify = layer_shell_handle_commit; wl_signal_add(&layer_surface->surface->events.commit, &layer_shell->commit); layer_shell->map.notify = layer_shell_handle_map; wl_signal_add(&layer_surface->surface->events.map, &layer_shell->map); layer_shell->unmap.notify = layer_shell_handle_unmap; wl_signal_add(&layer_surface->surface->events.unmap, &layer_shell->unmap); layer_shell->destroy.notify = layer_shell_handle_destroy; wl_signal_add(&layer_surface->events.destroy, &layer_shell->destroy); layer_shell->new_popup.notify = layer_shell_handle_new_popup; wl_signal_add(&layer_surface->events.new_popup, &layer_shell->new_popup); ky_scene_node_set_enabled(&layer_shell->tree->node, layer_surface->surface->mapped); } static void layer_output_destroy_shells(struct layer_output *layer_output) { struct layer_shell *layer_shell, *tmp; for (int i = 0; i < 4; i++) { wl_list_for_each_safe(layer_shell, tmp, &layer_output->shells[i], link) { wl_list_remove(&layer_shell->link); wl_list_init(&layer_shell->link); /* mark output is null, skip update output when unmap */ layer_shell->layer_surface->output = NULL; wlr_layer_surface_v1_destroy(layer_shell->layer_surface); } } } static void handle_output_off(struct wl_listener *listener, void *data) { struct layer_output *layer_output = wl_container_of(listener, layer_output, off); layer_output_destroy_shells(layer_output); } static void handle_output_destroy(struct wl_listener *listener, void *data) { struct layer_output *layer_output = wl_container_of(listener, layer_output, destroy); /* output is enabled when destroy */ if (layer_output->output->base.state.enabled) { layer_output_destroy_shells(layer_output); } wl_list_remove(&layer_output->destroy.link); wl_list_remove(&layer_output->geometry.link); wl_list_remove(&layer_output->usable_area.link); wl_list_remove(&layer_output->update_usable_area.link); wl_list_remove(&layer_output->off.link); wl_list_remove(&layer_output->link); free(layer_output); } static void handle_output_geometry(struct wl_listener *listener, void *data) { struct layer_output *layer_output = wl_container_of(listener, layer_output, geometry); /* configure layer-shells that exclusive_zone < 0 */ struct layer_shell *layer_shell; for (int i = 0; i < 4; i++) { wl_list_for_each(layer_shell, &layer_output->shells[i], link) { if (layer_shell->layer_surface->current.exclusive_zone >= 0) { continue; } layer_shell_configure_surface(layer_shell, &layer_output->output->geometry, &layer_output->output->usable_area); } } } static void handle_output_usable_area(struct wl_listener *listener, void *data) { struct layer_output *layer_output = wl_container_of(listener, layer_output, usable_area); /* configure layer-shells that exclusive_zone == 0 */ struct layer_shell *layer_shell; for (int i = 0; i < 4; i++) { wl_list_for_each(layer_shell, &layer_output->shells[i], link) { if (layer_shell->layer_surface->current.exclusive_zone != 0) { continue; } layer_shell_configure_surface(layer_shell, &layer_output->output->geometry, &layer_output->output->usable_area); } } } static void handle_output_update_usable_area(struct wl_listener *listener, void *data) { struct layer_output *layer_output = wl_container_of(listener, layer_output, update_usable_area); struct kywc_box *usable_area = data; /* update usable area and configure layer-shells that exclusive_zone > 0 */ struct layer_shell *layer_shell; for (int i = 0; i < 4; i++) { wl_list_for_each(layer_shell, &layer_output->shells[i], link) { if (layer_shell->layer_surface->current.exclusive_zone <= 0) { continue; } layer_shell_configure_surface(layer_shell, &layer_output->output->geometry, usable_area); } } } static void handle_new_output(struct wl_listener *listener, void *data) { struct layer_output *layer_output = calloc(1, sizeof(*layer_output)); if (!layer_output) { return; } struct output *output = output_from_kywc_output(data); for (int i = 0; i < 4; i++) { wl_list_init(&layer_output->shells[i]); } wl_list_insert(&manager->outputs, &layer_output->link); layer_output->output = output; layer_output->off.notify = handle_output_off; wl_signal_add(&output->base.events.off, &layer_output->off); layer_output->destroy.notify = handle_output_destroy; wl_signal_add(&output->base.events.destroy, &layer_output->destroy); layer_output->geometry.notify = handle_output_geometry; wl_signal_add(&output->events.geometry, &layer_output->geometry); layer_output->usable_area.notify = handle_output_usable_area; wl_signal_add(&output->events.usable_area, &layer_output->usable_area); layer_output->update_usable_area.notify = handle_output_update_usable_area; output_add_update_usable_area_listener(&output->base, &layer_output->update_usable_area, true); } static void handle_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->destroy.link); wl_list_remove(&manager->new_output.link); wl_list_remove(&manager->new_surface.link); free(manager); manager = NULL; } bool wlr_layer_shell_manager_create(struct server *server) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } struct wlr_layer_shell_v1 *wlr_layer_shell = wlr_layer_shell_v1_create(server->display, 4); if (!wlr_layer_shell) { kywc_log(KYWC_WARN, "Wlroots layer shell create failed"); free(manager); return false; } wl_list_init(&manager->outputs); /* create bottom and top layer tree not in workspace */ manager->layers[0] = *view_manager_get_layer(LAYER_DESKTOP, false); manager->layers[1] = *view_manager_get_layer(LAYER_BELOW, false); manager->layers[1].tree = ky_scene_tree_create(manager->layers[1].tree); manager->layers[2] = *view_manager_get_layer(LAYER_ABOVE, false); manager->layers[2].tree = ky_scene_tree_create(manager->layers[2].tree); manager->layers[3] = *view_manager_get_layer(LAYER_UNMANAGED, false); manager->destroy.notify = handle_destroy; wl_signal_add(&wlr_layer_shell->events.destroy, &manager->destroy); manager->new_surface.notify = handle_new_layer_surface; wl_signal_add(&wlr_layer_shell->events.new_surface, &manager->new_surface); manager->new_output.notify = handle_new_output; kywc_output_add_new_listener(&manager->new_output); return true; } kylin-wayland-compositor/src/view/ky_workspace.c0000664000175000017500000003010615160460057021071 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "kywc-workspace-v1-protocol.h" #include "view/workspace.h" #include "view_p.h" struct ky_workspace_manager { struct wl_event_loop *event_loop; struct wl_global *global; struct wl_list resources; struct wl_list workspaces; struct wl_event_source *idle_source; struct wl_listener new_workspace; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct ky_workspace { struct ky_workspace_manager *manager; struct wl_list link; struct wl_list resources; struct workspace *workspace; struct wl_listener name; struct wl_listener position; struct wl_listener activate; struct wl_listener destroy; }; static void workspace_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void workspace_handle_set_position(struct wl_client *client, struct wl_resource *resource, uint32_t position) { struct ky_workspace *ky_workspace = wl_resource_get_user_data(resource); if (!ky_workspace) { return; } workspace_set_position(ky_workspace->workspace, position); } static void workspace_handle_activate(struct wl_client *client, struct wl_resource *resource) { struct ky_workspace *ky_workspace = wl_resource_get_user_data(resource); if (!ky_workspace) { return; } workspace_activate(ky_workspace->workspace); } static void workspace_handle_remove(struct wl_client *client, struct wl_resource *resource) { struct ky_workspace *ky_workspace = wl_resource_get_user_data(resource); if (!ky_workspace) { return; } if (wl_list_length(&ky_workspace->manager->workspaces) == 1) { kywc_log(KYWC_WARN, "Reject to destroy the last workspace"); return; } workspace_destroy(ky_workspace->workspace); } static void workspace_handle_set_name(struct wl_client *client, struct wl_resource *resource, const char *name) { struct ky_workspace *ky_workspace = wl_resource_get_user_data(resource); if (!ky_workspace) { return; } workspace_update_name(ky_workspace->workspace, name); } static const struct kywc_workspace_v1_interface ky_workspace_impl = { .destroy = workspace_handle_destroy, .set_position = workspace_handle_set_position, .activate = workspace_handle_activate, .remove = workspace_handle_remove, .set_name = workspace_handle_set_name, }; static void ky_workspace_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static struct wl_resource * create_workspace_resource_for_resource(struct ky_workspace *ky_workspace, struct wl_resource *manager_resource) { struct wl_client *client = wl_resource_get_client(manager_resource); struct wl_resource *resource = wl_resource_create(client, &kywc_workspace_v1_interface, wl_resource_get_version(manager_resource), 0); if (!resource) { wl_client_post_no_memory(client); return NULL; } wl_resource_set_implementation(resource, &ky_workspace_impl, ky_workspace, ky_workspace_resource_destroy); wl_list_insert(&ky_workspace->resources, wl_resource_get_link(resource)); kywc_workspace_manager_v1_send_workspace(manager_resource, resource, ky_workspace->workspace->uuid); return resource; } static void workspace_send_details_to_workspace_resource(struct ky_workspace *ky_workspace, struct wl_resource *resource) { struct workspace *workspace = ky_workspace->workspace; kywc_workspace_v1_send_name(resource, workspace->name); kywc_workspace_v1_send_position(resource, workspace->position); if (workspace->activated) { kywc_workspace_v1_send_activated(resource); } else { kywc_workspace_v1_send_deactivated(resource); } } static void manager_handle_create_workspace(struct wl_client *client, struct wl_resource *resource, const char *name, uint32_t position) { workspace_create(name, position); } static void manager_handle_stop(struct wl_client *client, struct wl_resource *resource) { kywc_workspace_manager_v1_send_finished(resource); wl_resource_destroy(resource); } static const struct kywc_workspace_manager_v1_interface ky_workspace_manager_impl = { .create_workspace = manager_handle_create_workspace, .stop = manager_handle_stop, }; static void ky_workspace_manager_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static struct ky_workspace *get_ky_workspace(struct ky_workspace_manager *manager, struct workspace *workspace) { struct ky_workspace *ky_workspace; wl_list_for_each(ky_workspace, &manager->workspaces, link) { if (ky_workspace->workspace == workspace) { return ky_workspace; } } return NULL; } static void ky_workspace_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct ky_workspace_manager *manager = data; struct wl_resource *resource = wl_resource_create(client, &kywc_workspace_manager_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &ky_workspace_manager_impl, manager, ky_workspace_manager_resource_destroy); wl_list_insert(&manager->resources, wl_resource_get_link(resource)); /* send all workspaces and details */ struct ky_workspace *ky_workspace; for (uint32_t i = 0; i < workspace_manager_get_count(); i++) { ky_workspace = get_ky_workspace(manager, workspace_by_position(i)); struct wl_resource *workspace_resource = create_workspace_resource_for_resource(ky_workspace, resource); workspace_send_details_to_workspace_resource(ky_workspace, workspace_resource); } kywc_workspace_manager_v1_send_done(resource); } static void manager_idle_send_done(void *data) { struct ky_workspace_manager *manager = data; struct wl_resource *resource; wl_resource_for_each(resource, &manager->resources) { kywc_workspace_manager_v1_send_done(resource); } manager->idle_source = NULL; } static void manager_update_idle_source(struct ky_workspace_manager *manager) { if (manager->idle_source || wl_list_empty(&manager->resources)) { return; } manager->idle_source = wl_event_loop_add_idle(manager->event_loop, manager_idle_send_done, manager); } static void handle_workspace_destroy(struct wl_listener *listener, void *data) { struct ky_workspace *ky_workspace = wl_container_of(listener, ky_workspace, destroy); struct wl_resource *resource, *tmp; wl_resource_for_each_safe(resource, tmp, &ky_workspace->resources) { kywc_workspace_v1_send_removed(resource); // make the resource inert wl_list_remove(wl_resource_get_link(resource)); wl_list_init(wl_resource_get_link(resource)); wl_resource_set_user_data(resource, NULL); } wl_list_remove(&ky_workspace->name.link); wl_list_remove(&ky_workspace->position.link); wl_list_remove(&ky_workspace->activate.link); wl_list_remove(&ky_workspace->destroy.link); wl_list_remove(&ky_workspace->link); free(ky_workspace); } static void handle_workspace_activate(struct wl_listener *listener, void *data) { struct ky_workspace *ky_workspace = wl_container_of(listener, ky_workspace, activate); struct wl_resource *resource; wl_resource_for_each(resource, &ky_workspace->resources) { if (ky_workspace->workspace->activated) { kywc_workspace_v1_send_activated(resource); } else { kywc_workspace_v1_send_deactivated(resource); } } manager_update_idle_source(ky_workspace->manager); } static void handle_workspace_name(struct wl_listener *listener, void *data) { struct ky_workspace *ky_workspace = wl_container_of(listener, ky_workspace, name); struct wl_resource *resource; wl_resource_for_each(resource, &ky_workspace->resources) { kywc_workspace_v1_send_name(resource, ky_workspace->workspace->name); } manager_update_idle_source(ky_workspace->manager); } static void handle_workspace_position(struct wl_listener *listener, void *data) { struct ky_workspace *ky_workspace = wl_container_of(listener, ky_workspace, position); struct wl_resource *resource; wl_resource_for_each(resource, &ky_workspace->resources) { kywc_workspace_v1_send_position(resource, ky_workspace->workspace->position); } manager_update_idle_source(ky_workspace->manager); } static void handle_new_workspace(struct wl_listener *listener, void *data) { struct ky_workspace *ky_workspace = calloc(1, sizeof(*ky_workspace)); if (!ky_workspace) { return; } struct ky_workspace_manager *manager = wl_container_of(listener, manager, new_workspace); ky_workspace->manager = manager; wl_list_init(&ky_workspace->resources); wl_list_insert(&manager->workspaces, &ky_workspace->link); struct workspace *workspace = data; ky_workspace->workspace = workspace; ky_workspace->name.notify = handle_workspace_name; wl_signal_add(&workspace->events.name, &ky_workspace->name); ky_workspace->position.notify = handle_workspace_position; wl_signal_add(&workspace->events.position, &ky_workspace->position); ky_workspace->activate.notify = handle_workspace_activate; wl_signal_add(&workspace->events.activate, &ky_workspace->activate); ky_workspace->destroy.notify = handle_workspace_destroy; wl_signal_add(&workspace->events.destroy, &ky_workspace->destroy); /* send workspaces and detail */ struct wl_resource *resource, *workspace_resource; wl_resource_for_each(resource, &manager->resources) { workspace_resource = create_workspace_resource_for_resource(ky_workspace, resource); workspace_send_details_to_workspace_resource(ky_workspace, workspace_resource); kywc_workspace_manager_v1_send_done(resource); } } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct ky_workspace_manager *manager = wl_container_of(listener, manager, server_destroy); wl_list_remove(&manager->server_destroy.link); free(manager); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct ky_workspace_manager *manager = wl_container_of(listener, manager, display_destroy); wl_list_remove(&manager->new_workspace.link); wl_list_remove(&manager->display_destroy.link); if (manager->idle_source) { wl_event_source_remove(manager->idle_source); } wl_global_destroy(manager->global); } bool ky_workspace_manager_create(struct server *server) { struct ky_workspace_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->global = wl_global_create(server->display, &kywc_workspace_manager_v1_interface, 1, manager, ky_workspace_manager_bind); if (!manager->global) { kywc_log(KYWC_WARN, "KYWC workspace manager create failed"); free(manager); return false; } wl_list_init(&manager->resources); wl_list_init(&manager->workspaces); manager->event_loop = wl_display_get_event_loop(server->display); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &manager->display_destroy); manager->new_workspace.notify = handle_new_workspace; workspace_manager_add_new_listener(&manager->new_workspace); return true; } kylin-wayland-compositor/src/view/exclusive.c0000664000175000017500000002121015160460057020373 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "output.h" #include "scene/surface.h" #include "view/view.h" struct exclusive_zone_output { struct wl_list link; struct exclusive_zone *zone; struct kywc_output *output; struct wl_listener update_usable_area; }; struct exclusive_zone { struct wl_list outputs; struct wlr_surface *surface; struct wl_listener output_enter; struct wl_listener output_leave; struct view *view; struct wl_listener view_unmap; struct wl_listener view_minimize; struct wl_listener view_size; struct wl_listener view_position; enum kywc_edges edge; bool late_update, enabled; }; static void exclusive_zone_update_edge(struct exclusive_zone *zone) { struct view *view = zone->view; struct kywc_output *output = view->output; struct kywc_box geo; kywc_output_effective_geometry(output, &geo); struct kywc_box *view_geo = &view->base.geometry; int mid_x = view_geo->x + view_geo->width / 2; int mid_y = view_geo->y + view_geo->height / 2; if (view_geo->width > view_geo->height) { if (mid_y - geo.y < geo.y + geo.height - mid_y) { zone->edge = KYWC_EDGE_TOP; } else { zone->edge = KYWC_EDGE_BOTTOM; } } else { if (mid_x - geo.x < geo.x + geo.width - mid_x) { zone->edge = KYWC_EDGE_LEFT; } else { zone->edge = KYWC_EDGE_RIGHT; } } } static void exclusive_zone_output_destroy(struct exclusive_zone_output *output) { wl_list_remove(&output->link); wl_list_remove(&output->update_usable_area.link); output_update_usable_area(output->output); free(output); } static void handle_output_update_usable_area(struct wl_listener *listener, void *data) { struct exclusive_zone_output *output = wl_container_of(listener, output, update_usable_area); struct exclusive_zone *zone = output->zone; struct kywc_box *usable_area = data; if (zone->view->impl->update_usable_area) { zone->view->impl->update_usable_area(zone->view, output->output, usable_area, zone->edge); } } static struct exclusive_zone_output *exclusive_zone_output_create(struct exclusive_zone *zone, struct kywc_output *kywc_output) { struct exclusive_zone_output *output = calloc(1, sizeof(*output)); if (!output) { return NULL; } output->zone = zone; wl_list_insert(&zone->outputs, &output->link); output->output = kywc_output; output->update_usable_area.notify = handle_output_update_usable_area; output_add_update_usable_area_listener(kywc_output, &output->update_usable_area, zone->late_update); output_update_usable_area(output->output); return output; } static struct exclusive_zone_output *exclusive_zone_get_output(struct exclusive_zone *zone, struct kywc_output *kywc_output) { struct exclusive_zone_output *output; wl_list_for_each(output, &zone->outputs, link) { if (output->output == kywc_output) { return output; } } return NULL; } static void handle_output_enter(struct wl_listener *listener, void *data) { struct exclusive_zone *zone = wl_container_of(listener, zone, output_enter); struct ky_scene_output *scene_output = data; struct kywc_output *kywc_output = &output_from_wlr_output(scene_output->output)->base; if (exclusive_zone_get_output(zone, kywc_output)) { kywc_log(KYWC_ERROR, "%s enter output multiple times", zone->view->base.app_id); return; } assert(zone->view->base.mapped); exclusive_zone_output_create(zone, kywc_output); } static void handle_output_leave(struct wl_listener *listener, void *data) { struct exclusive_zone *zone = wl_container_of(listener, zone, output_leave); struct ky_scene_output *scene_output = data; struct kywc_output *kywc_output = &output_from_wlr_output(scene_output->output)->base; struct exclusive_zone_output *output = exclusive_zone_get_output(zone, kywc_output); if (!output) { kywc_log(KYWC_ERROR, "%s leave an output nerver entered", zone->view->base.app_id); return; } exclusive_zone_output_destroy(output); } static void exclusive_zone_set_enabled(struct exclusive_zone *zone, bool enabled) { if (zone->enabled == enabled) { return; } zone->enabled = enabled; if (enabled) { struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(zone->view->surface); assert(buffer); wl_signal_add(&buffer->events.output_enter, &zone->output_enter); wl_signal_add(&buffer->events.output_leave, &zone->output_leave); wl_signal_add(&zone->view->base.events.size, &zone->view_size); wl_signal_add(&zone->view->base.events.position, &zone->view_position); exclusive_zone_update_edge(zone); // add all outputs to zone struct wlr_surface_output *surface_output; wl_list_for_each(surface_output, &zone->surface->current_outputs, link) { exclusive_zone_output_create(zone, &output_from_wlr_output(surface_output->output)->base); } return; } wl_list_remove(&zone->output_enter.link); wl_list_init(&zone->output_enter.link); wl_list_remove(&zone->output_leave.link); wl_list_init(&zone->output_leave.link); wl_list_remove(&zone->view_size.link); wl_list_init(&zone->view_size.link); wl_list_remove(&zone->view_position.link); wl_list_init(&zone->view_position.link); struct exclusive_zone_output *output, *tmp; wl_list_for_each_safe(output, tmp, &zone->outputs, link) { exclusive_zone_output_destroy(output); } } static void exclusive_zone_destroy(struct exclusive_zone *zone) { wl_list_remove(&zone->view_unmap.link); wl_list_remove(&zone->view_minimize.link); exclusive_zone_set_enabled(zone, false); free(zone); } static void handle_view_unmap(struct wl_listener *listener, void *data) { struct exclusive_zone *zone = wl_container_of(listener, zone, view_unmap); zone->view->exclusive_zone = NULL; exclusive_zone_destroy(zone); } static void exclusive_zone_update(struct exclusive_zone *zone) { exclusive_zone_update_edge(zone); struct exclusive_zone_output *output; wl_list_for_each(output, &zone->outputs, link) { output_update_usable_area(output->output); } } static void handle_view_size(struct wl_listener *listener, void *data) { struct exclusive_zone *zone = wl_container_of(listener, zone, view_size); exclusive_zone_update(zone); } static void handle_view_position(struct wl_listener *listener, void *data) { struct exclusive_zone *zone = wl_container_of(listener, zone, view_position); exclusive_zone_update(zone); } static void handle_view_minimize(struct wl_listener *listener, void *data) { struct exclusive_zone *zone = wl_container_of(listener, zone, view_minimize); struct kywc_view *view = &zone->view->base; exclusive_zone_set_enabled(zone, !view->minimized); } static struct exclusive_zone *exclusive_zone_create(struct view *view) { if (!view->base.mapped) { kywc_log(KYWC_ERROR, "add unmapped view(%s) to exclusive zone", view->base.app_id); return NULL; } struct exclusive_zone *zone = calloc(1, sizeof(*zone)); if (!zone) { return NULL; } zone->late_update = false; zone->surface = view->surface; wl_list_init(&zone->outputs); zone->output_enter.notify = handle_output_enter; wl_list_init(&zone->output_enter.link); zone->output_leave.notify = handle_output_leave; wl_list_init(&zone->output_leave.link); zone->view = view; zone->view_unmap.notify = handle_view_unmap; wl_signal_add(&view->base.events.unmap, &zone->view_unmap); zone->view_minimize.notify = handle_view_minimize; wl_signal_add(&view->base.events.minimize, &zone->view_minimize); zone->view_size.notify = handle_view_size; zone->view_position.notify = handle_view_position; wl_list_init(&zone->view_size.link); wl_list_init(&zone->view_position.link); handle_view_minimize(&zone->view_minimize, NULL); return zone; } void view_set_exclusive(struct view *view, bool exclusive) { if (exclusive == !!view->exclusive_zone) { return; } if (exclusive) { view->exclusive_zone = exclusive_zone_create(view); } else { exclusive_zone_destroy(view->exclusive_zone); view->exclusive_zone = NULL; } } kylin-wayland-compositor/src/view/meson.build0000664000175000017500000000271415160461067020374 0ustar fengfengwlcom_sources += files( 'action.c', 'config.c', 'decoration.c', 'exclusive.c', 'global_authentication.c', 'interactive.c', 'maximize_switcher.c', 'modal.c', 'positioner.c', 'ssd.c', 'stack_mode.c', 'tablet_mode.c', 'tile_flyout.c', 'view.c', 'window_menu.c', 'workspace.c', 'xdg_dialog.c', 'xdg_popup.c', 'xdg_shell.c', 'xdg_activation.c', ) wlcom_sources += files( 'ky_toplevel.c', 'ky_workspace.c', ) if have_kde_virtual_desktop wlcom_sources += files( 'kde_virtual_desktop.c', ) endif if have_wlr_foreign_toplevel wlcom_sources += files( 'wlr_foreign_toplevel.c', ) endif if have_wlr_layer_shell wlcom_sources += files( 'wlr_layer_shell.c', ) endif if have_kde_plasma_shell wlcom_sources += files( 'kde_plasma_shell.c', ) endif if have_kde_plasma_window_management wlcom_sources += files( 'kde_plasma_window.c', ) endif if have_kde_blur wlcom_sources += files( 'kde_blur.c', ) endif if have_kde_slide wlcom_sources += files( 'kde_slide.c', ) endif if have_ukui_shell wlcom_sources += files( 'ukui_shell.c', ) endif if have_ukui_window_management wlcom_sources += files( 'ukui_window.c', ) endif if have_ukui_blur wlcom_sources += files( 'ukui_blur.c', ) endif if have_ukui_startup wlcom_sources += files( 'ukui_startup.c', ) endif if have_xdg_toplevel_icon wlcom_sources += files( 'xdg_toplevel_icon.c', ) endif subdir('tile') kylin-wayland-compositor/src/view/ssd.c0000664000175000017500000012750015160461067017170 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include "effect/fade.h" #include "input/cursor.h" #include "nls.h" #include "output.h" #include "painter.h" #include "scene/decoration.h" #include "theme.h" #include "util/color.h" #include "util/macros.h" #include "view/action.h" #include "view_p.h" #include "widget/scaled_buffer.h" #include "widget/widget.h" #define RESIZE_BORDER (13) enum { /* buttons */ SSD_BUTTON_MINIMIZE = 0, SSD_BUTTON_MAXIMIZE, SSD_BUTTON_CLOSE, /* titlebar */ SSD_TITLE_ICON, SSD_TITLE_TEXT, /* title_rect, border, extend */ SSD_FRAME_RECT, SSD_PART_COUNT, }; enum button_state { BUTTON_STATE_NONE = 0, BUTTON_STATE_HOVER, BUTTON_STATE_CLICKED, }; enum button_mask { BUTTON_MASK_NONE = 0, BUTTON_MASK_MINIMIZE = 1 << 0, BUTTON_MASK_MAXIMIZE = 1 << 1, BUTTON_MASK_CLOSE = 1 << 2, BUTTON_MASK_ALL = (1 << 3) - 1, }; enum ssd_update_cause { SSD_UPDATE_CAUSE_NONE = 0, SSD_UPDATE_CAUSE_SIZE = 1 << 0, SSD_UPDATE_CAUSE_MAXIMIZE = 1 << 1, SSD_UPDATE_CAUSE_TILE = 1 << 2, SSD_UPDATE_CAUSE_TITLE = 1 << 3, SSD_UPDATE_CAUSE_ACTIVATE = 1 << 4, SSD_UPDATE_CAUSE_FULLSCREEN = 1 << 5, SSD_UPDATE_CAUSE_CREATE = 1 << 6, SSD_UPDATE_CAUSE_ALL = (1 << 7) - 1, }; struct ssd_tooltip { struct wl_list link; struct ky_scene_tree *tree; struct ky_scene_decoration *deco; struct widget *minimize, *maximize, *restore, *close; struct wl_listener theme_update; struct seat *seat; struct wl_listener seat_destroy; struct ssd_part *hovered_part; struct wl_listener hovered_view_unmap; struct wl_event_source *timer; bool timer_triggered, timer_for_hidden; }; struct ssd_manager { /* enable or disable all ssds */ struct wl_list ssds; struct wl_list tooltips; struct wl_listener new_view; struct wl_listener server_destroy; }; struct ssd_part { int type; struct ssd *ssd; struct ky_scene_node *node; float scale; }; struct ssd { struct wl_list link; struct kywc_view *kywc_view; struct wl_listener view_map; struct wl_listener view_unmap; struct wl_listener view_destroy; struct wl_listener view_decoration; struct wl_listener view_activate; struct wl_listener view_size; struct wl_listener view_tile; struct wl_listener view_title; struct wl_listener view_maximize; struct wl_listener view_fullscreen; struct wl_listener view_capabilities; struct wl_listener view_icon_update; struct wl_listener theme_update; struct ky_scene_tree *tree; struct ky_scene_tree *button_tree; struct ky_scene_tree *titlebar_tree; struct ssd_part parts[SSD_PART_COUNT]; struct widget *title_text; bool created; /* view size to reduce redraw */ int view_width, view_height; uint32_t buttons; int button_count; }; static struct ssd_manager *manager = NULL; static const char *ssd_part_name[SSD_PART_COUNT] = { "button_minimize", "button_maximize", "button_close", "title_icon", "title_text", "frame_rect", }; /** * button tooltip support */ static struct ssd_tooltip *ssd_tooltip_create(struct seat *seat); static void ssd_check_buttons(struct ssd *ssd); static struct ssd_tooltip *ssd_tooltip_from_seat(struct seat *seat) { struct ssd_tooltip *tooltip; wl_list_for_each(tooltip, &manager->tooltips, link) { if (tooltip->seat == seat) { return tooltip; } } return ssd_tooltip_create(seat); } static void ssd_tooltip_show(struct seat *seat, struct ssd_part *part, bool enabled) { struct ssd_tooltip *tooltip = ssd_tooltip_from_seat(seat); if (!tooltip) { return; } struct theme *theme = theme_manager_get_theme(); struct widget *widget; switch (part->type) { case SSD_BUTTON_MINIMIZE: widget = tooltip->minimize; break; case SSD_BUTTON_MAXIMIZE: widget = part->ssd->kywc_view->maximized ? tooltip->restore : tooltip->maximize; break; case SSD_BUTTON_CLOSE: widget = tooltip->close; break; default: return; } if (!enabled) { wl_event_source_timer_update(tooltip->timer, 0); tooltip->timer_triggered = false; tooltip->timer_for_hidden = false; tooltip->hovered_part = NULL; wl_list_remove(&tooltip->hovered_view_unmap.link); wl_list_init(&tooltip->hovered_view_unmap.link); struct ky_scene_node *node = &tooltip->tree->node; if (node->enabled) { popup_add_fade_effect(node, FADE_OUT, true, false, widget_get_scale(widget)); ky_scene_node_set_enabled(node, false); } /* make sure restore and maximize widgets both are disabled */ if (part->type == SSD_BUTTON_MAXIMIZE) { widget_set_enabled(tooltip->restore, false); widget_update(tooltip->restore, true); widget_set_enabled(tooltip->maximize, false); widget_update(tooltip->maximize, true); } else { widget_set_enabled(widget, false); widget_update(widget, true); } return; } if (!tooltip->timer_triggered) { wl_event_source_timer_update(tooltip->timer, 500); tooltip->timer_for_hidden = false; tooltip->hovered_part = part; wl_list_remove(&tooltip->hovered_view_unmap.link); wl_signal_add(&part->ssd->kywc_view->events.unmap, &tooltip->hovered_view_unmap); return; } if (part->type == SSD_BUTTON_MAXIMIZE) { struct view *view = view_from_kywc_view(part->ssd->kywc_view); int x, y; // position in view ky_scene_node_coords(part->node, &x, &y); x -= part->ssd->kywc_view->geometry.x; y -= part->ssd->kywc_view->geometry.y; struct kywc_box box = { x, y, theme->button_width, theme->button_width }; if (view_show_tile_flyout(view, seat, &box)) { return; } } widget_set_enabled(widget, true); widget_update(widget, true); int x = seat->cursor->lx; int y = seat->cursor->ly + theme->icon_size; int w, h; widget_get_size(widget, &w, &h); struct output *output = input_current_output(seat); int max_x = output->geometry.x + output->geometry.width; int max_y = output->geometry.y + output->geometry.height; if (x + w > max_x) { x = max_x - w; } if (y + h > max_y) { y = seat->cursor->ly - h; } struct ky_scene_node *node = &tooltip->tree->node; ky_scene_decoration_set_surface_size(tooltip->deco, w, h); ky_scene_node_set_position(node, x, y); ky_scene_node_raise_to_top(node); ky_scene_node_set_enabled(node, true); popup_add_fade_effect(node, FADE_IN, true, false, widget_get_scale(widget)); tooltip->timer_for_hidden = true; wl_event_source_timer_update(tooltip->timer, 10000); } static void ssd_tooltip_draw_widget(struct widget *widget, const char *text) { struct theme *theme = theme_manager_get_theme(); int width = 0, height = 0; painter_get_text_size(text, theme->font_name, theme->font_size, &width, &height); widget_set_text(widget, text, TEXT_ALIGN_CENTER, TEXT_ATTR_NONE); widget_set_font(widget, theme->font_name, theme->font_size); widget_set_max_size(widget, width * 2, height * 2); widget_set_auto_resize(widget, AUTO_RESIZE_EXTEND); widget_set_front_color(widget, theme->active_text_color); widget_update(widget, true); } static void ssd_tooltip_draw_widgets(struct ssd_tooltip *tooltip) { struct theme *theme = theme_manager_get_theme(); ky_scene_decoration_set_shadow_count(tooltip->deco, theme->menu_shadow_color.num_layers); for (int i = 0; i < theme->menu_shadow_color.num_layers; i++) { struct theme_shadow_layer *shadow = &theme->menu_shadow_color.layers[i]; float shadow_color[4]; color_float_pa(shadow_color, shadow->color); ky_scene_decoration_set_shadow(tooltip->deco, i, shadow->off_x, shadow->off_y, shadow->spread, shadow->blur, shadow_color); } float border_color[4]; color_float_pa(border_color, theme->active_border_color); ky_scene_decoration_set_margin_color(tooltip->deco, (float[4]){ 0, 0, 0, 0 }, border_color); float bg_color[4]; color_float_pax(bg_color, theme->active_bg_color, theme->opacity / 100.0); ky_scene_decoration_set_surface_color(tooltip->deco, bg_color); int r = theme->menu_radius; ky_scene_decoration_set_round_corner_radius(tooltip->deco, (int[4]){ r, r, r, r }); ky_scene_decoration_set_surface_blurred(tooltip->deco, theme->opacity != 100); ssd_tooltip_draw_widget(tooltip->minimize, tr("Minimize")); ssd_tooltip_draw_widget(tooltip->maximize, tr("Maximize")); ssd_tooltip_draw_widget(tooltip->restore, tr("Restore")); ssd_tooltip_draw_widget(tooltip->close, tr("Close")); } static void ssd_tooltip_handle_theme_update(struct wl_listener *listener, void *data) { struct ssd_tooltip *tooltip = wl_container_of(listener, tooltip, theme_update); struct theme_update_event *update_event = data; uint32_t allowed_mask = THEME_UPDATE_MASK_FONT | THEME_UPDATE_MASK_BACKGROUND_COLOR | THEME_UPDATE_MASK_CORNER_RADIUS | THEME_UPDATE_MASK_OPACITY | THEME_UPDATE_MASK_SHADOW_COLOR | THEME_UPDATE_MASK_BORDER_COLOR; if (update_event->update_mask & allowed_mask) { ssd_tooltip_draw_widgets(tooltip); } } static int handle_tooltip(void *data) { struct ssd_tooltip *tooltip = data; tooltip->timer_triggered = true; ssd_tooltip_show(tooltip->seat, tooltip->hovered_part, !tooltip->timer_for_hidden); return 0; } static void ssd_tooltip_handle_seat_destroy(struct wl_listener *listener, void *data) { struct ssd_tooltip *tooltip = wl_container_of(listener, tooltip, seat_destroy); wl_list_remove(&tooltip->seat_destroy.link); wl_list_remove(&tooltip->theme_update.link); wl_list_remove(&tooltip->hovered_view_unmap.link); wl_list_remove(&tooltip->link); ky_scene_node_destroy(&tooltip->tree->node); wl_event_source_remove(tooltip->timer); free(tooltip); } static void ssd_tooltip_handle_hoverd_view_unmap(struct wl_listener *listener, void *data) { struct ssd_tooltip *tooltip = wl_container_of(listener, tooltip, hovered_view_unmap); wl_list_remove(&tooltip->hovered_view_unmap.link); wl_list_init(&tooltip->hovered_view_unmap.link); if (tooltip->hovered_part) { ssd_tooltip_show(tooltip->seat, tooltip->hovered_part, false); } } static struct ssd_tooltip *ssd_tooltip_create(struct seat *seat) { struct ssd_tooltip *tooltip = calloc(1, sizeof(*tooltip)); if (!tooltip) { return NULL; } tooltip->hovered_view_unmap.notify = ssd_tooltip_handle_hoverd_view_unmap; wl_list_init(&tooltip->hovered_view_unmap.link); tooltip->seat = seat; tooltip->seat_destroy.notify = ssd_tooltip_handle_seat_destroy; wl_signal_add(&seat->events.destroy, &tooltip->seat_destroy); wl_list_insert(&manager->tooltips, &tooltip->link); tooltip->theme_update.notify = ssd_tooltip_handle_theme_update; theme_manager_add_update_listener(&tooltip->theme_update, false); struct wl_event_loop *loop = wl_display_get_event_loop(seat->wlr_seat->display); tooltip->timer = wl_event_loop_add_timer(loop, handle_tooltip, tooltip); /* create widgets in popup layer */ struct view_layer *layer = view_manager_get_layer(LAYER_POPUP, false); tooltip->tree = ky_scene_tree_create(layer->tree); ky_scene_node_set_input_bypassed(&tooltip->tree->node, true); ky_scene_node_set_enabled(&tooltip->tree->node, false); tooltip->deco = ky_scene_decoration_create(tooltip->tree); ky_scene_decoration_set_margin(tooltip->deco, 0, 1); ky_scene_decoration_set_mask(tooltip->deco, DECORATION_MASK_ALL); ky_scene_node_set_position(ky_scene_node_from_decoration(tooltip->deco), -1, -1); tooltip->minimize = widget_create(tooltip->tree); tooltip->maximize = widget_create(tooltip->tree); tooltip->restore = widget_create(tooltip->tree); tooltip->close = widget_create(tooltip->tree); ssd_tooltip_draw_widgets(tooltip); return tooltip; } static uint32_t get_resize_type(struct ssd_part *part, double x, double y) { struct ky_scene_rect *frame = ky_scene_rect_from_node(part->node); struct theme *theme = theme_manager_get_theme(); int border = part->ssd->kywc_view->ssd & KYWC_SSD_BORDER ? theme->border_width : 0; int x1 = theme->window_radius + border; int x2 = frame->width - x1; int y2 = frame->height - x1; int sx = floor(x); int sy = floor(y); uint32_t resize_edges = KYWC_EDGE_NONE; if (sx <= x1) { if (sy <= x1) { resize_edges = KYWC_EDGE_TOP | KYWC_EDGE_LEFT; } else if (sy <= y2) { resize_edges = KYWC_EDGE_LEFT; } else { resize_edges = KYWC_EDGE_BOTTOM | KYWC_EDGE_LEFT; } } else if (sx >= x2) { if (sy <= x1) { resize_edges = KYWC_EDGE_TOP | KYWC_EDGE_RIGHT; } else if (sy < y2) { resize_edges = KYWC_EDGE_RIGHT; } else { resize_edges = KYWC_EDGE_BOTTOM | KYWC_EDGE_RIGHT; } } else if (sy >= y2) { resize_edges = KYWC_EDGE_BOTTOM; } else if (sy <= border) { resize_edges = KYWC_EDGE_TOP; } return resize_edges; } static void ssd_part_set_button_buffer(struct ssd_part *part, enum button_state state) { if (part->type > SSD_BUTTON_CLOSE || (part->ssd->buttons & (1 << part->type)) == 0) { return; } enum theme_button_type type = THEME_BUTTON_TYPE_MINIMIZE; if (part->type == SSD_BUTTON_MAXIMIZE) { type = part->ssd->kywc_view->maximized ? THEME_BUTTON_TYPE_RESTORE : THEME_BUTTON_TYPE_MAXIMIZE; } else if (part->type == SSD_BUTTON_CLOSE) { type = THEME_BUTTON_TYPE_CLOSE; } struct theme *theme = theme_manager_get_theme(); /* get actual type by current state */ type += state * 4; struct wlr_fbox src; struct wlr_buffer *buf = theme_button_buffer_load(theme, part->scale, type, &src, part->ssd->kywc_view->activated); struct ky_scene_buffer *buffer = ky_scene_buffer_from_node(part->node); if (buffer->buffer != buf) { ky_scene_buffer_set_buffer(buffer, buf); } /* shortcut here if set_buffer triggered scaled buffer update */ if (buffer->buffer != buf) { return; } ky_scene_buffer_set_dest_size(buffer, theme->button_width, theme->button_width); ky_scene_buffer_set_source_box(buffer, &src); } static bool ssd_hover(struct seat *seat, struct ky_scene_node *node, double x, double y, uint32_t time, bool first, bool hold, void *data) { if (seat_is_dragging(seat)) { return false; } struct ssd_part *part = data; /* we actually only need to process when first enter */ if ((!first && part->type != SSD_FRAME_RECT) || hold) { return false; } // kywc_log(KYWC_DEBUG, "ssd hover %s", ssd_part_name[part->type]); switch (part->type) { case SSD_BUTTON_MINIMIZE ... SSD_BUTTON_CLOSE: ssd_part_set_button_buffer(part, BUTTON_STATE_HOVER); ssd_tooltip_show(seat, part, true); cursor_set_image(seat->cursor, CURSOR_DEFAULT); break; case SSD_FRAME_RECT: if (!part->ssd->kywc_view->maximized && KYWC_VIEW_IS_RESIZABLE(part->ssd->kywc_view)) { uint32_t edges = get_resize_type(part, x, y); cursor_set_resize_image(seat->cursor, edges); view_show_tile_linkage_bar(view_from_kywc_view(part->ssd->kywc_view), edges); break; } // fallthrough default: cursor_set_image(seat->cursor, CURSOR_DEFAULT); break; } return false; } static void ssd_leave(struct seat *seat, struct ky_scene_node *node, bool last, void *data) { if (seat_is_dragging(seat)) { return; } struct ssd_part *part = data; // kywc_log(KYWC_ERROR, "ssd leave %s", ssd_part_name[part->type]); switch (part->type) { case SSD_BUTTON_MINIMIZE ... SSD_BUTTON_CLOSE: ssd_part_set_button_buffer(part, BUTTON_STATE_NONE); ssd_tooltip_show(seat, part, false); break; case SSD_FRAME_RECT: /* we may have changed cursor image when hover */ cursor_set_image(seat->cursor, CURSOR_DEFAULT); tile_linkage_resize_done(view_from_kywc_view(part->ssd->kywc_view), false); break; default: break; } } static void ssd_click(struct seat *seat, struct ky_scene_node *node, uint32_t button, bool pressed, uint32_t time, enum click_state state, void *data) { struct ssd_part *part = data; struct kywc_view *kywc_view = part->ssd->kywc_view; struct view *view = view_from_kywc_view(kywc_view); enum kywc_edges edges = KYWC_EDGE_NONE; if (part->type >= SSD_BUTTON_MINIMIZE && part->type <= SSD_BUTTON_CLOSE) { ssd_tooltip_show(seat, part, false); } /* active current view */ kywc_view_activate(kywc_view); view_set_focus(view, seat); if (CLICK_STATE_DOUBLE == state) { if (button != BTN_LEFT) { return; } switch (part->type) { case SSD_FRAME_RECT: edges = get_resize_type(part, seat->cursor->sx, seat->cursor->sy); if (edges != KYWC_EDGE_NONE) { break; } // fallthrough if click in title case SSD_TITLE_TEXT: if (KYWC_VIEW_IS_MAXIMIZABLE(kywc_view)) { kywc_view_toggle_maximized(kywc_view); } break; default: break; } return; } if (CLICK_STATE_FOCUS_LOST == state) { /* menu and ssd buttons do not effective */ return; } if (LEFT_BUTTON_PRESSED(button, pressed) && part->type <= SSD_BUTTON_CLOSE) { ssd_part_set_button_buffer(part, BUTTON_STATE_CLICKED); } switch (part->type) { case SSD_BUTTON_CLOSE: if (LEFT_BUTTON_RELEASED(button, pressed)) { kywc_view_close(kywc_view); } return; case SSD_BUTTON_MAXIMIZE: if (LEFT_BUTTON_RELEASED(button, pressed)) { kywc_view_toggle_maximized(kywc_view); break; } return; case SSD_BUTTON_MINIMIZE: if (LEFT_BUTTON_RELEASED(button, pressed)) { kywc_view_set_minimized(kywc_view, true); } return; case SSD_TITLE_ICON: if (LEFT_BUTTON_RELEASED(button, pressed) || RIGHT_BUTTON_RELEASED(button, pressed)) { view_show_window_menu(view, seat, seat->cursor->lx, seat->cursor->ly); } return; case SSD_FRAME_RECT: edges = get_resize_type(part, seat->cursor->sx, seat->cursor->sy); if (edges != KYWC_EDGE_NONE) { break; } // fallthrough if press in title case SSD_TITLE_TEXT: if (LEFT_BUTTON_PRESSED(button, pressed)) { window_begin_move(view, seat); } else if (RIGHT_BUTTON_PRESSED(button, pressed)) { /* show window menu, menu will grab seat to hide itself */ view_show_window_menu(view, seat, seat->cursor->lx, seat->cursor->ly); return; } break; default: break; } if (edges != KYWC_EDGE_NONE && pressed && button == BTN_LEFT) { window_begin_resize(view, edges, seat); } } static struct ky_scene_node *ssd_get_root(void *data) { struct ssd_part *part = data; return &part->ssd->tree->node; } static const struct input_event_node_impl ssd_impl = { .hover = ssd_hover, .leave = ssd_leave, .click = ssd_click, }; static void ssd_part_set_icon_buffer(struct ssd_part *part) { struct kywc_view *kywc_view = part->ssd->kywc_view; struct view *view = view_from_kywc_view(kywc_view); struct wlr_buffer *buf = view_get_icon_buffer(view, part->scale); if (!buf) { return; } struct ky_scene_buffer *buffer = ky_scene_buffer_from_node(part->node); if (buffer->buffer != buf) { ky_scene_buffer_set_buffer(buffer, buf); } if (buffer->buffer != buf) { return; } struct theme *theme = theme_manager_get_theme(); ky_scene_buffer_set_dest_size(buffer, theme->icon_size, theme->icon_size); } static void ssd_update_title_icon(struct ssd *ssd) { struct theme *theme = theme_manager_get_theme(); struct view *view = view_from_kywc_view(ssd->kywc_view); int title_height = view->parent ? theme->subtitle_height : theme->title_height; int y = theme->border_width + (title_height - theme->icon_size) / 2; if (theme->layout_is_right_to_left) { int view_w = ssd->kywc_view->geometry.width + 2 * theme->border_width; ky_scene_node_set_position(ssd->parts[SSD_TITLE_ICON].node, view_w - y - theme->button_width, y); } else { ky_scene_node_set_position(ssd->parts[SSD_TITLE_ICON].node, y, y); } } static void ssd_update_title_text(struct ssd *ssd, uint32_t cause) { struct theme *theme = theme_manager_get_theme(); struct kywc_view *view = ssd->kywc_view; struct view *tmp = view_from_kywc_view(view); int title_height = tmp->parent ? theme->subtitle_height : theme->title_height; int max_width = view->geometry.width - (ssd->button_count + 1.5) * theme->button_width; /* no space left for title text */ if (max_width <= 0) { widget_set_enabled(ssd->title_text, false); widget_update(ssd->title_text, true); return; } /* redraw title buffer */ if (cause & SSD_UPDATE_CAUSE_TITLE) { widget_set_text(ssd->title_text, view->title, TEXT_ALIGN_LEFT, TEXT_ATTR_NONE); widget_set_font(ssd->title_text, theme->font_name, theme->font_size); } if (cause & SSD_UPDATE_CAUSE_SIZE) { widget_set_max_size(ssd->title_text, max_width, title_height); widget_set_auto_resize(ssd->title_text, AUTO_RESIZE_ONLY); } if (cause & SSD_UPDATE_CAUSE_ACTIVATE) { widget_set_front_color(ssd->title_text, view->activated ? theme->active_text_color : theme->inactive_text_color); } widget_set_enabled(ssd->title_text, true); widget_update(ssd->title_text, true); /* skip setting position if activate changed only */ if (cause == SSD_UPDATE_CAUSE_ACTIVATE) { return; } /* get actual size when auto-sized */ int text_width, text_height; widget_get_size(ssd->title_text, &text_width, &text_height); /* calc the text position by jystify */ int x, y; y = theme->border_width + (title_height - text_height) / 2; if (theme->text_justify == JUSTIFY_LEFT) { x = theme->layout_is_right_to_left ? ssd->button_count * theme->button_width : theme->button_width; x += y; } else if (theme->text_justify == JUSTIFY_CENTER) { x = (view->geometry.width - text_width) / 2; /* add a left shift if close to button */ if (text_width + (ssd->button_count + 1) * theme->button_width > max_width) { x -= theme->button_width; } } else { x = theme->layout_is_right_to_left ? ssd->button_count * theme->button_width : theme->button_width; x += max_width - text_width + y; } /* setting position directly is better */ ky_scene_node_set_position(ssd->parts[SSD_TITLE_TEXT].node, x, y); } static void ssd_update_titlebar(struct ssd *ssd, uint32_t cause) { struct theme *theme = theme_manager_get_theme(); struct view *view = view_from_kywc_view(ssd->kywc_view); int border_w = theme->border_width; int button_w = theme->button_width; int title_h = view->parent ? theme->subtitle_height : theme->title_height; int view_w = ssd->kywc_view->geometry.width; /* set titlebar subtree position if theme changed */ if (cause & SSD_UPDATE_CAUSE_CREATE) { ky_scene_node_set_position(&ssd->titlebar_tree->node, -border_w, -(title_h + border_w)); } /* set button tree position when view w or title height changed */ if (cause & SSD_UPDATE_CAUSE_SIZE) { int pad = (title_h - button_w) / 2; int x = theme->layout_is_right_to_left ? pad : (view_w + border_w - 3 * button_w - pad); int y = pad + border_w; ky_scene_node_set_position(&ssd->button_tree->node, x, y); ssd_update_title_icon(ssd); } if (cause & SSD_UPDATE_CAUSE_CREATE) { ky_scene_node_set_enabled(ssd->parts[SSD_BUTTON_MINIMIZE].node, ssd->buttons & BUTTON_MASK_MINIMIZE); ky_scene_node_set_enabled(ssd->parts[SSD_BUTTON_MAXIMIZE].node, ssd->buttons & BUTTON_MASK_MAXIMIZE); ky_scene_node_set_enabled(ssd->parts[SSD_BUTTON_CLOSE].node, ssd->buttons & BUTTON_MASK_CLOSE); ky_scene_node_set_position(ssd->parts[SSD_BUTTON_MAXIMIZE].node, button_w, 0); if (theme->layout_is_right_to_left) { ky_scene_node_set_position( ssd->parts[SSD_BUTTON_MINIMIZE].node, ssd->buttons & BUTTON_MASK_MAXIMIZE ? 2 * button_w : button_w, 0); ky_scene_node_set_position(ssd->parts[SSD_BUTTON_CLOSE].node, 0, 0); } else { ky_scene_node_set_position(ssd->parts[SSD_BUTTON_MINIMIZE].node, ssd->buttons & BUTTON_MASK_MAXIMIZE ? 0 : button_w, 0); ky_scene_node_set_position(ssd->parts[SSD_BUTTON_CLOSE].node, 2 * button_w, 0); } ssd_part_set_button_buffer(&ssd->parts[SSD_BUTTON_MINIMIZE], BUTTON_STATE_NONE); ssd_part_set_button_buffer(&ssd->parts[SSD_BUTTON_CLOSE], BUTTON_STATE_NONE); } if (cause & (SSD_UPDATE_CAUSE_TITLE | SSD_UPDATE_CAUSE_ACTIVATE | SSD_UPDATE_CAUSE_SIZE)) { /* no need to redraw when resize height only */ if (!(cause == SSD_UPDATE_CAUSE_SIZE && ssd->view_width == view_w)) { ssd_update_title_text(ssd, cause); } } if (cause & SSD_UPDATE_CAUSE_ACTIVATE) { ssd_part_set_button_buffer(&ssd->parts[SSD_BUTTON_MINIMIZE], BUTTON_STATE_NONE); ssd_part_set_button_buffer(&ssd->parts[SSD_BUTTON_MAXIMIZE], BUTTON_STATE_NONE); ssd_part_set_button_buffer(&ssd->parts[SSD_BUTTON_CLOSE], BUTTON_STATE_NONE); } else if (cause & SSD_UPDATE_CAUSE_MAXIMIZE) { /* set maximize and restore */ ssd_part_set_button_buffer(&ssd->parts[SSD_BUTTON_MAXIMIZE], BUTTON_STATE_NONE); } } static void ssd_update_frame(struct ssd *ssd, uint32_t cause) { struct theme *theme = theme_manager_get_theme(); struct kywc_view *view = ssd->kywc_view; struct ky_scene_decoration *frame = ky_scene_decoration_from_node(ssd->parts[SSD_FRAME_RECT].node); if (cause & SSD_UPDATE_CAUSE_ACTIVATE) { float border_color[4]; color_float_pa(border_color, view->activated ? theme->active_border_color : theme->inactive_border_color); float bg_color[4]; color_float_pa(bg_color, view->activated ? theme->active_bg_color : theme->inactive_bg_color); ky_scene_decoration_set_margin_color(frame, bg_color, border_color); struct theme_shadow *shadow; if (view->modal) { shadow = view->activated ? &theme->modal_active_shadow_color : &theme->modal_inactive_shadow_color; } else { shadow = view->activated ? &theme->active_shadow_color : &theme->inactive_shadow_color; } ky_scene_decoration_set_shadow_count(frame, shadow->num_layers); for (int i = 0; i < shadow->num_layers; i++) { struct theme_shadow_layer *shadow_layer = &shadow->layers[i]; float shadow_color[4]; color_float_pa(shadow_color, shadow_layer->color); ky_scene_decoration_set_shadow(frame, i, shadow_layer->off_x, shadow_layer->off_y, shadow_layer->spread, shadow_layer->blur, shadow_color); } } if (cause & (SSD_UPDATE_CAUSE_TILE | SSD_UPDATE_CAUSE_MAXIMIZE)) { uint32_t deco_mask = DECORATION_MASK_ALL; if (view->maximized) { deco_mask = DECORATION_MASK_NONE; } else if (view->tiled == KYWC_TILE_TOP) { deco_mask = DECORATION_MASK_BOTTOM; } else if (view->tiled == KYWC_TILE_BOTTOM) { deco_mask = DECORATION_MASK_TOP; } else if (view->tiled == KYWC_TILE_LEFT) { deco_mask = DECORATION_MASK_RIGHT; } else if (view->tiled == KYWC_TILE_RIGHT) { deco_mask = DECORATION_MASK_LEFT; } else if (view->tiled == KYWC_TILE_TOP_LEFT) { deco_mask = DECORATION_MASK_RIGHT | DECORATION_MASK_BOTTOM; } else if (view->tiled == KYWC_TILE_BOTTOM_LEFT) { deco_mask = DECORATION_MASK_RIGHT | DECORATION_MASK_TOP; } else if (view->tiled == KYWC_TILE_TOP_RIGHT) { deco_mask = DECORATION_MASK_LEFT | DECORATION_MASK_BOTTOM; } else if (view->tiled == KYWC_TILE_BOTTOM_RIGHT) { deco_mask = DECORATION_MASK_LEFT | DECORATION_MASK_TOP; } ky_scene_decoration_set_mask(frame, deco_mask); } if (cause & SSD_UPDATE_CAUSE_SIZE) { ky_scene_decoration_set_surface_size(frame, view->geometry.width, view->geometry.height); } if (cause & SSD_UPDATE_CAUSE_CREATE) { struct view *tmp = view_from_kywc_view(view); int border = view->ssd & KYWC_SSD_BORDER ? theme->border_width : 0; int title = view->ssd & KYWC_SSD_TITLE ? tmp->parent ? theme->subtitle_height : theme->title_height : 0; int resize = view->ssd & KYWC_SSD_RESIZE ? RESIZE_BORDER : 0; int bottom = view->has_round_corner ? theme->window_radius : 0; int top = (view->ssd & KYWC_SSD_TITLE || view->has_round_corner) ? theme->window_radius : 0; ky_scene_decoration_set_resize_width(frame, resize); ky_scene_decoration_set_margin(frame, title, border); ky_scene_decoration_set_round_corner_radius(frame, (int[4]){ bottom, top, bottom, top }); ky_scene_node_set_position(ssd->parts[SSD_FRAME_RECT].node, -border, -border - title); } } static void ssd_update_margin(struct ssd *ssd) { struct kywc_view *view = ssd->kywc_view; struct theme *theme = theme_manager_get_theme(); int title = 0; if (view->ssd & KYWC_SSD_TITLE) { if (view_from_kywc_view(view)->parent) { title = theme->subtitle_height; } else { title = theme->title_height; } } view->margin.off_x = 0; view->margin.off_y = title; view->margin.off_width = 0; view->margin.off_height = title; } static void ssd_update_padding(struct ssd *ssd) { struct kywc_view *view = ssd->kywc_view; if (view->ssd == KYWC_SSD_NONE) { view->padding.top = view->padding.bottom = 0; view->padding.left = view->padding.right = 0; return; } struct theme *theme = theme_manager_get_theme(); struct theme_shadow *shadow; if (view->modal) { shadow = view->activated ? &theme->modal_active_shadow_color : &theme->modal_inactive_shadow_color; } else { shadow = view->activated ? &theme->active_shadow_color : &theme->inactive_shadow_color; } int border = view->ssd & KYWC_SSD_BORDER ? theme->border_width : 0; view->padding.top = border; view->padding.bottom = border; view->padding.left = border; view->padding.right = border; for (int i = 0; i < shadow->num_layers; i++) { struct theme_shadow_layer *shadow_layer = &shadow->layers[i]; int extend = border + shadow_layer->blur; view->padding.top = MAX(view->padding.top, extend - shadow_layer->off_y); view->padding.bottom = MAX(view->padding.bottom, extend + shadow_layer->off_y); view->padding.left = MAX(view->padding.left, extend - shadow_layer->off_x); view->padding.right = MAX(view->padding.right, extend + shadow_layer->off_x); } } static void ssd_update_parts(struct ssd *ssd, uint32_t cause) { assert(ssd->created && ssd->kywc_view->ssd != KYWC_SSD_NONE); if (cause & SSD_UPDATE_CAUSE_FULLSCREEN) { bool enabled = !ssd->kywc_view->fullscreen; ky_scene_node_set_enabled(&ssd->tree->node, enabled); } if (ssd->kywc_view->ssd & KYWC_SSD_TITLE) { ssd_update_titlebar(ssd, cause); } ssd_update_frame(ssd, cause); } static void ssd_update_buffer(struct ky_scene_buffer *buffer, float scale, void *data) { struct ssd_part *part = data; part->scale = scale; /* update scene_buffer with new buffer */ switch (part->type) { case SSD_BUTTON_MINIMIZE ... SSD_BUTTON_CLOSE: ssd_part_set_button_buffer(part, BUTTON_STATE_NONE); break; case SSD_TITLE_ICON: ssd_part_set_icon_buffer(part); break; } kywc_log(KYWC_DEBUG, "%s redraw in %f", ssd_part_name[part->type], scale); } static void ssd_destroy_buffer(struct ky_scene_buffer *buffer, void *data) { struct ssd_part *part = data; kywc_log(KYWC_DEBUG, "%s node destroy", ssd_part_name[part->type]); /* buffers are destroyed in theme */ } static void ssd_create_parts(struct ssd *ssd, float scale) { int start = ssd->kywc_view->ssd & KYWC_SSD_TITLE ? 0 : SSD_FRAME_RECT; /* create buffers from bottom to top */ for (int i = SSD_PART_COUNT - 1; i >= start; i--) { ssd->parts[i].type = i; ssd->parts[i].ssd = ssd; struct ky_scene_tree *parent; if (i <= SSD_BUTTON_CLOSE) { parent = ssd->button_tree; } else if (i <= SSD_TITLE_TEXT) { parent = ssd->titlebar_tree; } else { parent = ssd->tree; } if (i < SSD_FRAME_RECT) { if (i == SSD_TITLE_TEXT) { ssd->title_text = widget_create(parent); ssd->parts[i].node = ky_scene_node_from_widget(ssd->title_text); } else { struct ky_scene_buffer *buf = scaled_buffer_create( parent, scale, ssd_update_buffer, ssd_destroy_buffer, &ssd->parts[i]); ssd->parts[i].node = &buf->node; ssd->parts[i].scale = scale; /** * set_buffer will emit output_enter, * otherwise we cannot get initial output the view in. */ if (i == SSD_TITLE_ICON) { ssd_part_set_icon_buffer(&ssd->parts[i]); } else { ssd_part_set_button_buffer(&ssd->parts[i], BUTTON_STATE_NONE); } } } else { struct ky_scene_decoration *frame = ky_scene_decoration_create(parent); ssd->parts[i].node = ky_scene_node_from_decoration(frame); ky_scene_node_lower_to_bottom(ssd->parts[i].node); } input_event_node_create(ssd->parts[i].node, &ssd_impl, ssd_get_root, NULL, &ssd->parts[i]); } } static void handle_theme_update(struct wl_listener *listener, void *data) { struct ssd *ssd = wl_container_of(listener, ssd, theme_update); struct theme_update_event *update_event = data; uint32_t allowed_mask = THEME_UPDATE_MASK_FONT | THEME_UPDATE_MASK_BACKGROUND_COLOR | THEME_UPDATE_MASK_BORDER_COLOR | THEME_UPDATE_MASK_CORNER_RADIUS | THEME_UPDATE_MASK_SHADOW_COLOR | THEME_UPDATE_MASK_DECORATION_SIZE; if (update_event->update_mask & allowed_mask) { ssd_update_margin(ssd); ssd_update_padding(ssd); ssd_check_buttons(ssd); ssd_update_parts(ssd, SSD_UPDATE_CAUSE_ALL); } } static void handle_view_icon_update(struct wl_listener *listener, void *data) { struct ssd *ssd = wl_container_of(listener, ssd, view_icon_update); ssd_part_set_icon_buffer(&ssd->parts[SSD_TITLE_ICON]); } static void handle_view_activate(struct wl_listener *listener, void *data) { struct ssd *ssd = wl_container_of(listener, ssd, view_activate); ssd_update_padding(ssd); ssd_update_parts(ssd, SSD_UPDATE_CAUSE_ACTIVATE); } static void handle_view_size(struct wl_listener *listener, void *data) { struct ssd *ssd = wl_container_of(listener, ssd, view_size); ssd_update_parts(ssd, SSD_UPDATE_CAUSE_SIZE); ssd->view_width = ssd->kywc_view->geometry.width; ssd->view_height = ssd->kywc_view->geometry.height; } static void handle_view_tile(struct wl_listener *listener, void *data) { struct ssd *ssd = wl_container_of(listener, ssd, view_tile); ssd_update_parts(ssd, SSD_UPDATE_CAUSE_TILE); } static void handle_view_title(struct wl_listener *listener, void *data) { struct ssd *ssd = wl_container_of(listener, ssd, view_title); ssd_update_parts(ssd, SSD_UPDATE_CAUSE_TITLE); } static void handle_view_maximize(struct wl_listener *listener, void *data) { struct ssd *ssd = wl_container_of(listener, ssd, view_maximize); ssd_update_parts(ssd, SSD_UPDATE_CAUSE_MAXIMIZE); } static void handle_view_fullscreen(struct wl_listener *listener, void *data) { struct ssd *ssd = wl_container_of(listener, ssd, view_fullscreen); ssd_update_parts(ssd, SSD_UPDATE_CAUSE_FULLSCREEN); } static void handle_view_capabilities(struct wl_listener *listener, void *data) { struct kywc_view_capabilities_event *event = data; if ((event->mask & (KYWC_VIEW_MAXIMIZE_BUTTON | KYWC_VIEW_MINIMIZE_BUTTON)) == 0) { return; } struct ssd *ssd = wl_container_of(listener, ssd, view_capabilities); uint32_t button_mask = ssd->buttons; ssd_check_buttons(ssd); if (button_mask != ssd->buttons) { ssd_update_parts(ssd, SSD_UPDATE_CAUSE_CREATE); } } static void ssd_check_buttons(struct ssd *ssd) { struct kywc_view *kywc_view = ssd->kywc_view; /* always has a close button */ ssd->buttons = BUTTON_MASK_ALL; ssd->button_count = 3; if (!KYWC_VIEW_NEED_MAXIMIZE_BUTTON(kywc_view)) { ssd->buttons &= ~BUTTON_MASK_MAXIMIZE; ssd->button_count--; } if (!KYWC_VIEW_NEED_MINIMIZE_BUTTON(kywc_view)) { ssd->buttons &= ~BUTTON_MASK_MINIMIZE; ssd->button_count--; } } static void ssd_parts_create(struct ssd *ssd) { if (ssd->created) { return; } ssd->created = true; ssd->view_width = ssd->view_height = 0; struct kywc_view *kywc_view = ssd->kywc_view; struct view *view = view_from_kywc_view(kywc_view); ssd->tree = ky_scene_tree_create(view->tree); ky_scene_node_lower_to_bottom(&ssd->tree->node); ssd->view_size.notify = handle_view_size; wl_signal_add(&kywc_view->events.size, &ssd->view_size); ssd->view_tile.notify = handle_view_tile; wl_signal_add(&kywc_view->events.tile, &ssd->view_tile); ssd->view_maximize.notify = handle_view_maximize; wl_signal_add(&kywc_view->events.maximize, &ssd->view_maximize); ssd->view_fullscreen.notify = handle_view_fullscreen; wl_signal_add(&kywc_view->events.fullscreen, &ssd->view_fullscreen); ssd->view_capabilities.notify = handle_view_capabilities; wl_signal_add(&kywc_view->events.capabilities, &ssd->view_capabilities); wl_list_init(&ssd->view_activate.link); wl_list_init(&ssd->view_title.link); wl_list_init(&ssd->view_icon_update.link); wl_list_init(&ssd->theme_update.link); if (kywc_view->ssd & KYWC_SSD_TITLE) { ssd->titlebar_tree = ky_scene_tree_create(ssd->tree); /* buttons is subtree of titlebar, only need to set tree pos */ ssd->button_tree = ky_scene_tree_create(ssd->titlebar_tree); ssd->view_title.notify = handle_view_title; wl_signal_add(&kywc_view->events.title, &ssd->view_title); ssd->view_icon_update.notify = handle_view_icon_update; wl_signal_add(&view->events.icon_update, &ssd->view_icon_update); ssd_check_buttons(ssd); } if (kywc_view->ssd & (KYWC_SSD_TITLE | KYWC_SSD_BORDER)) { ssd->view_activate.notify = handle_view_activate; wl_signal_add(&kywc_view->events.activate, &ssd->view_activate); ssd->theme_update.notify = handle_theme_update; theme_manager_add_update_listener(&ssd->theme_update, false); } /** * detect scale by view geometry. * it doesn't matter if setting to 1.0, scale will be set to best value * in output_enter listener. */ ssd_create_parts(ssd, view->output->state.scale); ssd_update_parts(ssd, SSD_UPDATE_CAUSE_ALL); ssd->view_width = kywc_view->geometry.width; ssd->view_height = kywc_view->geometry.height; } static void ssd_parts_destroy(struct ssd *ssd) { if (!ssd->created) { return; } ssd->created = false; wl_list_remove(&ssd->view_activate.link); wl_list_remove(&ssd->view_size.link); wl_list_remove(&ssd->view_tile.link); wl_list_remove(&ssd->view_title.link); wl_list_remove(&ssd->view_maximize.link); wl_list_remove(&ssd->view_fullscreen.link); wl_list_remove(&ssd->view_capabilities.link); wl_list_remove(&ssd->theme_update.link); wl_list_remove(&ssd->view_icon_update.link); // XXX: destroyed in view_destroy, check ssd->tree ? ky_scene_node_destroy(&ssd->tree->node); } static void handle_view_decoration(struct wl_listener *listener, void *data) { struct ssd *ssd = wl_container_of(listener, ssd, view_decoration); ssd_update_margin(ssd); ssd_update_padding(ssd); /* view may not be mapped */ if (!ssd->kywc_view->mapped) { return; } /* destroy first, may switched between extend_only and all */ ssd_parts_destroy(ssd); if (ssd->kywc_view->ssd != KYWC_SSD_NONE) { ssd_parts_create(ssd); } } static void handle_view_map(struct wl_listener *listener, void *data) { struct ssd *ssd = wl_container_of(listener, ssd, view_map); /* skip if not need ssd */ if (ssd->kywc_view->ssd == KYWC_SSD_NONE) { return; } ssd_update_padding(ssd); ssd_parts_create(ssd); } static void handle_view_unmap(struct wl_listener *listener, void *data) { struct ssd *ssd = wl_container_of(listener, ssd, view_unmap); ssd_parts_destroy(ssd); } static void handle_view_destroy(struct wl_listener *listener, void *data) { struct ssd *ssd = wl_container_of(listener, ssd, view_destroy); wl_list_remove(&ssd->view_destroy.link); wl_list_remove(&ssd->view_decoration.link); wl_list_remove(&ssd->view_map.link); wl_list_remove(&ssd->view_unmap.link); wl_list_remove(&ssd->link); free(ssd); } static void handle_new_view(struct wl_listener *listener, void *data) { struct kywc_view *kywc_view = data; struct ssd *ssd = calloc(1, sizeof(*ssd)); if (!ssd) { return; } ssd->kywc_view = kywc_view; wl_list_insert(&manager->ssds, &ssd->link); ssd->view_decoration.notify = handle_view_decoration; wl_signal_add(&kywc_view->events.decoration, &ssd->view_decoration); ssd->view_map.notify = handle_view_map; wl_signal_add(&kywc_view->events.map, &ssd->view_map); ssd->view_unmap.notify = handle_view_unmap; wl_signal_add(&kywc_view->events.unmap, &ssd->view_unmap); ssd->view_destroy.notify = handle_view_destroy; wl_signal_add(&kywc_view->events.destroy, &ssd->view_destroy); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->server_destroy.link); wl_list_remove(&manager->new_view.link); free(manager); } bool server_decoration_manager_create(struct view_manager *view_manager) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } wl_list_init(&manager->ssds); wl_list_init(&manager->tooltips); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(view_manager->server, &manager->server_destroy); manager->new_view.notify = handle_new_view; kywc_view_add_new_listener(&manager->new_view); return true; } kylin-wayland-compositor/src/view/ukui_startup.c0000664000175000017500000001734715160461067021145 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include "ukui-startup-v2-protocol.h" #include "util/time.h" #include "view_p.h" #define UKUI_STARTUP_V2_VERSION 1 #define DEFAULT_RETIRE_TIME 5000 struct ukui_startup_management { struct wl_global *global; struct wl_list ukui_startup_infos; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct ukui_startup_info { struct wl_list link; struct wl_resource *resource; const char *app_id; pid_t pid; struct kywc_box box; uint32_t time_msec; }; static struct ukui_startup_management *management = NULL; static pid_t get_parent_pid(pid_t pid) { int ppid; char path[256]; char line[256]; snprintf(path, sizeof(path), "/proc/%d/status", pid); FILE *file = fopen(path, "r"); if (!file) { kywc_log(KYWC_ERROR, "Failed to open /proc/[pid]/status"); return -1; } while (fgets(line, sizeof(line), file)) { if (strncmp(line, "PPid:", 5) == 0) { sscanf(line, "PPid: %d", &ppid); fclose(file); return ppid; } } fclose(file); return -1; } static void ukui_startup_info_destroy(struct ukui_startup_info *info) { /* resource may be destroyed in destroy request */ if (info->resource) { wl_resource_set_user_data(info->resource, NULL); } wl_list_remove(&info->link); free((void *)info->app_id); free(info); } static void ukui_get_startup_geometry_recursive(pid_t pid, struct view *view) { struct ukui_startup_info *info, *tmp; wl_list_for_each_reverse_safe(info, tmp, &management->ukui_startup_infos, link) { if (info->pid == pid) { view->startup_geometry = info->box; ukui_startup_info_destroy(info); return; } } pid_t parent_pid = get_parent_pid(pid); /* init/systemd (PID=1) */ if (parent_pid > 1) { ukui_get_startup_geometry_recursive(parent_pid, view); return; } if (!view->base.app_id) { return; } wl_list_for_each_safe(info, tmp, &management->ukui_startup_infos, link) { if (info->app_id && strcasecmp(info->app_id, view->base.app_id) == 0) { kywc_log(KYWC_DEBUG, "Get startup_geometry from app_id %s", info->app_id); view->startup_geometry = info->box; ukui_startup_info_destroy(info); return; } } kywc_log(KYWC_INFO, "Failed to get startup_geometry for PID: %d, app_id: %s", pid, view->base.app_id); } static void ukui_get_startup_geometry(struct view *view, void *data) { if (wl_list_empty(&management->ukui_startup_infos)) { return; } /* remove retired info */ uint32_t time_now = current_time_msec(); struct ukui_startup_info *info, *tmp; wl_list_for_each_safe(info, tmp, &management->ukui_startup_infos, link) { if (time_now - info->time_msec > DEFAULT_RETIRE_TIME) { ukui_startup_info_destroy(info); } } if (wl_list_empty(&management->ukui_startup_infos)) { return; } ukui_get_startup_geometry_recursive(view->pid, view); } static void handle_set_startup_geometry(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, uint32_t width, uint32_t height) { struct ukui_startup_info *info = wl_resource_get_user_data(resource); if (!info) { return; } info->box = (struct kywc_box){ x, y, width, height }; } static void handle_set_pid(struct wl_client *client, struct wl_resource *resource, uint32_t pid) { struct ukui_startup_info *info = wl_resource_get_user_data(resource); if (!info) { return; } info->pid = pid; } static void handle_set_appid(struct wl_client *client, struct wl_resource *resource, const char *app_id) { struct ukui_startup_info *info = wl_resource_get_user_data(resource); if (!info) { return; } free((void *)info->app_id); info->app_id = strdup(app_id); } static void handle_info_destroy(struct wl_client *client, struct wl_resource *resource) { struct ukui_startup_info *info = wl_resource_get_user_data(resource); if (info) { info->resource = NULL; } wl_resource_destroy(resource); } static const struct ukui_startup_info_v2_interface ukui_startup_info_v2_ipml = { .set_startup_geometry = handle_set_startup_geometry, .set_pid = handle_set_pid, .set_appid = handle_set_appid, .destroy = handle_info_destroy, }; static void handle_create_startup_info(struct wl_client *client, struct wl_resource *resource, uint32_t id) { struct ukui_startup_info *info = calloc(1, sizeof(*info)); if (!info) { wl_client_post_no_memory(client); return; } int version = wl_resource_get_version(resource); struct wl_resource *info_resource = wl_resource_create(client, &ukui_startup_info_v2_interface, version, id); if (!resource) { free(info); wl_client_post_no_memory(client); return; } info->resource = info_resource; info->time_msec = current_time_msec(); wl_resource_set_implementation(info_resource, &ukui_startup_info_v2_ipml, info, NULL); wl_list_insert(&management->ukui_startup_infos, &info->link); } static void handle_management_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct ukui_startup_management_v2_interface ukui_startup_management_v2_ipml = { .destroy = handle_management_destroy, .create_startup_info = handle_create_startup_info, }; static void ukui_startup_management_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &ukui_startup_management_v2_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &ukui_startup_management_v2_ipml, management, NULL); } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&management->display_destroy.link); struct ukui_startup_info *info, *tmp; wl_list_for_each_safe(info, tmp, &management->ukui_startup_infos, link) { ukui_startup_info_destroy(info); } wl_global_destroy(management->global); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&management->server_destroy.link); free(management); management = NULL; } bool ukui_startup_management_create(struct view_manager *view_manager) { struct server *server = view_manager->server; management = calloc(1, sizeof(*management)); if (!management) { return false; } management->global = wl_global_create(server->display, &ukui_startup_management_v2_interface, UKUI_STARTUP_V2_VERSION, management, ukui_startup_management_bind); if (!management->global) { kywc_log(KYWC_WARN, "UKUI startup management create failed"); free(management); management = NULL; return false; } wl_list_init(&management->ukui_startup_infos); view_manager->impl.get_startup_geometry = ukui_get_startup_geometry; management->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &management->server_destroy); management->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &management->display_destroy); return true; } kylin-wayland-compositor/src/view/tablet_mode.c0000664000175000017500000005534315160461067020663 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include "effect/fade.h" #include "output.h" #include "theme.h" #include "util/macros.h" #include "view/workspace.h" #include "view_p.h" /* 0.15 is trigger window switcher threshold */ #define TRIGGER_SWITCHER_THRESHOLD (0.15) struct tablet_mode_view { struct wl_list link; struct view *view; struct wl_listener parent; struct wl_listener view_update_capabilities; bool remove_workspace; struct ky_scene_rect *blur_rect; }; struct task_bar { struct view *view; int default_y; struct wl_listener output_geometry; }; static struct tablet_mode_manager { struct wl_list tablet_views; struct task_bar task_bar; struct view *pc_desktop; struct view *tablet_desktop; struct ky_scene_rect *global_blur_rect; struct view_manager *view_manager; struct wl_listener theme_pre_update; } *manager = NULL; /* chase kwin, some special app require special treatment */ #define TASK_BAR "tablet-taskbar" #define PC_DESKTOP "peony-qt-desktop" #define TABLET_DESKTOP "ukui-tablet-desktop" static const char *special_appid[] = { "ukui-bluetooth", "kylin-nm", "ukui-search" }; static const char *special_title[] = { TASK_BAR }; static struct gesture { enum gesture_type type; enum gesture_stage stage; uint8_t fingers; uint32_t devices; uint32_t directions; uint32_t edges; uint32_t follow_direction; double follow_threshold; char *desc; struct gesture_binding *binding; } gestures[] = { { GESTURE_TYPE_SWIPE, GESTURE_STAGE_BEFORE, 1, GESTURE_DEVICE_TOUCHSCREEN, GESTURE_DIRECTION_UP, GESTURE_EDGE_BOTTOM, GESTURE_DIRECTION_UP, 0.0, "follow finger up", NULL, }, { GESTURE_TYPE_SWIPE, GESTURE_STAGE_BEFORE, 1, GESTURE_DEVICE_TOUCHSCREEN, GESTURE_DIRECTION_UP, GESTURE_EDGE_BOTTOM, GESTURE_DIRECTION_DOWN, 0.0, "follow finger down", NULL, }, { GESTURE_TYPE_SWIPE, GESTURE_STAGE_STOP, 1, GESTURE_DEVICE_TOUCHSCREEN, GESTURE_DIRECTION_NONE, GESTURE_EDGE_BOTTOM, GESTURE_DIRECTION_NONE, 0.0, "follow finger stop", NULL, }, }; static void tablet_mode_view_maximized(struct view *view, bool maximized, struct kywc_output *kywc_output); static void tablet_mode_view_minimized(struct view *view, bool minimized); static struct tablet_mode_view *tablet_mode_view_from_view(struct view *view) { return (struct tablet_mode_view *)view->mode_view; } static bool view_is_special(struct view *view) { for (uint32_t i = 0; i < ARRAY_SIZE(special_appid); i++) { if (strcmp(view->base.app_id, special_appid[i]) == 0) { return true; } } for (uint32_t i = 0; i < ARRAY_SIZE(special_title); i++) { if (strcmp(view->base.title, special_title[i]) == 0) { return true; } } return false; } static void task_bar_update_coord(void) { struct view *view = manager->task_bar.view; // default y is %4 distance from bottom struct output *output = output_from_kywc_output(view->output); manager->task_bar.default_y = output->geometry.height - output->geometry.height * 0.04 - view->base.geometry.height - view->base.margin.off_height; } static void handle_output_geometry(struct wl_listener *listener, void *data) { task_bar_update_coord(); } static void tablet_mode_set_task_bar(struct view *view) { if (!view) { manager->task_bar.view = NULL; manager->task_bar.default_y = 0; wl_list_remove(&manager->task_bar.output_geometry.link); wl_list_init(&manager->task_bar.output_geometry.link); return; } manager->task_bar.view = view; task_bar_update_coord(); struct output *output = output_from_kywc_output(view->output); wl_signal_add(&output->events.geometry, &manager->task_bar.output_geometry); } static void tablet_mode_task_bar_show(bool show) { struct view *view = manager->task_bar.view; if (!view) { return; } ky_scene_node_set_enabled(&view->tree->node, show); } static void handle_view_parent(struct wl_listener *listener, void *data) { struct tablet_mode_view *tablet_view = wl_container_of(listener, tablet_view, parent); if (!tablet_view->view->base.mapped) { return; } view_move_to_center(tablet_view->view); } static void tablet_mode_view_blur_rect_show(struct tablet_mode_view *tablet_view, bool show) { if (!tablet_view->blur_rect) { return; } if (!show) { ky_scene_node_set_enabled(&tablet_view->blur_rect->node, false); return; } ky_scene_node_place_below(&tablet_view->blur_rect->node, &tablet_view->view->current_proxy->tree->node); ky_scene_node_set_enabled(&tablet_view->blur_rect->node, true); } static void tablet_mode_handle_view_update_capabilities(struct wl_listener *listener, void *data) { struct tablet_mode_view *tablet_view = wl_container_of(listener, tablet_view, view_update_capabilities); struct view *view = tablet_view->view; struct view_update_capabilities_event *event = data; if (event->mask & KYWC_VIEW_MAXIMIZABLE) { if (view->parent) { event->state &= ~KYWC_VIEW_MAXIMIZABLE; } } if (event->mask & KYWC_VIEW_MAXIMIZE_BUTTON) { event->state &= ~KYWC_VIEW_MAXIMIZE_BUTTON; } } static struct view *tablet_mode_ancestor_from_view(struct view *view) { struct view *ancestor = view; while (ancestor->parent) { ancestor = ancestor->parent; } return ancestor; } static void tablet_mode_view_activate(struct view *view) { if (!KYWC_VIEW_IS_ACTIVATABLE(&view->base)) { return; } if (view != manager->task_bar.view) { tablet_mode_task_bar_show(false); } /* minimize the old activated view */ struct tablet_mode_view *tablet_view = tablet_mode_view_from_view(view); struct view *activated_view = view_manager_get_activated(); if (tablet_view && activated_view) { if (view != activated_view && !view_has_ancestor(view, activated_view) && !view_has_descendant(view, activated_view)) { tablet_mode_view_minimized(tablet_mode_ancestor_from_view(activated_view), true); } } /* activate the view */ struct view *descendant = view_find_descendant_modal(view); view_do_activate(descendant ? descendant : view); view_raise_to_top(view, true); if (tablet_view) { tablet_mode_view_blur_rect_show(tablet_view, true); } } static void tablet_mode_view_blur_rect_create(struct tablet_mode_view *tablet_view) { if (tablet_view->blur_rect) { return; } if (!tablet_view->view->current_proxy) { return; } float color[4] = { 0.5, 0.5, 0.5, 0.5 }; struct output *output = output_from_kywc_output(tablet_view->view->output); enum layer layer = tablet_view->view->base.kept_above ? LAYER_ABOVE : (tablet_view->view->base.kept_below ? LAYER_BELOW : LAYER_NORMAL); struct view_layer *view_layer = workspace_layer(tablet_view->view->current_proxy->workspace, layer); tablet_view->blur_rect = ky_scene_rect_create(view_layer->tree, output->geometry.width, output->geometry.height, color); if (!tablet_view->blur_rect) { return; } pixman_region32_t region; pixman_region32_init(®ion); ky_scene_node_set_blur_region(&tablet_view->blur_rect->node, ®ion); pixman_region32_fini(®ion); ky_scene_node_place_below(&tablet_view->blur_rect->node, &tablet_view->view->current_proxy->tree->node); } static void tablet_mode_view_blur_rect_destroy(struct tablet_mode_view *tablet_view) { if (!tablet_view->blur_rect) { return; } ky_scene_node_destroy(&tablet_view->blur_rect->node); tablet_view->blur_rect = NULL; } static struct tablet_mode_view *tablet_mode_view_create(struct view *view) { struct tablet_mode_view *tablet_view = calloc(1, sizeof(*tablet_view)); if (!tablet_view) { return NULL; } wl_list_insert(&manager->tablet_views, &tablet_view->link); tablet_view->view = view; tablet_view->parent.notify = handle_view_parent; wl_signal_add(&view->events.parent, &tablet_view->parent); tablet_view->view_update_capabilities.notify = tablet_mode_handle_view_update_capabilities; view_add_update_capabilities_listener(view, &tablet_view->view_update_capabilities); return tablet_view; } static void tablet_mode_view_destroy(struct tablet_mode_view *tablet_view) { wl_list_remove(&tablet_view->link); wl_list_remove(&tablet_view->parent.link); wl_list_remove(&tablet_view->view_update_capabilities.link); tablet_mode_view_blur_rect_destroy(tablet_view); free(tablet_view); } static void tablet_mode_view_apply_minimized(struct view *view, bool minimized) { view_do_minimized(view, minimized); } static void tablet_mode_view_minimized(struct view *view, bool minimized) { struct tablet_mode_view *tablet_view = tablet_mode_view_from_view(view); if (!tablet_view || !KYWC_VIEW_IS_MINIMIZABLE(&view->base)) { return; } struct view *ancestor = tablet_mode_ancestor_from_view(view); if (!minimized && !view->base.maximized) { // restore minimized view and then maximize the view tablet_mode_view_apply_minimized(ancestor, minimized); tablet_mode_view_maximized(ancestor, true, ancestor->output); return; } tablet_mode_view_blur_rect_show(tablet_view, !minimized); tablet_mode_view_apply_minimized(minimized ? view : ancestor, minimized); } static void tablet_mode_view_maximized(struct view *view, bool maximized, struct kywc_output *kywc_output) { struct tablet_mode_view *tablet_view = tablet_mode_view_from_view(view); if (!tablet_view || !maximized) { return; } if (KYWC_VIEW_IS_MAXIMIZABLE(&view->base)) { view_do_maximized(view, maximized, kywc_output); } else { view_move_to_center(view); if (!view->parent) { /* need blur if view can not maximized*/ tablet_mode_view_blur_rect_create(tablet_view); } } } static void tablet_mode_view_map(struct view *view) { positioner_add_new_view(view); if (!manager->task_bar.view) { if (strcmp(view->base.title, TASK_BAR) == 0) { tablet_mode_set_task_bar(view); tablet_mode_task_bar_show(false); } } if (!manager->tablet_desktop) { if (strcmp(view->base.title, TABLET_DESKTOP) == 0) { manager->tablet_desktop = view; } } if (!manager->pc_desktop) { if (strcmp(view->base.app_id, PC_DESKTOP) == 0) { manager->pc_desktop = view; } } if (view->base.role != KYWC_VIEW_ROLE_NORMAL || view_is_special(view)) { return; } view->mode_view = tablet_mode_view_create(view); tablet_mode_view_maximized(view, true, view->output); } static void tablet_mode_view_unmap(struct view *view) { if (view == manager->task_bar.view) { tablet_mode_set_task_bar(NULL); } else if (view == manager->tablet_desktop) { manager->tablet_desktop = NULL; } else if (view == manager->pc_desktop) { manager->pc_desktop = NULL; } struct tablet_mode_view *tablet_view = tablet_mode_view_from_view(view); if (!tablet_view) { return; } tablet_mode_view_destroy(tablet_view); view->mode_view = NULL; } static void global_blur_rect_create(void) { if (manager->global_blur_rect) { return; } float color[4] = { 0.1, 0.1, 0.1, 0.1 }; struct output *output = input_current_output(input_manager_get_default_seat()); struct view_layer *layer = view_manager_get_layer(LAYER_ON_SCREEN_DISPLAY, false); manager->global_blur_rect = ky_scene_rect_create(layer->tree, output->geometry.width, output->geometry.height, color); if (!manager->global_blur_rect) { return; } pixman_region32_t region; pixman_region32_init_rect(®ion, output->geometry.x, output->geometry.y, output->geometry.width, output->geometry.height); ky_scene_node_set_blur_region(&manager->global_blur_rect->node, ®ion); pixman_region32_fini(®ion); } static void global_blur_rect_destroy(void) { if (!manager->global_blur_rect) { return; } ky_scene_node_destroy(&manager->global_blur_rect->node); manager->global_blur_rect = NULL; } static void global_blur_rect_set_blur(float percent) { if (!manager->global_blur_rect) { global_blur_rect_create(); } if (!manager->global_blur_rect) { return; } if (percent == 0.0) { ky_scene_node_set_enabled(&manager->global_blur_rect->node, false); return; } /* offset 0-10 */ float offset = percent * 10.0 / 1.0; ky_scene_node_set_blur_level(&manager->global_blur_rect->node, 2, offset); ky_scene_node_set_enabled(&manager->global_blur_rect->node, true); } static void gestures_action(struct gesture_binding *binding, void *data, double dx, double dy) { struct gesture *gesture = data; struct output *output = input_current_output(input_manager_get_default_seat()); struct view *view = view_manager_get_activated(); struct view *task_bar = manager->task_bar.view; if (!task_bar) { return; } // task bar follow finger if activated view is normal view if (tablet_mode_view_from_view(view) || view == task_bar) { if (gesture->stage == GESTURE_STAGE_STOP) { int threshold = output->geometry.height - task_bar->base.geometry.height / 2; if (task_bar->base.geometry.y > threshold) { tablet_mode_task_bar_show(false); } else { view_do_move(task_bar, task_bar->base.geometry.x, manager->task_bar.default_y); } /* hide task bar if trigger switcher */ if (dy <= -TRIGGER_SWITCHER_THRESHOLD) { tablet_mode_task_bar_show(false); } return; } if (!task_bar->base.activated) { view_do_activate(task_bar); view_raise_to_top(task_bar, false); } // calculate follow y int y = output->geometry.y + output->geometry.height + dy * output->geometry.height; y = y < manager->task_bar.default_y ? manager->task_bar.default_y : y; tablet_mode_task_bar_show(true); view_do_move(task_bar, task_bar->base.geometry.x, y); return; } /* set blur */ if (gesture->stage == GESTURE_STAGE_STOP) { global_blur_rect_set_blur(0.0); return; } dy = CLAMP(dy, -TRIGGER_SWITCHER_THRESHOLD, 0.0); global_blur_rect_set_blur(-dy / TRIGGER_SWITCHER_THRESHOLD); } static void tablet_mode_update_or_restore_theme(bool update) { struct theme *theme = theme_manager_get_theme(); if (update) { theme->icon_size = 32; theme->button_width = 40; theme->title_height = 64; theme->subtitle_height = 48; return; } theme->icon_size = 24; theme->button_width = 32; theme->title_height = 38; theme->subtitle_height = 38; } static void tablet_mode_hanlde_theme_pre_update(struct wl_listener *listener, void *data) { struct theme_update_event *event = data; if (event->update_mask & THEME_UPDATE_MASK_DECORATION_SIZE) { event->update_mask &= ~THEME_UPDATE_MASK_DECORATION_SIZE; tablet_mode_update_or_restore_theme(true); } } static void tablet_mode_input_actions_block(bool enable) { kywc_key_binding_block_type(KEY_BINDING_TYPE_WIN_MENU, enable); kywc_key_binding_block_type(KEY_BINDING_TYPE_SWITCH_WORKSPACE, enable); } static void input_actions_register(void) { for (size_t i = 0; i < ARRAY_SIZE(gestures); i++) { struct gesture *gesture = &gestures[i]; if (gesture->binding) { kywc_gesture_binding_destroy(gesture->binding); } struct gesture_binding *binding = kywc_gesture_binding_create( gesture->type, gesture->stage, gesture->devices, gesture->directions, gesture->edges, gesture->fingers, gesture->follow_direction, gesture->follow_threshold, gesture->desc); if (!binding) { continue; } if (!kywc_gesture_binding_register(binding, gestures_action, gesture)) { kywc_gesture_binding_destroy(binding); continue; } gesture->binding = binding; } } static void input_actions_unregister(void) { for (size_t i = 0; i < ARRAY_SIZE(gestures); i++) { struct gesture *gesture = &gestures[i]; if (gesture->binding) { kywc_gesture_binding_destroy(gesture->binding); } gesture->binding = NULL; } } static void tablet_mode_enter(void) { /* update theme */ tablet_mode_update_or_restore_theme(true); theme_manager_update_theme(THEME_UPDATE_MASK_DECORATION_SIZE); theme_manager_add_update_listener(&manager->theme_pre_update, true); /* input actions */ tablet_mode_input_actions_block(true); input_actions_register(); struct view *view, *maximize_view = NULL; struct view_manager *view_manager = manager->view_manager; struct view *activated_view = view_manager_get_activated(); wl_list_for_each(view, &view_manager->views, link) { if (!view->base.mapped) { continue; } if (strcmp(view->base.title, TASK_BAR) == 0) { tablet_mode_set_task_bar(view); tablet_mode_task_bar_show(false); } else if (strcmp(view->base.title, TABLET_DESKTOP) == 0) { manager->tablet_desktop = view; } else if (strcmp(view->base.app_id, PC_DESKTOP) == 0) { manager->pc_desktop = view; } if (view->base.role != KYWC_VIEW_ROLE_NORMAL || view_is_special(view)) { continue; } view->mode_view = tablet_mode_view_create(view); /* workspace */ struct workspace *workspace = workspace_manager_get_current(); struct tablet_mode_view *tablet_view = tablet_mode_view_from_view(view); tablet_view->remove_workspace = workspace != view->current_proxy->workspace; view_add_workspace(view, workspace); if (view == activated_view) { maximize_view = view; continue; } if (view_has_ancestor(view, activated_view)) { view_move_to_center(view); continue; } if (view_has_descendant(view, activated_view)) { tablet_mode_view_maximized(view, true, view->output); continue; } if (view_has_modal_property(view)) { tablet_mode_view_maximized(view, true, view->output); continue; } tablet_mode_view_minimized(view, true); } if (manager->pc_desktop) { view_add_fade_effect(manager->pc_desktop, FADE_OUT); ky_scene_node_set_enabled(&manager->pc_desktop->tree->node, false); } if (manager->tablet_desktop) { ky_scene_node_set_enabled(&manager->tablet_desktop->tree->node, true); view_add_fade_effect(manager->tablet_desktop, FADE_IN); } if (maximize_view) { view_raise_to_top(maximize_view, true); tablet_mode_view_maximized(maximize_view, true, maximize_view->output); } } static void tablet_mode_leave(void) { /* desktop and task bar */ tablet_mode_task_bar_show(false); tablet_mode_set_task_bar(NULL); if (manager->tablet_desktop) { view_add_fade_effect(manager->tablet_desktop, FADE_OUT); ky_scene_node_set_enabled(&manager->tablet_desktop->tree->node, false); manager->tablet_desktop = NULL; } if (manager->pc_desktop) { ky_scene_node_set_enabled(&manager->pc_desktop->tree->node, true); view_add_fade_effect(manager->pc_desktop, FADE_IN); manager->pc_desktop = NULL; } /* input actions */ tablet_mode_input_actions_block(false); input_actions_unregister(); /* global blur rect */ global_blur_rect_destroy(); struct tablet_mode_view *tablet_view, *tmp; wl_list_for_each_safe(tablet_view, tmp, &manager->tablet_views, link) { if (tablet_view->remove_workspace) { view_remove_workspace(tablet_view->view, tablet_view->view->current_proxy->workspace); } tablet_view->view->mode_view = NULL; tablet_mode_view_destroy(tablet_view); } wl_list_remove(&manager->theme_pre_update.link); tablet_mode_update_or_restore_theme(false); theme_manager_update_theme(THEME_UPDATE_MASK_DECORATION_SIZE); } static void tablet_mode_view_click(struct seat *seat, struct view *view, uint32_t button, bool pressed, enum click_state state) { /* active current view */ kywc_view_activate(&view->base); view_set_focus(view, seat); } static void tablet_mode_destroy(void) { if (!manager) { return; } struct tablet_mode_view *tablet_view, *tmp; wl_list_for_each_safe(tablet_view, tmp, &manager->tablet_views, link) { tablet_mode_view_destroy(tablet_view); } free(manager); manager = NULL; } static const struct view_mode_interface tablet_mode_impl = { .name = "tablet_mode", .view_map = tablet_mode_view_map, .view_unmap = tablet_mode_view_unmap, .view_request_move = NULL, .view_request_resize = NULL, .view_request_minimized = tablet_mode_view_minimized, .view_request_maximized = tablet_mode_view_maximized, .view_request_fullscreen = NULL, .view_request_tiled = NULL, .view_request_activate = tablet_mode_view_activate, .view_request_show_menu = NULL, .view_click = tablet_mode_view_click, .view_hover = NULL, .view_mode_enter = tablet_mode_enter, .view_mode_leave = tablet_mode_leave, .mode_destroy = tablet_mode_destroy, }; void tablet_mode_register(struct view_manager *view_manager) { struct view_mode *mode = view_manager_mode_register(&tablet_mode_impl); if (!mode) { return; } manager = calloc(1, sizeof(*manager)); if (!manager) { view_manager_mode_unregister(mode); return; } wl_list_init(&manager->tablet_views); wl_list_init(&manager->task_bar.output_geometry.link); manager->view_manager = view_manager; manager->theme_pre_update.notify = tablet_mode_hanlde_theme_pre_update; manager->task_bar.output_geometry.notify = handle_output_geometry; tablet_mode_set_task_bar(NULL); } kylin-wayland-compositor/src/view/ky_toplevel.c0000664000175000017500000006710115160460057020732 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include "kywc-toplevel-v1-protocol.h" #include "theme.h" #include "view/workspace.h" #include "view_p.h" struct ky_toplevel_manager { struct wl_event_loop *event_loop; struct wl_global *global; struct wl_list resources; struct wl_list toplevels; struct wl_listener new_mapped_toplevel; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct ky_toplevel { struct ky_toplevel_manager *manager; struct wl_list link; struct wl_event_source *idle_source; struct wl_list resources; const char *icon_name; struct kywc_view *view; struct wl_listener unmap; struct wl_listener title; struct wl_listener app_id; struct wl_listener maximize; struct wl_listener minimize; struct wl_listener activate; struct wl_listener fullscreen; struct wl_listener position; struct wl_listener size; struct wl_listener icon_update; struct wl_listener output; struct wl_listener parent; struct wl_listener workspace_enter; struct wl_listener workspace_leave; }; static struct ky_toplevel *toplevel_for_view(struct ky_toplevel_manager *manager, struct kywc_view *view) { struct ky_toplevel *toplevel; wl_list_for_each(toplevel, &manager->toplevels, link) { if (toplevel->view == view) { return toplevel; } } return NULL; } static void toplevel_update_icon_name(struct ky_toplevel *toplevel) { struct view *view = view_from_kywc_view(toplevel->view); const char *icon_name = theme_icon_get_name(view->icon); if (toplevel->icon_name && strcmp(toplevel->icon_name, icon_name) == 0) { return; } free((void *)toplevel->icon_name); toplevel->icon_name = strdup(icon_name); } static void toplevel_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void toplevel_handle_set_maximized(struct wl_client *client, struct wl_resource *resource, const char *output) { struct ky_toplevel *toplevel = wl_resource_get_user_data(resource); if (!toplevel) { return; } struct kywc_output *kywc_output = NULL; if (output) { kywc_output = kywc_output_by_uuid(output); } kywc_view_set_maximized(toplevel->view, true, kywc_output); } static void toplevel_handle_unset_maximized(struct wl_client *client, struct wl_resource *resource) { struct ky_toplevel *toplevel = wl_resource_get_user_data(resource); if (!toplevel) { return; } kywc_view_set_maximized(toplevel->view, false, NULL); } static void toplevel_handle_set_minimized(struct wl_client *client, struct wl_resource *resource) { struct ky_toplevel *toplevel = wl_resource_get_user_data(resource); if (!toplevel) { return; } kywc_view_set_minimized(toplevel->view, true); } static void toplevel_handle_unset_minimized(struct wl_client *client, struct wl_resource *resource) { struct ky_toplevel *toplevel = wl_resource_get_user_data(resource); if (!toplevel) { return; } kywc_view_set_minimized(toplevel->view, false); } static void toplevel_handle_set_fullscreen(struct wl_client *client, struct wl_resource *resource, const char *output) { struct ky_toplevel *toplevel = wl_resource_get_user_data(resource); if (!toplevel) { return; } struct kywc_output *kywc_output = NULL; if (output) { kywc_output = kywc_output_by_uuid(output); } kywc_view_set_fullscreen(toplevel->view, true, kywc_output); } static void toplevel_handle_unset_fullscreen(struct wl_client *client, struct wl_resource *resource) { struct ky_toplevel *toplevel = wl_resource_get_user_data(resource); if (!toplevel) { return; } kywc_view_set_fullscreen(toplevel->view, false, NULL); } static void toplevel_handle_activate(struct wl_client *client, struct wl_resource *resource) { struct ky_toplevel *toplevel = wl_resource_get_user_data(resource); if (!toplevel) { return; } kywc_view_activate(toplevel->view); view_set_focus(view_from_kywc_view(toplevel->view), input_manager_get_default_seat()); } static void toplevel_handle_close(struct wl_client *client, struct wl_resource *resource) { struct ky_toplevel *toplevel = wl_resource_get_user_data(resource); if (!toplevel) { return; } kywc_view_close(toplevel->view); } static void toplevel_handle_enter_workspace(struct wl_client *client, struct wl_resource *resource, const char *id) { struct ky_toplevel *toplevel = wl_resource_get_user_data(resource); if (!toplevel) { return; } struct workspace *workspace = workspace_by_uuid(id); if (workspace) { view_add_workspace(view_from_kywc_view(toplevel->view), workspace); } } static void toplevel_handle_leave_workspace(struct wl_client *client, struct wl_resource *resource, const char *id) { struct ky_toplevel *toplevel = wl_resource_get_user_data(resource); if (!toplevel) { return; } struct workspace *workspace = workspace_by_uuid(id); if (workspace) { view_remove_workspace(view_from_kywc_view(toplevel->view), workspace); } } static void toplevel_handle_move_to_workspace(struct wl_client *client, struct wl_resource *resource, const char *id) { struct ky_toplevel *toplevel = wl_resource_get_user_data(resource); if (!toplevel) { return; } struct workspace *workspace = workspace_by_uuid(id); if (workspace) { view_set_workspace(view_from_kywc_view(toplevel->view), workspace); } } static void toplevel_handle_move_to_output(struct wl_client *client, struct wl_resource *resource, const char *id) { struct ky_toplevel *toplevel = wl_resource_get_user_data(resource); if (!toplevel) { return; } struct kywc_output *kywc_output = kywc_output_by_uuid(id); if (kywc_output) { view_move_to_output(view_from_kywc_view(toplevel->view), NULL, NULL, kywc_output); } } static void toplevel_handle_set_position(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y) { struct ky_toplevel *toplevel = wl_resource_get_user_data(resource); if (!toplevel) { return; } kywc_view_move(toplevel->view, x + toplevel->view->margin.off_x, y + toplevel->view->margin.off_y); } static void toplevel_handle_set_size(struct wl_client *client, struct wl_resource *resource, uint32_t width, uint32_t height) { struct ky_toplevel *toplevel = wl_resource_get_user_data(resource); if (!toplevel) { return; } struct kywc_box geo = { toplevel->view->geometry.x, toplevel->view->geometry.y, width - toplevel->view->margin.off_width, height - toplevel->view->margin.off_height }; kywc_view_resize(toplevel->view, &geo); } static const struct kywc_toplevel_v1_interface ky_toplevel_impl = { .destroy = toplevel_handle_destroy, .set_maximized = toplevel_handle_set_maximized, .unset_maximized = toplevel_handle_unset_maximized, .set_minimized = toplevel_handle_set_minimized, .unset_minimized = toplevel_handle_unset_minimized, .set_fullscreen = toplevel_handle_set_fullscreen, .unset_fullscreen = toplevel_handle_unset_fullscreen, .activate = toplevel_handle_activate, .close = toplevel_handle_close, .enter_workspace = toplevel_handle_enter_workspace, .leave_workspace = toplevel_handle_leave_workspace, .move_to_workspace = toplevel_handle_move_to_workspace, .move_to_output = toplevel_handle_move_to_output, .set_position = toplevel_handle_set_position, .set_size = toplevel_handle_set_size, }; static void ky_toplevel_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static struct wl_resource * create_toplevel_resource_for_resource(struct ky_toplevel *ky_toplevel, struct wl_resource *manager_resource) { struct wl_client *client = wl_resource_get_client(manager_resource); struct wl_resource *resource = wl_resource_create(client, &kywc_toplevel_v1_interface, wl_resource_get_version(manager_resource), 0); if (!resource) { wl_client_post_no_memory(client); return NULL; } wl_resource_set_implementation(resource, &ky_toplevel_impl, ky_toplevel, ky_toplevel_resource_destroy); wl_list_insert(&ky_toplevel->resources, wl_resource_get_link(resource)); kywc_toplevel_manager_v1_send_toplevel(manager_resource, resource, ky_toplevel->view->uuid); return resource; } static uint32_t toplevel_state(struct kywc_view *kywc_view) { uint32_t state = 0; if (kywc_view->maximized) { state |= KYWC_TOPLEVEL_V1_STATE_MAXIMIZED; } if (kywc_view->minimized) { state |= KYWC_TOPLEVEL_V1_STATE_MINIMIZED; } if (kywc_view->activated) { state |= KYWC_TOPLEVEL_V1_STATE_ACTIVATED; } if (kywc_view->fullscreen) { state |= KYWC_TOPLEVEL_V1_STATE_FULLSCREEN; } return state; } static uint32_t toplevel_caps(struct kywc_view *kywc_view) { uint32_t caps = 0; if (kywc_view->skip_taskbar) { caps |= KYWC_TOPLEVEL_V1_CAPABILITY_SKIP_TASKBAR; } if (kywc_view->skip_switcher) { caps |= KYWC_TOPLEVEL_V1_CAPABILITY_SKIP_SWITCHER; } return caps; } static void toplevel_send_details_to_toplevel_resource(struct ky_toplevel *toplevel, struct wl_resource *resource) { struct kywc_view *kywc_view = toplevel->view; struct view *view = view_from_kywc_view(kywc_view); if (kywc_view->app_id) { kywc_toplevel_v1_send_app_id(resource, kywc_view->app_id); } if (kywc_view->title) { kywc_toplevel_v1_send_title(resource, kywc_view->title); } if (view->output) { kywc_toplevel_v1_send_primary_output(resource, view->output->uuid); } kywc_toplevel_v1_send_pid(resource, view->pid); /* all workspaces the toplevel in */ struct view_proxy *proxy; wl_list_for_each(proxy, &view->view_proxies, view_link) { kywc_toplevel_v1_send_workspace_enter(resource, proxy->workspace->uuid); } kywc_toplevel_v1_send_capabilities(resource, toplevel_caps(toplevel->view)); kywc_toplevel_v1_send_state(resource, toplevel_state(toplevel->view)); if (view->parent) { struct ky_toplevel *parent_toplevel = toplevel_for_view(toplevel->manager, &view->parent->base); if (parent_toplevel) { struct wl_resource *parent_resource = wl_resource_find_for_client( &parent_toplevel->resources, wl_resource_get_client(resource)); if (parent_resource) { kywc_toplevel_v1_send_parent(resource, parent_resource); } } } else { kywc_toplevel_v1_send_parent(resource, NULL); } kywc_toplevel_v1_send_icon(resource, toplevel->icon_name); int32_t x = kywc_view->geometry.x - kywc_view->margin.off_x; int32_t y = kywc_view->geometry.y - kywc_view->margin.off_y; uint32_t width = kywc_view->geometry.width + kywc_view->margin.off_width; uint32_t height = kywc_view->geometry.height + kywc_view->margin.off_height; kywc_toplevel_v1_send_geometry(resource, x, y, width, height); kywc_toplevel_v1_send_done(resource); } static void manager_handle_stop(struct wl_client *client, struct wl_resource *resource) { kywc_toplevel_manager_v1_send_finished(resource); wl_resource_destroy(resource); } static const struct kywc_toplevel_manager_v1_interface ky_toplevel_manager_impl = { .stop = manager_handle_stop, }; static void ky_toplevel_manager_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static void ky_toplevel_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct ky_toplevel_manager *manager = data; struct wl_resource *resource = wl_resource_create(client, &kywc_toplevel_manager_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &ky_toplevel_manager_impl, manager, ky_toplevel_manager_resource_destroy); wl_list_insert(&manager->resources, wl_resource_get_link(resource)); /* send all toplevels and details */ struct ky_toplevel *toplevel, *tmp; wl_list_for_each_reverse_safe(toplevel, tmp, &manager->toplevels, link) { create_toplevel_resource_for_resource(toplevel, resource); } wl_list_for_each_reverse_safe(toplevel, tmp, &manager->toplevels, link) { struct wl_resource *toplevel_resource = wl_resource_find_for_client(&toplevel->resources, client); toplevel_send_details_to_toplevel_resource(toplevel, toplevel_resource); } } static void toplevel_idle_send_done(void *data) { struct ky_toplevel *toplevel = data; struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { kywc_toplevel_v1_send_done(resource); } toplevel->idle_source = NULL; } static void toplevel_update_idle_source(struct ky_toplevel *toplevel) { if (toplevel->idle_source || wl_list_empty(&toplevel->resources)) { return; } toplevel->idle_source = wl_event_loop_add_idle(toplevel->manager->event_loop, toplevel_idle_send_done, toplevel); } static void handle_toplevel_title(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = wl_container_of(listener, toplevel, title); if (!toplevel->view->title) { return; } struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { kywc_toplevel_v1_send_title(resource, toplevel->view->title); } toplevel_update_idle_source(toplevel); } static void handle_toplevel_app_id(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = wl_container_of(listener, toplevel, app_id); if (!toplevel->view->app_id) { return; } struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { kywc_toplevel_v1_send_app_id(resource, toplevel->view->app_id); } toplevel_update_idle_source(toplevel); } static void handle_toplevel_icon_update(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = wl_container_of(listener, toplevel, icon_update); struct view *view = view_from_kywc_view(toplevel->view); if (theme_icon_is_fallback(view->icon)) { return; } toplevel_update_icon_name(toplevel); struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { kywc_toplevel_v1_send_icon(resource, toplevel->icon_name); } toplevel_update_idle_source(toplevel); } static void handle_toplevel_maximize(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = wl_container_of(listener, toplevel, maximize); struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { kywc_toplevel_v1_send_state(resource, toplevel_state(toplevel->view)); } toplevel_update_idle_source(toplevel); } static void handle_toplevel_minimize(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = wl_container_of(listener, toplevel, minimize); struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { kywc_toplevel_v1_send_state(resource, toplevel_state(toplevel->view)); } toplevel_update_idle_source(toplevel); } static void toplevel_raise_children(struct ky_toplevel *toplevel, struct ky_toplevel *reference) { struct view *view = view_from_kywc_view(toplevel->view); struct ky_toplevel *child_toplevel; struct view *child; wl_list_for_each(child, &view->children, parent_link) { if (reference && reference->view == &child->base) { continue; } child_toplevel = toplevel_for_view(toplevel->manager, &child->base); if (!child_toplevel) { continue; } wl_list_remove(&child_toplevel->link); wl_list_insert(&toplevel->manager->toplevels, &child_toplevel->link); toplevel_raise_children(child_toplevel, NULL); } } static void toplevel_raise_with_parent(struct ky_toplevel *toplevel, struct ky_toplevel *reference) { struct view *view = view_from_kywc_view(toplevel->view); if (view->parent) { struct ky_toplevel *parent_toplevel = toplevel_for_view(toplevel->manager, &view->parent->base); if (parent_toplevel) { toplevel_raise_with_parent(parent_toplevel, toplevel); } } wl_list_remove(&toplevel->link); wl_list_insert(&toplevel->manager->toplevels, &toplevel->link); toplevel_raise_children(toplevel, reference); } static void handle_toplevel_activate(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = wl_container_of(listener, toplevel, activate); /* raise parent toplevel first */ if (toplevel->view->activated) { toplevel_raise_with_parent(toplevel, NULL); } struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { kywc_toplevel_v1_send_state(resource, toplevel_state(toplevel->view)); } toplevel_update_idle_source(toplevel); } static void handle_toplevel_fullscreen(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = wl_container_of(listener, toplevel, fullscreen); struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { kywc_toplevel_v1_send_state(resource, toplevel_state(toplevel->view)); } toplevel_update_idle_source(toplevel); } static void handle_toplevel_position(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = wl_container_of(listener, toplevel, position); struct kywc_view *view = toplevel->view; int32_t x = view->geometry.x - view->margin.off_x; int32_t y = view->geometry.y - view->margin.off_y; uint32_t width = view->geometry.width + view->margin.off_width; uint32_t height = view->geometry.height + view->margin.off_height; struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { kywc_toplevel_v1_send_geometry(resource, x, y, width, height); } toplevel_update_idle_source(toplevel); } static void handle_toplevel_size(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = wl_container_of(listener, toplevel, size); struct kywc_view *view = toplevel->view; int32_t x = view->geometry.x - view->margin.off_x; int32_t y = view->geometry.y - view->margin.off_y; uint32_t width = view->geometry.width + view->margin.off_width; uint32_t height = view->geometry.height + view->margin.off_height; struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { kywc_toplevel_v1_send_geometry(resource, x, y, width, height); } toplevel_update_idle_source(toplevel); } static void handle_toplevel_output(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = wl_container_of(listener, toplevel, output); struct view *view = view_from_kywc_view(toplevel->view); if (!view->output) { return; } struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { kywc_toplevel_v1_send_primary_output(resource, view->output->uuid); } toplevel_update_idle_source(toplevel); } static void handle_toplevel_parent(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = wl_container_of(listener, toplevel, parent); struct view *view = view_from_kywc_view(toplevel->view); struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { if (view->parent) { struct ky_toplevel *parent_toplevel = toplevel_for_view(toplevel->manager, &view->parent->base); if (parent_toplevel) { struct wl_resource *parent_resource = wl_resource_find_for_client( &parent_toplevel->resources, wl_resource_get_client(resource)); if (parent_resource) { kywc_toplevel_v1_send_parent(resource, parent_resource); } } } else { kywc_toplevel_v1_send_parent(resource, NULL); } } toplevel_update_idle_source(toplevel); } static void handle_toplevel_workspace_enter(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = wl_container_of(listener, toplevel, workspace_enter); struct workspace *workspace = data; struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { kywc_toplevel_v1_send_workspace_enter(resource, workspace->uuid); } toplevel_update_idle_source(toplevel); } static void handle_toplevel_workspace_leave(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = wl_container_of(listener, toplevel, workspace_leave); struct workspace *workspace = data; struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { kywc_toplevel_v1_send_workspace_leave(resource, workspace->uuid); } toplevel_update_idle_source(toplevel); } static void handle_toplevel_unmap(struct wl_listener *listener, void *data); static void handle_new_mapped_toplevel(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = calloc(1, sizeof(*toplevel)); if (!toplevel) { return; } struct ky_toplevel_manager *manager = wl_container_of(listener, manager, new_mapped_toplevel); toplevel->manager = manager; wl_list_init(&toplevel->resources); wl_list_insert(&manager->toplevels, &toplevel->link); struct kywc_view *kywc_view = data; toplevel->view = kywc_view; toplevel->unmap.notify = handle_toplevel_unmap; wl_signal_add(&kywc_view->events.unmap, &toplevel->unmap); toplevel->title.notify = handle_toplevel_title; wl_signal_add(&toplevel->view->events.title, &toplevel->title); toplevel->app_id.notify = handle_toplevel_app_id; wl_signal_add(&toplevel->view->events.app_id, &toplevel->app_id); toplevel->maximize.notify = handle_toplevel_maximize; wl_signal_add(&toplevel->view->events.maximize, &toplevel->maximize); toplevel->minimize.notify = handle_toplevel_minimize; wl_signal_add(&toplevel->view->events.minimize, &toplevel->minimize); toplevel->activate.notify = handle_toplevel_activate; wl_signal_add(&toplevel->view->events.activate, &toplevel->activate); toplevel->fullscreen.notify = handle_toplevel_fullscreen; wl_signal_add(&toplevel->view->events.fullscreen, &toplevel->fullscreen); toplevel->position.notify = handle_toplevel_position; wl_signal_add(&toplevel->view->events.position, &toplevel->position); toplevel->size.notify = handle_toplevel_size; wl_signal_add(&toplevel->view->events.size, &toplevel->size); struct view *view = view_from_kywc_view(toplevel->view); toplevel->icon_update.notify = handle_toplevel_icon_update; wl_signal_add(&view->events.icon_update, &toplevel->icon_update); toplevel->output.notify = handle_toplevel_output; wl_signal_add(&view->events.output, &toplevel->output); toplevel->parent.notify = handle_toplevel_parent; wl_signal_add(&view->events.parent, &toplevel->parent); toplevel->workspace_enter.notify = handle_toplevel_workspace_enter; wl_signal_add(&view->events.workspace_enter, &toplevel->workspace_enter); toplevel->workspace_leave.notify = handle_toplevel_workspace_leave; wl_signal_add(&view->events.workspace_leave, &toplevel->workspace_leave); toplevel_update_icon_name(toplevel); /* send toplevel and detail */ struct wl_resource *resource, *toplevel_resource; wl_resource_for_each(resource, &toplevel->manager->resources) { toplevel_resource = create_toplevel_resource_for_resource(toplevel, resource); toplevel_send_details_to_toplevel_resource(toplevel, toplevel_resource); } } static void handle_toplevel_unmap(struct wl_listener *listener, void *data) { struct ky_toplevel *toplevel = wl_container_of(listener, toplevel, unmap); struct wl_resource *resource, *tmp; wl_resource_for_each_safe(resource, tmp, &toplevel->resources) { kywc_toplevel_v1_send_closed(resource); // make the resource inert wl_list_remove(wl_resource_get_link(resource)); wl_list_init(wl_resource_get_link(resource)); wl_resource_set_user_data(resource, NULL); } if (toplevel->idle_source) { wl_event_source_remove(toplevel->idle_source); } wl_list_remove(&toplevel->title.link); wl_list_remove(&toplevel->app_id.link); wl_list_remove(&toplevel->icon_update.link); wl_list_remove(&toplevel->maximize.link); wl_list_remove(&toplevel->minimize.link); wl_list_remove(&toplevel->activate.link); wl_list_remove(&toplevel->fullscreen.link); wl_list_remove(&toplevel->position.link); wl_list_remove(&toplevel->size.link); wl_list_remove(&toplevel->output.link); wl_list_remove(&toplevel->parent.link); wl_list_remove(&toplevel->workspace_enter.link); wl_list_remove(&toplevel->workspace_leave.link); free((void *)toplevel->icon_name); toplevel->icon_name = NULL; wl_list_remove(&toplevel->unmap.link); wl_list_remove(&toplevel->link); free(toplevel); } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct ky_toplevel_manager *manager = wl_container_of(listener, manager, server_destroy); wl_list_remove(&manager->server_destroy.link); free(manager); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct ky_toplevel_manager *manager = wl_container_of(listener, manager, display_destroy); wl_list_remove(&manager->new_mapped_toplevel.link); wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); } bool ky_toplevel_manager_create(struct server *server) { struct ky_toplevel_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->global = wl_global_create(server->display, &kywc_toplevel_manager_v1_interface, 1, manager, ky_toplevel_manager_bind); if (!manager->global) { kywc_log(KYWC_WARN, "KYWC toplevel manager create failed"); free(manager); return false; } wl_list_init(&manager->resources); wl_list_init(&manager->toplevels); manager->event_loop = wl_display_get_event_loop(server->display); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &manager->display_destroy); manager->new_mapped_toplevel.notify = handle_new_mapped_toplevel; kywc_view_add_new_mapped_listener(&manager->new_mapped_toplevel); return true; } kylin-wayland-compositor/src/view/xdg_popup.c0000664000175000017500000004075115160461067020406 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "effect/action.h" #include "effect/effect.h" #include "input/cursor.h" #include "input/event.h" #include "output.h" #include "scene/decoration.h" #include "scene/xdg_shell.h" #include "theme.h" #include "util/color.h" #include "view_p.h" #define XDG_POPUP_NO_DECORATION (0) struct xdg_popup { struct wlr_xdg_popup *wlr_xdg_popup; struct ky_scene_tree *popup_tree; /* may a view/layer tree or popup tree */ struct ky_scene_tree *parent_tree; /* shell tree xdg-shell or layer-shell */ struct ky_scene_tree *shell_tree; /* for shadow and blur */ struct ky_scene_decoration *deco; struct wl_listener destroy; struct wl_listener new_popup; struct wl_listener commit; struct wl_listener reposition; struct wl_listener map; struct wl_listener unmap; struct wl_listener theme_update; struct view *parent_view; struct wl_listener parent_position; struct wl_listener parent_unmap; uint32_t decoration_flags; bool topmost_popup; bool topmost_force_enter; bool use_usable_area; }; enum xdg_popup_decoration_flags { ROUND_CORNER = 0, BORDER, SHADOW }; static void handle_xdg_popup_destroy(struct wl_listener *listener, void *data) { struct xdg_popup *popup = wl_container_of(listener, popup, destroy); wl_list_remove(&popup->destroy.link); wl_list_remove(&popup->new_popup.link); wl_list_remove(&popup->commit.link); wl_list_remove(&popup->reposition.link); wl_list_remove(&popup->map.link); wl_list_remove(&popup->unmap.link); wl_list_remove(&popup->theme_update.link); wl_list_remove(&popup->parent_position.link); wl_list_remove(&popup->parent_unmap.link); /* only need to destroy the topmost popup parent tree, * popup tree will be destroyed by xdg_surface destroy in scene */ if (popup->topmost_popup) { ky_scene_node_destroy(&popup->parent_tree->node); } free(popup); } static struct xdg_popup *_xdg_popup_create(struct wlr_xdg_popup *wlr_xdg_popup, struct ky_scene_tree *parent, struct ky_scene_tree *shell, bool use_usable_area); static void popup_handle_new_xdg_popup(struct wl_listener *listener, void *data) { struct xdg_popup *popup = wl_container_of(listener, popup, new_popup); struct wlr_xdg_popup *wlr_popup = data; _xdg_popup_create(wlr_popup, popup->popup_tree, popup->shell_tree, popup->use_usable_area); } static void popup_unconstrain(struct xdg_popup *popup) { /* TODO: popup unconstrain output, add input_manager_get_last_seat */ struct output *output = input_current_output(input_manager_get_default_seat()); struct kywc_box *output_box = popup->use_usable_area ? &output->usable_area : &output->geometry; int lx = 0, ly = 0; ky_scene_node_coords(&popup->shell_tree->node, &lx, &ly); struct wlr_box toplevel_space_box = { .x = output_box->x - lx, .y = output_box->y - ly, .width = output_box->width, .height = output_box->height, }; wlr_xdg_popup_unconstrain_from_box(popup->wlr_xdg_popup, &toplevel_space_box); } static void handle_xdg_popup_commit(struct wl_listener *listener, void *data) { struct xdg_popup *popup = wl_container_of(listener, popup, commit); struct wlr_xdg_surface *xdg_surface = popup->wlr_xdg_popup->base; if (xdg_surface->initial_commit) { popup_unconstrain(popup); } if (popup->deco) { struct wlr_box *geo = &xdg_surface->current.geometry; int width = geo->width, height = geo->height; if (width == 0 || height == 0) { width = xdg_surface->surface->current.width; height = xdg_surface->surface->current.height; } ky_scene_decoration_set_surface_size(popup->deco, width, height); } } static void handle_xdg_popup_reposition(struct wl_listener *listener, void *data) { struct xdg_popup *popup = wl_container_of(listener, popup, reposition); popup_unconstrain(popup); } static void xdg_popup_update_decoration(struct xdg_popup *xdg_popup, uint32_t flags) { struct theme *theme = theme_manager_get_theme(); if ((flags >> ROUND_CORNER) & 0x1) { int r = theme->menu_radius; struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(xdg_popup->wlr_xdg_popup->base->surface); ky_scene_node_set_radius(&buffer->node, (int[4]){ r, r, r, r }); ky_scene_decoration_set_round_corner_radius(xdg_popup->deco, (int[4]){ r, r, r, r }); } if ((flags >> BORDER) & 0x1) { float border_color[4]; color_float_pa(border_color, theme->active_border_color); ky_scene_decoration_set_margin(xdg_popup->deco, 0, theme->border_width); ky_scene_decoration_set_margin_color(xdg_popup->deco, (float[4]){ 0, 0, 0, 0 }, border_color); } if ((flags >> SHADOW) & 0x1) { ky_scene_decoration_set_shadow_count(xdg_popup->deco, theme->menu_shadow_color.num_layers); for (int i = 0; i < theme->menu_shadow_color.num_layers; i++) { struct theme_shadow_layer *shadow = &theme->menu_shadow_color.layers[i]; float shadow_color[4]; color_float_pa(shadow_color, shadow->color); ky_scene_decoration_set_shadow(xdg_popup->deco, i, shadow->off_x, shadow->off_y, shadow->spread, shadow->blur, shadow_color); } } int offset = (flags >> BORDER) & 0x1 ? -theme->border_width : 0; ky_scene_node_set_position(ky_scene_node_from_decoration(xdg_popup->deco), offset, offset); } static void create_popup_decoration(struct xdg_popup *xdg_popup) { struct wlr_xdg_surface *xdg_surface = xdg_popup->wlr_xdg_popup->base; uint32_t flags = ukui_shell_get_surface_decoration_flags(xdg_surface->surface); if (flags == XDG_POPUP_NO_DECORATION) { return; } xdg_popup->deco = ky_scene_decoration_create(xdg_popup->popup_tree); theme_manager_add_update_listener(&xdg_popup->theme_update, false); struct ky_scene_node *node = ky_scene_node_from_decoration(xdg_popup->deco); ky_scene_node_lower_to_bottom(node); ky_scene_node_set_input_bypassed(node, true); struct wlr_box *geo = &xdg_surface->current.geometry; int width = geo->width, height = geo->height; if (width == 0 || height == 0) { width = xdg_surface->surface->current.width; height = xdg_surface->surface->current.height; } ky_scene_decoration_set_surface_size(xdg_popup->deco, width, height); ky_scene_decoration_set_mask(xdg_popup->deco, DECORATION_MASK_ALL); xdg_popup->decoration_flags = flags; xdg_popup_update_decoration(xdg_popup, flags); } static void action_effect_options_adjust(enum action_effect_options_step step, enum effect_action action, struct action_effect_options *options, void *user_data) { struct xdg_popup *popup = user_data; switch (step) { case ACTION_EFFECT_OPTIONS_ADD: /* FADE_TYPE_CENTER */ options->style = 0; options->alpha = 0.0; options->width_scale = 1.0; options->height_scale = 1.0; bool topmost = popup->topmost_popup; bool seat = popup->wlr_xdg_popup->seat; if (action == EFFECT_ACTION_MAP) { options->duration = 220; if (topmost && !seat) { /* tooltips */ options->width_scale = 0.85; options->height_scale = 0.85; options->duration = 200; } else if (!topmost) { /* sub menu */ options->y_offset = -4; } } else { options->duration = 180; if (topmost && !seat) { options->width_scale = 0.85; options->height_scale = 0.85; options->duration = 150; } else if (!topmost) { options->y_offset = 4; } } struct animation *animation; if (topmost && seat) { animation = animation_manager_get(ANIMATION_TYPE_30_2_8_100); options->animations.alpha = animation; options->animations.geometry = animation; } else { animation = animation_manager_get(ANIMATION_TYPE_EASE); options->animations.alpha = animation; options->animations.geometry = animation; } options->surface = popup->wlr_xdg_popup->base->surface; options->scale = options->surface ? ky_scene_surface_get_scale(options->surface) : 1.0f; break; case ACTION_EFFECT_OPTIONS_SURFACE: case ACTION_EFFECT_OPTIONS_CONFIRM: break; } } static void handle_xdg_popup_map(struct wl_listener *listener, void *data) { struct xdg_popup *popup = wl_container_of(listener, popup, map); struct wlr_seat *seat = popup->wlr_xdg_popup->seat; /* force enter the popup surface if has grab */ popup->topmost_force_enter = popup->topmost_popup && seat; if (popup->topmost_force_enter) { struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat); if (keyboard) { wlr_seat_keyboard_enter(seat, popup->wlr_xdg_popup->base->surface, keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); } else { wlr_seat_keyboard_enter(seat, popup->wlr_xdg_popup->base->surface, NULL, 0, NULL); } } create_popup_decoration(popup); struct ky_scene_node *node = &popup->popup_tree->node; ky_scene_node_set_enabled(node, true); node_add_action_effect(node, EFFECT_ACTION_MAP, ACTION_EFFECT_FADE, action_effect_options_adjust, popup); cursor_rebase_all(false); } static void handle_xdg_popup_unmap(struct wl_listener *listener, void *data) { struct xdg_popup *popup = wl_container_of(listener, popup, unmap); struct ky_scene_node *node = &popup->popup_tree->node; node_add_action_effect(node, EFFECT_ACTION_UNMAP, ACTION_EFFECT_FADE, action_effect_options_adjust, popup); ky_scene_node_set_enabled(node, false); if (popup->deco) { ky_scene_node_destroy(ky_scene_node_from_decoration(popup->deco)); wl_list_remove(&popup->theme_update.link); wl_list_init(&popup->theme_update.link); popup->deco = NULL; } if (popup->topmost_force_enter) { /* end grab before view_activate_topmost */ wlr_seat_keyboard_end_grab(popup->wlr_xdg_popup->seat); struct view *parent = popup->parent_view; struct view *activated_view = view_manager_get_activated(); if (activated_view && activated_view != parent) { return; } if (parent && KYWC_VIEW_IS_ACTIVATABLE(&parent->base) && KYWC_VIEW_IS_FOCUSABLE(&parent->base)) { view_do_activate(parent); view_set_focus(parent, seat_from_wlr_seat(popup->wlr_xdg_popup->seat)); } else { view_activate_topmost(false); } } } static void xdg_popup_handle_theme_update(struct wl_listener *listener, void *data) { struct xdg_popup *xdg_popup = wl_container_of(listener, xdg_popup, theme_update); struct theme_update_event *update_event = data; uint32_t allowed_mask = THEME_UPDATE_MASK_CORNER_RADIUS | THEME_UPDATE_MASK_OPACITY | THEME_UPDATE_MASK_BORDER_COLOR | THEME_UPDATE_MASK_SHADOW_COLOR; if (update_event->update_mask & allowed_mask) { /* force update all items */ xdg_popup_update_decoration(xdg_popup, xdg_popup->decoration_flags); } } static void xdg_popup_handle_parent_position(struct wl_listener *listener, void *data) { struct xdg_popup *xdg_popup = wl_container_of(listener, xdg_popup, parent_position); int lx = xdg_popup->parent_view->base.geometry.x; int ly = xdg_popup->parent_view->base.geometry.y; ky_scene_node_set_position(&xdg_popup->parent_tree->node, lx, ly); } static void xdg_popup_handle_parent_unmap(struct wl_listener *listener, void *data) { struct xdg_popup *xdg_popup = wl_container_of(listener, xdg_popup, parent_unmap); wl_list_remove(&xdg_popup->parent_position.link); wl_list_init(&xdg_popup->parent_position.link); wl_list_remove(&xdg_popup->parent_unmap.link); wl_list_init(&xdg_popup->parent_unmap.link); xdg_popup->parent_view = NULL; } static struct xdg_popup *_xdg_popup_create(struct wlr_xdg_popup *wlr_xdg_popup, struct ky_scene_tree *parent, struct ky_scene_tree *shell, bool use_usable_area) { struct xdg_popup *popup = calloc(1, sizeof(*popup)); if (!popup) { return NULL; } popup->wlr_xdg_popup = wlr_xdg_popup; popup->parent_tree = parent; popup->shell_tree = shell; popup->use_usable_area = use_usable_area; /* add popup surface to parent tree, popup map and unmap is handled in scene */ popup->popup_tree = ky_scene_xdg_surface_create(parent, wlr_xdg_popup->base); ky_scene_node_set_enabled(&popup->popup_tree->node, wlr_xdg_popup->base->surface->mapped); popup->destroy.notify = handle_xdg_popup_destroy; wl_signal_add(&wlr_xdg_popup->events.destroy, &popup->destroy); popup->reposition.notify = handle_xdg_popup_reposition; wl_signal_add(&wlr_xdg_popup->events.reposition, &popup->reposition); popup->new_popup.notify = popup_handle_new_xdg_popup; wl_signal_add(&wlr_xdg_popup->base->events.new_popup, &popup->new_popup); popup->commit.notify = handle_xdg_popup_commit; wl_signal_add(&wlr_xdg_popup->base->surface->events.commit, &popup->commit); popup->map.notify = handle_xdg_popup_map; wl_signal_add(&wlr_xdg_popup->base->surface->events.map, &popup->map); popup->unmap.notify = handle_xdg_popup_unmap; wl_signal_add(&wlr_xdg_popup->base->surface->events.unmap, &popup->unmap); popup->theme_update.notify = xdg_popup_handle_theme_update; wl_list_init(&popup->theme_update.link); popup->parent_position.notify = xdg_popup_handle_parent_position; wl_list_init(&popup->parent_position.link); popup->parent_unmap.notify = xdg_popup_handle_parent_unmap; wl_list_init(&popup->parent_unmap.link); return popup; } static bool xdg_popup_hover(struct seat *seat, struct ky_scene_node *node, double x, double y, uint32_t time, bool first, bool hold, void *data) { struct wlr_surface *surface = wlr_surface_try_from_node(node); if (first) { kywc_log(KYWC_DEBUG, "First hover surface %p (%f %f)", surface, x, y); } seat_notify_motion(seat, surface, time, x, y, first); return false; } static void xdg_popup_click(struct seat *seat, struct ky_scene_node *node, uint32_t button, bool pressed, uint32_t time, enum click_state state, void *data) { seat_notify_button(seat, time, button, pressed); } static void xdg_popup_leave(struct seat *seat, struct ky_scene_node *node, bool last, void *data) { /* so surface will call set_cursor when enter again */ struct wlr_surface *surface = wlr_surface_try_from_node(node); seat_notify_leave(seat, surface); } static struct ky_scene_node *xdg_popup_get_root(void *data) { struct xdg_popup *popup = data; return &popup->parent_tree->node; } static const struct input_event_node_impl xdg_popup_event_node_impl = { .hover = xdg_popup_hover, .click = xdg_popup_click, .leave = xdg_popup_leave, }; void xdg_popup_create(struct wlr_xdg_popup *wlr_xdg_popup, struct ky_scene_tree *shell, struct view_layer *layer, bool use_usable_area) { struct ky_scene_tree *parent = ky_scene_tree_create(layer->tree); /* get shell layout coord, and set it to parent tree */ int lx, ly; ky_scene_node_coords(&shell->node, &lx, &ly); ky_scene_node_set_position(&parent->node, lx, ly); struct xdg_popup *popup = _xdg_popup_create(wlr_xdg_popup, parent, shell, use_usable_area); popup->topmost_popup = true; struct view *parent_view = view_try_from_wlr_surface(wlr_xdg_popup->parent); popup->parent_view = parent_view; if (parent_view) { wl_signal_add(&parent_view->base.events.position, &popup->parent_position); wl_signal_add(&parent_view->base.events.destroy, &popup->parent_unmap); } input_event_node_create(&parent->node, &xdg_popup_event_node_impl, xdg_popup_get_root, NULL, popup); } kylin-wayland-compositor/src/view/kde_plasma_window.c0000664000175000017500000010266515160461067022073 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "plasma-window-management-protocol.h" #include "theme.h" #include "view_p.h" #define PLASMA_WINDOW_MANAGEMENT_VERSION 17 struct kde_plasma_window_management { struct wl_global *global; struct wl_list resources; struct wl_list windows; struct wl_listener new_mapped_view; struct wl_listener show_desktop; struct wl_listener display_destroy; struct wl_listener server_destroy; uint32_t window_id_counter; }; struct kde_plasma_window { struct wl_list resources; struct wl_list link; struct kde_plasma_window_management *management; bool minimizable, maximizable, fullscreenable; bool closeable, movable, resizable; struct kywc_view *kywc_view; struct wl_listener view_unmap; struct wl_listener view_title; struct wl_listener view_app_id; struct wl_listener view_activate; struct wl_listener view_minimize; struct wl_listener view_maximize; struct wl_listener view_fullscreen; struct wl_listener view_above; struct wl_listener view_below; struct wl_listener view_skip_taskbar; struct wl_listener view_skip_switcher; struct wl_listener view_demands_attention; struct wl_listener view_capabilities; struct wl_listener view_position; struct wl_listener view_size; struct wl_listener view_icon_update; struct wl_listener view_update_capabilities; /* The internal window id and uuid */ uint32_t id; const char *uuid; /* bitfield of state flags */ uint32_t states; }; enum state_flag { STATE_FLAG_ACTIVE = 0, STATE_FLAG_MINIMIZED, STATE_FLAG_MAXIMIZED, STATE_FLAG_FULLSCREEN, STATE_FLAG_KEEP_ABOVE, STATE_FLAG_KEEP_BELOW, STATE_FLAG_ON_ALL_DESKTOPS, STATE_FLAG_DEMANDS_ATTENTION, STATE_FLAG_CLOSEABLE, STATE_FLAG_MINIMIZABLE, STATE_FLAG_MAXIMIZABLE, STATE_FLAG_FULLSCREENABLE, STATE_FLAG_SKIPTASKBAR, STATE_FLAG_SHADEABLE, STATE_FLAG_SHADED, STATE_FLAG_MOVABLE, STATE_FLAG_RESIZABLE, STATE_FLAG_VIRTUAL_DESKTOP_CHANGEABLE, STATE_FLAG_SKIPSWITCHER, STATE_FLAG_LAST, }; static void kde_plasma_window_set_state(struct kde_plasma_window *window, enum state_flag flag, bool state) { uint32_t mask = 0; switch (flag) { case STATE_FLAG_ACTIVE: assert(state); kywc_view_activate(window->kywc_view); view_set_focus(view_from_kywc_view(window->kywc_view), input_manager_get_default_seat()); break; case STATE_FLAG_MINIMIZED: kywc_view_set_minimized(window->kywc_view, state); break; case STATE_FLAG_MAXIMIZED: kywc_view_set_maximized(window->kywc_view, state, NULL); break; case STATE_FLAG_FULLSCREEN: kywc_view_set_fullscreen(window->kywc_view, state, NULL); break; case STATE_FLAG_KEEP_ABOVE: kywc_view_set_kept_above(window->kywc_view, state); break; case STATE_FLAG_KEEP_BELOW: kywc_view_set_kept_below(window->kywc_view, state); break; case STATE_FLAG_ON_ALL_DESKTOPS: break; case STATE_FLAG_DEMANDS_ATTENTION: window->kywc_view->demands_attention = state; break; case STATE_FLAG_CLOSEABLE: window->closeable = state; mask |= KYWC_VIEW_CLOSEABLE; break; case STATE_FLAG_MINIMIZABLE: window->minimizable = state; mask |= KYWC_VIEW_MINIMIZABLE | KYWC_VIEW_MINIMIZE_BUTTON; break; case STATE_FLAG_MAXIMIZABLE: window->maximizable = state; mask |= KYWC_VIEW_MAXIMIZABLE | KYWC_VIEW_MAXIMIZE_BUTTON; break; case STATE_FLAG_FULLSCREENABLE: window->fullscreenable = state; mask |= KYWC_VIEW_FULLSCREENABLE; break; case STATE_FLAG_SKIPTASKBAR: window->kywc_view->skip_taskbar = state; break; case STATE_FLAG_SHADEABLE: break; case STATE_FLAG_SHADED: break; case STATE_FLAG_MOVABLE: window->movable = state; mask |= KYWC_VIEW_MOVABLE; break; case STATE_FLAG_RESIZABLE: window->resizable = state; mask |= KYWC_VIEW_RESIZABLE; break; case STATE_FLAG_VIRTUAL_DESKTOP_CHANGEABLE: break; case STATE_FLAG_SKIPSWITCHER: window->kywc_view->skip_switcher = state; break; case STATE_FLAG_LAST: break; } if (mask != 0) { view_update_capabilities(view_from_kywc_view(window->kywc_view), mask); } } static void kde_plasma_window_send_state(struct kde_plasma_window *window, struct wl_resource *resource, bool force); static void handle_set_state(struct wl_client *client, struct wl_resource *resource, uint32_t flags, uint32_t state) { struct kde_plasma_window *window = wl_resource_get_user_data(resource); if (!window) { return; } for (int i = 0; i < STATE_FLAG_LAST; i++) { if ((flags >> i) & 0x1) { kde_plasma_window_set_state(window, i, (state >> i) & 0x1); } } kde_plasma_window_send_state(window, NULL, false); } static void handle_set_virtual_desktop(struct wl_client *client, struct wl_resource *resource, uint32_t number) { // Not implemented yet } static void handle_set_minimized_geometry(struct wl_client *client, struct wl_resource *resource, struct wl_resource *panel, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { // Not implemented yet } static void handle_unset_minimized_geometry(struct wl_client *client, struct wl_resource *resource, struct wl_resource *panel) { // Not implemented yet } static void handle_close(struct wl_client *client, struct wl_resource *resource) { struct kde_plasma_window *window = wl_resource_get_user_data(resource); if (!window) { return; } kywc_view_close(window->kywc_view); } static void handle_request_move(struct wl_client *client, struct wl_resource *resource) { // Not implemented yet } static void handle_request_resize(struct wl_client *client, struct wl_resource *resource) { // Not implemented yet } static void handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void handle_get_icon(struct wl_client *client, struct wl_resource *resource, int32_t fd) { // Not implemented yet } static void handle_request_enter_virtual_desktop(struct wl_client *client, struct wl_resource *resource, const char *id) { // Not implemented yet } static void handle_request_enter_new_virtual_desktop(struct wl_client *client, struct wl_resource *resource) { // Not implemented yet } static void handle_request_leave_virtual_desktop(struct wl_client *client, struct wl_resource *resource, const char *id) { // Not implemented yet } static void handle_request_enter_activity(struct wl_client *client, struct wl_resource *resource, const char *id) { // Not implemented yet } static void handle_request_leave_activity(struct wl_client *client, struct wl_resource *resource, const char *id) { // Not implemented yet } static void handle_send_to_output(struct wl_client *client, struct wl_resource *resource, struct wl_resource *output) { // Not implemented yet } static const struct org_kde_plasma_window_interface kde_plasma_window_impl = { .set_state = handle_set_state, .set_virtual_desktop = handle_set_virtual_desktop, .set_minimized_geometry = handle_set_minimized_geometry, .unset_minimized_geometry = handle_unset_minimized_geometry, .close = handle_close, .request_move = handle_request_move, .request_resize = handle_request_resize, .destroy = handle_destroy, .get_icon = handle_get_icon, .request_enter_virtual_desktop = handle_request_enter_virtual_desktop, .request_enter_new_virtual_desktop = handle_request_enter_new_virtual_desktop, .request_leave_virtual_desktop = handle_request_leave_virtual_desktop, .request_enter_activity = handle_request_enter_activity, .request_leave_activity = handle_request_leave_activity, .send_to_output = handle_send_to_output, }; static struct kde_plasma_window * kde_plasma_window_from_id(struct kde_plasma_window_management *management, uint32_t id) { struct kde_plasma_window *window; wl_list_for_each(window, &management->windows, link) { if (window->id == id) { return window; } } return NULL; } static struct kde_plasma_window * kde_plasma_window_from_uuid(struct kde_plasma_window_management *management, const char *uuid) { struct kde_plasma_window *window; wl_list_for_each(window, &management->windows, link) { if (strcmp(window->uuid, uuid) == 0) { return window; } } return NULL; } static void window_handle_resource_destroy(struct wl_resource *resource) { wl_resource_set_destructor(resource, NULL); wl_resource_set_user_data(resource, NULL); wl_list_remove(wl_resource_get_link(resource)); } static void window_handle_view_title(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_title); if (!window->kywc_view->title) { return; } struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_title_changed(resource, window->kywc_view->title); } } static void window_handle_view_app_id(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_app_id); if (!window->kywc_view->app_id) { return; } struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_app_id_changed(resource, window->kywc_view->app_id); } } #define set_state(states, prop, state) \ if (prop) { \ states |= ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_##state; \ } else { \ states &= ~ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_##state; \ } static void window_handle_view_activate(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_activate); set_state(window->states, window->kywc_view->activated, ACTIVE); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_state_changed(resource, window->states); } } static void window_handle_view_minimize(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_minimize); set_state(window->states, window->kywc_view->minimized, MINIMIZED); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_state_changed(resource, window->states); } } static void window_handle_view_maximize(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_maximize); set_state(window->states, window->kywc_view->maximized, MAXIMIZED); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_state_changed(resource, window->states); } } static void window_handle_view_fullscreen(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_fullscreen); set_state(window->states, window->kywc_view->fullscreen, FULLSCREEN); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_state_changed(resource, window->states); } } static void window_handle_view_above(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_above); set_state(window->states, window->kywc_view->kept_above, KEEP_ABOVE); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_state_changed(resource, window->states); } } static void window_handle_view_below(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_below); set_state(window->states, window->kywc_view->kept_below, KEEP_BELOW); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_state_changed(resource, window->states); } } static void window_handle_view_skip_taskbar(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_skip_taskbar); set_state(window->states, window->kywc_view->skip_taskbar, SKIPTASKBAR); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_state_changed(resource, window->states); } } static void window_handle_view_skip_switcher(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_skip_switcher); set_state(window->states, window->kywc_view->skip_switcher, SKIPSWITCHER); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_state_changed(resource, window->states); } } static void window_handle_view_demands_attention(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_demands_attention); set_state(window->states, window->kywc_view->demands_attention, DEMANDS_ATTENTION); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_state_changed(resource, window->states); } } static void window_handle_view_capabilities(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_capabilities); struct kywc_view_capabilities_event *event = data; if (event->mask & (KYWC_VIEW_MINIMIZABLE | KYWC_VIEW_MAXIMIZABLE | KYWC_VIEW_CLOSEABLE | KYWC_VIEW_FULLSCREENABLE | KYWC_VIEW_MOVABLE | KYWC_VIEW_RESIZABLE)) { kde_plasma_window_send_state(window, NULL, false); } } static void window_handle_view_position(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_position); struct kywc_view *view = window->kywc_view; int32_t x = view->geometry.x - view->margin.off_x; int32_t y = view->geometry.y - view->margin.off_y; uint32_t width = view->geometry.width + view->margin.off_width; uint32_t height = view->geometry.height + view->margin.off_height; struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_geometry(resource, x, y, width, height); } } static void window_handle_view_size(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_size); struct kywc_view *view = window->kywc_view; int32_t x = view->geometry.x - view->margin.off_x; int32_t y = view->geometry.y - view->margin.off_y; uint32_t width = view->geometry.width + view->margin.off_width; uint32_t height = view->geometry.height + view->margin.off_height; struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_geometry(resource, x, y, width, height); } } static void kde_plasma_window_send_state(struct kde_plasma_window *window, struct wl_resource *resource, bool force) { struct kywc_view *kywc_view = window->kywc_view; uint32_t states = window->states; set_state(states, kywc_view->activated, ACTIVE); set_state(states, kywc_view->minimized, MINIMIZED); set_state(states, kywc_view->maximized, MAXIMIZED); set_state(states, kywc_view->fullscreen, FULLSCREEN); set_state(states, kywc_view->kept_above, KEEP_ABOVE); set_state(states, kywc_view->kept_below, KEEP_BELOW); // ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_ON_ALL_DESKTOPS set_state(states, kywc_view->demands_attention, DEMANDS_ATTENTION); set_state(states, KYWC_VIEW_IS_CLOSEABLE(kywc_view), CLOSEABLE); set_state(states, KYWC_VIEW_IS_MINIMIZABLE(kywc_view), MINIMIZABLE); set_state(states, KYWC_VIEW_IS_MAXIMIZABLE(kywc_view), MAXIMIZABLE); set_state(states, KYWC_VIEW_IS_FULLSCREENABLE(kywc_view), FULLSCREENABLE); set_state(states, kywc_view->skip_taskbar, SKIPTASKBAR); // ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_SHADEABLE // ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_SHADED set_state(states, KYWC_VIEW_IS_MOVABLE(kywc_view), MOVABLE); set_state(states, KYWC_VIEW_IS_RESIZABLE(kywc_view), RESIZABLE); // ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_VIRTUAL_DESKTOP_CHANGEABLE set_state(states, kywc_view->skip_switcher, SKIPSWITCHER); if (force || states != window->states) { window->states = states; if (resource) { org_kde_plasma_window_send_state_changed(resource, window->states); } else { struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_state_changed(resource, window->states); } } } } #undef set_state static void kde_plasma_window_add_resource(struct kde_plasma_window *window, struct wl_resource *management_resource, uint32_t id) { struct wl_client *client = wl_resource_get_client(management_resource); uint32_t version = wl_resource_get_version(management_resource); struct wl_resource *resource = wl_resource_create(client, &org_kde_plasma_window_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_list_insert(&window->resources, wl_resource_get_link(resource)); wl_resource_set_implementation(resource, &kde_plasma_window_impl, window, window_handle_resource_destroy); /* send states */ kde_plasma_window_send_state(window, resource, true); struct kywc_view *kywc_view = window->kywc_view; struct view *view = view_from_kywc_view(kywc_view); if (kywc_view->title) { org_kde_plasma_window_send_title_changed(resource, kywc_view->title); } if (kywc_view->app_id) { org_kde_plasma_window_send_app_id_changed(resource, kywc_view->app_id); } if (view->pid) { org_kde_plasma_window_send_pid_changed(resource, view->pid); } int32_t x = kywc_view->geometry.x - kywc_view->margin.off_x; int32_t y = kywc_view->geometry.y - kywc_view->margin.off_y; uint32_t width = kywc_view->geometry.width + kywc_view->margin.off_width; uint32_t height = kywc_view->geometry.height + kywc_view->margin.off_height; org_kde_plasma_window_send_geometry(resource, x, y, width, height); // org_kde_plasma_window_send_parent_window // org_kde_plasma_window_send_virtual_desktop_changed // org_kde_plasma_window_send_virtual_desktop_entered // org_kde_plasma_window_send_virtual_desktop_left if (!theme_icon_is_fallback(view->icon)) { const char *icon_name = theme_icon_get_name(view->icon); org_kde_plasma_window_send_themed_icon_name_changed(resource, icon_name); } // org_kde_plasma_window_send_icon_changed // org_kde_plasma_window_send_application_menu // org_kde_plasma_window_send_activity_entered // org_kde_plasma_window_send_activity_left // org_kde_plasma_window_send_resource_name_changed org_kde_plasma_window_send_initial_state(resource); } /* deprecated */ static void handle_get_window(struct wl_client *client, struct wl_resource *management_resource, uint32_t id, uint32_t internal_window_id) { struct kde_plasma_window_management *management = wl_resource_get_user_data(management_resource); struct kde_plasma_window *window = kde_plasma_window_from_id(management, internal_window_id); if (!window) { return; } kde_plasma_window_add_resource(window, management_resource, id); } static void handle_get_window_by_uuid(struct wl_client *client, struct wl_resource *management_resource, uint32_t id, const char *internal_window_uuid) { struct kde_plasma_window_management *management = wl_resource_get_user_data(management_resource); struct kde_plasma_window *window = kde_plasma_window_from_uuid(management, internal_window_uuid); if (!window) { return; } kde_plasma_window_add_resource(window, management_resource, id); } static void handle_show_desktop(struct wl_client *client, struct wl_resource *resource, uint32_t state) { view_manager_show_desktop(state == ORG_KDE_PLASMA_WINDOW_MANAGEMENT_SHOW_DESKTOP_ENABLED, true); } static void handle_get_stacking_order(struct wl_client *client, struct wl_resource *management_resource, uint32_t id) { uint32_t version = wl_resource_get_version(management_resource); struct wl_resource *resource = wl_resource_create(client, &org_kde_plasma_stacking_order_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } // TODO: org_kde_plasma_stacking_order_send_window and org_kde_plasma_stacking_order_send_done wl_resource_destroy(resource); } static const struct org_kde_plasma_window_management_interface kde_plasma_window_management_impl = { .show_desktop = handle_show_desktop, .get_window = handle_get_window, .get_window_by_uuid = handle_get_window_by_uuid, .get_stacking_order = handle_get_stacking_order, }; static void management_handle_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static void kde_plasma_window_management_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct kde_plasma_window_management *management = data; struct wl_resource *resource = wl_resource_create(client, &org_kde_plasma_window_management_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_list_insert(&management->resources, wl_resource_get_link(resource)); wl_resource_set_implementation(resource, &kde_plasma_window_management_impl, management, management_handle_resource_destroy); org_kde_plasma_window_management_send_show_desktop_changed( resource, view_manager_get_show_desktop() ? ORG_KDE_PLASMA_WINDOW_MANAGEMENT_SHOW_DESKTOP_ENABLED : ORG_KDE_PLASMA_WINDOW_MANAGEMENT_SHOW_DESKTOP_DISABLED); struct kde_plasma_window *window; wl_list_for_each(window, &management->windows, link) { if (version >= ORG_KDE_PLASMA_WINDOW_MANAGEMENT_WINDOW_WITH_UUID_SINCE_VERSION) { org_kde_plasma_window_management_send_window_with_uuid(resource, window->id, window->uuid); } else { org_kde_plasma_window_management_send_window(resource, window->id); } } if (version >= ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STACKING_ORDER_CHANGED_2_SINCE_VERSION) { org_kde_plasma_window_management_send_stacking_order_changed_2(resource); } } static void window_handle_view_unmap(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_unmap); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { org_kde_plasma_window_send_unmapped(resource); } wl_list_remove(&window->view_unmap.link); wl_list_remove(&window->view_title.link); wl_list_remove(&window->view_app_id.link); wl_list_remove(&window->view_activate.link); wl_list_remove(&window->view_minimize.link); wl_list_remove(&window->view_maximize.link); wl_list_remove(&window->view_fullscreen.link); wl_list_remove(&window->view_above.link); wl_list_remove(&window->view_below.link); wl_list_remove(&window->view_skip_taskbar.link); wl_list_remove(&window->view_skip_switcher.link); wl_list_remove(&window->view_demands_attention.link); wl_list_remove(&window->view_capabilities.link); wl_list_remove(&window->view_position.link); wl_list_remove(&window->view_size.link); wl_list_remove(&window->view_icon_update.link); wl_list_remove(&window->view_update_capabilities.link); wl_list_remove(&window->link); struct wl_resource *tmp; wl_resource_for_each_safe(resource, tmp, &window->resources) { window_handle_resource_destroy(resource); } free(window); } static void window_handle_view_icon_update(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_icon_update); struct view *view = view_from_kywc_view(window->kywc_view); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { if (!theme_icon_is_fallback(view->icon)) { const char *icon_name = theme_icon_get_name(view->icon); org_kde_plasma_window_send_themed_icon_name_changed(resource, icon_name); } } } static void window_handle_view_update_capabilities(struct wl_listener *listener, void *data) { struct kde_plasma_window *window = wl_container_of(listener, window, view_update_capabilities); struct view_update_capabilities_event *event = data; if (event->mask & KYWC_VIEW_MINIMIZABLE) { if (!window->minimizable) { event->state &= ~KYWC_VIEW_MINIMIZABLE; } } if (event->mask & KYWC_VIEW_MAXIMIZABLE) { if (!window->maximizable) { event->state &= ~KYWC_VIEW_MAXIMIZABLE; } } if (event->mask & KYWC_VIEW_CLOSEABLE) { if (!window->closeable) { event->state &= ~KYWC_VIEW_CLOSEABLE; } } if (event->mask & KYWC_VIEW_FULLSCREENABLE) { if (!window->fullscreenable) { event->state &= ~KYWC_VIEW_FULLSCREENABLE; } } if (event->mask & KYWC_VIEW_MOVABLE) { if (!window->movable) { event->state &= ~KYWC_VIEW_MOVABLE; } } if (event->mask & KYWC_VIEW_RESIZABLE) { if (!window->resizable) { event->state &= ~KYWC_VIEW_RESIZABLE; } } if (event->mask & KYWC_VIEW_MINIMIZE_BUTTON) { if (!window->minimizable) { event->state &= ~KYWC_VIEW_MINIMIZE_BUTTON; } } if (event->mask & KYWC_VIEW_MAXIMIZE_BUTTON) { if (!window->maximizable) { event->state &= ~KYWC_VIEW_MAXIMIZE_BUTTON; } } } static void handle_new_mapped_view(struct wl_listener *listener, void *data) { struct kde_plasma_window_management *management = wl_container_of(listener, management, new_mapped_view); struct kywc_view *kywc_view = data; struct kde_plasma_window *window = calloc(1, sizeof(*window)); if (!window) { return; } window->management = management; wl_list_init(&window->resources); wl_list_insert(&management->windows, &window->link); window->id = management->window_id_counter++; window->uuid = kywc_view->uuid; window->minimizable = window->maximizable = window->fullscreenable = window->closeable = window->movable = window->resizable = true; window->kywc_view = kywc_view; window->view_unmap.notify = window_handle_view_unmap; wl_signal_add(&kywc_view->events.unmap, &window->view_unmap); window->view_title.notify = window_handle_view_title; wl_signal_add(&kywc_view->events.title, &window->view_title); window->view_app_id.notify = window_handle_view_app_id; wl_signal_add(&kywc_view->events.app_id, &window->view_app_id); window->view_activate.notify = window_handle_view_activate; wl_signal_add(&kywc_view->events.activate, &window->view_activate); window->view_minimize.notify = window_handle_view_minimize; wl_signal_add(&kywc_view->events.minimize, &window->view_minimize); window->view_maximize.notify = window_handle_view_maximize; wl_signal_add(&kywc_view->events.maximize, &window->view_maximize); window->view_fullscreen.notify = window_handle_view_fullscreen; wl_signal_add(&kywc_view->events.fullscreen, &window->view_fullscreen); window->view_above.notify = window_handle_view_above; wl_signal_add(&kywc_view->events.above, &window->view_above); window->view_below.notify = window_handle_view_below; wl_signal_add(&kywc_view->events.below, &window->view_below); window->view_skip_taskbar.notify = window_handle_view_skip_taskbar; wl_signal_add(&kywc_view->events.skip_taskbar, &window->view_skip_taskbar); window->view_skip_switcher.notify = window_handle_view_skip_switcher; wl_signal_add(&kywc_view->events.skip_switcher, &window->view_skip_switcher); window->view_demands_attention.notify = window_handle_view_demands_attention; wl_signal_add(&kywc_view->events.demands_attention, &window->view_demands_attention); window->view_capabilities.notify = window_handle_view_capabilities; wl_signal_add(&kywc_view->events.capabilities, &window->view_capabilities); window->view_position.notify = window_handle_view_position; wl_signal_add(&kywc_view->events.position, &window->view_position); window->view_size.notify = window_handle_view_size; wl_signal_add(&kywc_view->events.size, &window->view_size); struct view *view = view_from_kywc_view(kywc_view); window->view_icon_update.notify = window_handle_view_icon_update; wl_signal_add(&view->events.icon_update, &window->view_icon_update); window->view_update_capabilities.notify = window_handle_view_update_capabilities; view_add_update_capabilities_listener(view, &window->view_update_capabilities); struct wl_resource *resource; wl_resource_for_each(resource, &management->resources) { // org_kde_plasma_window_management_send_window(resource, window->id); org_kde_plasma_window_management_send_window_with_uuid(resource, window->id, window->uuid); } } static void handle_shown_desktop(struct wl_listener *listener, void *data) { struct kde_plasma_window_management *management = wl_container_of(listener, management, show_desktop); bool enabled = view_manager_get_show_desktop(); struct wl_resource *resource; wl_resource_for_each(resource, &management->resources) { org_kde_plasma_window_management_send_show_desktop_changed( resource, enabled ? ORG_KDE_PLASMA_WINDOW_MANAGEMENT_SHOW_DESKTOP_ENABLED : ORG_KDE_PLASMA_WINDOW_MANAGEMENT_SHOW_DESKTOP_DISABLED); } } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct kde_plasma_window_management *management = wl_container_of(listener, management, display_destroy); wl_list_remove(&management->display_destroy.link); wl_list_remove(&management->new_mapped_view.link); wl_list_remove(&management->show_desktop.link); wl_global_destroy(management->global); } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct kde_plasma_window_management *management = wl_container_of(listener, management, server_destroy); wl_list_remove(&management->server_destroy.link); free(management); } bool kde_plasma_window_management_create(struct server *server) { struct kde_plasma_window_management *management = calloc(1, sizeof(*management)); if (!management) { return false; } management->global = wl_global_create( server->display, &org_kde_plasma_window_management_interface, PLASMA_WINDOW_MANAGEMENT_VERSION, management, kde_plasma_window_management_bind); if (!management->global) { kywc_log(KYWC_WARN, "Kde plasma window management create failed"); free(management); return false; } wl_list_init(&management->windows); wl_list_init(&management->resources); management->window_id_counter = 0; management->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &management->server_destroy); management->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &management->display_destroy); management->new_mapped_view.notify = handle_new_mapped_view; kywc_view_add_new_mapped_listener(&management->new_mapped_view); management->show_desktop.notify = handle_shown_desktop; view_manager_add_show_desktop_listener(&management->show_desktop); return true; } kylin-wayland-compositor/src/view/modal.c0000664000175000017500000002357315160461067017500 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "effect/node_transform.h" #include "effect/shake_view.h" #include "input/event.h" #include "render/renderer.h" #include "scene/render.h" #include "scene/surface.h" #include "theme.h" #include "util/time.h" #include "view_p.h" static float modal_color[4] = { 18.0 / 255, 18.0 / 255, 18.0 / 255, 51.0 / 255 }; struct modal_manager { struct wl_list modals; struct wl_listener new_mapped; struct wl_listener server_destroy; bool hardware_renderer; }; struct modal { struct wl_list link; struct modal_manager *modal_manager; struct view *view; struct ky_scene_rect *modal_box; struct kywc_box geo; struct wl_listener modal; struct wl_listener view_unmap; struct wl_listener parent_unmap; struct wl_listener parent_size; }; static bool modal_hover(struct seat *seat, struct ky_scene_node *node, double x, double y, uint32_t time, bool first, bool hold, void *data) { return false; } static void modal_leave(struct seat *seat, struct ky_scene_node *node, bool last, void *data) {} static void modal_click(struct seat *seat, struct ky_scene_node *node, uint32_t button, bool pressed, uint32_t time, enum click_state state, void *data) { if (!pressed) { return; } struct modal *modal = data; struct view *view = view_find_descendant_modal(modal->view); view = view ? view : modal->view; /* active current view */ kywc_view_activate(&view->base); view_set_focus(view, seat); if (modal->modal_manager->hardware_renderer) { view_add_shake_effect(view); } } static struct ky_scene_node *modal_get_root(void *data) { struct modal *modal = data; return &modal->modal_box->node; } static const struct input_event_node_impl modal_impl = { .hover = modal_hover, .leave = modal_leave, .click = modal_click, }; static void modal_add_rect_opacity_transform(struct ky_scene_node *node, int duration, struct kywc_box geo, float start_alpha, float end_alpha, int64_t start_time, enum animation_type alpha_type) { struct transform_options options = { .start_time = start_time, .duration = duration, .start = { .geometry = geo, .alpha = start_alpha }, .end = { .geometry = geo, .alpha = end_alpha }, .animations = { .alpha = animation_manager_get(alpha_type) }, }; node_add_transform_effect(node, &options); } static void modal_deactive(struct modal *modal) { wl_list_remove(&modal->parent_unmap.link); wl_list_remove(&modal->parent_size.link); wl_list_init(&modal->parent_unmap.link); wl_list_init(&modal->parent_size.link); if (modal->modal_box) { modal_add_rect_opacity_transform(&modal->modal_box->node, 200, modal->geo, 1.0, 0, current_time_msec(), ANIMATION_TYPE_EASE); ky_scene_node_destroy(&modal->modal_box->node); } } static void handle_parent_unmap(struct wl_listener *listener, void *data) { struct modal *modal = wl_container_of(listener, modal, parent_unmap); kywc_view_set_modal(&modal->view->base, false); } static void modal_box_set_round_corner(struct ky_scene_rect *modal_box, struct view *view); static void handle_parent_size(struct wl_listener *listener, void *data) { struct modal *modal = wl_container_of(listener, modal, parent_size); struct kywc_view *parent = &modal->view->parent->base; struct kywc_box geo = { .x = -parent->margin.off_x, .y = -parent->margin.off_y, .width = parent->geometry.width + parent->margin.off_width, .height = parent->geometry.height + parent->margin.off_height, }; struct kywc_box geometry = { parent->geometry.x + geo.x, parent->geometry.y + geo.y, geo.width, geo.height }; modal->geo = geometry; ky_scene_rect_set_size(modal->modal_box, geo.width, geo.height); ky_scene_node_set_position(&modal->modal_box->node, geo.x, geo.y); modal_box_set_round_corner(modal->modal_box, modal->view->parent); } static void modal_box_set_round_corner(struct ky_scene_rect *modal_box, struct view *view) { struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(view->surface); if (!buffer) { return; } int radius[4] = { 0 }; memcpy(radius, buffer->node.radius, sizeof(radius)); /* set top corner if has ssd title */ struct kywc_view *kywc_view = &view->base; if (kywc_view->ssd & KYWC_SSD_TITLE) { bool need_corner = !kywc_view->maximized && !kywc_view->fullscreen && !kywc_view->tiled; struct theme *theme = theme_manager_get_theme(); radius[KY_SCENE_ROUND_CORNER_RT] = need_corner ? theme->window_radius : 0; radius[KY_SCENE_ROUND_CORNER_LT] = need_corner ? theme->window_radius : 0; } ky_scene_node_set_radius(&modal_box->node, radius); } static void modal_box_render(struct ky_scene_node *node, int lx, int ly, struct ky_scene_render_target *target) { /* When the modal disappears, obtain the rect thumbnail and render it */ if (!node->enabled || (!node->has_effect && target->options & KY_SCENE_RENDER_DISABLE_EFFECT)) { return; } struct ky_scene_rect *rect = ky_scene_rect_from_node(node); if (rect->color[3] == 0) { return; } bool render_with_visibility = !(target->options & KY_SCENE_RENDER_DISABLE_VISIBILITY); if (render_with_visibility && !pixman_region32_not_empty(&node->visible_region) && !pixman_region32_not_empty(&node->extend_render_region)) { return; } ky_scene_rect_render(node, (struct kywc_box){ lx, ly, rect->width, rect->height }, rect->color, render_with_visibility, target); } static void modal_active(struct modal *modal) { if (!modal->view->parent || !modal->view->parent->surface) { return; } struct kywc_view *parent = &modal->view->parent->base; struct kywc_box geo = { .x = -parent->margin.off_x, .y = -parent->margin.off_y, .width = parent->geometry.width + parent->margin.off_width, .height = parent->geometry.height + parent->margin.off_height, }; modal->modal_box = ky_scene_rect_create(modal->view->parent->tree, geo.width, geo.height, modal_color); if (!modal->modal_box) { return; } modal->modal_box->node.impl.render = modal_box_render; ky_scene_node_raise_to_top(&modal->modal_box->node); ky_scene_node_set_position(&modal->modal_box->node, geo.x, geo.y); modal_box_set_round_corner(modal->modal_box, modal->view->parent); struct kywc_box geometry = { parent->geometry.x + geo.x, parent->geometry.y + geo.y, geo.width, geo.height }; modal->geo = geometry; modal_add_rect_opacity_transform(&modal->modal_box->node, 300, modal->geo, 0, 1.0, current_time_msec(), ANIMATION_TYPE_EASE); input_event_node_create(&modal->modal_box->node, &modal_impl, modal_get_root, NULL, modal); view_add_parent_workspace(modal->view); wl_signal_add(&parent->events.unmap, &modal->parent_unmap); wl_signal_add(&parent->events.size, &modal->parent_size); } static void handle_modal_view_unmap(struct wl_listener *listener, void *data) { struct modal *modal = wl_container_of(listener, modal, view_unmap); if (modal->view->base.modal) { modal_deactive(modal); } wl_list_remove(&modal->modal.link); wl_list_remove(&modal->parent_unmap.link); wl_list_remove(&modal->parent_size.link); wl_list_remove(&modal->view_unmap.link); wl_list_remove(&modal->link); free(modal); } static void handle_view_modal(struct wl_listener *listener, void *data) { struct modal *modal = wl_container_of(listener, modal, modal); if (modal->view->base.modal) { modal_active(modal); } else { modal_deactive(modal); } } static void handle_new_mapped_view(struct wl_listener *listener, void *data) { struct modal_manager *modal_manager = wl_container_of(listener, modal_manager, new_mapped); struct kywc_view *kywc_view = data; struct modal *modal = calloc(1, sizeof(*modal)); if (!modal) { return; } modal->modal_manager = modal_manager; wl_list_insert(&modal_manager->modals, &modal->link); modal->modal.notify = handle_view_modal; wl_signal_add(&kywc_view->events.modal, &modal->modal); modal->view_unmap.notify = handle_modal_view_unmap; wl_signal_add(&kywc_view->events.unmap, &modal->view_unmap); modal->parent_unmap.notify = handle_parent_unmap; wl_list_init(&modal->parent_unmap.link); modal->parent_size.notify = handle_parent_size; wl_list_init(&modal->parent_size.link); modal->view = view_from_kywc_view(kywc_view); if (kywc_view->modal) { modal_active(modal); } } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct modal_manager *modal_manager = wl_container_of(listener, modal_manager, server_destroy); wl_list_remove(&modal_manager->server_destroy.link); wl_list_remove(&modal_manager->new_mapped.link); free(modal_manager); } bool modal_manager_create(struct server *server) { struct modal_manager *modal_manager = calloc(1, sizeof(*modal_manager)); if (!modal_manager) { return false; } wl_list_init(&modal_manager->modals); modal_manager->hardware_renderer = !ky_renderer_is_software(server->renderer); modal_manager->new_mapped.notify = handle_new_mapped_view; kywc_view_add_new_mapped_listener(&modal_manager->new_mapped); modal_manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &modal_manager->server_destroy); return true; } kylin-wayland-compositor/src/view/workspace.c0000664000175000017500000005277315160461067020406 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include "effect/translation.h" #include "input/cursor.h" #include "nls.h" #include "server.h" #include "util/macros.h" #include "util/string.h" #include "view/workspace.h" #include "view_p.h" #define TRANSLATION_THRESHOLD 0.35 struct workspace_manager { struct view_manager *view_manager; struct workspace_translation *translation; // for worskspace translation effect /* current activated workspace */ struct workspace *current; struct workspace **workspaces; struct { struct wl_signal new_workspace; } events; struct wl_listener display_destroy; struct wl_listener server_ready; struct wl_listener server_destroy; uint32_t count, rows, columns; }; static struct workspace_manager *workspace_manager = NULL; static struct shortcut { char *keybind; char *desc; int switch_workspace; } shortcuts[] = { { "Ctrl+Alt+Left:no", "switch to left workspace", DIRECTION_LEFT }, { "Ctrl+Alt+Right:no", "switch to right workspace", DIRECTION_RIGHT }, { "Ctrl+F1:no", "switch to workspace 0", 4 }, { "Ctrl+F2:no", "switch to workspace 1", 5 }, { "Ctrl+F3:no", "switch to workspace 2", 6 }, { "Ctrl+F4:no", "switch to workspace 3", 7 }, { "Win+ctrl+left:no", "switch to left workspace", DIRECTION_LEFT }, { "Win+ctrl+Right:no", "switch to right workspace", DIRECTION_RIGHT }, }; static void workspace_switch_to(int switch_workspace) { int32_t current = workspace_manager->current->position; int32_t last = workspace_manager->count - 1; int32_t pending = current; if (switch_workspace == DIRECTION_LEFT) { pending = current - 1; } else if (switch_workspace == DIRECTION_RIGHT) { pending = current + 1; } else { /* add ctrl+f1...f4 to workspace */ pending = switch_workspace - 4; if (pending == current || pending > last) { return; } switch_workspace = pending > current ? DIRECTION_RIGHT : DIRECTION_LEFT; } struct workspace *pending_workspace = pending < 0 || pending > last ? NULL : workspace_manager->workspaces[pending]; if (!workspace_add_automatic_translation_effect(workspace_manager->workspaces[current], pending_workspace, switch_workspace)) { workspace_activate(pending_workspace); } } static void shortcut_action(struct key_binding *binding, void *data) { struct shortcut *shortcut = data; workspace_switch_to(shortcut->switch_workspace); } static struct gesture { enum gesture_type type; enum gesture_stage stage; uint8_t fingers; uint32_t devices; uint32_t directions; uint32_t edges; uint32_t follow_direction; double follow_threshold; char *desc; } gestures[] = { { GESTURE_TYPE_SWIPE, GESTURE_STAGE_BEFORE, 4, GESTURE_DEVICE_TOUCHPAD, GESTURE_DIRECTION_NONE, GESTURE_EDGE_NONE, GESTURE_DIRECTION_LEFT, 0.0, "switch workspace tridge before", }, { GESTURE_TYPE_SWIPE, GESTURE_STAGE_BEFORE, 4, GESTURE_DEVICE_TOUCHPAD, GESTURE_DIRECTION_NONE, GESTURE_EDGE_NONE, GESTURE_DIRECTION_RIGHT, 0.0, "switch workspace tridge before", }, { GESTURE_TYPE_SWIPE, GESTURE_STAGE_TRIGGER, 4, GESTURE_DEVICE_TOUCHPAD, GESTURE_DIRECTION_LEFT, GESTURE_EDGE_NONE, GESTURE_DIRECTION_NONE, 0.0, "switch workspace tridge", }, { GESTURE_TYPE_SWIPE, GESTURE_STAGE_TRIGGER, 4, GESTURE_DEVICE_TOUCHPAD, GESTURE_DIRECTION_RIGHT, GESTURE_EDGE_NONE, GESTURE_DIRECTION_NONE, 0.0, "switch workspace tridge", }, { GESTURE_TYPE_SWIPE, GESTURE_STAGE_AFTER, 4, GESTURE_DEVICE_TOUCHPAD, GESTURE_DIRECTION_LEFT | GESTURE_DIRECTION_RIGHT, GESTURE_EDGE_NONE, GESTURE_DIRECTION_LEFT, 0.0, "switch workspace left follow finger", }, { GESTURE_TYPE_SWIPE, GESTURE_STAGE_AFTER, 4, GESTURE_DEVICE_TOUCHPAD, GESTURE_DIRECTION_LEFT | GESTURE_DIRECTION_RIGHT, GESTURE_EDGE_NONE, GESTURE_DIRECTION_RIGHT, 0.0, "switch workspace right follow finger", }, { GESTURE_TYPE_SWIPE, GESTURE_STAGE_STOP, 4, GESTURE_DEVICE_TOUCHPAD, GESTURE_DIRECTION_NONE, GESTURE_EDGE_NONE, GESTURE_DIRECTION_NONE, 0.0, "stop switch workspace follow finger", }, }; static void gestures_action(struct gesture_binding *binding, void *data, double dx, double dy) { struct gesture *gesture = data; /* the opposite direction of the gesture for the drag effect */ enum direction direct = DIRECTION_UP; if (gesture->follow_direction == GESTURE_DIRECTION_LEFT) { direct = DIRECTION_RIGHT; } else if (gesture->follow_direction == GESTURE_DIRECTION_RIGHT) { direct = DIRECTION_LEFT; } float percent = -dx / 300; if (gesture->stage == GESTURE_STAGE_BEFORE) { if (!workspace_manager->translation) { workspace_manager->translation = workspace_create_manual_translation_effect(workspace_manager->current); if (!workspace_manager->translation) { return; } kywc_log(KYWC_DEBUG, "Workspace manual translation direct: %d, dx = %f, dy = %f, percent = %f", direct, dx, dy, percent); } workspace_translation_manual(workspace_manager->translation, direct, percent); } else if (gesture->stage == GESTURE_STAGE_TRIGGER) { if (workspace_manager->translation && (direct == DIRECTION_RIGHT || direct == DIRECTION_LEFT)) { workspace_translation_manual(workspace_manager->translation, direct, percent); } else if (!workspace_manager->translation) { if (gesture->directions == GESTURE_DIRECTION_LEFT) { direct = DIRECTION_RIGHT; } else if (gesture->directions == GESTURE_DIRECTION_RIGHT) { direct = DIRECTION_LEFT; } workspace_switch_to(direct); kywc_log(KYWC_DEBUG, "Workspace auto translation direct: %d, dx = %f, dy = %f, percent = %f", direct, dx, dy, percent); } } else if (gesture->stage == GESTURE_STAGE_AFTER && workspace_manager->translation && (direct == DIRECTION_RIGHT || direct == DIRECTION_LEFT)) { workspace_translation_manual(workspace_manager->translation, direct, percent); } else if (gesture->stage == GESTURE_STAGE_STOP && workspace_manager->translation) { struct workspace *last_show = workspace_translation_destroy(workspace_manager->translation, TRANSLATION_THRESHOLD); if (last_show) { workspace_activate(last_show); } workspace_manager->translation = NULL; } } static void workspace_register_shortcut(void) { for (size_t i = 0; i < ARRAY_SIZE(shortcuts); i++) { struct shortcut *shortcut = &shortcuts[i]; struct key_binding *binding = kywc_key_binding_create(shortcut->keybind, shortcut->desc); if (!binding) { continue; } if (!kywc_key_binding_register(binding, KEY_BINDING_TYPE_SWITCH_WORKSPACE, shortcut_action, shortcut)) { kywc_key_binding_destroy(binding); continue; } } for (size_t i = 0; i < ARRAY_SIZE(gestures); i++) { struct gesture *gesture = &gestures[i]; struct gesture_binding *binding = kywc_gesture_binding_create( gesture->type, gesture->stage, gesture->devices, gesture->directions, gesture->edges, gesture->fingers, gesture->follow_direction, gesture->follow_threshold, gesture->desc); if (!binding) { continue; } if (!kywc_gesture_binding_register(binding, gestures_action, gesture)) { kywc_gesture_binding_destroy(binding); continue; } } } static void workspace_manager_update_count(uint32_t count) { uint32_t column = count / workspace_manager->rows; if (count % workspace_manager->rows > 0) { column++; } workspace_manager->count = count; workspace_manager->columns = column; } static void handle_server_destroy(struct wl_listener *listener, void *data) { assert(wl_list_empty(&workspace_manager->events.new_workspace.listener_list)); wl_list_remove(&workspace_manager->server_destroy.link); wl_list_remove(&workspace_manager->server_ready.link); free(workspace_manager->workspaces); free(workspace_manager); workspace_manager = NULL; } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&workspace_manager->display_destroy.link); workspace_manager->current = NULL; /* workspace_destroy will update count */ while (workspace_manager->count) { workspace_destroy(workspace_manager->workspaces[0]); } } static void handle_server_ready(struct wl_listener *listener, void *data) { /* create the default workspace and activate it */ workspace_activate( workspace_create(workspace_manager->view_manager->state.workspace_names[0], 0)); /* create workspaces according to configuration */ for (uint32_t i = 1; i < workspace_manager->view_manager->state.num_workspaces; i++) { workspace_create(workspace_manager->view_manager->state.workspace_names[i], i); } } bool workspace_manager_create(struct view_manager *view_manager) { workspace_manager = calloc(1, sizeof(*workspace_manager)); if (!workspace_manager) { return false; } workspace_manager->view_manager = view_manager; workspace_manager->workspaces = calloc(MAX_WORKSPACES, sizeof(struct workspace *)); workspace_manager->rows = 2; wl_signal_init(&workspace_manager->events.new_workspace); workspace_manager->server_ready.notify = handle_server_ready; wl_signal_add(&view_manager->server->events.ready, &workspace_manager->server_ready); workspace_manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(view_manager->server, &workspace_manager->server_destroy); workspace_manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(view_manager->server->display, &workspace_manager->display_destroy); ky_workspace_manager_create(view_manager->server); /* kde-plasma-virtual-desktop support */ kde_virtual_desktop_management_create(view_manager->server); workspace_register_shortcut(); return true; } void workspace_manager_add_new_listener(struct wl_listener *listener) { wl_signal_add(&workspace_manager->events.new_workspace, listener); } void workspace_manager_set_rows(uint32_t rows) { if (rows == workspace_manager->rows) { return; } workspace_manager->rows = rows; /* update columns */ workspace_manager_update_count(workspace_manager->count); } uint32_t workspace_manager_get_rows(void) { return workspace_manager->rows; } struct workspace *workspace_manager_get_current(void) { return workspace_manager->current; } uint32_t workspace_manager_get_count(void) { return workspace_manager->count; } void workspace_manager_for_each_workspace(workspace_iterator_func_t iterator, void *data) { for (uint32_t i = 0; i < workspace_manager->count; i++) { if (iterator(workspace_manager->workspaces[i], data)) { break; } } } static void workspace_set_enabled(struct workspace *workspace, bool enabled) { for (int i = 0; i < 3; i++) { ky_scene_node_set_enabled(&workspace->layers[i].tree->node, enabled); } } void workspace_update_name(struct workspace *workspace, const char *name) { if (workspace->name && name && strcmp(workspace->name, name) == 0) { return; } free((void *)workspace->name); workspace->name = STRING_VALID(name) ? strdup(name) : string_create("%s %d", tr("Desktop"), workspace->position + 1); workspace->has_custom_name = STRING_VALID(name); wl_signal_emit_mutable(&workspace->events.name, NULL); } /* auto add views in all workspaces */ static void workspace_auto_add_views(struct workspace *workspace) { if (!workspace_manager->view_manager->server->ready) { return; } struct workspace *first_workspace = workspace_manager->workspaces[0]; struct view_proxy *view_proxy; wl_list_for_each(view_proxy, &first_workspace->view_proxies, workspace_link) { struct view *view = view_proxy->view; if (view->base.sticky) { view_add_workspace(view, workspace); } } } struct workspace *workspace_create(const char *name, uint32_t position) { /* too many workspaces, reject it */ if (workspace_manager->count == MAX_WORKSPACES) { return NULL; } struct workspace *workspace = calloc(1, sizeof(*workspace)); if (!workspace) { return NULL; }; if (position > workspace_manager->count) { position = workspace_manager->count; } wl_list_init(&workspace->view_proxies); wl_signal_init(&workspace->events.name); wl_signal_init(&workspace->events.position); wl_signal_init(&workspace->events.activate); wl_signal_init(&workspace->events.destroy); wl_signal_init(&workspace->events.view_enter); wl_signal_init(&workspace->events.view_leave); /* create 3 tree per workspace and disabled default */ struct view_layer *layers = workspace_manager->view_manager->layers; workspace->layers[0].layer = LAYER_BELOW; workspace->layers[0].tree = ky_scene_tree_create(layers[LAYER_BELOW].tree); workspace->layers[0].tree->node.role.type = KY_SCENE_ROLE_WORKSPACE; workspace->layers[0].tree->node.role.data = &workspace->layers[0]; workspace->layers[1].layer = LAYER_NORMAL; workspace->layers[1].tree = ky_scene_tree_create(layers[LAYER_NORMAL].tree); workspace->layers[1].tree->node.role.type = KY_SCENE_ROLE_WORKSPACE; workspace->layers[1].tree->node.role.data = &workspace->layers[1]; workspace->layers[2].layer = LAYER_ABOVE; workspace->layers[2].tree = ky_scene_tree_create(layers[LAYER_ABOVE].tree); workspace->layers[2].tree->node.role.type = KY_SCENE_ROLE_WORKSPACE; workspace->layers[2].tree->node.role.data = &workspace->layers[2]; workspace_set_enabled(workspace, false); /* insert to workspace manager workspaces */ for (uint32_t i = workspace_manager->count; i > position; i--) { workspace_manager->workspaces[i] = workspace_manager->workspaces[i - 1]; workspace_manager->workspaces[i]->position = i; } workspace->position = position; workspace_manager->workspaces[position] = workspace; workspace_manager_update_count(workspace_manager->count + 1); workspace->uuid = kywc_identifier_uuid_generate(); workspace_update_name(workspace, name); wl_signal_emit_mutable(&workspace_manager->events.new_workspace, workspace); /* after new_workspace */ workspace_auto_add_views(workspace); return workspace; } static void fix_workspace(struct workspace *workspace) { /* fixup workspace position */ for (uint32_t i = workspace->position; i < workspace_manager->count - 1; i++) { workspace_manager->workspaces[i] = workspace_manager->workspaces[i + 1]; workspace_manager->workspaces[i]->position = i; wl_signal_emit_mutable(&workspace_manager->workspaces[i]->events.position, NULL); if (!workspace_manager->workspaces[i]->has_custom_name) { workspace_update_name(workspace_manager->workspaces[i], NULL); } } workspace_manager_update_count(workspace_manager->count - 1); if (workspace_manager->count == 0) { return; } /* fixup activated workspace */ struct workspace *activate_workspace = workspace_manager->workspaces[workspace->position ? workspace->position - 1 : 0]; if (workspace_manager->current == workspace) { workspace_activate(activate_workspace); } /* move all views to activate workspace */ struct view_proxy *view_proxy, *tmp; wl_list_for_each_safe(view_proxy, tmp, &workspace->view_proxies, workspace_link) { struct view_proxy *new_proxy = view_add_workspace(view_proxy->view, activate_workspace); if (new_proxy) { view_set_current_proxy(view_proxy->view, new_proxy); } view_proxy_destroy(view_proxy); } } void workspace_destroy(struct workspace *workspace) { kywc_log(KYWC_INFO, "Workspace %s destroy", workspace->name); fix_workspace(workspace); wl_signal_emit_mutable(&workspace->events.destroy, NULL); assert(wl_list_empty(&workspace->events.view_enter.listener_list)); assert(wl_list_empty(&workspace->events.view_leave.listener_list)); assert(wl_list_empty(&workspace->events.name.listener_list)); assert(wl_list_empty(&workspace->events.position.listener_list)); assert(wl_list_empty(&workspace->events.activate.listener_list)); assert(wl_list_empty(&workspace->events.destroy.listener_list)); /* destroy trees, trees must be empty */ for (int i = 0; i < 3; i++) { ky_scene_node_destroy(&workspace->layers[i].tree->node); } free((void *)workspace->uuid); free((void *)workspace->name); free(workspace); } static void workspace_set_activated(struct workspace *workspace, bool activated) { if (workspace->activated == activated) { return; } workspace_set_enabled(workspace, activated); /* reparent view_tree which in multi workspace */ struct view_proxy *view_proxy; wl_list_for_each(view_proxy, &workspace->view_proxies, workspace_link) { if (view_proxy->view->current_proxy != view_proxy) { view_set_current_proxy(view_proxy->view, view_proxy); } } workspace->activated = activated; wl_signal_emit_mutable(&workspace->events.activate, NULL); } void workspace_activate(struct workspace *workspace) { struct workspace *old = workspace_manager->current; if (!workspace || old == workspace) { return; } if (old) { workspace_set_activated(old, false); } /* disable show desktop when switching between workspaces */ view_manager_show_desktop(false, true); workspace_manager->current = workspace; workspace_set_activated(workspace, true); /* auto activate topmost enabled view */ view_activate_topmost(true); cursor_rebase_all(false); kywc_log(KYWC_INFO, "Workspace %s(%d) is activated", workspace->name, workspace->position); } void workspace_activate_with_effect(struct workspace *workspace) { struct workspace *current = workspace_manager->current; enum direction direction = current->position < workspace->position ? DIRECTION_RIGHT : DIRECTION_LEFT; if (!workspace_add_automatic_translation_effect(current, workspace, direction)) { workspace_activate(workspace); } } struct view_layer *workspace_layer(struct workspace *workspace, enum layer layer) { switch (layer) { case LAYER_BELOW: return &workspace->layers[0]; case LAYER_NORMAL: return &workspace->layers[1]; case LAYER_ABOVE: return &workspace->layers[2]; default: return NULL; } } struct workspace *workspace_by_position(uint32_t position) { if (position >= workspace_manager->count) { return NULL; } return workspace_manager->workspaces[position]; } struct workspace *workspace_by_uuid(const char *uuid) { struct workspace *workspace; for (uint32_t i = 0; i < workspace_manager->count; i++) { workspace = workspace_manager->workspaces[i]; if (strcmp(workspace->uuid, uuid) == 0) { return workspace; } } return NULL; } void workspace_set_position(struct workspace *workspace, uint32_t position) { /* insert to last if position is too bigger */ if (position >= workspace_manager->count) { position = workspace_manager->count - 1; } if (position == workspace->position) { return; } /* move workspaces */ for (uint32_t i = workspace->position; i > position; i--) { workspace_manager->workspaces[i] = workspace_manager->workspaces[i - 1]; workspace_manager->workspaces[i]->position = i; wl_signal_emit_mutable(&workspace_manager->workspaces[i]->events.position, NULL); if (!workspace_manager->workspaces[i]->has_custom_name) { workspace_update_name(workspace_manager->workspaces[i], NULL); } } for (uint32_t i = workspace->position; i < position; i++) { workspace_manager->workspaces[i] = workspace_manager->workspaces[i + 1]; workspace_manager->workspaces[i]->position = i; wl_signal_emit_mutable(&workspace_manager->workspaces[i]->events.position, NULL); if (!workspace_manager->workspaces[i]->has_custom_name) { workspace_update_name(workspace_manager->workspaces[i], NULL); } } workspace->position = position; workspace_manager->workspaces[position] = workspace; wl_signal_emit_mutable(&workspace->events.position, NULL); if (!workspace->has_custom_name) { workspace_update_name(workspace, NULL); } } kylin-wayland-compositor/src/view/wlr_foreign_toplevel.c0000664000175000017500000003060015160460057022616 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "input/seat.h" #include "output.h" #include "scene/surface.h" #include "view_p.h" struct wlr_foreign_manager { struct wlr_foreign_toplevel_manager_v1 *manager; struct wl_listener new_mapped_view; struct wl_listener destroy; }; struct wlr_foreign { struct wlr_foreign_manager *manager; struct wlr_foreign_toplevel_handle_v1 *toplevel_handle; struct kywc_view *toplevel_view; struct wlr_surface *surface; struct wl_listener output_enter; struct wl_listener output_leave; struct wl_listener view_unmap; struct wl_listener view_maximize; struct wl_listener view_minimize; struct wl_listener view_activate; struct wl_listener view_fullscreen; struct wl_listener view_title; struct wl_listener view_app_id; struct wl_listener request_maximize; struct wl_listener request_minimize; struct wl_listener request_activate; struct wl_listener request_fullscreen; struct wl_listener request_close; struct wl_listener set_rectangle; struct wl_listener destroy; }; static void handle_request_maximize(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, request_maximize); struct wlr_foreign_toplevel_handle_v1_maximized_event *event = data; kywc_view_set_maximized(foreign->toplevel_view, event->maximized, NULL); } static void handle_request_minimize(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, request_minimize); struct wlr_foreign_toplevel_handle_v1_minimized_event *event = data; kywc_view_set_minimized(foreign->toplevel_view, event->minimized); } static void handle_request_activate(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, request_activate); struct wlr_foreign_toplevel_handle_v1_activated_event *event = data; struct kywc_view *view = foreign->toplevel_view; kywc_view_activate(view); view_set_focus(view_from_kywc_view(view), seat_from_wlr_seat(event->seat)); } static void handle_request_fullscreen(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, request_fullscreen); struct wlr_foreign_toplevel_handle_v1_fullscreen_event *event = data; struct kywc_view *view = foreign->toplevel_view; struct kywc_output *kywc_output = NULL; if (event->output) { struct output *output = output_from_wlr_output(event->output); if (output && !output->base.destroying && output->base.state.enabled) { kywc_output = &output->base; } } kywc_view_set_fullscreen(view, event->fullscreen, kywc_output); } static void handle_request_close(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, request_close); struct kywc_view *view = foreign->toplevel_view; kywc_view_close(view); } static void handle_set_rectangle(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, request_close); struct wlr_foreign_toplevel_handle_v1_set_rectangle_event *event = data; kywc_log(KYWC_DEBUG, "Set surface %p rectangle (%d, %d), %d x %d", event->surface, event->x, event->y, event->width, event->height); } static void handle_foreign_destroy(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, destroy); wl_list_remove(&foreign->output_enter.link); wl_list_remove(&foreign->output_leave.link); wl_list_remove(&foreign->view_maximize.link); wl_list_remove(&foreign->view_minimize.link); wl_list_remove(&foreign->view_activate.link); wl_list_remove(&foreign->view_fullscreen.link); wl_list_remove(&foreign->view_title.link); wl_list_remove(&foreign->view_app_id.link); wl_list_remove(&foreign->request_maximize.link); wl_list_remove(&foreign->request_minimize.link); wl_list_remove(&foreign->request_activate.link); wl_list_remove(&foreign->request_fullscreen.link); wl_list_remove(&foreign->request_close.link); wl_list_remove(&foreign->set_rectangle.link); wl_list_remove(&foreign->destroy.link); } static void handle_view_maximize(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, view_maximize); struct wlr_foreign_toplevel_handle_v1 *toplevel = foreign->toplevel_handle; struct kywc_view *view = foreign->toplevel_view; wlr_foreign_toplevel_handle_v1_set_maximized(toplevel, view->maximized); } static void handle_view_minimize(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, view_minimize); struct wlr_foreign_toplevel_handle_v1 *toplevel = foreign->toplevel_handle; struct kywc_view *view = foreign->toplevel_view; wlr_foreign_toplevel_handle_v1_set_minimized(toplevel, view->minimized); } static void handle_view_activate(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, view_activate); struct wlr_foreign_toplevel_handle_v1 *toplevel = foreign->toplevel_handle; struct kywc_view *view = foreign->toplevel_view; wlr_foreign_toplevel_handle_v1_set_activated(toplevel, view->activated); } static void handle_view_fullscreen(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, view_fullscreen); struct wlr_foreign_toplevel_handle_v1 *toplevel = foreign->toplevel_handle; struct kywc_view *view = foreign->toplevel_view; wlr_foreign_toplevel_handle_v1_set_fullscreen(toplevel, view->fullscreen); } static void handle_view_title(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, view_title); struct wlr_foreign_toplevel_handle_v1 *toplevel = foreign->toplevel_handle; struct kywc_view *view = foreign->toplevel_view; if (view->title) { wlr_foreign_toplevel_handle_v1_set_title(toplevel, view->title); } } static void handle_view_app_id(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, view_app_id); struct wlr_foreign_toplevel_handle_v1 *toplevel = foreign->toplevel_handle; struct kywc_view *view = foreign->toplevel_view; if (view->app_id) { wlr_foreign_toplevel_handle_v1_set_app_id(toplevel, view->app_id); } } static void handle_view_unmap(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, view_unmap); if (foreign->toplevel_handle) { wlr_foreign_toplevel_handle_v1_destroy(foreign->toplevel_handle); foreign->toplevel_handle = NULL; } wl_list_remove(&foreign->view_unmap.link); free(foreign); } static void handle_output_enter(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, output_enter); struct ky_scene_output *output = data; wlr_foreign_toplevel_handle_v1_output_enter(foreign->toplevel_handle, output->output); } static void handle_output_leave(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = wl_container_of(listener, foreign, output_leave); struct ky_scene_output *output = data; wlr_foreign_toplevel_handle_v1_output_leave(foreign->toplevel_handle, output->output); } static void handle_new_mapped_view(struct wl_listener *listener, void *data) { struct wlr_foreign *foreign = calloc(1, sizeof(*foreign)); if (!foreign) { return; } struct wlr_foreign_manager *manager = wl_container_of(listener, manager, new_mapped_view); foreign->manager = manager; foreign->toplevel_handle = wlr_foreign_toplevel_handle_v1_create(manager->manager); if (!foreign->toplevel_handle) { free(foreign); return; } foreign->request_maximize.notify = handle_request_maximize; wl_signal_add(&foreign->toplevel_handle->events.request_maximize, &foreign->request_maximize); foreign->request_minimize.notify = handle_request_minimize; wl_signal_add(&foreign->toplevel_handle->events.request_minimize, &foreign->request_minimize); foreign->request_activate.notify = handle_request_activate; wl_signal_add(&foreign->toplevel_handle->events.request_activate, &foreign->request_activate); foreign->request_fullscreen.notify = handle_request_fullscreen; wl_signal_add(&foreign->toplevel_handle->events.request_fullscreen, &foreign->request_fullscreen); foreign->request_close.notify = handle_request_close; wl_signal_add(&foreign->toplevel_handle->events.request_close, &foreign->request_close); foreign->set_rectangle.notify = handle_set_rectangle; wl_signal_add(&foreign->toplevel_handle->events.set_rectangle, &foreign->set_rectangle); foreign->destroy.notify = handle_foreign_destroy; wl_signal_add(&foreign->toplevel_handle->events.destroy, &foreign->destroy); struct kywc_view *view = data; foreign->toplevel_view = view; foreign->view_unmap.notify = handle_view_unmap; wl_signal_add(&view->events.unmap, &foreign->view_unmap); foreign->view_maximize.notify = handle_view_maximize; wl_signal_add(&view->events.maximize, &foreign->view_maximize); foreign->view_minimize.notify = handle_view_minimize; wl_signal_add(&view->events.minimize, &foreign->view_minimize); foreign->view_activate.notify = handle_view_activate; wl_signal_add(&view->events.activate, &foreign->view_activate); foreign->view_fullscreen.notify = handle_view_fullscreen; wl_signal_add(&view->events.fullscreen, &foreign->view_fullscreen); foreign->view_title.notify = handle_view_title; wl_signal_add(&view->events.title, &foreign->view_title); foreign->view_app_id.notify = handle_view_app_id; wl_signal_add(&view->events.app_id, &foreign->view_app_id); struct wlr_surface *surface = view_from_kywc_view(view)->surface; foreign->surface = surface; struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(surface); foreign->output_enter.notify = handle_output_enter; wl_signal_add(&buffer->events.output_enter, &foreign->output_enter); foreign->output_leave.notify = handle_output_leave; wl_signal_add(&buffer->events.output_enter, &foreign->output_leave); /* set toplevel state */ struct wlr_foreign_toplevel_handle_v1 *toplevel = foreign->toplevel_handle; struct wlr_surface_output *surface_output; wl_list_for_each(surface_output, &surface->current_outputs, link) { wlr_foreign_toplevel_handle_v1_output_enter(foreign->toplevel_handle, surface_output->output); } // TODO: foreign_toplevel_handle_from_view // wlr_foreign_toplevel_handle_v1_set_parent(toplevel); if (view->title) { wlr_foreign_toplevel_handle_v1_set_title(toplevel, view->title); } if (view->app_id) { wlr_foreign_toplevel_handle_v1_set_app_id(toplevel, view->app_id); } wlr_foreign_toplevel_handle_v1_set_maximized(toplevel, view->maximized); wlr_foreign_toplevel_handle_v1_set_minimized(toplevel, view->minimized); wlr_foreign_toplevel_handle_v1_set_fullscreen(toplevel, view->fullscreen); wlr_foreign_toplevel_handle_v1_set_activated(toplevel, view->activated); } static void handle_destroy(struct wl_listener *listener, void *data) { struct wlr_foreign_manager *manager = wl_container_of(listener, manager, destroy); wl_list_remove(&manager->destroy.link); wl_list_remove(&manager->new_mapped_view.link); free(manager); } bool wlr_foreign_toplevel_manager_create(struct server *server) { struct wlr_foreign_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->manager = wlr_foreign_toplevel_manager_v1_create(server->display); if (!manager->manager) { free(manager); return false; } /* monitor new mapped view */ manager->new_mapped_view.notify = handle_new_mapped_view; kywc_view_add_new_mapped_listener(&manager->new_mapped_view); manager->destroy.notify = handle_destroy; wl_signal_add(&manager->manager->events.destroy, &manager->destroy); return true; } kylin-wayland-compositor/src/view/view.c0000664000175000017500000026003315160461067017350 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include "effect/action.h" #include "effect/magic_lamp.h" #include "effect/scale.h" #include "effect/slide.h" #include "input/cursor.h" #include "input/seat.h" #include "output.h" #include "scene/surface.h" #include "server.h" #include "theme.h" #include "util/macros.h" #include "view/workspace.h" #include "view_p.h" #include "xwayland.h" static struct view_manager *view_manager = NULL; static struct view *view_find_fullscreen_ancestor(struct view *view); struct view *view_manager_get_activated(void) { return view_manager->activated.view; } void view_manager_add_activate_view_listener(struct wl_listener *listener) { wl_signal_add(&view_manager->events.activate_view, listener); } void view_manager_show_tile_assist(struct view *view, struct seat *seat, struct kywc_output *kywc_output) { if (view_manager->impl.show_tile_assist) { view_manager->impl.show_tile_assist(view, seat, kywc_output); } } struct view_layer *view_manager_get_layer(enum layer layer, bool in_workspace) { if (!in_workspace) { return &view_manager->layers[layer]; } switch (layer) { case LAYER_BELOW: case LAYER_NORMAL: case LAYER_ABOVE: return workspace_layer(workspace_manager_get_current(), layer); default: return &view_manager->layers[layer]; } } struct view_layer *view_manager_get_layer_by_node(struct ky_scene_node *node, bool in_workspace) { struct ky_scene_tree *tree = node->parent; while (tree && (!in_workspace || tree->node.role.type != KY_SCENE_ROLE_WORKSPACE) && tree->node.role.type != KY_SCENE_ROLE_LAYER) { tree = tree->node.parent; } return tree->node.role.data; } static void view_update_output(struct view *view, struct kywc_output *output) { /* use fallback output if no valid output */ if (!output) { output = output_manager_get_fallback(); } if (view->output == output) { return; } view->output = output; wl_list_remove(&view->output_destroy.link); wl_signal_add(&output->events.destroy, &view->output_destroy); wl_signal_emit_mutable(&view->events.output, NULL); } static void view_handle_output_destroy(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, output_destroy); view_update_output(view, NULL); } static void view_handle_outputs_update(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, outputs_update); struct ky_scene_outputs_update_event *event = data; /* skip update if the primary is empty, will clear when output destroy */ if (!event->primary) { return; } view_update_output(view, &output_from_wlr_output(event->primary->output)->base); } void view_fix_geometry(struct view *view, struct kywc_box *geo, struct kywc_box *src_box, struct kywc_box *dst_box) { struct kywc_view *kywc_view = &view->base; /* actual view geometry with margin */ int x = geo->x - kywc_view->margin.off_x; int y = geo->y - kywc_view->margin.off_y; int w = geo->width + kywc_view->margin.off_width; int h = geo->height + kywc_view->margin.off_height; if (w > dst_box->width) { geo->width = dst_box->width - kywc_view->margin.off_width; geo->x = dst_box->x + kywc_view->margin.off_x; } else if (src_box->x == x) { geo->x = dst_box->x + kywc_view->margin.off_x; } else if (src_box->x + src_box->width == x + w) { geo->x = dst_box->x + dst_box->width - w + kywc_view->margin.off_x; } else { double frac_x = (double)dst_box->width / src_box->width; geo->x = round(MAX(x - src_box->x, 0) * frac_x) + dst_box->x + kywc_view->margin.off_x; } if (h > dst_box->height) { geo->height = dst_box->height - kywc_view->margin.off_height; geo->y = dst_box->y + kywc_view->margin.off_y; } else if (src_box->y == y) { geo->y = dst_box->y + kywc_view->margin.off_y; } else if (src_box->y + src_box->height == y + h) { geo->y = dst_box->y + dst_box->height - h + kywc_view->margin.off_y; } else { double frac_y = (double)dst_box->height / src_box->height; geo->y = round(MAX(y - src_box->y, 0) * frac_y) + dst_box->y + kywc_view->margin.off_y; } } static void view_fix_tiled_geometry(struct view *view, struct kywc_box *geo, struct kywc_output *kywc_output) { if (view_manager->impl.fix_geometry) { view_manager->impl.fix_geometry(view, geo, kywc_output); } } void view_move_to_output(struct view *view, struct kywc_box *src_box, struct kywc_box *preferred, struct kywc_output *kywc_output) { struct kywc_view *kywc_view = &view->base; struct output *dst = output_from_kywc_output(kywc_output); struct kywc_box *dst_box = &dst->usable_area; /* default to view output usable area */ if (src_box == NULL) { src_box = &output_from_kywc_output(view->output)->usable_area; } if (kywc_view->fullscreen) { view_fix_geometry(view, &view->saved.geometry, src_box, dst_box); view_do_resize(view, &dst->geometry); return; } if (kywc_view->maximized) { struct kywc_box geo; view_get_tiled_geometry(view, &geo, kywc_output, KYWC_TILE_ALL); view_fix_geometry(view, &view->saved.geometry, src_box, dst_box); view_do_resize(view, &geo); return; } if (kywc_view->tiled) { struct kywc_box geo; view_fix_tiled_geometry(view, &geo, kywc_output); view_fix_geometry(view, &view->saved.geometry, src_box, dst_box); view_do_resize(view, &geo); return; } if (!KYWC_VIEW_IS_MOVABLE(kywc_view)) { return; } struct kywc_box geo = view_action_change_size(view->pending.configure_action) ? view->pending.configure_geometry : kywc_view->geometry; if (kywc_box_not_empty(preferred)) { geo.width = preferred->width; geo.height = preferred->height; } view_fix_geometry(view, &geo, src_box, dst_box); /* resize to dst */ view_do_resize(view, &geo); } void sub_view_move_to_output(struct view *view, struct kywc_output *kywc_output) { struct view *child; wl_list_for_each(child, &view->children, parent_link) { sub_view_move_to_output(child, kywc_output); view_move_to_output(child, NULL, NULL, kywc_output); } } static void view_handle_update_capabilities(struct wl_listener *listener, void *data); void view_init(struct view *view, const struct view_impl *impl, void *data) { struct kywc_view *kywc_view = &view->base; wl_signal_init(&kywc_view->events.premap); wl_signal_init(&kywc_view->events.map); wl_signal_init(&kywc_view->events.unmap); wl_signal_init(&kywc_view->events.destroy); wl_signal_init(&kywc_view->events.activate); wl_signal_init(&kywc_view->events.maximize); wl_signal_init(&kywc_view->events.minimize); wl_signal_init(&kywc_view->events.fullscreen); wl_signal_init(&kywc_view->events.tile); wl_signal_init(&kywc_view->events.above); wl_signal_init(&kywc_view->events.below); wl_signal_init(&kywc_view->events.sticky); wl_signal_init(&kywc_view->events.skip_taskbar); wl_signal_init(&kywc_view->events.skip_switcher); wl_signal_init(&kywc_view->events.demands_attention); wl_signal_init(&kywc_view->events.modal); wl_signal_init(&kywc_view->events.capabilities); wl_signal_init(&kywc_view->events.title); wl_signal_init(&kywc_view->events.app_id); wl_signal_init(&kywc_view->events.position); wl_signal_init(&kywc_view->events.size); wl_signal_init(&kywc_view->events.decoration); wl_list_init(&view->children); wl_list_init(&view->parent_link); wl_list_init(&view->view_proxies); wl_signal_init(&view->events.parent); wl_signal_init(&view->events.workspace); wl_signal_init(&view->events.workspace_enter); wl_signal_init(&view->events.workspace_leave); wl_signal_init(&view->events.output); wl_signal_init(&view->events.icon_update); wl_signal_init(&view->events.position); wl_signal_init(&view->events.update_capabilities); view->impl = impl; view->data = data; kywc_view->uuid = kywc_identifier_uuid_generate(); kywc_view->focused_seat = input_manager_get_default_seat(); wl_list_insert(&view_manager->views, &view->link); /* create view tree and disable it */ struct view_layer *layer = view_manager_get_layer(LAYER_NORMAL, true); view->tree = ky_scene_tree_create(layer->tree); ky_scene_node_set_enabled(&view->tree->node, false); struct output *output = input_current_output(input_manager_get_default_seat()); view->output = output ? &output->base : output_manager_get_fallback(); view->output_destroy.notify = view_handle_output_destroy; wl_signal_add(&view->output->events.destroy, &view->output_destroy); view->outputs_update.notify = view_handle_outputs_update; wl_list_init(&view->outputs_update.link); kywc_view->role = KYWC_VIEW_ROLE_UNDEF; view_set_role(view, KYWC_VIEW_ROLE_NORMAL); view->update_capabilities.notify = view_handle_update_capabilities; view_add_update_capabilities_listener(view, &view->update_capabilities); wl_signal_emit_mutable(&view_manager->events.new_view, kywc_view); } void view_get_tiled_geometry(struct view *view, struct kywc_box *geometry, struct kywc_output *kywc_output, enum kywc_tile tile) { if (!tile || tile > KYWC_TILE_BOTTOM_RIGHT) { struct output *output = output_from_kywc_output(kywc_output); struct kywc_view *kywc_view = &view->base; int32_t min_width = kywc_view->min_width + kywc_view->margin.off_width; int32_t min_height = kywc_view->min_height + kywc_view->margin.off_height; struct kywc_box *usable = &output->usable_area; int32_t half_width = usable->width / 2; int32_t half_height = usable->height / 2; bool use_min_width = half_width < min_width; bool use_min_height = half_height < min_height; if (tile == KYWC_TILE_NONE) { *geometry = view->saved.geometry; } else if (tile == KYWC_TILE_CENTER) { geometry->x = use_min_width ? usable->x + half_width - min_width / 2 + kywc_view->margin.off_x : usable->x + usable->width / 4 + kywc_view->margin.off_x; geometry->y = use_min_height ? usable->y + half_height - min_height / 2 + kywc_view->margin.off_y : usable->y + usable->height / 4 + kywc_view->margin.off_y; geometry->width = use_min_width ? min_width - kywc_view->margin.off_width : half_width - kywc_view->margin.off_width; geometry->height = use_min_height ? min_height - kywc_view->margin.off_height : half_height - kywc_view->margin.off_height; } else if (tile == KYWC_TILE_ALL) { geometry->x = usable->x + kywc_view->margin.off_x; geometry->y = usable->y + kywc_view->margin.off_y; geometry->width = usable->width - kywc_view->margin.off_width; geometry->height = usable->height - kywc_view->margin.off_height; } return; } if (view_manager->impl.get_tiled_geometry) { view_manager->impl.get_tiled_geometry(view, geometry, kywc_output, tile); return; } } struct wlr_buffer *view_get_icon_buffer(struct view *view, float scale) { struct theme *theme = theme_manager_get_theme(); struct wlr_buffer *buf = NULL; struct wlr_buffer *theme_buf = theme_icon_get_buffer(view->icon, theme->icon_size, scale); if (view->icon_name) { buf = theme_buf; } if (!buf && view->impl->get_icon_buffer) { buf = view->impl->get_icon_buffer(view, theme->icon_size, scale); } if (!buf) { buf = theme_buf; } return buf; } /* set surface round corner by ssd type, tiled, fullscreen and maximized state */ void view_update_round_corner(struct view *view) { struct kywc_view *kywc_view = &view->base; if (!kywc_view->mapped) { return; } struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(view->surface); if (!buffer) { return; } int radius[4] = { 0 }; if (!kywc_view->has_round_corner) { ky_scene_node_set_radius(&buffer->node, radius); return; } /* don't draw top round corner if ssd has title */ struct theme *theme = theme_manager_get_theme(); bool need_corner = !kywc_view->maximized && !kywc_view->fullscreen && !kywc_view->tiled; if (!view_manager->state.csd_round_corner) { need_corner &= kywc_view->ssd != KYWC_SSD_NONE; } bool need_top_corner = need_corner && !(kywc_view->ssd & KYWC_SSD_TITLE); radius[KY_SCENE_ROUND_CORNER_RB] = need_corner ? theme->window_radius : 0; radius[KY_SCENE_ROUND_CORNER_RT] = need_top_corner ? theme->window_radius : 0; radius[KY_SCENE_ROUND_CORNER_LB] = need_corner ? theme->window_radius : 0; radius[KY_SCENE_ROUND_CORNER_LT] = need_top_corner ? theme->window_radius : 0; ky_scene_node_set_radius(&buffer->node, radius); } void view_set_icon(struct view *view, bool buffer_changed) { struct theme *theme = theme_manager_get_theme(); const char *name = view->base.app_id; bool need_update = false; if (view->icon_name) { name = view->icon_name; } else if (buffer_changed) { struct wlr_buffer *buf = view->impl->get_icon_buffer(view, theme->icon_size, view->output->state.scale); /* set icon fallback if has any icon buffer */ if (buf) { name = NULL; } } struct icon *old = view->icon; view->icon = theme_icon_from_app_id(name); need_update = theme_icon_is_fallback(view->icon) && buffer_changed; if (need_update || old != view->icon) { wl_signal_emit_mutable(&view->events.icon_update, NULL); } } static void view_end_keyboard_grab(struct view *view) { struct view *descendant = view_find_descendant_modal(view); if (descendant) { view = descendant; } if (!KYWC_VIEW_IS_FOCUSABLE(&view->base)) { return; } struct seat *seat = view->base.focused_seat; if (!seat_is_dragging(seat)) { wlr_seat_keyboard_end_grab(seat->wlr_seat); } } static void action_effect_options_adjust(enum action_effect_options_step step, enum effect_action action, struct action_effect_options *options, void *user_data) { switch (step) { case ACTION_EFFECT_OPTIONS_ADD: if (options->effect_type == ACTION_EFFECT_FADE) { struct view *view = user_data; options->surface = view->surface; if (action == EFFECT_ACTION_MAP) { options->width_scale = 0.9f; options->height_scale = 0.9f; options->duration = 300; } else if (action == EFFECT_ACTION_UNMAP) { options->width_scale = 0.8f; options->height_scale = 0.8f; options->duration = 260; } options->scale = view->surface ? ky_scene_surface_get_scale(options->surface) : 1.0f; } break; case ACTION_EFFECT_OPTIONS_SURFACE: case ACTION_EFFECT_OPTIONS_CONFIRM: break; } } void view_map(struct view *view) { struct kywc_view *kywc_view = &view->base; wl_signal_emit_mutable(&kywc_view->events.premap, NULL); view_set_icon(view, false); if (view_manager->mode->impl->view_map) { view_manager->mode->impl->view_map(view); } /* assume that request_minimize may emitted before map */ ky_scene_node_set_enabled(&view->tree->node, !kywc_view->minimized); kywc_view->mapped = true; if (view->pending.action) { /* add a fallback geometry */ if (kywc_view->maximized || kywc_view->fullscreen) { view_get_tiled_geometry(view, &view->saved.geometry, view->output, KYWC_TILE_CENTER); } if (view->impl->configure) { view->impl->configure(view); } } struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(view->surface); if (buffer->primary_output) { view_update_output(view, &output_from_wlr_output(buffer->primary_output->output)->base); } wl_signal_add(&buffer->events.outputs_update, &view->outputs_update); kywc_view->has_initial_position = false; kywc_view_activate(kywc_view); view_end_keyboard_grab(view); view_set_focus(view, kywc_view->focused_seat); view_update_round_corner(view); if (view->parent && view->parent->base.mapped) { kywc_view->skip_taskbar = true; kywc_view->skip_switcher = true; } kywc_log(KYWC_DEBUG, "Kywc_view %p map", kywc_view); if (!view_add_slide_effect(view, true) && kywc_view->role == KYWC_VIEW_ROLE_NORMAL) { if (view_manager->impl.get_startup_geometry) { view_manager->impl.get_startup_geometry(view, view_manager); kywc_log(KYWC_DEBUG, "%s startup_geometry is %d, %d, %d, %d", kywc_view->app_id, view->startup_geometry.x, view->startup_geometry.y, view->startup_geometry.width, view->startup_geometry.height); } if (kywc_box_not_empty(&view->startup_geometry)) { view_add_scale_effect(view, SCALE_MINIMIZE); } else { view_add_action_effect(view, EFFECT_ACTION_MAP, ACTION_EFFECT_FADE, action_effect_options_adjust, view); } } /* clear startup geometry */ view->startup_geometry = (struct kywc_box){ 0 }; wl_signal_emit_mutable(&kywc_view->events.map, NULL); wl_signal_emit_mutable(&view_manager->events.new_mapped_view, kywc_view); struct view_proxy *proxy; wl_list_for_each(proxy, &view->view_proxies, view_link) { wl_signal_emit_mutable(&proxy->workspace->events.view_enter, view); } if (view->current_proxy && !kywc_view->minimized) { if (view_manager->show_desktop_enabled) { view_manager_show_desktop(false, false); } if (view_manager->show_activated_only_enabled) { view_manager_show_activated_only(false, false); } } cursor_rebase_all(false); } void view_unmap(struct view *view) { struct kywc_view *kywc_view = &view->base; kywc_view->mapped = false; kywc_log(KYWC_DEBUG, "Kywc_view %p unmap", kywc_view); if (!kywc_view->minimized && !view_add_slide_effect(view, false) && kywc_view->role == KYWC_VIEW_ROLE_NORMAL) { view_add_action_effect(view, EFFECT_ACTION_UNMAP, ACTION_EFFECT_FADE, action_effect_options_adjust, view); } if (view == view_manager_get_global_authentication()) { view_manager_set_global_authentication(NULL); } else if (view == view_manager->desktop) { view_manager->desktop = NULL; } wl_signal_emit_mutable(&kywc_view->events.unmap, NULL); if (view_manager->mode->impl->view_unmap) { view_manager->mode->impl->view_unmap(view); } struct view_proxy *proxy; wl_list_for_each(proxy, &view->view_proxies, view_link) { wl_signal_emit_mutable(&proxy->workspace->events.view_leave, view); } kywc_view->title = kywc_view->app_id = NULL; wl_list_remove(&view->outputs_update.link); wl_list_init(&view->outputs_update.link); ky_scene_node_set_enabled(&view->tree->node, false); if (view->pending.configure_timeout) { wl_event_source_remove(view->pending.configure_timeout); view->pending.configure_timeout = NULL; } wl_list_remove(&view->parent_link); wl_list_init(&view->parent_link); if (view->parent) { view_update_capabilities(view->parent, KYWC_VIEW_MINIMIZABLE | KYWC_VIEW_MAXIMIZABLE | KYWC_VIEW_FULLSCREENABLE | KYWC_VIEW_MOVABLE | KYWC_VIEW_RESIZABLE); view->parent = NULL; } struct view *child, *tmp; wl_list_for_each_safe(child, tmp, &view->children, parent_link) { wl_list_remove(&child->parent_link); wl_list_init(&child->parent_link); child->parent = NULL; view_update_capabilities(child, KYWC_VIEW_MAXIMIZABLE | KYWC_VIEW_FULLSCREENABLE); } cursor_rebase_all(false); } static int view_handle_configure_timeout(void *data) { struct view *view = data; kywc_log(KYWC_INFO, "Client (%s) did not respond to configure request", view->base.app_id); if (view->impl->configure_timeout) { view->impl->configure_timeout(view); } /* fallback for pending actions */ if (view_action_change_size(view->pending.configure_action)) { struct kywc_box *pending = &view->pending.configure_geometry; int x = pending->x, y = pending->y; /* fix wobbling when user resize */ if (view->pending.configure_action & VIEW_ACTION_RESIZE) { struct kywc_box *current = &view->base.geometry; uint32_t resize_edges = view->current_resize_edges; if (resize_edges & KYWC_EDGE_LEFT) { x += pending->width - current->width; } if (resize_edges & KYWC_EDGE_TOP) { y += pending->height - current->height; } } view_helper_move(view, x, y); } view_configured(view, true); return 0; } void view_configure(struct view *view, uint32_t serial) { view->pending.configure_serial = serial; view->pending.configure_action |= view->pending.action; view->pending.configure_geometry = view->pending.geometry; view->pending.action = VIEW_ACTION_NOP; view->pending.geometry = (struct kywc_box){ 0 }; view->pending.geometry = view->base.geometry; if (serial == 0) { return; } if (!view->pending.configure_timeout) { view->pending.configure_timeout = wl_event_loop_add_timer( view_manager->server->event_loop, view_handle_configure_timeout, view); } uint32_t timeout = view_manager_get_configure_timeout(view); wl_event_source_timer_update(view->pending.configure_timeout, timeout); } void view_configured(struct view *view, bool reset) { struct kywc_view *kywc_view = &view->base; if (view->pending.configure_timeout && reset) { wl_event_source_remove(view->pending.configure_timeout); view->pending.configure_timeout = NULL; } if (view->pending.configure_action == VIEW_ACTION_NOP) { return; } if (view->pending.configure_action & VIEW_ACTION_FULLSCREEN) { view_add_scale_effect(view, SCALE_FULLSCREEN); wl_signal_emit_mutable(&kywc_view->events.fullscreen, NULL); } if (view->pending.configure_action & VIEW_ACTION_MAXIMIZE) { view_add_scale_effect(view, SCALE_MAXIMIZE); wl_signal_emit_mutable(&kywc_view->events.maximize, NULL); } if (view->pending.configure_action & VIEW_ACTION_TILE) { wl_signal_emit_mutable(&kywc_view->events.tile, NULL); } if ((view->pending.configure_action & VIEW_ACTION_RESIZE) == 0 && !view->interactive_moving) { cursor_rebase_all(true); } if (view->pending.configure_action & (VIEW_ACTION_FULLSCREEN | VIEW_ACTION_TILE | VIEW_ACTION_MAXIMIZE)) { view_update_round_corner(view); } view->pending.configure_serial = 0; view->pending.configure_action = VIEW_ACTION_NOP; view->pending.configure_geometry = (struct kywc_box){ 0 }; view->pending.geometry = (struct kywc_box){ 0 }; } void view_send_ping(struct view *view) { if (view->impl->ping) { view->impl->ping(view); } } void view_proxy_destroy(struct view_proxy *view_proxy) { if (!view_proxy) { return; } if (view_proxy->view->base.mapped) { wl_signal_emit_mutable(&view_proxy->workspace->events.view_leave, view_proxy->view); wl_signal_emit_mutable(&view_proxy->view->events.workspace_leave, view_proxy->workspace); } wl_list_remove(&view_proxy->view_link); wl_list_remove(&view_proxy->workspace_link); ky_scene_node_destroy(&view_proxy->tree->node); free(view_proxy); } void view_set_current_proxy(struct view *view, struct view_proxy *view_proxy) { if (view->current_proxy == view_proxy) { return; } if (!view_proxy) { assert(wl_list_empty(&view->view_proxies)); } if (view_proxy) { ky_scene_node_reparent(&view->tree->node, view_proxy->tree); } view->current_proxy = view_proxy; view_update_capabilities(view, KYWC_VIEW_ABOVEABLE | KYWC_VIEW_BELOWABLE); wl_signal_emit_mutable(&view->events.workspace, NULL); } static void view_proxies_destroy(struct view *view) { struct view_proxy *proxy, *tmp; wl_list_for_each_safe(proxy, tmp, &view->view_proxies, view_link) { view_proxy_destroy(proxy); } view_set_current_proxy(view, NULL); } void view_destroy(struct view *view) { struct kywc_view *kywc_view = &view->base; kywc_log(KYWC_DEBUG, "Kywc_view %p destroy", kywc_view); wl_signal_emit_mutable(&kywc_view->events.destroy, NULL); wl_list_remove(&view->link); wl_list_remove(&view->output_destroy.link); wl_list_remove(&view->outputs_update.link); wl_list_remove(&view->update_capabilities.link); assert(wl_list_empty(&view->events.output.listener_list)); assert(wl_list_empty(&view->events.parent.listener_list)); assert(wl_list_empty(&view->events.workspace.listener_list)); assert(wl_list_empty(&view->events.workspace_enter.listener_list)); assert(wl_list_empty(&view->events.workspace_leave.listener_list)); assert(wl_list_empty(&view->events.icon_update.listener_list)); assert(wl_list_empty(&view->events.position.listener_list)); assert(wl_list_empty(&view->events.update_capabilities.listener_list)); assert(wl_list_empty(&kywc_view->events.premap.listener_list)); assert(wl_list_empty(&kywc_view->events.map.listener_list)); assert(wl_list_empty(&kywc_view->events.unmap.listener_list)); assert(wl_list_empty(&kywc_view->events.destroy.listener_list)); assert(wl_list_empty(&kywc_view->events.activate.listener_list)); assert(wl_list_empty(&kywc_view->events.maximize.listener_list)); assert(wl_list_empty(&kywc_view->events.minimize.listener_list)); assert(wl_list_empty(&kywc_view->events.fullscreen.listener_list)); assert(wl_list_empty(&kywc_view->events.tile.listener_list)); assert(wl_list_empty(&kywc_view->events.above.listener_list)); assert(wl_list_empty(&kywc_view->events.below.listener_list)); assert(wl_list_empty(&kywc_view->events.sticky.listener_list)); assert(wl_list_empty(&kywc_view->events.skip_taskbar.listener_list)); assert(wl_list_empty(&kywc_view->events.skip_switcher.listener_list)); assert(wl_list_empty(&kywc_view->events.demands_attention.listener_list)); assert(wl_list_empty(&kywc_view->events.modal.listener_list)); assert(wl_list_empty(&kywc_view->events.capabilities.listener_list)); assert(wl_list_empty(&kywc_view->events.title.listener_list)); assert(wl_list_empty(&kywc_view->events.app_id.listener_list)); assert(wl_list_empty(&kywc_view->events.position.listener_list)); assert(wl_list_empty(&kywc_view->events.size.listener_list)); assert(wl_list_empty(&kywc_view->events.decoration.listener_list)); // some apps might be destroyed directly without a map struct view *child, *tmp; wl_list_for_each_safe(child, tmp, &view->children, parent_link) { wl_list_remove(&child->parent_link); wl_list_init(&child->parent_link); child->parent = NULL; view_update_capabilities(child, KYWC_VIEW_MAXIMIZABLE | KYWC_VIEW_FULLSCREENABLE); } ky_scene_node_destroy(&view->tree->node); view_proxies_destroy(view); free(view->icon_name); free((void *)kywc_view->uuid); view->impl->destroy(view); } void view_set_title(struct view *view, const char *title) { struct kywc_view *kywc_view = &view->base; kywc_view->title = title; kywc_log(KYWC_DEBUG, "Kywc_view %p title %s", kywc_view, title); wl_signal_emit_mutable(&kywc_view->events.title, NULL); } void view_set_app_id(struct view *view, const char *app_id) { struct kywc_view *kywc_view = &view->base; kywc_view->app_id = app_id; kywc_log(KYWC_DEBUG, "Kywc_view %p app_id %s", kywc_view, app_id); if (kywc_view->mapped && !view->icon_name) { view_set_icon(view, false); } wl_signal_emit_mutable(&kywc_view->events.app_id, NULL); } void view_set_decoration(struct view *view, enum kywc_ssd ssd) { struct kywc_view *kywc_view = &view->base; if (kywc_view->ssd == ssd) { return; } kywc_view->ssd = ssd; view_update_round_corner(view); kywc_log(KYWC_DEBUG, "Kywc_view %p need ssd %d", kywc_view, ssd); wl_signal_emit_mutable(&kywc_view->events.decoration, NULL); } void view_set_icon_name(struct view *view, const char *icon_name) { if ((!view->icon_name && !icon_name) || (view->icon_name && icon_name && !strcmp(view->icon_name, icon_name))) { return; } free(view->icon_name); view->icon_name = icon_name ? strdup(icon_name) : NULL; if (view->base.mapped) { view_set_icon(view, false); } } struct view_proxy *view_proxy_by_workspace(struct view *view, struct workspace *workspace) { struct view_proxy *view_proxy; wl_list_for_each(view_proxy, &view->view_proxies, view_link) { if (view_proxy->workspace == workspace) { return view_proxy; } } return NULL; } static struct view_proxy *view_proxy_create(struct view *view, struct workspace *workspace) { struct view_proxy *proxy = calloc(1, sizeof(*proxy)); if (!proxy) { return NULL; } proxy->view = view; proxy->workspace = workspace; wl_list_insert(&workspace->view_proxies, &proxy->workspace_link); wl_list_insert(&view->view_proxies, &proxy->view_link); /* create view_proxy tree from new workspace view_layer */ enum layer layer = view->base.kept_above ? LAYER_ABOVE : (view->base.kept_below ? LAYER_BELOW : LAYER_NORMAL); struct view_layer *view_layer = workspace_layer(workspace, layer); proxy->tree = ky_scene_tree_create(view_layer->tree); if (view->base.mapped) { wl_signal_emit_mutable(&workspace->events.view_enter, view); wl_signal_emit_mutable(&view->events.workspace_enter, workspace); } return proxy; } struct view *view_find_ancestor(struct view *view) { struct view *ancestor = view; while (ancestor->parent) { ancestor = ancestor->parent; } return ancestor; } static void view_do_set_workspace(struct view *view, struct workspace *workspace) { struct view_proxy *proxy = view_proxy_by_workspace(view, workspace); if (!proxy) { proxy = view_proxy_create(view, workspace); if (!proxy) { return; } } if (proxy->tree != view->tree->node.parent) { view_set_current_proxy(view, proxy); } // destroy all proxies except the new worskpace struct view_proxy *view_proxy, *tmp; wl_list_for_each_safe(view_proxy, tmp, &view->view_proxies, view_link) { if (view_proxy != proxy) { view_proxy_destroy(view_proxy); } } if (view->base.sticky) { view->base.sticky = false; wl_signal_emit_mutable(&view->base.events.sticky, NULL); } struct view *child; wl_list_for_each_reverse(child, &view->children, parent_link) { view_do_set_workspace(child, workspace); } } void view_set_workspace(struct view *view, struct workspace *workspace) { assert(view && workspace); struct view *ancestor = view_find_ancestor(view); view_do_set_workspace(ancestor, workspace); } static void view_do_unset_workspace(struct view *view, struct view_layer *layer) { /* set workspace to null must reparent view tree first */ view_proxies_destroy(view); wl_list_init(&view->view_proxies); struct view *child; wl_list_for_each_reverse(child, &view->children, parent_link) { ky_scene_node_reparent(&child->tree->node, layer->tree); view_do_unset_workspace(child, layer); } } void view_unset_workspace(struct view *view, struct view_layer *layer) { assert(layer->layer != LAYER_BELOW && layer->layer != LAYER_NORMAL && layer->layer != LAYER_ABOVE && view); struct view *ancestor = view_find_ancestor(view); ky_scene_node_reparent(&ancestor->tree->node, layer->tree); view_do_unset_workspace(ancestor, layer); } static struct view_proxy *view_do_add_workspace(struct view *view, struct workspace *workspace) { struct view_proxy *view_proxy = view_proxy_by_workspace(view, workspace); if (!view_proxy) { view_proxy = view_proxy_create(view, workspace); struct workspace *current_workspace = workspace_manager_get_current(); if (workspace == current_workspace) { view_set_current_proxy(view, view_proxy); } } struct view *child; wl_list_for_each_reverse(child, &view->children, parent_link) { view_do_add_workspace(child, workspace); } return view_proxy; } struct view_proxy *view_add_workspace(struct view *view, struct workspace *workspace) { if (!view || !workspace) { return NULL; } struct view *ancestor = view_find_ancestor(view); view_do_add_workspace(ancestor, workspace); return view_proxy_by_workspace(view, workspace); } static void view_do_remove_workspace(struct view *view, struct workspace *workspace) { struct view_proxy *view_proxy = view_proxy_by_workspace(view, workspace); if (!view_proxy) { return; } struct view_proxy *proxy; wl_list_for_each_reverse(proxy, &view->view_proxies, view_link) { if (proxy != view_proxy) { break; } } /* add to all workspace if the view only exists in this workspace */ if (&proxy->view_link == &view->view_proxies) { view_add_all_workspace(view); return; } /* del the proxy if view exists in multi workspaces */ if (view->current_proxy == view_proxy) { view_set_current_proxy(view, proxy); } view_proxy_destroy(view_proxy); if (view->base.sticky) { view->base.sticky = false; wl_signal_emit_mutable(&view->base.events.sticky, NULL); } struct view *child; wl_list_for_each_reverse(child, &view->children, parent_link) { view_do_remove_workspace(child, workspace); } } void view_remove_workspace(struct view *view, struct workspace *workspace) { if (!view || !workspace) { return; } struct view *ancestor = view_find_ancestor(view); view_do_remove_workspace(ancestor, workspace); } void view_add_all_workspace(struct view *view) { if (!view || view->base.sticky) { return; } int workspace_num = workspace_manager_get_count(); struct workspace *workspace = NULL; for (int i = 0; i < workspace_num; i++) { workspace = workspace_by_position(i); view_add_workspace(view, workspace); } view->base.sticky = true; wl_signal_emit_mutable(&view->base.events.sticky, NULL); } static void view_set_layer_in_workspace(struct view *view, enum layer layer) { if (!view || !view->current_proxy) { return; } struct view_layer *view_layer = NULL; if (layer == LAYER_ACTIVE) { view_layer = view_manager_get_layer(LAYER_ACTIVE, false); ky_scene_node_reparent(&view->tree->node, view_layer->tree); return; } struct view_proxy *proxy; wl_list_for_each(proxy, &view->view_proxies, view_link) { view_layer = workspace_layer(proxy->workspace, layer); if (!view_layer) { continue; } ky_scene_node_reparent(&proxy->tree->node, view_layer->tree); } } static void view_follow_parent_layer(struct view *view, struct view *parent) { struct view_layer *layer = view_manager_get_layer_by_node(&parent->tree->node, false); if (!layer) { return; } if (view->current_proxy && parent->current_proxy) { view_set_layer_in_workspace(view, layer->layer); } else if (view->current_proxy && !parent->current_proxy) { view_unset_workspace(view, layer); } else if (!view->current_proxy && parent->current_proxy) { view_add_workspace(view, parent->current_proxy->workspace); view_set_layer_in_workspace(view, layer->layer); } else { ky_scene_node_reparent(&view->tree->node, layer->tree); } } void view_add_parent_workspace(struct view *view) { if (!view || !view->parent) { return; } struct view *parent = view->parent; struct view_proxy *proxy; wl_list_for_each(proxy, &parent->view_proxies, view_link) { view_add_workspace(view, proxy->workspace); } } void view_set_parent(struct view *view, struct view *parent) { if (view->parent == parent) { return; } wl_list_remove(&view->parent_link); if (parent) { wl_list_insert(&parent->children, &view->parent_link); struct view_layer *parent_layer = view_manager_get_layer_by_node(&parent->tree->node, false); struct view_layer *layer = view_manager_get_layer_by_node(&view->tree->node, false); /* do not set layer if parent layer below view layer */ if (parent_layer && layer && parent_layer->layer > layer->layer) { view_follow_parent_layer(view, parent); } } else { wl_list_init(&view->parent_link); } kywc_log(KYWC_DEBUG, "View %p set parent to %p", view, parent); view->parent = parent; view_update_descendant_capabilities(parent ? parent : view, KYWC_VIEW_MAXIMIZABLE | KYWC_VIEW_FULLSCREENABLE | KYWC_VIEW_MOVABLE | KYWC_VIEW_RESIZABLE); view_add_parent_workspace(view); wl_signal_emit_mutable(&view->events.parent, NULL); } void kywc_view_add_new_listener(struct wl_listener *listener) { wl_signal_add(&view_manager->events.new_view, listener); } void kywc_view_add_new_mapped_listener(struct wl_listener *listener) { wl_signal_add(&view_manager->events.new_mapped_view, listener); } struct kywc_view *kywc_view_by_uuid(const char *uuid) { struct view *view; wl_list_for_each(view, &view_manager->views, link) { if (strcmp(uuid, view->base.uuid) == 0) { return &view->base; } } return NULL; } struct view *view_from_kywc_view(struct kywc_view *kywc_view) { struct view *view = wl_container_of(kywc_view, view, base); return view; } struct view *view_try_from_wlr_surface(struct wlr_surface *wlr_surface) { return wlr_surface->data; } void kywc_view_close(struct kywc_view *kywc_view) { struct view *view = view_from_kywc_view(kywc_view); if (!KYWC_VIEW_IS_CLOSEABLE(kywc_view)) { return; } if (view->impl->close) { view->impl->close(view); } view_send_ping(view); } void view_do_move(struct view *view, int x, int y) { view->pending.action |= VIEW_ACTION_MOVE; view->pending.geometry.x = x; view->pending.geometry.y = y; if (view->base.mapped && view->impl->configure) { view->impl->configure(view); } } void kywc_view_move(struct kywc_view *kywc_view, int x, int y) { if (view_manager->mode->impl->view_request_move) { view_manager->mode->impl->view_request_move(view_from_kywc_view(kywc_view), x, y); } } void view_do_resize(struct view *view, struct kywc_box *geometry) { view->pending.action |= VIEW_ACTION_RESIZE; view->pending.geometry = *geometry; if (view->base.mapped && view->impl->configure) { view->impl->configure(view); } } void kywc_view_resize(struct kywc_view *kywc_view, struct kywc_box *geometry) { if (view_manager->mode->impl->view_request_resize) { view_manager->mode->impl->view_request_resize(view_from_kywc_view(kywc_view), geometry); } } static void view_set_activated(struct view *view, bool activated); static void handle_activated_view_minimized(struct wl_listener *listener, void *data) { struct view *view = view_manager->activated.view; if (!view->base.minimized) { return; } view_set_activated(view, false); view_activate_topmost(false); } static void handle_activated_view_unmap(struct wl_listener *listener, void *data) { struct view *view = view_manager->activated.view; view_set_activated(view, false); view_activate_topmost(false); } static void view_reparent_fullscreen(struct view *view, bool active); static void view_set_activated(struct view *view, bool activated) { struct kywc_view *kywc_view = &view->base; if (kywc_view->activated == activated) { return; } if (activated) { /* listen activated view's minimize and unmap signals, * so that we can auto activate another view. */ view_manager->activated.view = view; wl_signal_add(&kywc_view->events.minimize, &view_manager->activated.minimize); wl_signal_add(&kywc_view->events.unmap, &view_manager->activated.unmap); } else { wl_list_remove(&view_manager->activated.minimize.link); wl_list_remove(&view_manager->activated.unmap.link); view_manager->activated.view = NULL; } /* change fullscreen layer when activation changed */ if (kywc_view->fullscreen) { view_reparent_fullscreen(view, activated); } /* change parent fullscreen layer if parent is fullscreen */ struct view *ancestor = view_find_fullscreen_ancestor(view); if (ancestor) { view_reparent_fullscreen(ancestor, activated); } if (kywc_view->minimized && activated) { kywc_view_set_minimized(kywc_view, false); } kywc_view->activated = activated; view->pending.action |= VIEW_ACTION_ACTIVATE; if (kywc_view->mapped && view->impl->configure) { view->impl->configure(view); } wl_signal_emit_mutable(&kywc_view->events.activate, NULL); } static struct view *view_find_fullscreen_ancestor(struct view *view) { if (!view || !view->parent) { return NULL; } struct view *ancestor = NULL; while (view->parent) { if (view->parent->base.fullscreen) { ancestor = view->parent; } view = view->parent; } return ancestor; } void view_do_activate(struct view *view) { if (view && !KYWC_VIEW_IS_ACTIVATABLE(&view->base)) { return; } struct view *last = view_manager->activated.view; if (last != view) { if (view_manager->show_activated_only_enabled) { view_manager_show_activated_only(false, false); } if (last) { view_set_activated(last, false); } if (view) { view_set_activated(view, true); } wl_signal_emit_mutable(&view_manager->events.activate_view, view_manager->activated.view); } else if (view) { struct view *ancestor = view_find_fullscreen_ancestor(view); if (!ancestor) { ancestor = view; } if (ancestor->base.fullscreen) { view_reparent_fullscreen(ancestor, true); } } if (!view) { return; } struct workspace *workspace = view->current_proxy ? view->current_proxy->workspace : NULL; if (workspace && workspace != workspace_manager_get_current()) { workspace_activate_with_effect(view->current_proxy->workspace); } } static void view_hide_fullscreen_view_in_empty_workspace(void) { struct workspace *workspace = workspace_manager_get_current(); struct view *view = view_manager->activated.view; if (!view) { return; } struct view *ancestor = view_find_fullscreen_ancestor(view); if (!ancestor) { ancestor = view; } if (ancestor->base.fullscreen) { struct view_proxy *view_proxy = view_proxy_by_workspace(ancestor, workspace); if (!view_proxy) { view_reparent_fullscreen(ancestor, false); view_set_activated(ancestor, false); } } } void view_activate_topmost(bool force) { struct view *view = view_manager_get_global_authentication(); /* activate desktop at last */ struct view *current_view = NULL; if (view_manager->activated.view && view_manager->activated.view->base.role != KYWC_VIEW_ROLE_DESKTOP) { current_view = view_manager->activated.view; } if (!view && current_view && (!force || !current_view->current_proxy) && current_view->base.mapped && !current_view->base.minimized) { /* keep current activated view */ view = current_view; } if (!view) { struct view_proxy *view_proxy; struct workspace *workspace = workspace_manager_get_current(); /* find topmost enabled(mapped and not minimized) view and activate it */ wl_list_for_each(view_proxy, &workspace->view_proxies, workspace_link) { if (view_proxy->view->base.mapped && !view_proxy->view->base.minimized && KYWC_VIEW_IS_ACTIVATABLE(&view_proxy->view->base)) { view = view_proxy->view; break; } } } if (!view) { view = view_manager->desktop; } if (view) { view_do_activate(view); view_set_focus(view, input_manager_get_default_seat()); return; } view_do_activate(NULL); /* clear keyboard focus */ seat_focus_surface(input_manager_get_default_seat(), NULL); /* workaround to hide fullscreen view when no view in workspace */ view_hide_fullscreen_view_in_empty_workspace(); wl_signal_emit_mutable(&view_manager->events.activate_view, view_manager->activated.view); } void view_raise_to_top(struct view *view, bool find_parent) { if (!view) { return; } if (view->parent && find_parent) { view_raise_to_top(view->parent, true); } if (view->current_proxy) { /* insert view proxy in workspace topmost */ struct view_proxy *view_proxy; wl_list_for_each(view_proxy, &view->view_proxies, view_link) { wl_list_remove(&view_proxy->workspace_link); wl_list_insert(&view_proxy->workspace->view_proxies, &view_proxy->workspace_link); ky_scene_node_raise_to_top(&view_proxy->tree->node); } } else { ky_scene_node_raise_to_top(&view->tree->node); } /* raise children if any */ struct view *child; wl_list_for_each(child, &view->children, parent_link) { view_raise_to_top(child, false); } } void kywc_view_activate(struct kywc_view *kywc_view) { struct view *view = kywc_view ? view_from_kywc_view(kywc_view) : NULL; if (!view) { view_do_activate(NULL); return; } if (view_manager->mode->impl->view_request_activate) { view_manager->mode->impl->view_request_activate(view); } } void view_set_focus(struct view *view, struct seat *seat) { if (!view || !seat) { return; } struct view *descendant = view_find_descendant_modal(view); if (descendant) { view = descendant; } if (!KYWC_VIEW_IS_FOCUSABLE(&view->base)) { return; } seat_focus_surface(seat, view->surface); view_send_ping(view); } void view_do_tiled(struct view *view, enum kywc_tile tile, struct kywc_output *kywc_output, struct kywc_box *geometry) { struct kywc_view *kywc_view = &view->base; /* tiled mode may switch between outputs */ if (kywc_view->tiled == tile && (!kywc_output || kywc_output == view->output) && kywc_box_equal(&kywc_view->geometry, geometry)) { if (kywc_box_not_empty(&view->tile_start_geometry)) { view_add_scale_effect(view, SCALE_RESIZE); } return; } /* may switch between tiled modes */ if (kywc_view->tiled == KYWC_TILE_NONE && tile != KYWC_TILE_NONE) { if (!kywc_view->maximized && !view->interactive_moving && (!kywc_output || kywc_output == view->output)) { view->saved.geometry = view_action_change_size(view->pending.configure_action) ? view->pending.configure_geometry : kywc_view->geometry; } } if (kywc_view->maximized) { view->pending.action |= VIEW_ACTION_MAXIMIZE; kywc_view->maximized = false; } kywc_view->tiled = tile; view->pending.action |= VIEW_ACTION_TILE; view->pending.geometry = *geometry; view_add_scale_effect(view, SCALE_RESIZE); if (kywc_view->mapped && view->impl->configure) { view->impl->configure(view); } } void kywc_view_set_tiled(struct kywc_view *kywc_view, enum kywc_tile tile, struct kywc_output *kywc_output) { if (view_manager->mode->impl->view_request_tiled) { view_manager->mode->impl->view_request_tiled(view_from_kywc_view(kywc_view), tile, kywc_output); } } void view_do_minimized(struct view *view, bool minimized) { struct kywc_view *kywc_view = &view->base; if (kywc_view->minimized == minimized) { return; } kywc_view->minimized = minimized; view->pending.action |= VIEW_ACTION_MINIMIZE; if (!kywc_view->mapped) { return; } ky_scene_node_set_enabled(&view->tree->node, !minimized); if (view->impl->configure) { view->impl->configure(view); } bool used_slide = false; if (kywc_view->role == KYWC_VIEW_ROLE_SYSTEMWINDOW) { used_slide = view_add_slide_effect(view, !kywc_view->minimized); } if (!used_slide) { /* if view is the activated view, process it in activated.minimize listener */ if (view_manager->state.minimize_effect_type == MINIMIZE_EFFECT_TYPE_MAGIC_LAMP) { if (!view_add_magic_lamp_effect(view)) { view_add_scale_effect(view, SCALE_MINIMIZE); } } else { view_add_scale_effect(view, SCALE_MINIMIZE); } } wl_signal_emit_mutable(&kywc_view->events.minimize, NULL); if (!kywc_view->minimized) { if (view_manager->show_desktop_enabled) { view_manager_show_desktop(false, false); } if (view_manager->show_activated_only_enabled) { view_manager_show_activated_only(false, false); } } cursor_rebase_all(false); if (!view->minimized_when_show_desktop) { struct view *child; wl_list_for_each(child, &view->children, parent_link) { view_do_minimized(child, minimized); } } } void kywc_view_set_minimized(struct kywc_view *kywc_view, bool minimized) { if (view_manager->mode->impl->view_request_minimized) { view_manager->mode->impl->view_request_minimized(view_from_kywc_view(kywc_view), minimized); } } void kywc_view_toggle_minimized(struct kywc_view *kywc_view) { kywc_view_set_minimized(kywc_view, !kywc_view->minimized); } void view_do_maximized(struct view *view, bool maximized, struct kywc_output *kywc_output) { struct kywc_view *kywc_view = &view->base; /* tiled to unmaximized after tiled from maximized */ if (!kywc_view->tiled && kywc_view->maximized == maximized && (!kywc_output || kywc_output == view->output)) { return; } struct kywc_box geo = { 0 }; if (maximized) { view_get_tiled_geometry(view, &geo, kywc_output ? kywc_output : view->output, KYWC_TILE_ALL); if (!kywc_view->tiled && !view->interactive_moving && (!kywc_output || kywc_output == view->output)) { view->saved.geometry = view_action_change_size(view->pending.configure_action) ? view->pending.configure_geometry : kywc_view->geometry; } } else { if (kywc_box_not_empty(&view->restore_geometry)) { geo = view->restore_geometry; } else { geo = view->saved.geometry; } } /* don't restore tiled mode followed other compositors */ if (kywc_view->tiled) { view->pending.action |= VIEW_ACTION_TILE; kywc_view->tiled = KYWC_TILE_NONE; } kywc_view->maximized = maximized; view->pending.action |= VIEW_ACTION_MAXIMIZE; view->pending.geometry = geo; if (kywc_view->mapped && view->impl->configure) { view->impl->configure(view); } } void kywc_view_set_maximized(struct kywc_view *kywc_view, bool maximized, struct kywc_output *kywc_output) { if (view_manager->mode->impl->view_request_maximized) { view_manager->mode->impl->view_request_maximized(view_from_kywc_view(kywc_view), maximized, kywc_output); } } void kywc_view_toggle_maximized(struct kywc_view *kywc_view) { kywc_view_set_maximized(kywc_view, !kywc_view->maximized, NULL); } static void view_reparent_fullscreen(struct view *view, bool active) { struct view_layer *layer = NULL; if (active) { layer = view_manager_get_layer(LAYER_ACTIVE, false); if (layer->tree == view->tree->node.parent) { ky_scene_node_raise_to_top(&view->tree->node); } else { ky_scene_node_reparent(&view->tree->node, layer->tree); } } else if (view->current_proxy) { ky_scene_node_reparent(&view->tree->node, view->current_proxy->tree); } struct view *child; wl_list_for_each(child, &view->children, parent_link) { view_reparent_fullscreen(child, active); } } void view_do_fullscreen(struct view *view, bool fullscreen, struct kywc_output *kywc_output) { struct kywc_view *kywc_view = &view->base; if (kywc_view->fullscreen == fullscreen && (!kywc_output || kywc_output == view->output)) { return; } struct kywc_box geo = { 0 }; if (fullscreen) { kywc_output_effective_geometry(kywc_output ? kywc_output : view->output, &geo); if (!kywc_view->maximized && !kywc_view->tiled) { view->saved.geometry = view_action_change_size(view->pending.configure_action) ? view->pending.configure_geometry : kywc_view->geometry; } } else if (kywc_view->maximized) { view_get_tiled_geometry(view, &geo, view->output, KYWC_TILE_ALL); } else if (kywc_view->tiled) { view_get_tiled_geometry(view, &geo, view->output, kywc_view->tiled); } else { if (kywc_box_not_empty(&view->restore_geometry)) { geo = view->restore_geometry; } else { geo = view->saved.geometry; } } view_reparent_fullscreen(view, fullscreen); kywc_view->fullscreen = fullscreen; view_update_capabilities(view, KYWC_VIEW_MAXIMIZABLE | KYWC_VIEW_MOVABLE | KYWC_VIEW_RESIZABLE); view->pending.action |= VIEW_ACTION_FULLSCREEN; view->pending.geometry = geo; if (kywc_view->mapped && view->impl->configure) { view->impl->configure(view); } } void kywc_view_set_fullscreen(struct kywc_view *kywc_view, bool fullscreen, struct kywc_output *kywc_output) { if (view_manager->mode->impl->view_request_fullscreen) { view_manager->mode->impl->view_request_fullscreen(view_from_kywc_view(kywc_view), fullscreen, kywc_output); } } void kywc_view_toggle_fullscreen(struct kywc_view *kywc_view) { kywc_view_set_fullscreen(kywc_view, !kywc_view->fullscreen, NULL); } static void view_do_set_kept_above(struct view *view, bool kept_above) { if (view->base.kept_above != kept_above) { view->base.kept_above = kept_above; view->base.kept_below = false; enum layer layer = kept_above ? LAYER_ABOVE : LAYER_NORMAL; struct view_proxy *proxy; wl_list_for_each(proxy, &view->view_proxies, view_link) { struct view_layer *view_layer = workspace_layer(proxy->workspace, layer); ky_scene_node_reparent(&proxy->tree->node, view_layer->tree); } wl_signal_emit_mutable(&view->base.events.above, NULL); } if (kept_above) { struct view *child; wl_list_for_each_reverse(child, &view->children, parent_link) { view_do_set_kept_above(child, kept_above); } } else if (view->parent) { view_do_set_kept_above(view->parent, kept_above); } } void kywc_view_set_kept_above(struct kywc_view *kywc_view, bool kept_above) { struct view *view = view_from_kywc_view(kywc_view); if (!KYWC_VIEW_IS_ABOVEABLE(kywc_view)) { return; } view_do_set_kept_above(view, kept_above); view_raise_to_top(view, false); } void kywc_view_toggle_kept_above(struct kywc_view *kywc_view) { kywc_view_set_kept_above(kywc_view, !kywc_view->kept_above); } static void view_do_set_kept_below(struct view *view, bool kept_below) { /* ancestor may already keep below, skip and go on */ if (view->base.kept_below != kept_below) { view->base.kept_below = kept_below; view->base.kept_above = false; enum layer layer = kept_below ? LAYER_BELOW : LAYER_NORMAL; struct view_proxy *proxy; wl_list_for_each(proxy, &view->view_proxies, view_link) { struct view_layer *view_layer = workspace_layer(proxy->workspace, layer); ky_scene_node_reparent(&proxy->tree->node, view_layer->tree); } wl_signal_emit_mutable(&view->base.events.below, NULL); } if (!kept_below) { struct view *child; wl_list_for_each_reverse(child, &view->children, parent_link) { view_do_set_kept_below(child, kept_below); } } else if (view->parent) { view_do_set_kept_below(view->parent, kept_below); } } void kywc_view_set_kept_below(struct kywc_view *kywc_view, bool kept_below) { struct view *view = view_from_kywc_view(kywc_view); if (!KYWC_VIEW_IS_BELOWABLE(kywc_view)) { return; } view_do_set_kept_below(view, kept_below); view_raise_to_top(view, false); } void kywc_view_toggle_kept_below(struct kywc_view *kywc_view) { kywc_view_set_kept_below(kywc_view, !kywc_view->kept_below); } void kywc_view_set_skip_taskbar(struct kywc_view *kywc_view, bool skip_taskbar) { if (kywc_view->skip_taskbar == skip_taskbar) { return; } kywc_view->skip_taskbar = skip_taskbar; wl_signal_emit_mutable(&kywc_view->events.skip_taskbar, NULL); } void kywc_view_set_skip_switcher(struct kywc_view *kywc_view, bool skip_switcher) { if (kywc_view->skip_switcher == skip_switcher) { return; } kywc_view->skip_switcher = skip_switcher; wl_signal_emit_mutable(&kywc_view->events.skip_switcher, NULL); } void kywc_view_set_demands_attention(struct kywc_view *kywc_view, bool demands_attention) { if (kywc_view->demands_attention == demands_attention) { return; } kywc_view->demands_attention = demands_attention; wl_signal_emit_mutable(&kywc_view->events.demands_attention, NULL); } void kywc_view_set_modal(struct kywc_view *kywc_view, bool modal) { if (kywc_view->modal == modal) { return; } kywc_view->modal = modal; struct view *ancestor = view_find_ancestor(view_from_kywc_view(kywc_view)); view_update_descendant_capabilities( ancestor, KYWC_VIEW_MINIMIZABLE | KYWC_VIEW_MAXIMIZABLE | KYWC_VIEW_CLOSEABLE | KYWC_VIEW_FULLSCREENABLE | KYWC_VIEW_MOVABLE | KYWC_VIEW_RESIZABLE | KYWC_VIEW_MINIMIZE_BUTTON | KYWC_VIEW_MAXIMIZE_BUTTON); wl_signal_emit_mutable(&kywc_view->events.modal, NULL); } void kywc_view_set_sticky(struct kywc_view *kywc_view, bool sticky) { if (kywc_view->sticky == sticky) { return; } struct view *view = view_from_kywc_view(kywc_view); if (sticky) { view_add_all_workspace(view); } else { view_set_workspace(view, workspace_manager_get_current()); } } void view_helper_move(struct view *view, int x, int y) { struct kywc_box *geo = &view->base.geometry; if (geo->x != x || geo->y != y) { geo->x = x, geo->y = y; ky_scene_node_set_position(&view->tree->node, x, y); wl_signal_emit_mutable(&view->base.events.position, NULL); } if (!view->interactive_moving && view->current_resize_edges == KYWC_EDGE_NONE) { wl_signal_emit_mutable(&view->events.position, NULL); } } static bool view_has_modal_child(struct view *view) { struct view *child; wl_list_for_each(child, &view->children, parent_link) { if (child->base.mapped && (child->base.modal || view_has_modal_child(child))) { return true; } } return false; } bool view_has_modal_property(struct view *view) { return view->base.modal || view_has_modal_child(view); } struct view *view_find_descendant_modal(struct view *view) { struct view *child; wl_list_for_each(child, &view->children, parent_link) { if (!child->base.mapped || !child->base.modal) { continue; } return view_find_descendant_modal(child); } return view->base.modal ? view : NULL; } static void view_handle_update_capabilities(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, update_capabilities); struct kywc_view *kywc_view = &view->base; struct view_update_capabilities_event *event = data; if (event->mask & KYWC_VIEW_MINIMIZABLE) { if (!view->minimized_when_show_desktop && view_has_modal_property(view)) { event->state &= ~KYWC_VIEW_MINIMIZABLE; } } if (event->mask & KYWC_VIEW_MAXIMIZABLE) { if (kywc_view->fullscreen || view_has_modal_child(view) || KYWC_VIEW_HAS_FIXED_SIZE(kywc_view)) { event->state &= ~KYWC_VIEW_MAXIMIZABLE; } } if (event->mask & KYWC_VIEW_CLOSEABLE) { if (view_has_modal_child(view)) { event->state &= ~KYWC_VIEW_CLOSEABLE; } } if (event->mask & KYWC_VIEW_FULLSCREENABLE) { if (view_has_modal_child(view) || (KYWC_VIEW_HAS_FIXED_SIZE(kywc_view) && view->parent)) { event->state &= ~KYWC_VIEW_FULLSCREENABLE; } } if (event->mask & KYWC_VIEW_MOVABLE) { if (kywc_view->fullscreen || view_has_modal_child(view)) { event->state &= ~KYWC_VIEW_MOVABLE; } } if (event->mask & KYWC_VIEW_RESIZABLE) { if (kywc_view->fullscreen || view_has_modal_child(view) || KYWC_VIEW_HAS_FIXED_SIZE(kywc_view)) { event->state &= ~KYWC_VIEW_RESIZABLE; } } if (event->mask & KYWC_VIEW_ABOVEABLE) { if (!view->current_proxy) { event->state &= ~KYWC_VIEW_ABOVEABLE; } } if (event->mask & KYWC_VIEW_BELOWABLE) { if (!view->current_proxy) { event->state &= ~KYWC_VIEW_BELOWABLE; } } if (event->mask & KYWC_VIEW_MINIMIZE_BUTTON) { if (kywc_view->modal) { event->state &= ~KYWC_VIEW_MINIMIZE_BUTTON; } } if (event->mask & KYWC_VIEW_MAXIMIZE_BUTTON) { if (kywc_view->modal || KYWC_VIEW_HAS_FIXED_SIZE(kywc_view)) { event->state &= ~KYWC_VIEW_MAXIMIZE_BUTTON; } } } struct view_layer *view_manager_get_layer_by_role(enum kywc_view_role role) { switch (role) { default: return NULL; case KYWC_VIEW_ROLE_NORMAL: return view_manager_get_layer(LAYER_NORMAL, true); case KYWC_VIEW_ROLE_DESKTOP: return view_manager_get_layer(LAYER_DESKTOP, false); case KYWC_VIEW_ROLE_PANEL: case KYWC_VIEW_ROLE_APPLETPOPUP: return view_manager_get_layer(LAYER_DOCK, false); case KYWC_VIEW_ROLE_ONSCREENDISPLAY: return view_manager_get_layer(LAYER_ON_SCREEN_DISPLAY, false); case KYWC_VIEW_ROLE_NOTIFICATION: return view_manager_get_layer(LAYER_NOTIFICATION, false); case KYWC_VIEW_ROLE_TOOLTIP: return view_manager_get_layer(LAYER_POPUP, false); case KYWC_VIEW_ROLE_CRITICALNOTIFICATION: return view_manager_get_layer(LAYER_CRITICAL_NOTIFICATION, false); case KYWC_VIEW_ROLE_SYSTEMWINDOW: return view_manager_get_layer(LAYER_SYSTEM_WINDOW, false); case KYWC_VIEW_ROLE_SWITCHER: return view_manager_get_layer(LAYER_SWITCHER, false); case KYWC_VIEW_ROLE_INPUTPANEL: return view_manager_get_layer(LAYER_INPUT_PANEL, false); case KYWC_VIEW_ROLE_LOGOUT: return view_manager_get_layer(LAYER_LOGOUT, false); case KYWC_VIEW_ROLE_SCREENLOCKNOTIFICATION: return view_manager_get_layer(LAYER_SCREEN_LOCK_NOTIFICATION, false); case KYWC_VIEW_ROLE_WATERMARK: return view_manager_get_layer(LAYER_WATERMARK, false); case KYWC_VIEW_ROLE_SCREENLOCK: return view_manager_get_layer(LAYER_SCREEN_LOCK, false); case KYWC_VIEW_ROLE_AUTHENTICATION: return view_manager_get_layer(LAYER_CRITICAL_NOTIFICATION, false); } } void view_set_role(struct view *view, enum kywc_view_role role) { struct kywc_view *kywc_view = &view->base; if (kywc_view->role == role) { return; } kywc_view->role = role; if (kywc_view->role == KYWC_VIEW_ROLE_AUTHENTICATION) { /* cleared when view is unmapoed */ global_authentication_create(view); } if (kywc_view->role == KYWC_VIEW_ROLE_DESKTOP) { view_manager->desktop = view; } if (kywc_view->role == KYWC_VIEW_ROLE_NORMAL) { view_set_workspace(view, workspace_manager_get_current()); } else { struct view_layer *layer = view_manager_get_layer_by_role(kywc_view->role); view_unset_workspace(view, layer); } kywc_view->capabilities = 0; if (kywc_view->role == KYWC_VIEW_ROLE_NORMAL) { kywc_view->capabilities |= KYWC_VIEW_CAPABILITIES_ALL; } else { // not normal if (kywc_view->role != KYWC_VIEW_ROLE_DESKTOP && kywc_view->role != KYWC_VIEW_ROLE_PANEL) { kywc_view->capabilities |= KYWC_VIEW_CLOSEABLE; } if (kywc_view->role == KYWC_VIEW_ROLE_AUTHENTICATION) { kywc_view->capabilities |= KYWC_VIEW_MOVABLE; } if (kywc_view->role == KYWC_VIEW_ROLE_PANEL) { kywc_view->capabilities |= KYWC_VIEW_RESIZABLE; } if (kywc_view->role != KYWC_VIEW_ROLE_PANEL && kywc_view->role != KYWC_VIEW_ROLE_TOOLTIP && kywc_view->role != KYWC_VIEW_ROLE_WATERMARK && kywc_view->role != KYWC_VIEW_ROLE_SWITCHER && kywc_view->role != KYWC_VIEW_ROLE_NOTIFICATION) { kywc_view->capabilities |= KYWC_VIEW_ACTIVATABLE; } if (kywc_view->role == KYWC_VIEW_ROLE_DESKTOP || kywc_view->role == KYWC_VIEW_ROLE_SYSTEMWINDOW || kywc_view->role == KYWC_VIEW_ROLE_SCREENLOCK || kywc_view->role == KYWC_VIEW_ROLE_APPLETPOPUP || kywc_view->role == KYWC_VIEW_ROLE_ONSCREENDISPLAY || kywc_view->role == KYWC_VIEW_ROLE_AUTHENTICATION) { kywc_view->capabilities |= KYWC_VIEW_FOCUSABLE; } } // panel need to be unconstrained kywc_view->unconstrained = kywc_view->role == KYWC_VIEW_ROLE_PANEL; kywc_view->has_round_corner = kywc_view->role == KYWC_VIEW_ROLE_NORMAL || kywc_view->role == KYWC_VIEW_ROLE_SYSTEMWINDOW || kywc_view->role == KYWC_VIEW_ROLE_APPLETPOPUP || kywc_view->role == KYWC_VIEW_ROLE_AUTHENTICATION; view_update_round_corner(view); } void view_update_size(struct view *view, int width, int height, int min_width, int min_height, int max_width, int max_height) { struct kywc_view *kywc_view = &view->base; if (kywc_view->min_width != min_width || kywc_view->min_height != min_height) { kywc_view->min_width = min_width; kywc_view->min_height = min_height; kywc_log(KYWC_DEBUG, "View %p minimal size to %d x %d", view, min_width, min_height); view_update_capabilities(view, KYWC_VIEW_MAXIMIZABLE | KYWC_VIEW_FULLSCREENABLE | KYWC_VIEW_RESIZABLE | KYWC_VIEW_MAXIMIZE_BUTTON); } if (kywc_view->max_width != max_width || kywc_view->max_height != max_height) { kywc_view->max_width = max_width; kywc_view->max_height = max_height; kywc_log(KYWC_DEBUG, "View %p maximal size to %d x %d", view, max_width, max_height); view_update_capabilities(view, KYWC_VIEW_MAXIMIZABLE | KYWC_VIEW_FULLSCREENABLE | KYWC_VIEW_RESIZABLE | KYWC_VIEW_MAXIMIZE_BUTTON); } if (kywc_view->geometry.width != width || kywc_view->geometry.height != height) { kywc_view->geometry.width = width; kywc_view->geometry.height = height; kywc_log(KYWC_DEBUG, "View %p size to %d x %d", view, width, height); wl_signal_emit_mutable(&view->base.events.size, NULL); } } void view_update_descendant_capabilities(struct view *view, uint32_t mask) { view_update_capabilities(view, mask); struct view *child; wl_list_for_each(child, &view->children, parent_link) { view_update_descendant_capabilities(child, mask); } } void view_update_capabilities(struct view *view, uint32_t mask) { struct kywc_view *kywc_view = &view->base; struct view_update_capabilities_event event = { .mask = mask, .state = KYWC_VIEW_CAPABILITIES_ALL & mask, }; wl_signal_emit_mutable(&view->events.update_capabilities, &event); uint32_t cleared = kywc_view->capabilities & ~mask; uint32_t new_state = event.state & mask; uint32_t capabilities = cleared | new_state; if (kywc_view->capabilities == capabilities) { return; } mask = kywc_view->capabilities ^ capabilities; kywc_view->capabilities = capabilities; struct kywc_view_capabilities_event ev = { .mask = mask, }; wl_signal_emit_mutable(&kywc_view->events.capabilities, &ev); } void view_add_update_capabilities_listener(struct view *view, struct wl_listener *listener) { wl_signal_add(&view->events.update_capabilities, listener); } void view_close_popups(struct view *view) { if (view->impl->close_popups) { view->impl->close_popups(view); } } void view_show_window_menu(struct view *view, struct seat *seat, int x, int y) { if (view_manager->mode->impl->view_request_show_menu) { view_manager->mode->impl->view_request_show_menu(view, seat, x, y); } } bool view_show_tile_flyout(struct view *view, struct seat *seat, struct kywc_box *box) { if (view_manager->mode->impl->view_request_show_tile_flyout) { view_manager->mode->impl->view_request_show_tile_flyout(view, seat, box); return true; } return false; } bool view_show_tile_linkage_bar(struct view *view, uint32_t edges) { if (view_manager->mode->impl->view_request_show_tile_linkage_bar) { view_manager->mode->impl->view_request_show_tile_linkage_bar(view, edges); return true; } return false; } void highlight_view(struct view *view, bool enable) { view_manager->highlight = enable ? view : NULL; struct workspace *workspace = workspace_manager_get_current(); struct view_proxy *view_proxy; wl_list_for_each_reverse(view_proxy, &workspace->view_proxies, workspace_link) { if (!view_proxy->view->base.mapped || view_proxy->view->base.minimized) { continue; } ky_scene_node_set_enabled(&view_proxy->view->tree->node, !enable); } if (view) { // Unset highlight window need to restore status when highlight window is minimized ky_scene_node_set_enabled(&view->tree->node, enable ? enable : !view->base.minimized); } wl_signal_emit_mutable(&view_manager->events.highlight, view); } void view_manager_add_highlight_view_listener(struct wl_listener *listener) { wl_signal_add(&view_manager->events.highlight, listener); } struct view *view_manager_get_highlight(void) { return view_manager->highlight; } void view_show_hang_window(struct view *view) { // TODO: implement show_hang_window kywc_log(KYWC_WARN, "%s view is not responding", view->base.app_id); if (view_manager->impl.show_hang_window) { view_manager->impl.show_hang_window(view); } } void view_manager_show_desktop(bool enabled, bool apply) { if (view_manager->show_desktop_enabled == enabled) { return; } view_manager->show_desktop_enabled = enabled; /* minimize all view in current workspace */ struct workspace *workspace = workspace_manager_get_current(); struct view_proxy *view_proxy; wl_list_for_each_reverse(view_proxy, &workspace->view_proxies, workspace_link) { struct view *view = view_proxy->view; /* skip views not mapped */ if (!view->base.mapped) { continue; } /* skip restoring views not minimized by show desktop */ if (!enabled && !view->minimized_when_show_desktop) { continue; } /* true only the view is not minimized when going show desktop */ if (enabled) { view->minimized_when_show_desktop = !view->base.minimized; view_update_capabilities(view, KYWC_VIEW_MINIMIZABLE); } /* don't restoring views if the state is breaked */ if (apply || view_has_modal_property(view)) { kywc_view_set_minimized(&view->base, enabled); } if (!enabled) { view->minimized_when_show_desktop = false; view_update_capabilities(view, KYWC_VIEW_MINIMIZABLE); } } if (apply && !enabled) { view_activate_topmost(false); } /* set focus to desktop after showing desktop */ if (apply && enabled && view_manager->desktop) { view_set_focus(view_manager->desktop, input_manager_get_default_seat()); } wl_signal_emit_mutable(&view_manager->events.show_desktop, NULL); } void view_manager_add_show_desktop_listener(struct wl_listener *listener) { wl_signal_add(&view_manager->events.show_desktop, listener); } bool view_manager_get_show_desktop(void) { return view_manager->show_desktop_enabled; } bool view_manager_get_show_switcher(void) { return view_manager->switcher_shown; } void view_manager_show_activated_only(bool enabled, bool apply) { if (view_manager->show_activated_only_enabled == enabled) { return; } view_manager->show_activated_only_enabled = enabled; struct view *current_view = view_manager_get_activated(); if (!current_view) { return; } /* minimize all view in current workspace */ struct workspace *workspace = workspace_manager_get_current(); struct view_proxy *view_proxy; wl_list_for_each_reverse(view_proxy, &workspace->view_proxies, workspace_link) { struct view *view = view_proxy->view; if (!view->base.mapped || view == current_view || view_has_modal_property(view)) { continue; } /* skip restoring views not minimized by show only current view */ if (!enabled && !view->minimized_when_show_active_only) { continue; } if (enabled) { view->minimized_when_show_active_only = !view->base.minimized; } /* don't restoring views if the state is breaked */ if (apply) { if (view->base.minimized == enabled || !KYWC_VIEW_IS_MINIMIZABLE(&view->base)) { continue; } ky_scene_node_set_enabled(&view->tree->node, !enabled); view->base.minimized = enabled; view->pending.action |= VIEW_ACTION_MINIMIZE; if (view->impl->configure) { view->impl->configure(view); } wl_signal_emit_mutable(&view->base.events.minimize, NULL); } if (!enabled) { view->minimized_when_show_active_only = false; } } } bool view_manager_get_show_activte_only(void) { return view_manager->show_activated_only_enabled; } uint32_t view_manager_get_adsorption(void) { return view_manager->state.view_adsorption; } uint32_t view_manager_get_resize_filter(struct view *view) { if (xwayland_check_view(view)) { return view_manager->state.resize_filter[1]; } else { return view_manager->state.resize_filter[0]; } } uint32_t view_manager_get_configure_timeout(struct view *view) { if (!view) { return MAX(view_manager->state.configure_timeout[0], view_manager->state.configure_timeout[1]); } else if (xwayland_check_view(view)) { return view_manager->state.configure_timeout[1]; } else { return view_manager->state.configure_timeout[0]; } } void view_manager_set_global_authentication(struct view *view) { view_manager->global_authentication_view = view; } struct view *view_manager_get_global_authentication(void) { return view_manager->global_authentication_view; } static void handle_server_ready(struct wl_listener *listener, void *data) { theme_manager_add_update_listener(&view_manager->theme_update, false); theme_manager_add_icon_update_listener(&view_manager->theme_icon_update); window_menu_manager_create(view_manager); maximize_switcher_create(view_manager); tile_flyout_manager_create(view_manager); stack_mode_register(view_manager); tablet_mode_register(view_manager); if (!view_manager->mode) { view_manager->mode = view_manager_mode_from_name("stack_mode"); } if (view_manager->mode->impl->view_mode_enter) { view_manager->mode->impl->view_mode_enter(); } } static void handle_server_destroy(struct wl_listener *listener, void *data) { assert(wl_list_empty(&view_manager->events.new_view.listener_list)); assert(wl_list_empty(&view_manager->events.new_mapped_view.listener_list)); assert(wl_list_empty(&view_manager->events.show_desktop.listener_list)); assert(wl_list_empty(&view_manager->events.activate_view.listener_list)); assert(wl_list_empty(&view_manager->events.highlight.listener_list)); wl_list_remove(&view_manager->server_destroy.link); wl_list_remove(&view_manager->server_ready.link); wl_list_remove(&view_manager->server_terminate.link); wl_list_remove(&view_manager->theme_update.link); wl_list_remove(&view_manager->theme_icon_update.link); struct view_mode *mode, *tmp; wl_list_for_each_safe(mode, tmp, &view_manager->view_modes, link) { if (mode->impl->mode_destroy) { mode->impl->mode_destroy(); } view_manager_mode_unregister(mode); } for (int layer = LAYER_FIRST; layer < LAYER_NUMBER; layer++) { ky_scene_node_destroy(&view_manager->layers[layer].tree->node); } free(view_manager); view_manager = NULL; } static void handle_server_terminate(struct wl_listener *listener, void *data) { view_manager->state.num_workspaces = workspace_manager_get_count(); view_write_config(view_manager); } static void handle_theme_update(struct wl_listener *listener, void *data) { struct theme_update_event *update_event = data; if (!(update_event->update_mask & THEME_UPDATE_MASK_CORNER_RADIUS)) { return; } struct view *view; wl_list_for_each(view, &view_manager->views, link) { view_update_round_corner(view); } } static void handle_theme_icon_update(struct wl_listener *listener, void *data) { struct view *view; wl_list_for_each(view, &view_manager->views, link) { if (view->base.mapped) { view_set_icon(view, false); } } } void view_manager_set_switcher_shown(bool shown) { view_manager->switcher_shown = shown; } void view_click(struct seat *seat, struct view *view, uint32_t button, bool pressed, enum click_state state) { if (view_manager->mode->impl->view_click) { view_manager->mode->impl->view_click(seat, view, button, pressed, state); } } void view_hover(struct seat *seat, struct view *view) { if (view_manager->mode->impl->view_hover) { view_manager->mode->impl->view_hover(seat, view); } } struct view_mode *view_manager_mode_from_name(const char *name) { if (!name || !*name) { return NULL; } struct view_mode *mode; wl_list_for_each(mode, &view_manager->view_modes, link) { if (mode->impl->name && strcmp(mode->impl->name, name) == 0) { return mode; } } return NULL; } bool view_manager_set_view_mode(const char *name) { struct view_mode *view_mode = view_manager_mode_from_name(name); if (!view_mode) { return false; } if (view_manager->mode == view_mode) { return true; } if (!view_manager->server->ready) { view_manager->mode = view_mode; return true; } if (view_manager->mode && view_manager->mode->impl->view_mode_leave) { view_manager->mode->impl->view_mode_leave(); } view_manager->mode = view_mode; if (view_manager->mode->impl->view_mode_enter) { view_manager->mode->impl->view_mode_enter(); } return true; } void view_manager_set_effect_options_impl(action_effect_options_adjust_func_t impl) { view_manager->impl.action_effect_options_adjust = impl; } void view_manager_adjust_effect_options(enum effect_action action, struct action_effect_options *options, void *user_data) { if (!view_manager->impl.action_effect_options_adjust) { return; } view_manager->impl.action_effect_options_adjust(ACTION_EFFECT_OPTIONS_SURFACE, action, options, user_data); } bool view_has_descendant(struct view *view, struct view *descendant) { if (!descendant) { return !wl_list_empty(&view->children); } struct view *tmp; wl_list_for_each(tmp, &view->children, parent_link) { if (tmp == descendant) { return true; } if (view_has_descendant(tmp, descendant)) { return true; } } return false; } bool view_has_ancestor(struct view *view, struct view *ancestor) { if (!view->parent) { return false; } if (!ancestor) { return !!view->parent; } if (view->parent == ancestor) { return true; } return view_has_ancestor(view->parent, ancestor); } void view_move_to_center(struct view *view) { struct output *output = output_from_kywc_output(view->output); int x_center = output->geometry.x + output->geometry.width / 2; int y_center = output->geometry.y + output->geometry.height / 2; int x = x_center - view->base.geometry.width / 2; int y = y_center - view->base.geometry.height / 2; view_do_move(view, x, y); } bool view_validate_move_or_resize_request(struct view *view, struct seat *seat) { bool left_button_pressed = LEFT_BUTTON_PRESSED(seat->cursor->last_click_button, seat->cursor->last_click_pressed); bool pointer_touch_id = seat->cursor->pointer_touch_id >= 0; bool tablet_tool_tip_down = seat->cursor->tablet_tool_tip_down; /** * Check for touch or tablet, then check for mouse. * * The touch device first clicks the SSD area, records the mouse state, * and triggers a grab. After grabbing, it does not update. At this point, * if using a touch device to click the surface, the mouse state remains as before. */ struct ky_scene_node *node = NULL; if (pointer_touch_id || tablet_tool_tip_down) { node = seat->cursor->hover.node; } else if (left_button_pressed) { node = seat->cursor->focus.node; } return node && wlr_surface_try_from_node(node) == view->surface; } struct view_mode *view_manager_mode_register(const struct view_mode_interface *impl) { struct view_mode *mode = malloc(sizeof(*mode)); if (!mode) { return NULL; } wl_list_insert(&view_manager->view_modes, &mode->link); mode->impl = impl; return mode; } void view_manager_mode_unregister(struct view_mode *mode) { wl_list_remove(&mode->link); free(mode); } void view_manager_for_each_view(view_iterator_func_t iterator, bool mapped, void *data) { struct view *view; wl_list_for_each(view, &view_manager->views, link) { if (view->base.mapped == mapped) { if (iterator(&view->base, data)) { break; } } } } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&view_manager->display_destroy.link); wl_list_remove(&view_manager->new_xdg_toplevel.link); } static void xdg_foreign_create(struct server *server) { struct wlr_xdg_foreign_registry *foreign_registry = wlr_xdg_foreign_registry_create(server->display); wlr_xdg_foreign_v1_create(server->display, foreign_registry); wlr_xdg_foreign_v2_create(server->display, foreign_registry); } struct view_manager *view_manager_create(struct server *server) { view_manager = calloc(1, sizeof(*view_manager)); if (!view_manager) { return NULL; } view_manager->server = server; wl_list_init(&view_manager->views); wl_signal_init(&view_manager->events.new_view); wl_signal_init(&view_manager->events.new_mapped_view); wl_signal_init(&view_manager->events.show_desktop); wl_signal_init(&view_manager->events.activate_view); wl_signal_init(&view_manager->events.highlight); view_manager->theme_update.notify = handle_theme_update; wl_list_init(&view_manager->theme_update.link); view_manager->theme_icon_update.notify = handle_theme_icon_update; wl_list_init(&view_manager->theme_icon_update.link); view_manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &view_manager->display_destroy); view_manager->server_terminate.notify = handle_server_terminate; wl_signal_add(&server->events.terminate, &view_manager->server_terminate); view_manager->server_ready.notify = handle_server_ready; wl_signal_add(&server->events.ready, &view_manager->server_ready); view_manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &view_manager->server_destroy); view_manager->activated.minimize.notify = handle_activated_view_minimized; view_manager->activated.unmap.notify = handle_activated_view_unmap; /* create all layers */ for (int layer = LAYER_FIRST; layer < LAYER_NUMBER; layer++) { view_manager->layers[layer].layer = layer; view_manager->layers[layer].tree = ky_scene_tree_create(&server->scene->tree); view_manager->layers[layer].tree->node.role.type = KY_SCENE_ROLE_LAYER; view_manager->layers[layer].tree->node.role.data = &view_manager->layers[layer]; } wl_list_init(&view_manager->view_modes); view_manager_config_init(view_manager); view_manager->state.num_workspaces = 4; view_manager->state.view_adsorption = VIEW_ADSORPTION_ALL; view_manager->state.resize_filter[0] = 10; view_manager->state.resize_filter[1] = 40; view_manager->state.configure_timeout[0] = 200; view_manager->state.configure_timeout[1] = 400; view_read_config(view_manager); workspace_manager_create(view_manager); decoration_manager_create(view_manager); server_decoration_manager_create(view_manager); positioner_manager_create(view_manager); tile_manager_create(view_manager); window_actions_create(view_manager); view_manager_actions_create(view_manager); wl_list_init(&view_manager->new_xdg_toplevel.link); xdg_shell_init(view_manager); ukui_startup_management_create(view_manager); modal_manager_create(server); wlr_layer_shell_manager_create(server); wlr_foreign_toplevel_manager_create(server); ky_toplevel_manager_create(server); xdg_toplevel_icon_manager_create(server); kde_plasma_shell_create(server); kde_plasma_window_management_create(server); kde_blur_manager_create(server); kde_slide_manager_create(server); xdg_dialog_create(server); xdg_activation_create(server); ukui_shell_create(server); ukui_window_management_create(server); ukui_blur_manager_create(server); xdg_foreign_create(server); return view_manager; } kylin-wayland-compositor/src/view/view_p.h0000664000175000017500000002414315160461067017674 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _VIEW_P_H_ #define _VIEW_P_H_ #include #include "server.h" #include "view/view.h" #include "view/workspace.h" struct view_mode_interface { const char *name; void (*view_map)(struct view *view); void (*view_unmap)(struct view *view); void (*view_request_move)(struct view *view, int x, int y); void (*view_request_resize)(struct view *view, struct kywc_box *geometry); void (*view_request_minimized)(struct view *view, bool minimized); void (*view_request_maximized)(struct view *view, bool maximized, struct kywc_output *kywc_output); void (*view_request_fullscreen)(struct view *view, bool fullscreen, struct kywc_output *kywc_output); void (*view_request_tiled)(struct view *view, enum kywc_tile tile, struct kywc_output *kywc_output); void (*view_request_activate)(struct view *view); void (*view_request_show_menu)(struct view *view, struct seat *seat, int x, int y); void (*view_request_show_tile_flyout)(struct view *view, struct seat *seat, struct kywc_box *box); void (*view_request_show_tile_linkage_bar)(struct view *view, uint32_t edges); void (*view_click)(struct seat *seat, struct view *view, uint32_t button, bool pressed, enum click_state state); void (*view_hover)(struct seat *seat, struct view *view); void (*view_mode_enter)(void); void (*view_mode_leave)(void); void (*mode_destroy)(void); }; enum minimize_effect_type { MINIMIZE_EFFECT_TYPE_SCALE, MINIMIZE_EFFECT_TYPE_MAGIC_LAMP, MINIMIZE_EFFECT_TYPE_NUM, }; struct view_mode { struct wl_list link; const struct view_mode_interface *impl; }; struct view_manager_impl { void (*get_startup_geometry)(struct view *view, void *data); void (*set_tiled)(struct view *view, enum kywc_tile tile, struct kywc_output *kywc_output); void (*get_tiled_geometry)(struct view *view, struct kywc_box *geometry, struct kywc_output *kywc_output, enum kywc_tile tile); void (*fix_geometry)(struct view *view, struct kywc_box *geo, struct kywc_output *kywc_output); void (*show_tile_assist)(struct view *view, struct seat *seat, struct kywc_output *kywc_output); void (*show_hang_window)(struct view *view); action_effect_options_adjust_func_t action_effect_options_adjust; }; struct view_manager { struct server *server; struct wl_list views; struct view *global_authentication_view; struct view *desktop; struct view *highlight; struct { struct wl_signal new_view; struct wl_signal new_mapped_view; struct wl_signal show_desktop; struct wl_signal activate_view; struct wl_signal highlight; } events; struct view_layer layers[LAYER_NUMBER]; // TODO: views keyboard focused when multi-seat struct { /* only one activated view in all workspaces at once */ struct view *view; struct wl_listener minimize; struct wl_listener unmap; } activated; struct config *config; struct view_manager_impl impl; struct { uint32_t num_workspaces; const char *workspace_names[MAX_WORKSPACES]; uint32_t view_adsorption; bool csd_round_corner; enum minimize_effect_type minimize_effect_type; /* 0 is wayland resize_filter. 1 is xwayland resize_filter */ uint32_t resize_filter[2]; uint32_t configure_timeout[2]; } state; struct wl_listener theme_update; struct wl_listener theme_icon_update; struct wl_listener new_xdg_toplevel; struct wl_listener display_destroy; struct wl_listener server_terminate; struct wl_listener server_ready; struct wl_listener server_destroy; bool show_desktop_enabled; bool show_activated_only_enabled; bool switcher_shown; struct wl_list view_modes; // struct view_mode.link struct view_mode *mode; }; bool view_manager_config_init(struct view_manager *view_manager); void view_manager_set_switcher_shown(bool shown); bool view_read_config(struct view_manager *view_manager); void view_write_config(struct view_manager *view_manager); void view_close_popups(struct view *view); void view_update_round_corner(struct view *view); bool xdg_shell_init(struct view_manager *view_manager); struct wlr_xdg_surface; void xdg_view_add_icon_buffer(struct wlr_xdg_surface *wlr_xdg_surface, int scale, struct wlr_buffer *wlr_buffer); void xdg_view_clear_icon_buffer(struct wlr_xdg_surface *wlr_xdg_surface); bool decoration_manager_create(struct view_manager *view_manager); void view_proxy_destroy(struct view_proxy *view_proxy); void view_set_current_proxy(struct view *view, struct view_proxy *view_proxy); struct view_proxy *view_proxy_by_workspace(struct view *view, struct workspace *workspace); bool positioner_manager_create(struct view_manager *view_manager); void positioner_add_new_view(struct view *view); bool server_decoration_manager_create(struct view_manager *view_manager); bool window_actions_create(struct view_manager *view_manager); bool view_manager_actions_create(struct view_manager *view_manager); bool window_menu_manager_create(struct view_manager *view_manager); void window_menu_show(struct view *view, struct seat *seat, int x, int y); bool maximize_switcher_create(struct view_manager *view_manager); void global_authentication_create(struct view *view); void view_manager_set_global_authentication(struct view *view); struct view *view_manager_get_global_authentication(void); bool tile_manager_create(struct view_manager *view_manager); void view_manager_show_tile_assist(struct view *view, struct seat *seat, struct kywc_output *kywc_output); void tile_linkage_bar_show(struct view *view, uint32_t edges); bool tile_linkage_view_resize(struct view *view, uint32_t edges, double l_x, double l_y); void tile_linkage_resize_done(struct view *view, bool cancel); bool tile_flyout_manager_create(struct view_manager *view_manager); void tile_flyout_show(struct view *view, struct seat *seat, struct kywc_box *box); struct view *view_manager_get_highlight(void); void view_manager_add_highlight_view_listener(struct wl_listener *listener); void highlight_view(struct view *view, bool enable); void view_fix_geometry(struct view *view, struct kywc_box *geo, struct kywc_box *src_box, struct kywc_box *dst_box); struct wlr_xdg_popup; void xdg_popup_create(struct wlr_xdg_popup *wlr_xdg_popup, struct ky_scene_tree *shell, struct view_layer *layer, bool use_usable_area); bool ky_workspace_manager_create(struct server *server); bool ky_toplevel_manager_create(struct server *server); bool modal_manager_create(struct server *server); bool xdg_dialog_create(struct server *server); bool xdg_activation_create(struct server *server); struct view_mode *view_manager_mode_from_name(const char *name); struct view_mode *view_manager_mode_register(const struct view_mode_interface *impl); void view_manager_mode_unregister(struct view_mode *mode); void stack_mode_register(struct view_manager *view_manager); void tablet_mode_register(struct view_manager *view_manager); #if HAVE_KDE_VIRTUAL_DESKTOP bool kde_virtual_desktop_management_create(struct server *server); #else static __attribute__((unused)) inline bool kde_virtual_desktop_management_create(struct server *server) { return false; } #endif #if HAVE_WLR_FOREIGN_TOPLEVEL bool wlr_foreign_toplevel_manager_create(struct server *server); #else static __attribute__((unused)) inline bool wlr_foreign_toplevel_manager_create(struct server *server) { return false; } #endif #if HAVE_WLR_LAYER_SHELL bool wlr_layer_shell_manager_create(struct server *server); #else static __attribute__((unused)) inline bool wlr_layer_shell_manager_create(struct server *server) { return false; } #endif #if HAVE_KDE_PLASMA_SHELL bool kde_plasma_shell_create(struct server *server); #else static __attribute__((unused)) inline bool kde_plasma_shell_create(struct server *server) { return false; } #endif #if HAVE_KDE_PLASMA_WINDOW_MANAGEMENT bool kde_plasma_window_management_create(struct server *server); #else static __attribute__((unused)) inline bool kde_plasma_window_management_create(struct server *server) { return false; } #endif #if HAVE_KDE_BLUR bool kde_blur_manager_create(struct server *server); #else static __attribute__((unused)) inline bool kde_blur_manager_create(struct server *server) { return false; } #endif #if HAVE_KDE_SLIDE bool kde_slide_manager_create(struct server *server); #else static __attribute__((unused)) inline bool kde_slide_manager_create(struct server *server) { return false; } #endif #if HAVE_UKUI_SHELL bool ukui_shell_create(struct server *server); uint32_t ukui_shell_get_surface_decoration_flags(struct wlr_surface *surface); #else static __attribute__((unused)) inline bool ukui_shell_create(struct server *server) { return false; } static __attribute__((unused)) inline uint32_t ukui_shell_get_surface_decoration_flags(struct wlr_surface *surface) { return 0; } #endif #if HAVE_UKUI_WINDOW_MANAGEMENT bool ukui_window_management_create(struct server *server); #else static __attribute__((unused)) inline bool ukui_window_management_create(struct server *server) { return false; } #endif #if HAVE_UKUI_BLUR bool ukui_blur_manager_create(struct server *server); #else static __attribute__((unused)) inline bool ukui_blur_manager_create(struct server *server) { return false; } #endif #if HAVE_UKUI_STARTUP bool ukui_startup_management_create(struct view_manager *view_manager); #else static __attribute__((unused)) inline bool ukui_startup_management_create(struct view_manager *view_manager) { return false; } #endif #if HAVE_XDG_TOPLEVEL_ICON bool xdg_toplevel_icon_manager_create(struct server *server); #else static __attribute__((unused)) inline bool xdg_toplevel_icon_manager_create(struct server *server) { return false; } #endif #endif /* _VIEW_P_H_ */ kylin-wayland-compositor/src/view/decoration.c0000664000175000017500000002412415160461067020524 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "view_p.h" struct decoration_manager { struct wl_list decos; struct wl_listener xdg_toplevel_decoration; struct wl_listener server_decoration; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct decoration { struct wl_list link; struct wlr_surface *surface; struct wlr_server_decoration *server_deco; struct wl_listener server_deco_apply_mode; struct wl_listener server_deco_destroy; struct wlr_xdg_toplevel_decoration_v1 *xdg_deco; struct wl_listener xdg_deco_request_mode; struct wl_listener xdg_deco_destroy; struct wl_listener surface_commit; struct view *view; bool should_use_ssd; }; static struct decoration_manager *manager = NULL; static void decoration_apply(struct decoration *deco) { if (!deco->view) { return; } enum kywc_ssd ssd = deco->should_use_ssd ? KYWC_SSD_ALL : KYWC_SSD_NONE; if (ssd == KYWC_SSD_ALL && deco->view->base.ssd != KYWC_SSD_NONE) { ssd = deco->view->base.ssd; } view_set_decoration(deco->view, ssd); } static struct decoration *decoration_from_surface(struct wlr_surface *surface) { struct decoration *deco; wl_list_for_each(deco, &manager->decos, link) { if (deco->surface == surface) { return deco; } } deco = calloc(1, sizeof(*deco)); if (!deco) { return NULL; } deco->surface = surface; deco->view = view_try_from_wlr_surface(surface); wl_list_insert(&manager->decos, &deco->link); return deco; } static void decoration_consider_destroy(struct decoration *deco) { if (deco->xdg_deco || deco->server_deco) { return; } wl_list_remove(&deco->link); free(deco); } static void handle_server_deco_destroy(struct wl_listener *listener, void *data) { struct decoration *deco = wl_container_of(listener, deco, server_deco_destroy); wl_list_remove(&deco->server_deco_destroy.link); wl_list_remove(&deco->server_deco_apply_mode.link); deco->server_deco = NULL; decoration_consider_destroy(deco); } static void handle_xdg_deco_destroy(struct wl_listener *listener, void *data) { struct decoration *deco = wl_container_of(listener, deco, xdg_deco_destroy); wl_list_remove(&deco->xdg_deco_destroy.link); wl_list_remove(&deco->xdg_deco_request_mode.link); wl_list_remove(&deco->surface_commit.link); deco->xdg_deco = NULL; decoration_consider_destroy(deco); } static void handle_surface_commit(struct wl_listener *listener, void *data) { struct decoration *deco = wl_container_of(listener, deco, surface_commit); if (!deco->xdg_deco->toplevel->base->initial_commit) { return; } enum wlr_xdg_toplevel_decoration_v1_mode xdg_mode = deco->should_use_ssd ? WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; wlr_xdg_toplevel_decoration_v1_set_mode(deco->xdg_deco, xdg_mode); wl_list_remove(&deco->surface_commit.link); wl_list_init(&deco->surface_commit.link); deco->surface_commit.notify = NULL; } static void xdg_toplevel_decoration_set_mode(struct decoration *deco, struct wlr_xdg_toplevel_decoration_v1 *xdg_deco) { if (xdg_deco->toplevel->base->initialized) { enum wlr_xdg_toplevel_decoration_v1_mode xdg_mode = deco->should_use_ssd ? WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; wlr_xdg_toplevel_decoration_v1_set_mode(xdg_deco, xdg_mode); return; } // set in surface initial commit if (!deco->surface_commit.notify) { deco->surface_commit.notify = handle_surface_commit; wl_signal_add(&deco->surface->events.commit, &deco->surface_commit); } } static void handle_xdg_deco_request_mode(struct wl_listener *listener, void *data) { struct decoration *deco = wl_container_of(listener, deco, xdg_deco_request_mode); struct wlr_xdg_toplevel_decoration_v1 *wlr_xdg_decoration = data; enum wlr_xdg_toplevel_decoration_v1_mode mode = wlr_xdg_decoration->requested_mode; if (mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE) { mode = ((deco->server_deco || deco->xdg_deco) && !deco->should_use_ssd) ? WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE : WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; } kywc_log(KYWC_DEBUG, "Surface %p xdg-decoration mode is %d", deco->surface, mode); deco->should_use_ssd = mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; xdg_toplevel_decoration_set_mode(deco, wlr_xdg_decoration); if (deco->server_deco) { uint32_t server_mode = deco->should_use_ssd ? WLR_SERVER_DECORATION_MANAGER_MODE_SERVER : WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT; if (deco->server_deco->mode != server_mode) { deco->server_deco->mode = server_mode; /* org_kde_kwin_server_decoration_send_mode */ wl_resource_post_event(deco->server_deco->resource, 0, server_mode); } } decoration_apply(deco); } static void xdg_toplevel_decoration(struct wl_listener *listener, void *data) { struct wlr_xdg_toplevel_decoration_v1 *wlr_xdg_decoration = data; struct wlr_surface *surface = wlr_xdg_decoration->toplevel->base->surface; struct decoration *deco = decoration_from_surface(surface); if (!deco) { return; } deco->xdg_deco_destroy.notify = handle_xdg_deco_destroy; wl_signal_add(&wlr_xdg_decoration->events.destroy, &deco->xdg_deco_destroy); deco->xdg_deco_request_mode.notify = handle_xdg_deco_request_mode; wl_signal_add(&wlr_xdg_decoration->events.request_mode, &deco->xdg_deco_request_mode); wl_list_init(&deco->surface_commit.link); handle_xdg_deco_request_mode(&deco->xdg_deco_request_mode, wlr_xdg_decoration); deco->xdg_deco = wlr_xdg_decoration; } static void handle_server_deco_apply_mode(struct wl_listener *listener, void *data) { struct decoration *deco = wl_container_of(listener, deco, server_deco_apply_mode); struct wlr_server_decoration *wlr_server_decoration = data; uint32_t mode = wlr_server_decoration->mode; kywc_log(KYWC_DEBUG, "Surface %p server decoration mode is %d", deco->surface, mode); deco->should_use_ssd = mode == WLR_SERVER_DECORATION_MANAGER_MODE_SERVER; if (deco->xdg_deco) { xdg_toplevel_decoration_set_mode(deco, deco->xdg_deco); } decoration_apply(deco); } static void server_decoration(struct wl_listener *listener, void *data) { struct wlr_server_decoration *wlr_server_decoration = data; struct wlr_surface *surface = wlr_server_decoration->surface; struct decoration *deco = decoration_from_surface(surface); if (!deco) { return; } /* multi decoration for one surface, use the last one */ if (deco->server_deco) { wl_list_remove(&deco->server_deco_destroy.link); wl_list_remove(&deco->server_deco_apply_mode.link); } deco->server_deco_destroy.notify = handle_server_deco_destroy; wl_signal_add(&wlr_server_decoration->events.destroy, &deco->server_deco_destroy); deco->server_deco_apply_mode.notify = handle_server_deco_apply_mode; wl_signal_add(&wlr_server_decoration->events.mode, &deco->server_deco_apply_mode); /* apply current deco type only when first time, may no mode signal */ if (!deco->xdg_deco && !deco->server_deco) { handle_server_deco_apply_mode(&deco->server_deco_apply_mode, wlr_server_decoration); } deco->server_deco = wlr_server_decoration; } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->server_destroy.link); free(manager); manager = NULL; } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->display_destroy.link); wl_list_remove(&manager->xdg_toplevel_decoration.link); wl_list_remove(&manager->server_decoration.link); } bool decoration_manager_create(struct view_manager *view_manager) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } wl_list_init(&manager->decos); wl_list_init(&manager->xdg_toplevel_decoration.link); wl_list_init(&manager->server_decoration.link); manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(view_manager->server->display, &manager->display_destroy); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(view_manager->server, &manager->server_destroy); struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager = wlr_xdg_decoration_manager_v1_create(view_manager->server->display); if (!xdg_decoration_manager) { kywc_log(KYWC_ERROR, "Unable to create the XDG deco manager"); } else { manager->xdg_toplevel_decoration.notify = xdg_toplevel_decoration; wl_signal_add(&xdg_decoration_manager->events.new_toplevel_decoration, &manager->xdg_toplevel_decoration); } struct wlr_server_decoration_manager *server_decoration_manager = wlr_server_decoration_manager_create(view_manager->server->display); if (!server_decoration_manager) { kywc_log(KYWC_ERROR, "Unable to create the server deco manager"); } else { uint32_t default_mode = WLR_SERVER_DECORATION_MANAGER_MODE_SERVER; wlr_server_decoration_manager_set_default_mode(server_decoration_manager, default_mode); manager->server_decoration.notify = server_decoration; wl_signal_add(&server_decoration_manager->events.new_decoration, &manager->server_decoration); } /* oops, all decoration manager create failed */ if (!xdg_decoration_manager && !server_decoration_manager) { free(manager); return false; } return true; } kylin-wayland-compositor/src/view/kde_plasma_shell.c0000664000175000017500000003515615160461067021673 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "input/cursor.h" #include "input/seat.h" #include "plasma-shell-protocol.h" #include "scene/surface.h" #include "util/wayland.h" #include "view_p.h" #define PLASMA_SHELL_VERSION 8 struct kde_plasma_shell { struct wl_global *global; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct kde_plasma_surface { struct wlr_surface *wlr_surface; struct wl_listener surface_map; struct wl_listener surface_destroy; /* for popup position */ struct ky_scene_buffer *buffer; /* get in map listener */ struct view *view; bool takes_focus; struct wl_listener view_map; struct wl_listener view_unmap; struct wl_listener view_destroy; struct wl_listener view_update_capabilities; int32_t x, y; enum org_kde_plasma_surface_role role; int32_t skip_taskbar, skip_switcher; bool open_under_cursor; }; static void handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void handle_set_output(struct wl_client *client, struct wl_resource *resource, struct wl_resource *output) { // Not implemented yet } static void kde_plasma_surface_apply_position(struct kde_plasma_surface *surface) { if (surface->view) { view_do_move(surface->view, surface->x, surface->y); } else if (surface->buffer) { struct ky_scene_node *node = &surface->buffer->node; int lx, ly; ky_scene_node_coords(node, &lx, &ly); ky_scene_node_set_position(&surface->buffer->node, node->x + surface->x - lx, node->y + surface->y - ly); } } static void handle_set_position(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y) { struct kde_plasma_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } surface->x = x; surface->y = y; kde_plasma_surface_apply_position(surface); } static enum kywc_view_role role_from_plasma_surface(struct kde_plasma_surface *surface) { switch (surface->role) { default: return KYWC_VIEW_ROLE_NORMAL; case ORG_KDE_PLASMA_SURFACE_ROLE_DESKTOP: return KYWC_VIEW_ROLE_DESKTOP; case ORG_KDE_PLASMA_SURFACE_ROLE_PANEL: return KYWC_VIEW_ROLE_PANEL; case ORG_KDE_PLASMA_SURFACE_ROLE_TOOLTIP: return KYWC_VIEW_ROLE_TOOLTIP; case ORG_KDE_PLASMA_SURFACE_ROLE_ONSCREENDISPLAY: return KYWC_VIEW_ROLE_ONSCREENDISPLAY; case ORG_KDE_PLASMA_SURFACE_ROLE_NOTIFICATION: return KYWC_VIEW_ROLE_NOTIFICATION; case ORG_KDE_PLASMA_SURFACE_ROLE_CRITICALNOTIFICATION: return KYWC_VIEW_ROLE_CRITICALNOTIFICATION; case ORG_KDE_PLASMA_SURFACE_ROLE_APPLETPOPUP: return KYWC_VIEW_ROLE_APPLETPOPUP; } } static void handle_set_role(struct wl_client *client, struct wl_resource *resource, uint32_t role) { struct kde_plasma_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } if (surface->view) { kywc_log(KYWC_WARN, "Plasma-shell: reject set_role(%d) request after mapped from %s", role, surface->view->base.app_id); return; } surface->role = role; } static void handle_set_panel_behavior(struct wl_client *client, struct wl_resource *resource, uint32_t flag) { // Not implemented yet } static void handle_set_skip_taskbar(struct wl_client *client, struct wl_resource *resource, uint32_t skip) { struct kde_plasma_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } surface->skip_taskbar = skip; if (surface->view) { kywc_view_set_skip_taskbar(&surface->view->base, skip); } } static void handle_panel_auto_hide_hide(struct wl_client *client, struct wl_resource *resource) { // Not implemented yet } static void handle_panel_auto_hide_show(struct wl_client *client, struct wl_resource *resource) { // Not implemented yet } static void handle_set_panel_takes_focus(struct wl_client *client, struct wl_resource *resource, uint32_t takes_focus) { struct kde_plasma_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface || !surface->view) { return; } struct kywc_view *kywc_view = &surface->view->base; if (kywc_view->role == KYWC_VIEW_ROLE_PANEL && surface->takes_focus != takes_focus) { surface->takes_focus = takes_focus; view_update_capabilities(surface->view, KYWC_VIEW_FOCUSABLE | KYWC_VIEW_ACTIVATABLE); } } static void handle_set_skip_switcher(struct wl_client *client, struct wl_resource *resource, uint32_t skip) { struct kde_plasma_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } surface->skip_switcher = skip; if (surface->view) { kywc_view_set_skip_switcher(&surface->view->base, skip); } } static void handle_open_under_cursor(struct wl_client *client, struct wl_resource *resource) { struct kde_plasma_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } surface->open_under_cursor = true; if (surface->view) { struct seat *seat = surface->view->base.focused_seat; view_do_move(surface->view, seat->cursor->lx, seat->cursor->ly); } } static const struct org_kde_plasma_surface_interface kde_plasma_surface_impl = { .destroy = handle_destroy, .set_output = handle_set_output, .set_position = handle_set_position, .set_role = handle_set_role, .set_panel_behavior = handle_set_panel_behavior, .set_skip_taskbar = handle_set_skip_taskbar, .panel_auto_hide_hide = handle_panel_auto_hide_hide, .panel_auto_hide_show = handle_panel_auto_hide_show, .set_panel_takes_focus = handle_set_panel_takes_focus, .set_skip_switcher = handle_set_skip_switcher, .open_under_cursor = handle_open_under_cursor, }; static void kde_plasma_surface_set_usable_area(struct kde_plasma_surface *surface, bool enabled) { bool has_area = enabled && surface->view->base.mapped && surface->view->base.role == KYWC_VIEW_ROLE_PANEL; view_set_exclusive(surface->view, has_area); } static void surface_handle_view_map(struct wl_listener *listener, void *data) { struct kde_plasma_surface *surface = wl_container_of(listener, surface, view_map); kde_plasma_surface_set_usable_area(surface, true); } static void surface_handle_view_unmap(struct wl_listener *listener, void *data) { struct kde_plasma_surface *surface = wl_container_of(listener, surface, view_unmap); kde_plasma_surface_set_usable_area(surface, false); } static void surface_handle_view_destroy(struct wl_listener *listener, void *data) { struct kde_plasma_surface *surface = wl_container_of(listener, surface, view_destroy); wl_list_remove(&surface->view_destroy.link); wl_list_remove(&surface->view_map.link); wl_list_remove(&surface->view_unmap.link); wl_list_remove(&surface->view_update_capabilities.link); surface->view = NULL; } static void surface_handle_view_update_capabilities(struct wl_listener *listener, void *data) { struct kde_plasma_surface *surface = wl_container_of(listener, surface, view_update_capabilities); struct view_update_capabilities_event *event = data; if (event->mask & KYWC_VIEW_FOCUSABLE) { if (!surface->takes_focus) { event->state &= ~KYWC_VIEW_FOCUSABLE; } } if (event->mask & KYWC_VIEW_ACTIVATABLE) { if (!surface->takes_focus) { event->state &= ~KYWC_VIEW_ACTIVATABLE; } } } static void surface_handle_map(struct wl_listener *listener, void *data) { struct kde_plasma_surface *surface = wl_container_of(listener, surface, surface_map); /* useless once we connect surface and view */ wl_list_remove(&surface->surface_map.link); wl_list_init(&surface->surface_map.link); /* get view from surface */ if (!surface->view) { surface->view = view_try_from_wlr_surface(surface->wlr_surface); if (!surface->view) { kywc_log(KYWC_DEBUG, "Surface is not a toplevel"); surface->buffer = ky_scene_buffer_try_from_surface(surface->wlr_surface); return; } wl_signal_add(&surface->view->base.events.destroy, &surface->view_destroy); view_add_update_capabilities_listener(surface->view, &surface->view_update_capabilities); } if (surface->skip_taskbar != -1) { kywc_view_set_skip_taskbar(&surface->view->base, surface->skip_taskbar); } if (surface->skip_switcher != -1) { kywc_view_set_skip_switcher(&surface->view->base, surface->skip_switcher); } if (surface->role != UINT32_MAX) { view_set_role(surface->view, role_from_plasma_surface(surface)); } if (surface->open_under_cursor) { struct seat *seat = surface->view->base.focused_seat; surface->view->base.has_initial_position = true; view_do_move(surface->view, seat->cursor->lx, seat->cursor->ly); } /* apply set_position called before map */ if (surface->x != INT32_MAX || surface->y != INT32_MAX) { surface->view->base.has_initial_position = true; kde_plasma_surface_apply_position(surface); } surface->view_map.notify = surface_handle_view_map; wl_signal_add(&surface->view->base.events.map, &surface->view_map); surface->view_unmap.notify = surface_handle_view_unmap; wl_signal_add(&surface->view->base.events.unmap, &surface->view_unmap); /* workaround to fix this listener is behind view map */ if (surface->view->base.mapped) { surface_handle_view_map(&surface->view_map, NULL); } } static void surface_handle_destroy(struct wl_listener *listener, void *data) { struct kde_plasma_surface *surface = wl_container_of(listener, surface, surface_destroy); wl_list_remove(&surface->surface_map.link); wl_list_remove(&surface->surface_destroy.link); surface->wlr_surface = NULL; } static void kde_plasma_surface_handle_resource_destroy(struct wl_resource *resource) { struct kde_plasma_surface *surface = wl_resource_get_user_data(resource); if (surface->wlr_surface) { surface_handle_destroy(&surface->surface_destroy, NULL); } if (surface->view) { if (surface->view->base.mapped) { surface_handle_view_unmap(&surface->view_unmap, NULL); } surface_handle_view_destroy(&surface->view_destroy, NULL); } free(surface); } static void handle_get_surface(struct wl_client *client, struct wl_resource *shell_resource, uint32_t id, struct wl_resource *surface_resource) { struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface_resource); /* create a plasma surface */ struct kde_plasma_surface *surface = calloc(1, sizeof(*surface)); if (!surface) { wl_client_post_no_memory(client); return; } int version = wl_resource_get_version(shell_resource); struct wl_resource *resource = wl_resource_create(client, &org_kde_plasma_surface_interface, version, id); if (!resource) { free(surface); wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &kde_plasma_surface_impl, surface, kde_plasma_surface_handle_resource_destroy); surface->x = surface->y = INT32_MAX; surface->role = UINT32_MAX; surface->skip_taskbar = surface->skip_switcher = -1; surface->takes_focus = true; surface->wlr_surface = wlr_surface; surface->surface_map.notify = surface_handle_map; wl_signal_add_next(&wlr_surface->events.map, &surface->surface_map); surface->surface_destroy.notify = surface_handle_destroy; wl_signal_add(&wlr_surface->events.destroy, &surface->surface_destroy); surface->view = view_try_from_wlr_surface(surface->wlr_surface); surface->view_destroy.notify = surface_handle_view_destroy; surface->view_update_capabilities.notify = surface_handle_view_update_capabilities; if (surface->view) { wl_list_init(&surface->view_map.link); wl_list_init(&surface->view_unmap.link); wl_signal_add(&surface->view->base.events.destroy, &surface->view_destroy); view_add_update_capabilities_listener(surface->view, &surface->view_update_capabilities); } /* workaround to fix this request is behind surface map */ if (surface->wlr_surface->mapped) { surface_handle_map(&surface->surface_map, NULL); } } static const struct org_kde_plasma_shell_interface kde_plasma_shell_impl = { .get_surface = handle_get_surface, }; static void kde_plasma_shell_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &org_kde_plasma_shell_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } struct kde_plasma_shell *shell = data; wl_resource_set_implementation(resource, &kde_plasma_shell_impl, shell, NULL); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct kde_plasma_shell *shell = wl_container_of(listener, shell, display_destroy); wl_list_remove(&shell->display_destroy.link); wl_global_destroy(shell->global); } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct kde_plasma_shell *shell = wl_container_of(listener, shell, server_destroy); wl_list_remove(&shell->server_destroy.link); free(shell); } bool kde_plasma_shell_create(struct server *server) { struct kde_plasma_shell *shell = calloc(1, sizeof(*shell)); if (!shell) { return false; } shell->global = wl_global_create(server->display, &org_kde_plasma_shell_interface, PLASMA_SHELL_VERSION, shell, kde_plasma_shell_bind); if (!shell->global) { kywc_log(KYWC_WARN, "Kde plasma shell create failed"); free(shell); return false; } shell->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &shell->server_destroy); shell->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &shell->display_destroy); return true; } kylin-wayland-compositor/src/view/maximize_switcher.c0000664000175000017500000005256415160461067022141 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include "input/cursor.h" #include "input/seat.h" #include "output.h" #include "painter.h" #include "theme.h" #include "util/macros.h" #include "view/workspace.h" #include "view_p.h" #include "widget/scaled_buffer.h" #include "widget/widget.h" #define SELECT_WIDTH_GAP (5) #define ICON_OPACIPY (0.4) #define ITEM_HEIGHT (48) #define MAX_DISPLAY_VIEW (25) #define MIN_DISPLAY_VIEW (4) #define MAX_WIDTH_RATIO (0.8) #define MAX_HEIGHT_RATIO (0.8) #define MIN_WIDTH_RATIO (0.2) enum set_dir { NONE = 0, BOTTOM, TOP, }; struct maximize_switcher_color { float background_color[4]; float font_color[4]; float select_color[4]; }; static struct maximize_switcher_color light = { .background_color = { 207.0 / 255.0, 207.0 / 255.0, 207.0 / 255.0, 1.0 }, .font_color = { 0, 0, 0, 1.0 }, .select_color = { 173.0 / 255.0, 216.0 / 255.0, 230.0 / 255.0, 1.0 }, }; static struct maximize_switcher_color dark = { .background_color = { 54.0 / 255, 54.0 / 255, 54.0 / 255, 1.0 }, .font_color = { 1.0, 1.0, 1.0, 1.0 }, .select_color = { 173.0 / 255.0, 216.0 / 255.0, 230.0 / 255.0, 1.0 }, }; struct item_view { struct kywc_view *kywc_view; struct widget *title_text; struct ky_scene_tree *tree; struct ky_scene_rect *background; struct ky_scene_node *icon_node; struct ky_scene_node *text_node; int text_width, text_height; float scale; char *text; struct wl_listener view_destroy; }; static struct maximize_switcher { struct ky_scene_tree *tree; struct ky_scene_rect *background; struct item_view *active; struct maximize_switcher_color *color; struct item_view **item_views; struct seat_pointer_grab pointer_grab; struct seat_keyboard_grab keyboard_grab; struct seat_touch_grab touch_grab; int pending, current; int width, height; int max_width, max_height; /* * * restrict page display items when the height * exceeds the maximum height of the background. */ int views_control; int num_windows; int num_view; int direction; int last_position; bool enable; float icon_opacity; struct wlr_output *output; struct wl_listener output_frame; struct wl_listener theme_update; struct wl_listener server_destroy; } *switcher = NULL; static struct shortcut { char *keybind; char *desc; } shortcuts[] = { { "Alt+m", "traverse all maximized views" }, }; static void maximize_switcher_set_enable(bool enable); static void ensure_thumbnails_size(int num) { if (switcher->num_windows > num) { return; } int alloc = switcher->num_windows ? switcher->num_windows : 16; while (alloc < num) { alloc *= 2; } if (!switcher->item_views) { switcher->item_views = malloc(alloc * sizeof(struct item_view *)); } else if (alloc > switcher->num_windows) { switcher->item_views = realloc(switcher->item_views, alloc * sizeof(struct item_view *)); } switcher->num_windows = alloc; } static bool item_hover(struct seat *seat, struct ky_scene_node *node, double x, double y, uint32_t time, bool first, bool hold, void *data) { return false; } static void item_leave(struct seat *seat, struct ky_scene_node *node, bool last, void *data) {} static void item_click(struct seat *seat, struct ky_scene_node *node, uint32_t button, bool pressed, uint32_t time, enum click_state state, void *data) { /* do actions when released */ if (pressed || button != BTN_LEFT) { return; } struct item_view *item = data; struct item_view *item_view; for (int i = 0; i < switcher->num_view; i++) { item_view = switcher->item_views[i]; if (item == item_view) { switcher->pending = i; switcher->direction = NONE; } } output_schedule_frame(switcher->output); } static const struct input_event_node_impl item_impl = { .hover = item_hover, .leave = item_leave, .click = item_click, }; static void pointer_grab_cancel(struct seat_pointer_grab *pointer_grab) { maximize_switcher_set_enable(false); } static bool pointer_grab_button(struct seat_pointer_grab *pointer_grab, uint32_t time, uint32_t button, bool pressed) { struct maximize_switcher *window_menu = pointer_grab->data; struct seat *seat = pointer_grab->seat; /* check current hover node in the window menu tree */ struct input_event_node *inode = input_event_node_from_node(seat->cursor->hover.node); struct ky_scene_node *node = input_event_node_root(inode); if (node == &window_menu->tree->node) { inode->impl->click(seat, seat->cursor->hover.node, button, pressed, time, CLICK_STATE_NONE, inode->data); } else if (pressed) { maximize_switcher_set_enable(false); } return true; } static bool pointer_grab_motion(struct seat_pointer_grab *pointer_grab, uint32_t time, double lx, double ly) { return false; } static bool pointer_grab_axis(struct seat_pointer_grab *pointer_grab, uint32_t time, bool vertical, double value) { return true; } static const struct seat_pointer_grab_interface pointer_grab_impl = { .motion = pointer_grab_motion, .button = pointer_grab_button, .axis = pointer_grab_axis, .cancel = pointer_grab_cancel, }; static bool touch_grab_touch(struct seat_touch_grab *touch_grab, uint32_t time, bool down) { // FIXME: interactive grab end struct maximize_switcher *maximize_switcher = touch_grab->data; return pointer_grab_button(&maximize_switcher->pointer_grab, time, BTN_LEFT, down); } static bool touch_grab_motion(struct seat_touch_grab *touch_grab, uint32_t time, double lx, double ly) { return false; } static void touch_grab_cancel(struct seat_touch_grab *touch_grab) { maximize_switcher_set_enable(false); } static const struct seat_touch_grab_interface touch_grab_impl = { .touch = touch_grab_touch, .motion = touch_grab_motion, .cancel = touch_grab_cancel, }; static bool keyboard_grab_key(struct seat_keyboard_grab *keyboard_grab, struct keyboard *keyboard, uint32_t time, uint32_t key, bool pressed, uint32_t modifiers) { if (!pressed) { if (key != KEY_LEFTALT && key != KEY_RIGHTALT) { return true; } if (switcher->active->kywc_view) { kywc_view_activate(switcher->active->kywc_view); view_set_focus(view_from_kywc_view(switcher->active->kywc_view), keyboard_grab->seat); } maximize_switcher_set_enable(false); return true; } switch (key) { case KEY_UP: switcher->direction = TOP; switcher->pending--; break; case KEY_M: case KEY_DOWN: switcher->direction = BOTTOM; switcher->pending++; break; case KEY_ESC: maximize_switcher_set_enable(false); break; } output_schedule_frame(switcher->output); return true; } static void keyboard_grab_cancel(struct seat_keyboard_grab *keyboard_grab) { maximize_switcher_set_enable(false); } static const struct seat_keyboard_grab_interface keyboard_grab_impl = { .key = keyboard_grab_key, .cancel = keyboard_grab_cancel, }; static void update_title_text(struct item_view *item_view) { struct theme *theme = theme_manager_get_theme(); int select_width_gap = SELECT_WIDTH_GAP * 2; int max_width = switcher->max_width - theme->button_width - select_width_gap; widget_set_text(item_view->title_text, item_view->text, JUSTIFY_CENTER, item_view->kywc_view->minimized ? TEXT_ATTR_SLANT : TEXT_ATTR_NONE); widget_set_font(item_view->title_text, theme->font_name, theme->font_size); widget_set_front_color(item_view->title_text, switcher->color->font_color); widget_set_max_size(item_view->title_text, max_width, ITEM_HEIGHT); widget_set_auto_resize(item_view->title_text, AUTO_RESIZE_ONLY); widget_set_enabled(item_view->title_text, true); widget_update(item_view->title_text, true); int text_width, text_height; widget_get_size(item_view->title_text, &text_width, &text_height); if (text_width > max_width) { switcher->width = max_width + theme->button_width + select_width_gap; } else if (text_width > switcher->width - theme->button_width - select_width_gap) { switcher->width = text_width + theme->button_width + select_width_gap; } item_view->text_width = text_width; item_view->text_height = text_height; } static void set_icon_buffer(struct item_view *item_view, float opacity) { struct kywc_view *kywc_view = item_view->kywc_view; struct view *view = view_from_kywc_view(kywc_view); struct wlr_buffer *buf = view_get_icon_buffer(view, item_view->scale); if (!buf) { return; } struct ky_scene_buffer *buffer = ky_scene_buffer_from_node(item_view->icon_node); if (buffer->buffer != buf) { ky_scene_buffer_set_buffer(buffer, buf); } if (buffer->buffer != buf) { return; } ky_scene_buffer_set_opacity(buffer, opacity); struct theme *theme = theme_manager_get_theme(); ky_scene_buffer_set_dest_size(buffer, theme->icon_size, theme->icon_size); } static void update_buffer(struct ky_scene_buffer *buffer, float scale, void *data) { struct item_view *item_view = data; item_view->scale = scale; /* update scene_buffer with new buffer */ set_icon_buffer(item_view, switcher->icon_opacity); } static void destroy_buffer(struct ky_scene_buffer *buffer, void *data) { /* buffers are destroyed in theme */ } static struct ky_scene_node *item_get_root(void *data) { return &switcher->tree->node; } static void handle_view_destroy(struct wl_listener *listener, void *data) { struct item_view *item_view = wl_container_of(listener, item_view, view_destroy); maximize_switcher_set_enable(false); } static void get_maximize_views(int *num_views) { struct workspace *workspace = workspace_manager_get_current(); struct kywc_output *kywc_output = kywc_output_get_primary(); float color[4] = { 0 }; struct view_proxy *view_proxy; wl_list_for_each(view_proxy, &workspace->view_proxies, workspace_link) { struct view *view = view_proxy->view; if (!view->base.mapped || (!view->base.maximized && !view->base.fullscreen)) { continue; } struct item_view *item_view = calloc(1, sizeof(*item_view)); if (!item_view) { continue; } item_view->kywc_view = &view->base; item_view->scale = kywc_output->state.scale; item_view->view_destroy.notify = handle_view_destroy; wl_signal_add(&view->base.events.destroy, &item_view->view_destroy); item_view->tree = ky_scene_tree_create(switcher->tree); input_event_node_create(&item_view->tree->node, &item_impl, item_get_root, NULL, item_view); /* create background */ item_view->background = ky_scene_rect_create(item_view->tree, 0, 0, color); /* create title */ if (view->base.minimized) { char left_bracket[] = "("; char right_bracket[] = ")"; int len = strlen(view->base.title) + strlen(left_bracket) + strlen(right_bracket) + 1; item_view->text = malloc(len * sizeof(char)); sprintf(item_view->text, "%s%s%s", left_bracket, view->base.title, right_bracket); } else { item_view->text = strdup(view->base.title); } item_view->title_text = widget_create(item_view->tree); item_view->text_node = ky_scene_node_from_widget(item_view->title_text); /* create icon */ struct ky_scene_buffer *buf = scaled_buffer_create( item_view->tree, view->output->state.scale, update_buffer, destroy_buffer, item_view); item_view->icon_node = &buf->node; /* update */ set_icon_buffer(item_view, switcher->icon_opacity); update_title_text(item_view); /* add item_views */ ensure_thumbnails_size(*num_views + 1); switcher->item_views[*num_views] = item_view; *num_views += 1; } } static void set_select_item_view(int index, struct item_view *current, struct item_view *new) { if (current) { float color[4] = { 0 }; ky_scene_rect_set_color(current->background, color); set_icon_buffer(current, switcher->icon_opacity); } ky_scene_rect_set_color(new->background, switcher->color->select_color); set_icon_buffer(new, 1); } static void show_current_page_items(int index) { struct theme *theme = theme_manager_get_theme(); int icon_width = theme->button_width; int select_width_gap = SELECT_WIDTH_GAP; int icon_x = theme->layout_is_right_to_left ? switcher->width - select_width_gap - icon_width : select_width_gap; icon_x += (icon_width - theme->icon_size) / 2; int icon_y = (ITEM_HEIGHT - theme->icon_size) / 2; struct item_view *item_view; for (int i = 0; i < switcher->num_view; i++) { /* skipped view */ int start_i = i - index; if (start_i < 0) { continue; } if (i >= switcher->views_control + index) { break; } item_view = switcher->item_views[i]; ky_scene_node_set_enabled(&item_view->tree->node, true); ky_scene_node_set_position(&item_view->tree->node, 0, start_i * ITEM_HEIGHT); /* set icon position */ ky_scene_node_set_position(item_view->icon_node, icon_x, icon_y); /* set text position */ int text_x = theme->layout_is_right_to_left ? 0 : icon_width + select_width_gap; text_x += (switcher->width - icon_width - item_view->text_width - select_width_gap * 2) / 2; int text_y = (ITEM_HEIGHT - item_view->text_height) / 2; ky_scene_node_set_position(item_view->text_node, text_x, text_y); ky_scene_rect_set_size(item_view->background, switcher->width, ITEM_HEIGHT); } } static void handle_output_frame(struct wl_listener *listener, void *data) { int num_view = switcher->num_view; int pending = switcher->pending; int i_index = 0; if (num_view == 1 || pending >= num_view) { pending = 0; } else if (pending < 0) { pending = num_view - 1; i_index = pending - switcher->views_control >= 0 ? pending + 1 - switcher->views_control : 0; } else { if (switcher->direction == BOTTOM) { i_index = pending < switcher->last_position + switcher->views_control ? switcher->last_position : pending + 1 - switcher->views_control; } else if (switcher->direction == TOP) { i_index = pending < switcher->last_position ? pending : switcher->last_position; } else { i_index = switcher->last_position; } } switcher->last_position = i_index; /* hide all items */ for (int i = 0; i < switcher->num_view; i++) { ky_scene_node_set_enabled(&switcher->item_views[i]->tree->node, false); } show_current_page_items(i_index); /* select */ struct item_view *current = switcher->current >= 0 ? switcher->item_views[switcher->current] : NULL; set_select_item_view(pending - i_index, current, switcher->item_views[pending]); switcher->pending = pending; switcher->current = pending; switcher->active = switcher->item_views[switcher->current]; } static void hide_maximize_switcher(void) { struct item_view *item_view; for (int i = 0; i < switcher->num_view; i++) { item_view = switcher->item_views[i]; wl_list_remove(&item_view->view_destroy.link); ky_scene_node_destroy(&item_view->tree->node); free(item_view->text); free(item_view); } free(switcher->item_views); switcher->item_views = NULL; switcher->num_windows = 0; wl_list_remove(&switcher->output_frame.link); ky_scene_node_set_enabled(&switcher->tree->node, false); } static bool show_maximize_switcher(void) { struct output *output = input_current_output(input_manager_get_default_seat()); struct kywc_box *usable_area = &output->usable_area; switcher->max_width = usable_area->width * MAX_WIDTH_RATIO; switcher->max_height = usable_area->height * MAX_HEIGHT_RATIO; switcher->width = usable_area->width * MIN_WIDTH_RATIO; int num_view = 0; get_maximize_views(&num_view); if (num_view == 0) { return false; } if (num_view >= MAX_DISPLAY_VIEW) { switcher->height = ITEM_HEIGHT * MAX_DISPLAY_VIEW; } else if (num_view <= MIN_DISPLAY_VIEW) { switcher->height = ITEM_HEIGHT * MIN_DISPLAY_VIEW; } else { switcher->height = ITEM_HEIGHT * num_view; } if (switcher->height > switcher->max_height) { int max_num = switcher->max_height / ITEM_HEIGHT; switcher->views_control = max_num; switcher->height = max_num * ITEM_HEIGHT; } else { switcher->views_control = MAX_DISPLAY_VIEW; } switcher->num_view = num_view; int x = usable_area->x + usable_area->width / 2 - switcher->width / 2; int y = usable_area->y + usable_area->height / 2 - switcher->height / 2; ky_scene_rect_set_size(switcher->background, switcher->width, switcher->height); ky_scene_node_set_position(&switcher->tree->node, x, y); ky_scene_node_set_enabled(&switcher->tree->node, true); switcher->output_frame.notify = handle_output_frame; wl_signal_add(&output->scene_output->events.frame, &switcher->output_frame); switcher->pending = 1; switcher->current = -1; switcher->direction = BOTTOM; switcher->output = output->wlr_output; output_schedule_frame(switcher->output); return true; } static void maximize_switcher_set_enable(bool enable) { if (switcher->enable == enable) { return; } struct seat *seat = input_manager_get_default_seat(); switcher->enable = enable; if (!enable) { hide_maximize_switcher(); seat_end_pointer_grab(seat, &switcher->pointer_grab); seat_end_keyboard_grab(seat, &switcher->keyboard_grab); seat_end_touch_grab(seat, &switcher->touch_grab); return; } if (!show_maximize_switcher()) { switcher->enable = false; return; } seat_start_pointer_grab(seat, &switcher->pointer_grab); seat_start_keyboard_grab(seat, &switcher->keyboard_grab); seat_start_touch_grab(seat, &switcher->touch_grab); } static void shortcut_action(struct key_binding *binding, void *data) { maximize_switcher_set_enable(true); } static void switcher_register_shortcuts(void) { for (size_t i = 0; i < ARRAY_SIZE(shortcuts); i++) { struct shortcut *shortcut = &shortcuts[i]; struct key_binding *binding = kywc_key_binding_create(shortcut->keybind, shortcut->desc); if (!binding) { continue; } if (!kywc_key_binding_register(binding, KEY_BINDING_TYPE_MAXIMIZED_VIEWS, shortcut_action, shortcut)) { kywc_key_binding_destroy(binding); continue; } } } static void handle_theme_update(struct wl_listener *listener, void *data) { struct theme_update_event *update_event = data; if (!(update_event->update_mask & THEME_UPDATE_MASK_TYPE)) { return; } switcher->color = update_event->theme_type == THEME_TYPE_LIGHT ? &light : &dark; ky_scene_rect_set_color(switcher->background, switcher->color->background_color); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&switcher->server_destroy.link); wl_list_remove(&switcher->theme_update.link); ky_scene_node_destroy(&switcher->tree->node); free(switcher); switcher = NULL; } bool maximize_switcher_create(struct view_manager *view_manager) { switcher = calloc(1, sizeof(*switcher)); if (!switcher) { return false; } struct theme *theme = theme_manager_get_theme(); struct view_layer *layer = view_manager_get_layer(LAYER_ON_SCREEN_DISPLAY, false); switcher->tree = ky_scene_tree_create(layer->tree); ky_scene_node_set_enabled(&switcher->tree->node, false); switch (theme->type) { case THEME_TYPE_UNDEFINED: case THEME_TYPE_LIGHT: switcher->color = &light; break; case THEME_TYPE_DARK: switcher->color = &dark; break; } switcher->icon_opacity = ICON_OPACIPY; switcher->background = ky_scene_rect_create(switcher->tree, 0, 0, switcher->color->background_color); switcher->pointer_grab.data = switcher; switcher->pointer_grab.interface = &pointer_grab_impl; switcher->keyboard_grab.data = switcher; switcher->keyboard_grab.interface = &keyboard_grab_impl; switcher->touch_grab.data = switcher; switcher->touch_grab.interface = &touch_grab_impl; switcher->theme_update.notify = handle_theme_update; theme_manager_add_update_listener(&switcher->theme_update, false); switcher->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(view_manager->server, &switcher->server_destroy); switcher_register_shortcuts(); return true; } kylin-wayland-compositor/src/view/xdg_dialog.c0000664000175000017500000001470215160461067020477 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "view_p.h" #include "xdg-dialog-v1-protocol.h" #define XDG_DIALOG_VERSION 1 struct xdg_dialog_manager { struct wl_global *global; struct wl_list xdg_dialogs; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct xdg_dialog { struct wl_list link; bool modal; struct wlr_xdg_toplevel *toplevel; struct wl_listener toplevel_destroy; }; static struct xdg_dialog *xdg_dialog_from_toplevel(struct xdg_dialog_manager *manager, struct wlr_xdg_toplevel *toplevel) { struct xdg_dialog *xdg_dialog; wl_list_for_each(xdg_dialog, &manager->xdg_dialogs, link) { if (xdg_dialog->toplevel == toplevel) { return xdg_dialog; } } return NULL; } static void handle_dialog_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void xdg_dialog_set_modal(struct xdg_dialog *xdg_dialog, bool modal) { xdg_dialog->modal = modal; /* inert */ if (!xdg_dialog->toplevel) { return; } struct view *view = view_try_from_wlr_surface(xdg_dialog->toplevel->base->surface); kywc_view_set_modal(&view->base, xdg_dialog->modal); } static void handle_set_modal(struct wl_client *client, struct wl_resource *resource) { struct xdg_dialog *xdg_dialog = wl_resource_get_user_data(resource); xdg_dialog_set_modal(xdg_dialog, true); } static void handle_unset_modal(struct wl_client *client, struct wl_resource *resource) { struct xdg_dialog *xdg_dialog = wl_resource_get_user_data(resource); xdg_dialog_set_modal(xdg_dialog, false); } static const struct xdg_dialog_v1_interface xdg_dialog_v1_interface_impl = { .destroy = handle_dialog_destroy, .set_modal = handle_set_modal, .unset_modal = handle_unset_modal, }; static void xdg_dialog_handle_resource_destroy(struct wl_resource *resource) { struct xdg_dialog *xdg_dialog = wl_resource_get_user_data(resource); wl_list_remove(&xdg_dialog->link); wl_list_remove(&xdg_dialog->toplevel_destroy.link); /* unapply its effects if xdg_toplevel is not destroyed */ xdg_dialog_set_modal(xdg_dialog, false); free(xdg_dialog); } static void handle_toplevel_destroy(struct wl_listener *listener, void *data) { struct xdg_dialog *xdg_dialog = wl_container_of(listener, xdg_dialog, toplevel_destroy); wl_list_remove(&xdg_dialog->toplevel_destroy.link); wl_list_init(&xdg_dialog->toplevel_destroy.link); /* If the xdg_toplevel is destroyed, the xdg_dialog_v1 becomes inert */ xdg_dialog->toplevel = NULL; } static void handle_get_xdg_dialog(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *toplevel) { struct xdg_dialog_manager *manager = wl_resource_get_user_data(resource); struct wlr_xdg_toplevel *xdg_toplevel = wlr_xdg_toplevel_from_resource(toplevel); /* the xdg_toplevel object has already been used to create a xdg_dialog_v1 */ struct xdg_dialog *xdg_dialog = NULL; if (xdg_toplevel && (xdg_dialog = xdg_dialog_from_toplevel(manager, xdg_toplevel))) { wl_resource_post_error( resource, XDG_WM_DIALOG_V1_ERROR_ALREADY_USED, "the xdg_toplevel object has already been used to create a xdg_dialog_v1"); return; } xdg_dialog = calloc(1, sizeof(*xdg_dialog)); if (!xdg_dialog) { wl_client_post_no_memory(client); return; } struct wl_resource *xdg_dialog_resource = wl_resource_create(client, &xdg_dialog_v1_interface, wl_resource_get_version(resource), id); if (!xdg_dialog_resource) { free(xdg_dialog); wl_client_post_no_memory(client); return; } wl_resource_set_implementation(xdg_dialog_resource, &xdg_dialog_v1_interface_impl, xdg_dialog, xdg_dialog_handle_resource_destroy); xdg_dialog->toplevel = xdg_toplevel; wl_list_insert(&manager->xdg_dialogs, &xdg_dialog->link); xdg_dialog->toplevel_destroy.notify = handle_toplevel_destroy; wl_list_init(&xdg_dialog->toplevel_destroy.link); if (!xdg_toplevel) { return; } /* If this object is destroyed before the related xdg_toplevel */ wl_signal_add(&xdg_toplevel->events.destroy, &xdg_dialog->toplevel_destroy); } static void handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct xdg_wm_dialog_v1_interface xdg_wm_dialog_v1_interface_impl = { .destroy = handle_destroy, .get_xdg_dialog = handle_get_xdg_dialog, }; static void xdg_dialog_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &xdg_wm_dialog_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } struct xdg_dialog_manager *manager = data; wl_resource_set_implementation(resource, &xdg_wm_dialog_v1_interface_impl, manager, NULL); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct xdg_dialog_manager *manager = wl_container_of(listener, manager, display_destroy); wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct xdg_dialog_manager *manager = wl_container_of(listener, manager, server_destroy); wl_list_remove(&manager->server_destroy.link); free(manager); } bool xdg_dialog_create(struct server *server) { struct xdg_dialog_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->global = wl_global_create(server->display, &xdg_wm_dialog_v1_interface, XDG_DIALOG_VERSION, manager, xdg_dialog_bind); if (!manager->global) { kywc_log(KYWC_WARN, "XDG dialog create failed"); free(manager); return false; } wl_list_init(&manager->xdg_dialogs); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &manager->display_destroy); return true; } kylin-wayland-compositor/src/view/ukui_window.c0000664000175000017500000011207115160461067020740 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include #include "theme.h" #include "ukui-window-management-protocol.h" #include "view/workspace.h" #include "view_p.h" #define UKUI_WINDOW_MANAGEMENT_VERSION 1 struct ukui_window_management { struct wl_global *global; struct wl_list resources; struct wl_list windows; struct ukui_window *highlight_window; struct wl_listener new_mapped_view; struct wl_listener show_desktop; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct ukui_window { struct wl_list resources; struct wl_list link; struct ukui_window_management *management; bool minimizable, maximizable, fullscreenable; bool closeable, movable, resizable; bool focusable; struct kywc_view *kywc_view; struct wl_listener view_unmap; struct wl_listener view_title; struct wl_listener view_app_id; struct wl_listener view_activate; struct wl_listener view_minimize; struct wl_listener view_maximize; struct wl_listener view_fullscreen; struct wl_listener view_above; struct wl_listener view_below; struct wl_listener view_skip_taskbar; struct wl_listener view_skip_switcher; struct wl_listener view_demands_attention; struct wl_listener view_modal; struct wl_listener view_capabilities; struct wl_listener workspace_enter; struct wl_listener workspace_leave; struct wl_listener view_position; struct wl_listener view_size; struct wl_listener view_icon_update; struct wl_listener view_update_capabilities; struct wl_listener panel_surface_destroy; /* The internal window id and uuid */ const char *uuid; /* bitfield of state flags */ uint32_t states; }; enum state_flag { STATE_FLAG_ACTIVE = 0, STATE_FLAG_MINIMIZED, STATE_FLAG_MAXIMIZED, STATE_FLAG_FULLSCREEN, STATE_FLAG_KEEP_ABOVE, STATE_FLAG_KEEP_BELOW, STATE_FLAG_ON_ALL_DESKTOPS, STATE_FLAG_DEMANDS_ATTENTION, STATE_FLAG_CLOSEABLE, STATE_FLAG_MINIMIZABLE, STATE_FLAG_MAXIMIZABLE, STATE_FLAG_FULLSCREENABLE, STATE_FLAG_SHADEABLE, STATE_FLAG_SHADED, STATE_FLAG_MOVABLE, STATE_FLAG_RESIZABLE, STATE_FLAG_VIRTUAL_DESKTOP_CHANGEABLE, STATE_FLAG_ACCEPT_FOCUS, STATE_FLAG_SKIPTASKBAR, STATE_FLAG_SKIPSWITCHER, STATE_FLAG_MODALITY, STATE_FLAG_LAST, }; static void ukui_window_set_state(struct ukui_window *window, enum state_flag flag, bool state) { uint32_t mask = 0; switch (flag) { case STATE_FLAG_ACTIVE: assert(state); kywc_view_activate(window->kywc_view); view_set_focus(view_from_kywc_view(window->kywc_view), input_manager_get_default_seat()); break; case STATE_FLAG_MINIMIZED: kywc_view_set_minimized(window->kywc_view, state); break; case STATE_FLAG_MAXIMIZED: kywc_view_set_maximized(window->kywc_view, state, NULL); break; case STATE_FLAG_FULLSCREEN: kywc_view_set_fullscreen(window->kywc_view, state, NULL); break; case STATE_FLAG_KEEP_ABOVE: kywc_view_set_kept_above(window->kywc_view, state); break; case STATE_FLAG_KEEP_BELOW: kywc_view_set_kept_below(window->kywc_view, state); break; case STATE_FLAG_ON_ALL_DESKTOPS: break; case STATE_FLAG_DEMANDS_ATTENTION: window->kywc_view->demands_attention = state; break; case STATE_FLAG_CLOSEABLE: window->closeable = state; mask |= KYWC_VIEW_CLOSEABLE; break; case STATE_FLAG_MINIMIZABLE: window->minimizable = state; mask |= KYWC_VIEW_MINIMIZABLE | KYWC_VIEW_MINIMIZE_BUTTON; break; case STATE_FLAG_MAXIMIZABLE: window->maximizable = state; mask |= KYWC_VIEW_MAXIMIZABLE | KYWC_VIEW_MAXIMIZE_BUTTON; break; case STATE_FLAG_FULLSCREENABLE: window->fullscreenable = state; mask |= KYWC_VIEW_FULLSCREENABLE; break; case STATE_FLAG_SHADEABLE: break; case STATE_FLAG_SHADED: break; case STATE_FLAG_MOVABLE: window->movable = state; mask |= KYWC_VIEW_MOVABLE; break; case STATE_FLAG_RESIZABLE: window->resizable = state; mask |= KYWC_VIEW_RESIZABLE; break; case STATE_FLAG_VIRTUAL_DESKTOP_CHANGEABLE: break; case STATE_FLAG_ACCEPT_FOCUS: window->focusable = state; mask |= KYWC_VIEW_FOCUSABLE; break; case STATE_FLAG_SKIPTASKBAR: window->kywc_view->skip_taskbar = state; break; case STATE_FLAG_SKIPSWITCHER: window->kywc_view->skip_switcher = state; break; case STATE_FLAG_MODALITY: kywc_view_set_modal(window->kywc_view, state); break; case STATE_FLAG_LAST: break; } if (mask != 0) { view_update_capabilities(view_from_kywc_view(window->kywc_view), mask); } } static void ukui_window_send_state(struct ukui_window *window, struct wl_resource *resource, bool force); static void handle_set_state(struct wl_client *client, struct wl_resource *resource, uint32_t flags, uint32_t state) { struct ukui_window *window = wl_resource_get_user_data(resource); if (!window) { return; } for (int i = 0; i < STATE_FLAG_LAST; i++) { if ((flags >> i) & 0x1) { ukui_window_set_state(window, i, (state >> i) & 0x1); } } ukui_window_send_state(window, NULL, false); } static void handle_set_startup_geometry(struct wl_client *client, struct wl_resource *resource, struct wl_resource *entry, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { } static void handle_panel_surface_destroy(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, panel_surface_destroy); wl_list_remove(&window->panel_surface_destroy.link); wl_list_init(&window->panel_surface_destroy.link); struct view *view = view_from_kywc_view(window->kywc_view); view->minimized_geometry.panel_surface = NULL; } static void handle_set_minimized_geometry(struct wl_client *client, struct wl_resource *resource, struct wl_resource *panel, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { struct ukui_window *window = wl_resource_get_user_data(resource); if (!window) { return; } struct wlr_surface *panel_surface = wlr_surface_from_resource(panel); struct view *view = view_from_kywc_view(window->kywc_view); view->minimized_geometry.panel_surface = panel_surface; view->minimized_geometry.geometry = (struct kywc_box){ x, y, width, height }; wl_list_remove(&window->panel_surface_destroy.link); wl_signal_add(&panel_surface->events.destroy, &window->panel_surface_destroy); } static void handle_unset_minimized_geometry(struct wl_client *client, struct wl_resource *resource, struct wl_resource *panel) { struct ukui_window *window = wl_resource_get_user_data(resource); if (!window) { return; } struct wlr_surface *panel_surface = wlr_surface_from_resource(panel); struct view *view = view_from_kywc_view(window->kywc_view); if (view->minimized_geometry.panel_surface && view->minimized_geometry.panel_surface == panel_surface) { wl_list_remove(&window->panel_surface_destroy.link); wl_list_init(&window->panel_surface_destroy.link); view->minimized_geometry.panel_surface = NULL; } } static void handle_close(struct wl_client *client, struct wl_resource *resource) { struct ukui_window *window = wl_resource_get_user_data(resource); if (!window) { return; } kywc_view_close(window->kywc_view); } static void handle_request_move(struct wl_client *client, struct wl_resource *resource) { // Not implemented yet } static void handle_request_resize(struct wl_client *client, struct wl_resource *resource) { // Not implemented yet } static void handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void handle_get_icon(struct wl_client *client, struct wl_resource *resource, int32_t fd) { struct ukui_window *window = wl_resource_get_user_data(resource); if (!window) { return; } struct view *view = view_from_kywc_view(window->kywc_view); if (!view->impl->get_icon_buffer) { return; } struct theme *theme = theme_manager_get_theme(); float scale = view->output->state.scale; struct wlr_buffer *wlr_buffer = view->impl->get_icon_buffer(view, theme->icon_size, scale); if (!wlr_buffer) { return; } void *data; uint32_t format; size_t stride; if (wlr_buffer->impl->begin_data_ptr_access && wlr_buffer->impl->begin_data_ptr_access(wlr_buffer, WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data, &format, &stride)) { int32_t size[2]; size[0] = wlr_buffer->width; size[1] = wlr_buffer->height; write(fd, size, sizeof(size)); write(fd, data, stride * wlr_buffer->height); close(fd); wlr_buffer->impl->end_data_ptr_access(wlr_buffer); } } static void handle_request_enter_virtual_desktop(struct wl_client *client, struct wl_resource *resource, const char *id) { // Not implemented yet } static void handle_request_enter_new_virtual_desktop(struct wl_client *client, struct wl_resource *resource) { // Not implemented yet } static void handle_request_leave_virtual_desktop(struct wl_client *client, struct wl_resource *resource, const char *id) { // Not implemented yet } static void handle_request_enter_activity(struct wl_client *client, struct wl_resource *resource, const char *id) { // Not implemented yet } static void handle_request_leave_activity(struct wl_client *client, struct wl_resource *resource, const char *id) { // Not implemented yet } static void handle_send_to_output(struct wl_client *client, struct wl_resource *resource, struct wl_resource *output) { // Not implemented yet } static void ukui_window_highlight(struct ukui_window *ukui_window, bool enable) { struct view *view = ukui_window ? view_from_kywc_view(ukui_window->kywc_view) : NULL; highlight_view(view, enable); } static void handle_request_highlight(struct wl_client *client, struct wl_resource *resource) { struct ukui_window *window = wl_resource_get_user_data(resource); if (!window || window->management->highlight_window == window) { return; } if (window->management->highlight_window) { ukui_window_highlight(window->management->highlight_window, false); } ukui_window_highlight(window, true); window->management->highlight_window = window; } static void handle_request_unset_highlight(struct wl_client *client, struct wl_resource *resource) { struct ukui_window *window = wl_resource_get_user_data(resource); if (!window || window->management->highlight_window != window) { return; } ukui_window_highlight(window, false); window->management->highlight_window = NULL; } static const struct ukui_window_interface ukui_window_impl = { .set_state = handle_set_state, .set_startup_geometry = handle_set_startup_geometry, .set_minimized_geometry = handle_set_minimized_geometry, .unset_minimized_geometry = handle_unset_minimized_geometry, .close = handle_close, .request_move = handle_request_move, .request_resize = handle_request_resize, .destroy = handle_destroy, .get_icon = handle_get_icon, .request_enter_virtual_desktop = handle_request_enter_virtual_desktop, .request_enter_new_virtual_desktop = handle_request_enter_new_virtual_desktop, .request_leave_virtual_desktop = handle_request_leave_virtual_desktop, .request_enter_activity = handle_request_enter_activity, .request_leave_activity = handle_request_leave_activity, .send_to_output = handle_send_to_output, .highlight = handle_request_highlight, .unset_highlight = handle_request_unset_highlight, }; static struct ukui_window *ukui_window_from_uuid(struct ukui_window_management *management, const char *uuid) { struct ukui_window *window; wl_list_for_each(window, &management->windows, link) { if (strcmp(window->uuid, uuid) == 0) { return window; } } return NULL; } static void window_handle_resource_destroy(struct wl_resource *resource) { wl_resource_set_destructor(resource, NULL); wl_resource_set_user_data(resource, NULL); wl_list_remove(wl_resource_get_link(resource)); } static void window_handle_view_title(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_title); if (!window->kywc_view->title) { return; } struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_title_changed(resource, window->kywc_view->title); } } static void window_handle_view_app_id(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_app_id); if (!window->kywc_view->app_id) { return; } struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_app_id_changed(resource, window->kywc_view->app_id); } } #define set_state(states, prop, state) \ if (prop) { \ states |= UKUI_WINDOW_STATE_##state; \ } else { \ states &= ~UKUI_WINDOW_STATE_##state; \ } static void window_handle_view_activate(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_activate); set_state(window->states, window->kywc_view->activated, ACTIVE); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_state_changed(resource, window->states); } } static void window_handle_view_minimize(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_minimize); set_state(window->states, window->kywc_view->minimized, MINIMIZED); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_state_changed(resource, window->states); } } static void window_handle_view_maximize(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_maximize); set_state(window->states, window->kywc_view->maximized, MAXIMIZED); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_state_changed(resource, window->states); } } static void window_handle_view_fullscreen(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_fullscreen); set_state(window->states, window->kywc_view->fullscreen, FULLSCREEN); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_state_changed(resource, window->states); } } static void window_handle_view_above(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_above); set_state(window->states, window->kywc_view->kept_above, KEEP_ABOVE); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_state_changed(resource, window->states); } } static void window_handle_view_below(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_below); set_state(window->states, window->kywc_view->kept_below, KEEP_BELOW); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_state_changed(resource, window->states); } } static void window_handle_view_skip_taskbar(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_skip_taskbar); set_state(window->states, window->kywc_view->skip_taskbar, SKIPTASKBAR); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_state_changed(resource, window->states); } } static void window_handle_view_skip_switcher(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_skip_switcher); set_state(window->states, window->kywc_view->skip_switcher, SKIPSWITCHER); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_state_changed(resource, window->states); } } static void window_handle_view_demands_attention(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_demands_attention); set_state(window->states, window->kywc_view->demands_attention, DEMANDS_ATTENTION); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_state_changed(resource, window->states); } } static void window_handle_view_modal(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_modal); set_state(window->states, window->kywc_view->modal, MODALITY); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_state_changed(resource, window->states); } } static void window_handle_view_capabilities(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_capabilities); struct kywc_view_capabilities_event *event = data; if (event->mask & (KYWC_VIEW_MINIMIZABLE | KYWC_VIEW_MAXIMIZABLE | KYWC_VIEW_CLOSEABLE | KYWC_VIEW_FULLSCREENABLE | KYWC_VIEW_MOVABLE | KYWC_VIEW_RESIZABLE | KYWC_VIEW_FOCUSABLE)) { ukui_window_send_state(window, NULL, false); } } static void window_handle_view_position(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_position); struct kywc_view *view = window->kywc_view; struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_geometry(resource, view->geometry.x, view->geometry.y, view->geometry.width, view->geometry.height); } } static void window_handle_view_size(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_size); struct kywc_view *view = window->kywc_view; struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_geometry(resource, view->geometry.x, view->geometry.y, view->geometry.width, view->geometry.height); } } static void ukui_window_send_state(struct ukui_window *window, struct wl_resource *resource, bool force) { struct kywc_view *kywc_view = window->kywc_view; uint32_t states = window->states; set_state(states, kywc_view->activated, ACTIVE); set_state(states, kywc_view->minimized, MINIMIZED); set_state(states, kywc_view->maximized, MAXIMIZED); set_state(states, kywc_view->fullscreen, FULLSCREEN); set_state(states, kywc_view->kept_above, KEEP_ABOVE); set_state(states, kywc_view->kept_below, KEEP_BELOW); // ukui_window_MANAGEMENT_STATE_ON_ALL_DESKTOPS set_state(states, kywc_view->demands_attention, DEMANDS_ATTENTION); set_state(states, kywc_view->modal, MODALITY); set_state(states, KYWC_VIEW_IS_CLOSEABLE(kywc_view), CLOSEABLE); set_state(states, KYWC_VIEW_IS_MINIMIZABLE(kywc_view), MINIMIZABLE); set_state(states, KYWC_VIEW_IS_MAXIMIZABLE(kywc_view), MAXIMIZABLE); set_state(states, KYWC_VIEW_IS_FULLSCREENABLE(kywc_view), FULLSCREENABLE); set_state(states, kywc_view->skip_taskbar, SKIPTASKBAR); // ukui_window_MANAGEMENT_STATE_SHADEABLE // ukui_window_MANAGEMENT_STATE_SHADED set_state(states, KYWC_VIEW_IS_MOVABLE(kywc_view), MOVABLE); set_state(states, KYWC_VIEW_IS_RESIZABLE(kywc_view), RESIZABLE); // ukui_window_MANAGEMENT_STATE_VIRTUAL_DESKTOP_CHANGEABLE set_state(states, kywc_view->skip_switcher, SKIPSWITCHER); set_state(states, KYWC_VIEW_IS_FOCUSABLE(kywc_view), ACCEPT_FOCUS); if (force || states != window->states) { window->states = states; if (resource) { ukui_window_send_state_changed(resource, window->states); } else { struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_state_changed(resource, window->states); } } } } #undef set_state static void ukui_window_add_resource(struct ukui_window *window, struct wl_resource *management_resource, uint32_t id) { struct wl_client *client = wl_resource_get_client(management_resource); uint32_t version = wl_resource_get_version(management_resource); struct wl_resource *resource = wl_resource_create(client, &ukui_window_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &ukui_window_impl, window, window_handle_resource_destroy); if (!window) { wl_list_init(wl_resource_get_link(resource)); ukui_window_send_unmapped(resource); return; } wl_list_insert(&window->resources, wl_resource_get_link(resource)); /* send states */ ukui_window_send_state(window, resource, true); struct kywc_view *kywc_view = window->kywc_view; struct view *view = view_from_kywc_view(kywc_view); if (kywc_view->title) { ukui_window_send_title_changed(resource, kywc_view->title); } if (kywc_view->app_id) { ukui_window_send_app_id_changed(resource, kywc_view->app_id); } if (view->pid) { ukui_window_send_pid_changed(resource, view->pid); } ukui_window_send_geometry(resource, kywc_view->geometry.x, kywc_view->geometry.y, kywc_view->geometry.width, kywc_view->geometry.height); // ukui_window_send_parent_window // ukui_window_send_virtual_desktop_changed // ukui_window_send_virtual_desktop_entered // ukui_window_send_virtual_desktop_left if (!theme_icon_is_fallback(view->icon)) { const char *icon_name = theme_icon_get_name(view->icon); ukui_window_send_themed_icon_name_changed(resource, icon_name); } else { ukui_window_send_icon_changed(resource); } // ukui_window_send_application_menu // ukui_window_send_activity_entered // ukui_window_send_activity_left // ukui_window_send_resource_name_changed ukui_window_send_initial_state(resource); struct view_proxy *proxy; wl_list_for_each(proxy, &view->view_proxies, view_link) { ukui_window_send_virtual_desktop_entered(resource, proxy->workspace->uuid); } } static void handle_create_window(struct wl_client *client, struct wl_resource *management_resource, uint32_t id, const char *internal_window_uuid) { struct ukui_window_management *management = wl_resource_get_user_data(management_resource); struct ukui_window *window = ukui_window_from_uuid(management, internal_window_uuid); ukui_window_add_resource(window, management_resource, id); } static void handle_show_desktop(struct wl_client *client, struct wl_resource *resource, uint32_t state) { view_manager_show_desktop(state == UKUI_WINDOW_MANAGEMENT_SHOW_DESKTOP_ENABLED, true); } static const struct ukui_window_management_interface ukui_window_management_impl = { .show_desktop = handle_show_desktop, .create_window = handle_create_window, }; static void management_handle_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static void ukui_window_management_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct ukui_window_management *management = data; struct wl_resource *resource = wl_resource_create(client, &ukui_window_management_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_list_insert(&management->resources, wl_resource_get_link(resource)); wl_resource_set_implementation(resource, &ukui_window_management_impl, management, management_handle_resource_destroy); ukui_window_management_send_show_desktop_changed( resource, view_manager_get_show_desktop() ? UKUI_WINDOW_MANAGEMENT_SHOW_DESKTOP_ENABLED : UKUI_WINDOW_MANAGEMENT_SHOW_DESKTOP_DISABLED); struct ukui_window *window; wl_list_for_each(window, &management->windows, link) { ukui_window_management_send_window_created(resource, window->uuid); } // TODO: stacking_order_changed } static void handle_window_workspace_enter(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, workspace_enter); struct workspace *workspace = data; struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_virtual_desktop_entered(resource, workspace->uuid); } } static void handle_window_workspace_leave(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, workspace_leave); struct workspace *workspace = data; struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_virtual_desktop_left(resource, workspace->uuid); } } static void window_handle_view_unmap(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_unmap); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { ukui_window_send_unmapped(resource); } wl_list_remove(&window->view_unmap.link); wl_list_remove(&window->view_title.link); wl_list_remove(&window->view_app_id.link); wl_list_remove(&window->view_activate.link); wl_list_remove(&window->view_minimize.link); wl_list_remove(&window->view_maximize.link); wl_list_remove(&window->view_fullscreen.link); wl_list_remove(&window->view_above.link); wl_list_remove(&window->view_below.link); wl_list_remove(&window->view_skip_taskbar.link); wl_list_remove(&window->view_skip_switcher.link); wl_list_remove(&window->view_demands_attention.link); wl_list_remove(&window->view_modal.link); wl_list_remove(&window->view_capabilities.link); wl_list_remove(&window->workspace_enter.link); wl_list_remove(&window->workspace_leave.link); wl_list_remove(&window->view_position.link); wl_list_remove(&window->view_size.link); wl_list_remove(&window->view_icon_update.link); wl_list_remove(&window->view_update_capabilities.link); wl_list_remove(&window->panel_surface_destroy.link); wl_list_remove(&window->link); struct wl_resource *tmp; wl_resource_for_each_safe(resource, tmp, &window->resources) { window_handle_resource_destroy(resource); } if (window->management->highlight_window == window) { window->management->highlight_window = NULL; ukui_window_highlight(window->management->highlight_window, false); } free(window); } static void window_handle_view_icon_update(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_icon_update); struct view *view = view_from_kywc_view(window->kywc_view); struct wl_resource *resource; wl_resource_for_each(resource, &window->resources) { if (!theme_icon_is_fallback(view->icon)) { const char *icon_name = theme_icon_get_name(view->icon); ukui_window_send_themed_icon_name_changed(resource, icon_name); } else { ukui_window_send_icon_changed(resource); } } } static void window_handle_view_update_capabilities(struct wl_listener *listener, void *data) { struct ukui_window *window = wl_container_of(listener, window, view_update_capabilities); struct view_update_capabilities_event *event = data; if (event->mask & KYWC_VIEW_MINIMIZABLE) { if (!window->minimizable) { event->state &= ~KYWC_VIEW_MINIMIZABLE; } } if (event->mask & KYWC_VIEW_MAXIMIZABLE) { if (!window->maximizable) { event->state &= ~KYWC_VIEW_MAXIMIZABLE; } } if (event->mask & KYWC_VIEW_CLOSEABLE) { if (!window->closeable) { event->state &= ~KYWC_VIEW_CLOSEABLE; } } if (event->mask & KYWC_VIEW_FULLSCREENABLE) { if (!window->fullscreenable) { event->state &= ~KYWC_VIEW_FULLSCREENABLE; } } if (event->mask & KYWC_VIEW_MOVABLE) { if (!window->movable) { event->state &= ~KYWC_VIEW_MOVABLE; } } if (event->mask & KYWC_VIEW_RESIZABLE) { if (!window->resizable) { event->state &= ~KYWC_VIEW_RESIZABLE; } } if (event->mask & KYWC_VIEW_FOCUSABLE) { if (!window->focusable) { event->state &= ~KYWC_VIEW_FOCUSABLE; } } if (event->mask & KYWC_VIEW_MINIMIZE_BUTTON) { if (!window->minimizable) { event->state &= ~KYWC_VIEW_MINIMIZE_BUTTON; } } if (event->mask & KYWC_VIEW_MAXIMIZE_BUTTON) { if (!window->maximizable) { event->state &= ~KYWC_VIEW_MAXIMIZE_BUTTON; } } } static void handle_new_mapped_view(struct wl_listener *listener, void *data) { struct ukui_window_management *management = wl_container_of(listener, management, new_mapped_view); struct kywc_view *kywc_view = data; struct view *view = view_from_kywc_view(kywc_view); struct ukui_window *window = calloc(1, sizeof(*window)); if (!window) { return; } window->management = management; wl_list_init(&window->resources); wl_list_insert(&management->windows, &window->link); window->uuid = kywc_view->uuid; window->minimizable = window->maximizable = window->fullscreenable = window->closeable = window->movable = window->resizable = window->focusable = true; window->kywc_view = kywc_view; window->view_unmap.notify = window_handle_view_unmap; wl_signal_add(&kywc_view->events.unmap, &window->view_unmap); window->view_title.notify = window_handle_view_title; wl_signal_add(&kywc_view->events.title, &window->view_title); window->view_app_id.notify = window_handle_view_app_id; wl_signal_add(&kywc_view->events.app_id, &window->view_app_id); window->view_activate.notify = window_handle_view_activate; wl_signal_add(&kywc_view->events.activate, &window->view_activate); window->view_minimize.notify = window_handle_view_minimize; wl_signal_add(&kywc_view->events.minimize, &window->view_minimize); window->view_maximize.notify = window_handle_view_maximize; wl_signal_add(&kywc_view->events.maximize, &window->view_maximize); window->view_fullscreen.notify = window_handle_view_fullscreen; wl_signal_add(&kywc_view->events.fullscreen, &window->view_fullscreen); window->view_above.notify = window_handle_view_above; wl_signal_add(&kywc_view->events.above, &window->view_above); window->view_below.notify = window_handle_view_below; wl_signal_add(&kywc_view->events.below, &window->view_below); window->view_skip_taskbar.notify = window_handle_view_skip_taskbar; wl_signal_add(&kywc_view->events.skip_taskbar, &window->view_skip_taskbar); window->view_skip_switcher.notify = window_handle_view_skip_switcher; wl_signal_add(&kywc_view->events.skip_switcher, &window->view_skip_switcher); window->view_demands_attention.notify = window_handle_view_demands_attention; wl_signal_add(&kywc_view->events.demands_attention, &window->view_demands_attention); window->view_modal.notify = window_handle_view_modal; wl_signal_add(&kywc_view->events.modal, &window->view_modal); window->view_capabilities.notify = window_handle_view_capabilities; wl_signal_add(&kywc_view->events.capabilities, &window->view_capabilities); window->workspace_enter.notify = handle_window_workspace_enter; wl_signal_add(&view->events.workspace_enter, &window->workspace_enter); window->workspace_leave.notify = handle_window_workspace_leave; wl_signal_add(&view->events.workspace_leave, &window->workspace_leave); window->view_position.notify = window_handle_view_position; wl_signal_add(&kywc_view->events.position, &window->view_position); window->view_size.notify = window_handle_view_size; wl_signal_add(&kywc_view->events.size, &window->view_size); window->view_icon_update.notify = window_handle_view_icon_update; wl_signal_add(&view->events.icon_update, &window->view_icon_update); window->view_update_capabilities.notify = window_handle_view_update_capabilities; view_add_update_capabilities_listener(view, &window->view_update_capabilities); window->panel_surface_destroy.notify = handle_panel_surface_destroy; wl_list_init(&window->panel_surface_destroy.link); struct wl_resource *resource; wl_resource_for_each(resource, &management->resources) { ukui_window_management_send_window_created(resource, window->uuid); } if (management->highlight_window) { ukui_window_highlight(management->highlight_window, false); management->highlight_window = NULL; } } static void handle_shown_desktop(struct wl_listener *listener, void *data) { struct ukui_window_management *management = wl_container_of(listener, management, show_desktop); bool enabled = view_manager_get_show_desktop(); struct wl_resource *resource; wl_resource_for_each(resource, &management->resources) { ukui_window_management_send_show_desktop_changed( resource, enabled ? UKUI_WINDOW_MANAGEMENT_SHOW_DESKTOP_ENABLED : UKUI_WINDOW_MANAGEMENT_SHOW_DESKTOP_DISABLED); } } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct ukui_window_management *management = wl_container_of(listener, management, display_destroy); wl_list_remove(&management->display_destroy.link); wl_list_remove(&management->new_mapped_view.link); wl_list_remove(&management->show_desktop.link); wl_global_destroy(management->global); } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct ukui_window_management *management = wl_container_of(listener, management, server_destroy); wl_list_remove(&management->server_destroy.link); free(management); } bool ukui_window_management_create(struct server *server) { struct ukui_window_management *management = calloc(1, sizeof(*management)); if (!management) { return false; } management->global = wl_global_create(server->display, &ukui_window_management_interface, UKUI_WINDOW_MANAGEMENT_VERSION, management, ukui_window_management_bind); if (!management->global) { kywc_log(KYWC_WARN, "UKUI window management create failed"); free(management); return false; } wl_list_init(&management->windows); wl_list_init(&management->resources); management->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &management->server_destroy); management->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &management->display_destroy); management->new_mapped_view.notify = handle_new_mapped_view; kywc_view_add_new_mapped_listener(&management->new_mapped_view); management->show_desktop.notify = handle_shown_desktop; view_manager_add_show_desktop_listener(&management->show_desktop); return true; } kylin-wayland-compositor/src/view/xdg_activation.c0000664000175000017500000001607515160461067021406 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "input/seat.h" #include "output.h" #include "view/workspace.h" #include "view_p.h" struct xdg_activation_token { struct wlr_xdg_activation_token_v1 *token; struct wl_listener token_destroy; struct seat *seat; struct wl_listener seat_destroy; struct kywc_output *output; struct wl_listener output_destroy; struct workspace *workspace; struct wl_listener workspace_destroy; struct view *view; struct wl_listener view_premap; struct wl_listener view_destroy; }; struct xdg_activation_manager { struct wlr_xdg_activation_v1 *xdg_activation; struct wl_listener new_token; struct wl_listener request_activate; struct wl_listener destroy; }; static void xdg_activation_token_destroy(struct xdg_activation_token *token) { if (token->view) { return; } wl_list_remove(&token->token_destroy.link); wl_list_remove(&token->seat_destroy.link); wl_list_remove(&token->output_destroy.link); wl_list_remove(&token->workspace_destroy.link); wl_list_remove(&token->view_premap.link); wl_list_remove(&token->view_destroy.link); free(token); } static void token_handle_seat_destroy(struct wl_listener *listener, void *data) { struct xdg_activation_token *token = wl_container_of(listener, token, seat_destroy); token->seat = NULL; wl_list_remove(&token->seat_destroy.link); wl_list_init(&token->seat_destroy.link); } static void token_handle_output_destroy(struct wl_listener *listener, void *data) { struct xdg_activation_token *token = wl_container_of(listener, token, output_destroy); token->output = NULL; wl_list_remove(&token->output_destroy.link); wl_list_init(&token->output_destroy.link); } static void token_handle_workspace_destroy(struct wl_listener *listener, void *data) { struct xdg_activation_token *token = wl_container_of(listener, token, workspace_destroy); token->workspace = NULL; wl_list_remove(&token->workspace_destroy.link); wl_list_init(&token->workspace_destroy.link); } static void token_handle_token_destroy(struct wl_listener *listener, void *data) { struct xdg_activation_token *token = wl_container_of(listener, token, token_destroy); token->token = NULL; xdg_activation_token_destroy(token); } static void token_handle_view_premap(struct wl_listener *listener, void *data) { struct xdg_activation_token *token = wl_container_of(listener, token, view_premap); struct view *view = token->view; /* set view output */ if (token->output && view->output != token->output) { view->output = token->output; wl_list_remove(&view->output_destroy.link); wl_signal_add(&view->output->events.destroy, &view->output_destroy); } if (token->workspace) { view_set_workspace(view, token->workspace); } view->base.focused_seat = token->seat ? token->seat : input_manager_get_default_seat(); token->view = NULL; xdg_activation_token_destroy(token); } static void token_handle_view_destroy(struct wl_listener *listener, void *data) { struct xdg_activation_token *token = wl_container_of(listener, token, view_destroy); token->view = NULL; xdg_activation_token_destroy(token); } static void handle_request_activate(struct wl_listener *listener, void *data) { const struct wlr_xdg_activation_v1_request_activate_event *event = data; struct wlr_xdg_surface *wlr_xdg_surface = wlr_xdg_surface_try_from_wlr_surface(event->surface); struct xdg_activation_token *token = event->token->data; if (!wlr_xdg_surface || !token) { return; } struct view *view = view_try_from_wlr_surface(event->surface); if (!view) { return; } if (event->surface->mapped) { /* activation request */ kywc_view_activate(&view->base); view_set_focus(view, token->seat ? token->seat : input_manager_get_default_seat()); return; } /* startup app notification. now token destroy by view_premap or view_destroy*/ wl_list_remove(&token->token_destroy.link); wl_list_init(&token->token_destroy.link); token->view = view; token->view_premap.notify = token_handle_view_premap; wl_signal_add(&token->view->base.events.premap, &token->view_premap); token->view_destroy.notify = token_handle_view_destroy; wl_signal_add(&token->view->base.events.destroy, &token->view_destroy); } static void handle_new_token(struct wl_listener *listener, void *data) { struct xdg_activation_token *token = calloc(1, sizeof(*token)); if (!token) { return; } struct wlr_xdg_activation_token_v1 *token_v1 = data; token_v1->data = token; token->token = token_v1; token->token_destroy.notify = token_handle_token_destroy; wl_signal_add(&token_v1->events.destroy, &token->token_destroy); struct seat *seat = token_v1->seat ? seat_from_wlr_seat(token_v1->seat) : input_manager_get_default_seat(); token->seat = seat; token->seat_destroy.notify = token_handle_seat_destroy; wl_signal_add(&seat->events.destroy, &token->seat_destroy); token->output = input_current_output(seat) ? &input_current_output(seat)->base : NULL; token->output_destroy.notify = token_handle_output_destroy; wl_signal_add(&token->output->events.destroy, &token->output_destroy); token->workspace = workspace_manager_get_current(); if (token_v1->surface) { /* workspace from surface */ struct view *view = view_try_from_wlr_surface(token_v1->surface); if (view) { token->workspace = view->current_proxy->workspace; } } token->workspace_destroy.notify = token_handle_workspace_destroy; wl_signal_add(&token->workspace->events.destroy, &token->workspace_destroy); wl_list_init(&token->view_premap.link); wl_list_init(&token->view_destroy.link); } static void handle_destroy(struct wl_listener *listener, void *data) { struct xdg_activation_manager *manager = wl_container_of(listener, manager, destroy); wl_list_remove(&manager->destroy.link); wl_list_remove(&manager->request_activate.link); wl_list_remove(&manager->new_token.link); free(manager); } bool xdg_activation_create(struct server *server) { struct xdg_activation_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->xdg_activation = wlr_xdg_activation_v1_create(server->display); if (!manager->xdg_activation) { free(manager); return false; } manager->request_activate.notify = handle_request_activate; wl_signal_add(&manager->xdg_activation->events.request_activate, &manager->request_activate); manager->new_token.notify = handle_new_token; wl_signal_add(&manager->xdg_activation->events.new_token, &manager->new_token); manager->destroy.notify = handle_destroy; wl_signal_add(&manager->xdg_activation->events.destroy, &manager->destroy); return true; } kylin-wayland-compositor/src/view/ukui_blur.c0000664000175000017500000002575315160461067020407 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "scene/surface.h" #include "ukui-blur-v1-protocol.h" #include "util/macros.h" #include "view_p.h" #define UKUI_BLUR_MANAGER_VERSION 2 enum UKUI_BLUR_STATE_MASK { UKUI_BLUR_STATE_NONE = 0, UKUI_BLUR_STATE_REGION = 1 << 0, UKUI_BLUR_STATE_LEVEL = 1 << 1, UKUI_BLUR_STATE_OPACITY = 1 << 2, }; struct ukui_blur_manager { struct wl_global *global; struct wl_list ukui_blur_surfaces; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct ukui_blur_surface { struct wl_list link; struct wl_resource *resource; struct wlr_surface *wlr_surface; struct wl_listener surface_commit; struct wl_listener surface_map; struct wl_listener surface_destroy; struct ky_scene_buffer *scene_buffer; struct wl_listener node_destroy; pixman_region32_t pending_region; uint32_t pending_level; float pending_opacity; uint32_t pending_mask; }; static struct ukui_blur_manager *manager = NULL; struct blur_level { int iterations; float offset; }; static const struct blur_level blur_levels[] = { { 1, 1.5 }, { 1, 2 }, { 2, 2.5 }, { 2, 3.0 }, { 3, 2.6 }, { 3, 3.2 }, { 3, 3.8 }, { 3, 4.4 }, { 3, 5 }, { 4, 3.83333 }, { 4, 4.66667 }, { 4, 5.5 }, { 4, 6.33333 }, { 4, 7.16667 }, { 4, 8 }, }; static void blur_surface_apply_state(struct ukui_blur_surface *blur_surface) { /* the level is twelve, if not set. iterations = 4, offset = 5.5 */ if (blur_surface->pending_mask & UKUI_BLUR_STATE_REGION) { ky_scene_node_set_blur_region(&blur_surface->scene_buffer->node, &blur_surface->pending_region); } if (blur_surface->pending_mask & UKUI_BLUR_STATE_LEVEL) { const struct blur_level *level = &blur_levels[blur_surface->pending_level - 1]; ky_scene_node_set_blur_level(&blur_surface->scene_buffer->node, level->iterations, level->offset); } if (blur_surface->pending_mask & UKUI_BLUR_STATE_OPACITY) { ky_scene_node_set_blur_opacity(&blur_surface->scene_buffer->node, blur_surface->pending_opacity); } blur_surface->pending_mask = UKUI_BLUR_STATE_NONE; } static void blur_surface_destroy(struct ukui_blur_surface *blur_surface) { /* remove blur if surface is not destroyed */ if (blur_surface->scene_buffer) { ky_scene_node_set_blur_region(&blur_surface->scene_buffer->node, NULL); } /* clear destructor when surface destroyed before blur resources */ wl_resource_set_user_data(blur_surface->resource, NULL); wl_resource_set_destructor(blur_surface->resource, NULL); wl_list_remove(&blur_surface->link); wl_list_remove(&blur_surface->surface_commit.link); wl_list_remove(&blur_surface->surface_map.link); wl_list_remove(&blur_surface->surface_destroy.link); wl_list_remove(&blur_surface->node_destroy.link); pixman_region32_fini(&blur_surface->pending_region); free(blur_surface); } static void blur_surface_handle_node_destroy(struct wl_listener *listener, void *data) { struct ukui_blur_surface *blur_surface = wl_container_of(listener, blur_surface, node_destroy); blur_surface->scene_buffer = NULL; blur_surface_destroy(blur_surface); } static void blur_surface_handle_surface_map(struct wl_listener *listener, void *data) { struct ukui_blur_surface *blur_surface = wl_container_of(listener, blur_surface, surface_map); blur_surface->scene_buffer = ky_scene_buffer_try_from_surface(blur_surface->wlr_surface); wl_signal_add(&blur_surface->scene_buffer->node.events.destroy, &blur_surface->node_destroy); } static void blur_surface_handle_surface_destroy(struct wl_listener *listener, void *data) { struct ukui_blur_surface *blur_surface = wl_container_of(listener, blur_surface, surface_destroy); blur_surface_destroy(blur_surface); } static void blur_surface_handle_surface_commit(struct wl_listener *listener, void *data) { struct ukui_blur_surface *blur_surface = wl_container_of(listener, blur_surface, surface_commit); if (!blur_surface->wlr_surface->mapped) { return; } if (blur_surface->pending_mask != UKUI_BLUR_STATE_NONE) { blur_surface_apply_state(blur_surface); } } static void ukui_blur_surface_handle_resource_destroy(struct wl_resource *resource) { struct ukui_blur_surface *blur_surface = wl_resource_get_user_data(resource); blur_surface_destroy(blur_surface); } static void ukui_blur_surface_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void ukui_blur_surface_handle_set_region(struct wl_client *client, struct wl_resource *resource, struct wl_resource *region_resource) { struct ukui_blur_surface *blur_surface = wl_resource_get_user_data(resource); if (!blur_surface) { return; } if (region_resource) { const pixman_region32_t *region = wlr_region_from_resource(region_resource); pixman_region32_copy(&blur_surface->pending_region, region); } else { pixman_region32_clear(&blur_surface->pending_region); } blur_surface->pending_mask |= UKUI_BLUR_STATE_REGION; } static void ukui_blur_surface_handle_set_level(struct wl_client *client, struct wl_resource *resource, uint32_t level) { struct ukui_blur_surface *blur_surface = wl_resource_get_user_data(resource); if (!blur_surface) { return; } if (level < 1 || level > 15) { kywc_log(KYWC_DEBUG, "UKUI blur level is invalid"); return; } blur_surface->pending_level = level; blur_surface->pending_mask |= UKUI_BLUR_STATE_LEVEL; } static void ukui_blur_surface_handle_set_opacity(struct wl_client *client, struct wl_resource *resource, wl_fixed_t opacity) { struct ukui_blur_surface *blur_surface = wl_resource_get_user_data(resource); if (!blur_surface) { return; } blur_surface->pending_opacity = CLAMP(wl_fixed_to_double(opacity), 0.0f, 1.0f); blur_surface->pending_mask |= UKUI_BLUR_STATE_OPACITY; } static const struct ukui_blur_surface_v1_interface ukui_blur_surface_impl = { .destroy = ukui_blur_surface_handle_destroy, .set_region = ukui_blur_surface_handle_set_region, .set_level = ukui_blur_surface_handle_set_level, .set_opacity = ukui_blur_surface_handle_set_opacity, }; static struct ukui_blur_surface *blur_surface_from_wlr_surface(struct wlr_surface *wlr_surface) { struct ukui_blur_surface *blur_surface, *tmp; wl_list_for_each_safe(blur_surface, tmp, &manager->ukui_blur_surfaces, link) { if (blur_surface->wlr_surface == wlr_surface) { return blur_surface; } } return NULL; } static void handle_blur_manager_get_blur(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id, struct wl_resource *surface_resource) { struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface_resource); struct ukui_blur_surface *blur_surface = blur_surface_from_wlr_surface(wlr_surface); if (blur_surface) { wl_resource_post_error(manager_resource, UKUI_BLUR_MANAGER_V1_ERROR_BLUR_EXISTS, "ukui blur surface already exists for this surface"); return; } blur_surface = calloc(1, sizeof(*blur_surface)); if (!blur_surface) { wl_resource_post_no_memory(manager_resource); return; } int version = wl_resource_get_version(manager_resource); struct wl_resource *resource = wl_resource_create(client, &ukui_blur_surface_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); free(blur_surface); return; } blur_surface->resource = resource; wl_list_insert(&manager->ukui_blur_surfaces, &blur_surface->link); pixman_region32_init(&blur_surface->pending_region); blur_surface->wlr_surface = wlr_surface; blur_surface->surface_map.notify = blur_surface_handle_surface_map; wl_signal_add(&wlr_surface->events.map, &blur_surface->surface_map); blur_surface->surface_destroy.notify = blur_surface_handle_surface_destroy; wl_signal_add(&wlr_surface->events.destroy, &blur_surface->surface_destroy); blur_surface->surface_commit.notify = blur_surface_handle_surface_commit; wl_signal_add(&wlr_surface->events.commit, &blur_surface->surface_commit); blur_surface->node_destroy.notify = blur_surface_handle_node_destroy; wl_list_init(&blur_surface->node_destroy.link); wl_resource_set_implementation(resource, &ukui_blur_surface_impl, blur_surface, ukui_blur_surface_handle_resource_destroy); if (wlr_surface->mapped) { blur_surface_handle_surface_map(&blur_surface->surface_map, NULL); } } static void handle_blur_manager_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct ukui_blur_manager_v1_interface ukui_blur_manager_v1_impl = { .destroy = handle_blur_manager_destroy, .get_blur = handle_blur_manager_get_blur, }; static void ukui_blur_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &ukui_blur_manager_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &ukui_blur_manager_v1_impl, manager, NULL); } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->server_destroy.link); free(manager); manager = NULL; } bool ukui_blur_manager_create(struct server *server) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->global = wl_global_create(server->display, &ukui_blur_manager_v1_interface, UKUI_BLUR_MANAGER_VERSION, manager, ukui_blur_manager_bind); if (!manager->global) { kywc_log(KYWC_WARN, "UKUI blur manager create failed"); free(manager); return false; } wl_list_init(&manager->ukui_blur_surfaces); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &manager->display_destroy); return true; } kylin-wayland-compositor/src/view/kde_slide.c0000664000175000017500000002010215160461067020310 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "slide-protocol.h" #include "util/wayland.h" #include "view_p.h" #define KDE_KWIN_SLIDE_MANAGER_VERSION 1 struct kde_slide_manager { struct wl_global *global; struct wl_list kde_slides; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct kde_slide { struct wl_list link; struct wl_list resources; struct wlr_surface *wlr_surface; struct wl_listener surface_map; struct wl_listener surface_destroy; uint32_t location, pending_location; int32_t offset, pending_offset; }; static struct kde_slide_manager *manager = NULL; static void kde_slide_apply_state(struct kde_slide *slide) { slide->offset = slide->pending_offset; slide->location = slide->pending_location; } static struct kde_slide *kde_slide_from_wlr_surface(struct wlr_surface *wlr_surface) { struct kde_slide *slide; wl_list_for_each(slide, &manager->kde_slides, link) { if (slide->wlr_surface == wlr_surface) { return slide; } } return NULL; } static void view_apply_slide(struct kde_slide *slide) { struct view *view = view_try_from_wlr_surface(slide->wlr_surface); if (!view) { kywc_log(KYWC_DEBUG, "Surface is not a toplevel"); return; } view->slide.offset = slide->offset; view->slide.location = slide->location; view->use_slide = true; } static void kde_slide_handle_commit(struct wl_client *client, struct wl_resource *resource) { struct kde_slide *slide = wl_resource_get_user_data(resource); if (!slide) { return; } kde_slide_apply_state(slide); if (slide->wlr_surface->mapped) { view_apply_slide(slide); } } static void kde_slide_handle_set_location(struct wl_client *client, struct wl_resource *resource, uint32_t location) { struct kde_slide *slide = wl_resource_get_user_data(resource); if (!slide) { return; } if (location != slide->location) { slide->pending_location = location; } } static void kde_slide_handle_set_offset(struct wl_client *client, struct wl_resource *resource, int32_t offset) { struct kde_slide *slide = wl_resource_get_user_data(resource); if (!slide) { return; } if (offset != slide->pending_offset) { slide->pending_offset = offset; } } static void kde_slide_handle_release(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct org_kde_kwin_slide_interface kde_slide_impl = { .commit = kde_slide_handle_commit, .set_location = kde_slide_handle_set_location, .set_offset = kde_slide_handle_set_offset, .release = kde_slide_handle_release, }; static void kde_slide_destroy(struct kde_slide *slide) { /* clear destructor when surface destroyed before slide resources */ struct wl_resource *resource, *tmp; wl_resource_for_each_safe(resource, tmp, &slide->resources) { wl_resource_set_user_data(resource, NULL); wl_resource_set_destructor(resource, NULL); wl_list_remove(wl_resource_get_link(resource)); wl_list_init(wl_resource_get_link(resource)); } wl_list_remove(&slide->link); wl_list_remove(&slide->surface_map.link); wl_list_remove(&slide->surface_destroy.link); free(slide); } static void kde_slide_handle_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); /* destroy slide if no slide resource */ struct kde_slide *slide = wl_resource_get_user_data(resource); if (slide && wl_list_empty(&slide->resources)) { kde_slide_destroy(slide); } } static void slide_handle_surface_destroy(struct wl_listener *listener, void *data) { struct kde_slide *slide = wl_container_of(listener, slide, surface_destroy); slide->wlr_surface = NULL; kde_slide_destroy(slide); } static void slide_handle_surface_map(struct wl_listener *listener, void *data) { struct kde_slide *slide = wl_container_of(listener, slide, surface_map); wl_list_remove(&slide->surface_map.link); wl_list_init(&slide->surface_map.link); kde_slide_apply_state(slide); kywc_log(KYWC_DEBUG, "Surface %p slide map", slide->wlr_surface); view_apply_slide(slide); } static void handle_create(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id, struct wl_resource *surface_resource) { struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface_resource); struct kde_slide *slide = kde_slide_from_wlr_surface(wlr_surface); if (!slide) { slide = calloc(1, sizeof(*slide)); if (!slide) { wl_client_post_no_memory(client); return; } wl_list_init(&slide->resources); wl_list_insert(&manager->kde_slides, &slide->link); slide->wlr_surface = wlr_surface; slide->surface_map.notify = slide_handle_surface_map; slide->surface_destroy.notify = slide_handle_surface_destroy; wl_signal_add(&wlr_surface->events.destroy, &slide->surface_destroy); if (wlr_surface->mapped) { wl_list_init(&slide->surface_map.link); } else { wl_signal_add_next(&wlr_surface->events.map, &slide->surface_map); } } int version = wl_resource_get_version(manager_resource); struct wl_resource *resource = wl_resource_create(client, &org_kde_kwin_slide_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_list_insert(&slide->resources, wl_resource_get_link(resource)); wl_resource_set_implementation(resource, &kde_slide_impl, slide, kde_slide_handle_resource_destroy); } static void handle_unset(struct wl_client *client, struct wl_resource *manager_resource, struct wl_resource *surface_resource) { struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface_resource); struct kde_slide *slide = kde_slide_from_wlr_surface(wlr_surface); if (slide) { kde_slide_destroy(slide); } struct view *view = view_try_from_wlr_surface(wlr_surface); if (view) { view->use_slide = false; } } static const struct org_kde_kwin_slide_manager_interface kde_slide_manager_impl = { .create = handle_create, .unset = handle_unset, }; static void kde_slide_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &org_kde_kwin_slide_manager_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &kde_slide_manager_impl, manager, NULL); } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->server_destroy.link); free(manager); manager = NULL; } bool kde_slide_manager_create(struct server *server) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->global = wl_global_create(server->display, &org_kde_kwin_slide_manager_interface, KDE_KWIN_SLIDE_MANAGER_VERSION, manager, kde_slide_manager_bind); if (!manager->global) { kywc_log(KYWC_WARN, "Kde slide manager create failed"); free(manager); return false; } wl_list_init(&manager->kde_slides); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &manager->display_destroy); return true; } kylin-wayland-compositor/src/view/action.c0000664000175000017500000004007215160461067017652 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "effect/capture.h" #include "input/cursor.h" #include "input/seat.h" #include "nls.h" #include "output.h" #include "scene/thumbnail.h" #include "util/dbus.h" #include "util/file.h" #include "util/macros.h" #include "util/string.h" #include "view/action.h" #include "view_p.h" static struct window_shortcut { char *keybind; char *desc; enum window_action action; enum key_binding_type type; } window_shortcuts[] = { { "Alt+F10", "window maximized", WINDOW_ACTION_MAXIMIZE, KEY_BINDING_TYPE_WINDOW_ACTION_MAXIMIZE }, { "Alt+F9", "window minimized", WINDOW_ACTION_MINIMIZE, KEY_BINDING_TYPE_WINDOW_ACTION_MINIMIZE }, { "Alt+F4", "window closed", WINDOW_ACTION_CLOSE, KEY_BINDING_TYPE_WINDOW_ACTION_CLOSE }, { "Alt+F3", "window menu", WINDOW_ACTION_MENU, KEY_BINDING_TYPE_WINDOW_ACTION_MENU }, { "win+up:no", "window tile up", WINDOW_ACTION_TILE_TOP, KEY_BINDING_TYPE_WINDOW_ACTION_TILED }, { "win+down:no", "window tile down", WINDOW_ACTION_TILE_BOTTOM, KEY_BINDING_TYPE_WINDOW_ACTION_TILED }, { "win+left:no", "window tile left", WINDOW_ACTION_TILE_LEFT, KEY_BINDING_TYPE_WINDOW_ACTION_TILED }, { "win+right:no", "window tile right", WINDOW_ACTION_TILE_RIGHT, KEY_BINDING_TYPE_WINDOW_ACTION_TILED }, { "win+alt+up:no", "window tiled to top half screen", WINDOW_ACTION_TILE_TOP_HALF_SCREEN, KEY_BINDING_TYPE_WINDOW_ACTION_TILED }, { "win+alt+down:no", "window tiled to bottom half screen", WINDOW_ACTION_TILE_BOTTOM_HALF_SCREEN, KEY_BINDING_TYPE_WINDOW_ACTION_TILED }, { "win+alt+left:no", "window tiled to left half screen", WINDOW_ACTION_TILE_LEFT_HALF_SCREEN, KEY_BINDING_TYPE_WINDOW_ACTION_TILED }, { "win+alt+right:no", "window tiled to right half screen", WINDOW_ACTION_TILE_RIGHT_HALF_SCREEN, KEY_BINDING_TYPE_WINDOW_ACTION_TILED }, { "win+shift+left:no", "window send to prev output", WINDOW_ACTION_SEND_LEFT_OUTPUT, KEY_BINDING_TYPE_WINDOW_ACTION_OUTPUT }, { "win+shift+right:no", "window send to next output", WINDOW_ACTION_SEND_RIGHT_OUTPUT, KEY_BINDING_TYPE_WINDOW_ACTION_OUTPUT }, { "ctrl+shift+left:no", "window send to prev output and maximize", WINDOW_ACTION_SEND_LEFT_OUTPUT_MAXIMIZE, KEY_BINDING_TYPE_WINDOW_ACTION_SEND }, { "ctrl+shift+right:no", "window send to next output and maximize", WINDOW_ACTION_SEND_RIGHT_OUTPUT_MAXIMIZE, KEY_BINDING_TYPE_WINDOW_ACTION_SEND }, }; #define MIRROR_BUFFER_DEBUG 0 struct view_capture { struct thumbnail *thumbnail; struct wl_listener thumbnail_update; struct wl_listener thumbnail_destroy; #if MIRROR_BUFFER_DEBUG struct ky_scene_buffer *buffer; struct wl_event_source *timer; #endif }; static void view_capture_destroy(struct view_capture *capture) { wl_list_remove(&capture->thumbnail_update.link); wl_list_remove(&capture->thumbnail_destroy.link); #if MIRROR_BUFFER_DEBUG if (capture->buffer) { ky_scene_node_destroy(&capture->buffer->node); } wl_event_source_remove(capture->timer); #endif if (capture->thumbnail) { thumbnail_destroy(capture->thumbnail); } free(capture); } static void capture_handle_thumbnail_destroy(struct wl_listener *listener, void *data) { struct view_capture *capture = wl_container_of(listener, capture, thumbnail_destroy); capture->thumbnail = NULL; view_capture_destroy(capture); } #if MIRROR_BUFFER_DEBUG static void capture_handle_thumbnail_update(struct wl_listener *listener, void *data) { struct view_capture *capture = wl_container_of(listener, capture, thumbnail_update); struct thumbnail_update_event *event = data; ky_scene_buffer_set_opacity(capture->buffer, 0.5); ky_scene_buffer_set_dest_size(capture->buffer, event->buffer->width, event->buffer->height); ky_scene_buffer_set_buffer(capture->buffer, event->buffer); thumbnail_mark_wants_update(capture->thumbnail, false); wl_event_source_timer_update(capture->timer, 250); } static int handle_capture(void *data) { struct view_capture *capture = data; thumbnail_mark_wants_update(capture->thumbnail, true); return 0; } #else static void capture_done(const char *path, void *data) { char href[512]; snprintf(href, 512, "%s", path, path); dbus_notify(tr("Screenshot"), tr("Capture saved to"), href, "kylin-screenshot"); free(data); } static void capture_handle_thumbnail_update(struct wl_listener *listener, void *data) { struct view_capture *capture = wl_container_of(listener, capture, thumbnail_update); struct thumbnail_update_event *event = data; const char *dir = file_get_xdg_pictures_dir(); const char *file = kywc_identifier_time_generate("", ".png"); const char *path = string_create("%s/%s", dir, file); free((void *)dir); free((void *)file); capture_write_file(event->buffer, event->buffer->width, event->buffer->height, path, capture_done, (void *)path); view_capture_destroy(capture); } #endif static void window_capture_create(struct view *view, struct seat *seat) { if (!view->base.mapped) { return; } struct view_capture *capture = calloc(1, sizeof(*capture)); if (!capture) { return; } // struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(view->surface); capture->thumbnail = thumbnail_create_from_view(view, THUMBNAIL_ENABLE_SECURITY, 1.0); if (!capture->thumbnail) { free(capture); return; } #if MIRROR_BUFFER_DEBUG struct view_layer *layer = view_manager_get_layer(LAYER_ON_SCREEN_DISPLAY, false); capture->buffer = ky_scene_buffer_create(layer->tree, NULL); ky_scene_node_set_input_bypassed(&capture->buffer->node, true); struct wl_event_loop *loop = wl_display_get_event_loop(seat->wlr_seat->display); capture->timer = wl_event_loop_add_timer(loop, handle_capture, capture); #endif capture->thumbnail_update.notify = capture_handle_thumbnail_update; thumbnail_add_update_listener(capture->thumbnail, &capture->thumbnail_update); capture->thumbnail_destroy.notify = capture_handle_thumbnail_destroy; thumbnail_add_destroy_listener(capture->thumbnail, &capture->thumbnail_destroy); } static struct output *target_output_by_view(struct view *view, enum window_action action) { if (output_state_is_mirror_mode()) { return NULL; } struct output *output = output_from_kywc_output(view->output); enum layout_edge edge = LAYOUT_EDGE_RIGHT; if (action == WINDOW_ACTION_SEND_LEFT_OUTPUT || action == WINDOW_ACTION_SEND_LEFT_OUTPUT_MAXIMIZE) { edge = LAYOUT_EDGE_LEFT; } return output_find_specified_output(output, edge); } /* Move the view directly to the next output in equal proportions */ static void view_send_to_output(struct view *view, enum window_action action) { if (view->base.fullscreen || view_has_modal_property(view)) { return; } struct output *output = target_output_by_view(view, action); if (output) { view_move_to_output(view, NULL, NULL, &output->base); sub_view_move_to_output(view, &output->base); } } /** * Move the view to the next output and maximize it, * but restore the original view size when the output state * of the original view is not the maximized state */ static void view_send_to_output_and_maximize(struct view *view, enum window_action action) { if (view->base.fullscreen || view_has_modal_property(view) || !KYWC_VIEW_IS_MAXIMIZABLE(&view->base)) { return; } struct output *output = target_output_by_view(view, action); if (!output) { return; } if (!view->saved.output || !view->base.maximized) { view->saved.tiled = view->base.maximized ? KYWC_TILE_ALL : view->base.tiled; view->saved.output = view->output; } view_move_to_output(view, NULL, NULL, &output->base); sub_view_move_to_output(view, &output->base); if (output_from_kywc_output(view->saved.output) != output) { kywc_view_set_maximized(&view->base, true, &output->base); return; } if (view->saved.tiled == KYWC_TILE_ALL || !view->saved.tiled) { kywc_view_set_maximized(&view->base, view->saved.tiled ? true : false, &output->base); } else { kywc_view_set_tiled(&view->base, view->saved.tiled, &output->base); } view->saved.output = NULL; } void window_action(struct view *view, struct seat *seat, enum window_action action) { struct kywc_view *kywc_view = &view->base; int lx, ly; switch (action) { case WINDOW_ACTION_NONE: break; case WINDOW_ACTION_MINIMIZE: kywc_view_set_minimized(kywc_view, true); break; case WINDOW_ACTION_MAXIMIZE: kywc_view_toggle_maximized(kywc_view); break; case WINDOW_ACTION_CLOSE: kywc_view_close(kywc_view); break; case WINDOW_ACTION_MOVE: /* seat cursor to middle of the view */ lx = kywc_view->geometry.x + kywc_view->geometry.width / 2; ly = kywc_view->geometry.y + kywc_view->geometry.height / 2; cursor_move(seat->cursor, NULL, lx, ly, false, false); window_begin_move(view, seat); break; case WINDOW_ACTION_RESIZE: lx = kywc_view->geometry.x + kywc_view->geometry.width; ly = kywc_view->geometry.y + kywc_view->geometry.height; cursor_move(seat->cursor, NULL, lx + 8, ly + 8, false, false); window_begin_resize(view, KYWC_EDGE_RIGHT | KYWC_EDGE_BOTTOM, seat); break; case WINDOW_ACTION_KEEP_ABOVE: kywc_view_toggle_kept_above(kywc_view); break; case WINDOW_ACTION_KEEP_BELOW: kywc_view_toggle_kept_below(kywc_view); break; case WINDOW_ACTION_MENU: if (kywc_view->ssd & KYWC_SSD_TITLE && !kywc_view->fullscreen) { lx = kywc_view->geometry.x; ly = kywc_view->geometry.y; view_show_window_menu(view, seat, lx, ly); } break; case WINDOW_ACTION_TILE_TOP: case WINDOW_ACTION_TILE_BOTTOM: case WINDOW_ACTION_TILE_LEFT: case WINDOW_ACTION_TILE_RIGHT: window_begin_tile(view, action, seat); break; case WINDOW_ACTION_TILE_TOP_HALF_SCREEN: case WINDOW_ACTION_TILE_BOTTOM_HALF_SCREEN: case WINDOW_ACTION_TILE_LEFT_HALF_SCREEN: case WINDOW_ACTION_TILE_RIGHT_HALF_SCREEN: window_begin_tile_half_screen(view, action, seat); break; case WINDOW_ACTION_SEND_LEFT_OUTPUT: case WINDOW_ACTION_SEND_RIGHT_OUTPUT: view_send_to_output(view, action); break; case WINDOW_ACTION_SEND_LEFT_OUTPUT_MAXIMIZE: case WINDOW_ACTION_SEND_RIGHT_OUTPUT_MAXIMIZE: view_send_to_output_and_maximize(view, action); break; case WINDOW_ACTION_CAPTURE: window_capture_create(view, seat); break; } } static void view_shortcuts(struct key_binding *binding, void *data) { struct view *view = view_manager_get_activated(); if (!view || view->base.role != KYWC_VIEW_ROLE_NORMAL) { return; } struct window_shortcut *shortcut = data; window_action(view, input_manager_get_default_seat(), shortcut->action); } bool window_actions_create(struct view_manager *view_manager) { for (size_t i = 0; i < ARRAY_SIZE(window_shortcuts); i++) { struct window_shortcut *shortcut = &window_shortcuts[i]; struct key_binding *binding = kywc_key_binding_create(shortcut->keybind, shortcut->desc); if (!binding) { continue; } if (!kywc_key_binding_register(binding, shortcut->type, view_shortcuts, shortcut)) { kywc_key_binding_destroy(binding); continue; } } return true; } enum { TOGGLE_SHOW_DESKTOP = 0, SHOW_DESKTOP, RESTORE_DESKTOP, MINIMIZE_ALL_VIEWS, RESTORE_ALL_VIEWS, TOGGLE_SHOW_ACTIVE_ONLY, }; static struct shortcut { char *keybind; char *desc; uint32_t action; enum key_binding_type type; } shortcuts[] = { { "win+d:no", "toggle show desktop", TOGGLE_SHOW_DESKTOP, KEY_BINDING_TYPE_TOGGLE_SHOW_DESKTOP }, { "win+h", "show desktop", SHOW_DESKTOP, KEY_BINDING_TYPE_SHOW_DESKTOP }, { "win+g", "restore desktop", RESTORE_DESKTOP, KEY_BINDING_TYPE_RESTORE_DESKTOP }, { "win+m", "minimize all views", MINIMIZE_ALL_VIEWS, KEY_BINDING_TYPE_TOGGLE_SHOW_VIEWS }, { "win+shift+m", "restore all views", RESTORE_ALL_VIEWS, KEY_BINDING_TYPE_TOGGLE_SHOW_VIEWS }, { "win+home", "toggle show active window only", TOGGLE_SHOW_ACTIVE_ONLY, KEY_BINDING_TYPE_TOGGLE_SHOW_WINDOWS }, }; static struct gesture { enum gesture_type type; enum gesture_stage stage; uint8_t fingers; uint32_t devices; uint32_t directions; uint32_t edges; uint32_t follow_direction; double follow_threshold; char *desc; enum direction direction; } gestures[] = { { GESTURE_TYPE_SWIPE, GESTURE_STAGE_TRIGGER, 3, GESTURE_DEVICE_TOUCHPAD, GESTURE_DIRECTION_UP, GESTURE_EDGE_NONE, GESTURE_DIRECTION_NONE, 0.0, "switch app or switcher", DIRECTION_UP, }, { GESTURE_TYPE_SWIPE, GESTURE_STAGE_TRIGGER, 3, GESTURE_DEVICE_TOUCHPAD, GESTURE_DIRECTION_DOWN, GESTURE_EDGE_NONE, GESTURE_DIRECTION_NONE, 0.0, "hide switcher or show desktop", DIRECTION_DOWN, }, }; static void shortcuts_action(struct key_binding *binding, void *data) { struct shortcut *shortcut = data; switch (shortcut->action) { case TOGGLE_SHOW_DESKTOP: view_manager_show_desktop(!view_manager_get_show_desktop(), true); break; case SHOW_DESKTOP: case MINIMIZE_ALL_VIEWS: view_manager_show_desktop(true, true); break; case RESTORE_DESKTOP: case RESTORE_ALL_VIEWS: view_manager_show_desktop(false, true); break; case TOGGLE_SHOW_ACTIVE_ONLY: view_manager_show_activated_only(!view_manager_get_show_activte_only(), true); break; } } static void view_manager_show_switcher(bool show) { if (!dbus_call_method("org.kylin.switch", "/MultitaskView", "org.kylin.switch.MultitaskView", show ? "show" : "hide", NULL, NULL)) { kywc_log(KYWC_ERROR, "DBUS call Multitaskview failed"); } } static void gestures_action(struct gesture_binding *binding, void *data, double dx, double dy) { struct gesture *gesture = data; switch (gesture->direction) { case DIRECTION_UP: if (!view_manager_get_show_desktop()) { view_manager_show_switcher(true); } else { view_manager_show_desktop(false, true); } break; case DIRECTION_DOWN: if (view_manager_get_show_switcher()) { view_manager_show_switcher(false); } else { view_manager_show_desktop(true, true); } default: break; } } bool view_manager_actions_create(struct view_manager *view_manager) { for (size_t i = 0; i < ARRAY_SIZE(shortcuts); i++) { struct shortcut *shortcut = &shortcuts[i]; struct key_binding *binding = kywc_key_binding_create(shortcut->keybind, shortcut->desc); if (!binding) { continue; } if (!kywc_key_binding_register(binding, shortcut->type, shortcuts_action, shortcut)) { kywc_key_binding_destroy(binding); continue; } } for (size_t i = 0; i < ARRAY_SIZE(gestures); i++) { struct gesture *gesture = &gestures[i]; struct gesture_binding *binding = kywc_gesture_binding_create( gesture->type, gesture->stage, gesture->devices, gesture->directions, gesture->edges, gesture->fingers, gesture->follow_direction, gesture->follow_threshold, gesture->desc); if (!binding) { continue; } if (!kywc_gesture_binding_register(binding, gestures_action, gesture)) { kywc_gesture_binding_destroy(binding); continue; } } return true; } kylin-wayland-compositor/src/view/ukui_shell.c0000664000175000017500000012521415160461067020543 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include "input/cursor.h" #include "input/keyboard.h" #include "input/seat.h" #include "output.h" #include "scene/surface.h" #include "ukui-shell-v1-protocol.h" #include "util/macros.h" #include "util/wayland.h" #include "view_p.h" #define UKUI_SHELL_V1_VERSION 4 struct ukui_shell { struct wl_global *global; struct wl_list ukui_surfaces; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct ukui_surface { struct wl_list link; struct wl_resource *resource; struct wlr_surface *wlr_surface; struct wl_listener surface_map; struct wl_listener surface_commit; struct wl_listener surface_destroy; /* for popup position */ struct ky_scene_buffer *buffer; bool minimizable, maximizable, fullscreenable; bool closeable, movable, resizable; bool focusable, activatable; /* get in map listener */ struct view *view; struct wl_listener view_map; struct wl_listener view_unmap; struct wl_listener view_position; struct wl_listener view_update_capabilities; struct wl_listener view_destroy; struct wl_listener view_activate; struct wl_listener view_minimize; struct wl_listener view_maximize; struct wl_listener view_fullscreen; struct wl_listener view_above; struct wl_listener view_below; struct wl_listener view_tile; struct seat_keyboard_grab keyboard_grab; struct wl_list ukui_keyboard_grabs; char *icon_name; /* "surface_state" is the status return to the application */ uint32_t surface_state; /* "state" is the status sent by the application */ uint32_t state, flags; struct kywc_box startup; struct kywc_box restore; int32_t x, y, offset_x, offset_y; enum ukui_surface_v1_role role; int32_t skip_taskbar, skip_switcher; bool panel_auto_hide; }; struct ukui_keyboard_grab { struct seat_keyboard_grab keyboard_grab; struct wl_list link; }; struct ukui_decoration { struct wl_resource *resource; struct wlr_surface *wlr_surface; struct wl_listener surface_map; struct wlr_addon addon; uint32_t flags; int32_t no_titlebar; }; enum surface_state_flag { STATE_FLAG_MINIMIZABLE = 0, STATE_FLAG_MAXIMIZABLE, STATE_FLAG_CLOSEABLE, STATE_FLAG_FULLSCREENABLE, STATE_FLAG_MOVABLE, STATE_FLAG_RESIZABLE, STATE_FLAG_FOCUSABLE, STATE_FLAG_ACTIVATABLE, STATE_FLAG_KEEPABOVE, STATE_FLAG_KEEPBELOW, STATE_FLAG_LAST, }; static void handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void position_apply_immediately(struct ukui_surface *surface) { view_do_move(surface->view, surface->x, surface->y); if (surface->view->base.mapped) { cursor_rebase_all(false); } surface->x = surface->y = INT32_MAX; } static void ukui_surface_apply_position(struct ukui_surface *surface, bool need_commit) { if (surface->view) { if (!need_commit) { position_apply_immediately(surface); } else if (!surface->view->base.mapped) { view_do_move(surface->view, surface->x, surface->y); } } else if (surface->buffer) { struct wlr_xdg_popup *xdg_popup = wlr_xdg_popup_try_from_wlr_surface(surface->wlr_surface); if (xdg_popup) { int lx, ly; ky_scene_node_coords(&surface->buffer->node, &lx, &ly); xdg_popup->pending.geometry.x = xdg_popup->current.geometry.x + surface->x - lx; xdg_popup->pending.geometry.y = xdg_popup->current.geometry.y + surface->y - ly; } } } static void handle_set_position(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } surface->x = x; surface->y = y; ukui_surface_apply_position(surface, false); } static enum kywc_view_role role_from_ukui_surface(struct ukui_surface *surface) { switch (surface->role) { default: return KYWC_VIEW_ROLE_NORMAL; case UKUI_SURFACE_V1_ROLE_DESKTOP: return KYWC_VIEW_ROLE_DESKTOP; case UKUI_SURFACE_V1_ROLE_PANEL: return KYWC_VIEW_ROLE_PANEL; case UKUI_SURFACE_V1_ROLE_ONSCREENDISPLAY: return KYWC_VIEW_ROLE_ONSCREENDISPLAY; case UKUI_SURFACE_V1_ROLE_NOTIFICATION: return KYWC_VIEW_ROLE_NOTIFICATION; case UKUI_SURFACE_V1_ROLE_TOOLTIP: return KYWC_VIEW_ROLE_TOOLTIP; case UKUI_SURFACE_V1_ROLE_CRITICALNOTIFICATION: return KYWC_VIEW_ROLE_CRITICALNOTIFICATION; case UKUI_SURFACE_V1_ROLE_APPLETPOPUP: return KYWC_VIEW_ROLE_APPLETPOPUP; case UKUI_SURFACE_V1_ROLE_SCREENLOCK: return KYWC_VIEW_ROLE_SCREENLOCK; case UKUI_SURFACE_V1_ROLE_WATERMARK: return KYWC_VIEW_ROLE_WATERMARK; case UKUI_SURFACE_V1_ROLE_SYSTEMWINDOW: return KYWC_VIEW_ROLE_SYSTEMWINDOW; case UKUI_SURFACE_V1_ROLE_INPUTPANEL: return KYWC_VIEW_ROLE_INPUTPANEL; case UKUI_SURFACE_V1_ROLE_LOGOUT: return KYWC_VIEW_ROLE_LOGOUT; case UKUI_SURFACE_V1_ROLE_SCREENLOCKNOTIFICATION: return KYWC_VIEW_ROLE_SCREENLOCKNOTIFICATION; case UKUI_SURFACE_V1_ROLE_SWITCHER: return KYWC_VIEW_ROLE_SWITCHER; case UKUI_SURFACE_V1_ROLE_AUTHENTICATION: return KYWC_VIEW_ROLE_AUTHENTICATION; } } static void handle_set_role(struct wl_client *client, struct wl_resource *resource, uint32_t role) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } if (surface->wlr_surface->mapped) { wl_resource_post_error(resource, UKUI_SURFACE_V1_ERROR_MAPPED, "wl_surface object already mapped"); return; } surface->role = role; } static void handle_set_skip_taskbar(struct wl_client *client, struct wl_resource *resource, uint32_t skip) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } if (surface->wlr_surface->mapped) { wl_resource_post_error(resource, UKUI_SURFACE_V1_ERROR_MAPPED, "wl_surface object already mapped"); return; } surface->skip_taskbar = skip; } static void handle_set_skip_switcher(struct wl_client *client, struct wl_resource *resource, uint32_t skip) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } if (surface->wlr_surface->mapped) { wl_resource_post_error(resource, UKUI_SURFACE_V1_ERROR_MAPPED, "wl_surface object already mapped"); return; } surface->skip_switcher = skip; } #define set_state(states, prop, state) \ if (prop) { \ states |= UKUI_SURFACE_V1_SURFACE_STATE_##state; \ } else { \ states &= ~UKUI_SURFACE_V1_SURFACE_STATE_##state; \ } static void surface_send_surface_state(struct ukui_surface *surface, uint32_t mask) { if (wl_resource_get_version(surface->resource) < UKUI_SURFACE_V1_SURFACE_STATE_SINCE_VERSION) { return; } uint32_t states = surface->surface_state; if (mask & UKUI_SURFACE_V1_SURFACE_STATE_MINIMIZED) { set_state(surface->surface_state, surface->view->base.minimized, MINIMIZED); } if (mask & UKUI_SURFACE_V1_SURFACE_STATE_MAXIMIZED) { set_state(surface->surface_state, surface->view->base.maximized, MAXIMIZED); } if (mask & UKUI_SURFACE_V1_SURFACE_STATE_FULLSCREEN) { set_state(surface->surface_state, surface->view->base.fullscreen, FULLSCREEN); } if (mask & UKUI_SURFACE_V1_SURFACE_STATE_ACTIVE) { set_state(surface->surface_state, surface->view->base.activated, ACTIVE); } if (mask & UKUI_SURFACE_V1_SURFACE_STATE_ABOVE) { set_state(surface->surface_state, surface->view->base.kept_above, ABOVE); } if (mask & UKUI_SURFACE_V1_SURFACE_STATE_BELOW) { set_state(surface->surface_state, surface->view->base.kept_below, BELOW); } if (mask & UKUI_SURFACE_V1_SURFACE_STATE_TILED) { set_state(surface->surface_state, !!surface->view->base.tiled, TILED); } if (states == surface->surface_state) { return; } ukui_surface_v1_send_surface_state(surface->resource, surface->surface_state); } static void surface_handle_view_activate(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, view_activate); surface_send_surface_state(surface, UKUI_SURFACE_V1_SURFACE_STATE_ACTIVE); } static void surface_handle_view_minimize(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, view_minimize); surface_send_surface_state(surface, UKUI_SURFACE_V1_SURFACE_STATE_MINIMIZED); } static void surface_handle_view_maximize(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, view_maximize); surface_send_surface_state(surface, UKUI_SURFACE_V1_SURFACE_STATE_MAXIMIZED); } static void surface_handle_view_fullscreen(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, view_fullscreen); surface_send_surface_state(surface, UKUI_SURFACE_V1_SURFACE_STATE_FULLSCREEN); } static void surface_handle_view_above(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, view_above); surface_send_surface_state(surface, UKUI_SURFACE_V1_SURFACE_STATE_ABOVE); } static void surface_handle_view_below(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, view_below); surface_send_surface_state(surface, UKUI_SURFACE_V1_SURFACE_STATE_BELOW); } static void surface_handle_view_tile(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, view_tile); surface_send_surface_state(surface, UKUI_SURFACE_V1_SURFACE_STATE_TILED); } static void ukui_surface_set_state(struct ukui_surface *surface, enum surface_state_flag flag, bool state) { uint32_t mask = 0; switch (flag) { case STATE_FLAG_MINIMIZABLE: surface->minimizable = state; mask |= KYWC_VIEW_MINIMIZABLE | KYWC_VIEW_MINIMIZE_BUTTON; break; case STATE_FLAG_MAXIMIZABLE: surface->maximizable = state; mask |= KYWC_VIEW_MAXIMIZABLE | KYWC_VIEW_MAXIMIZE_BUTTON; break; case STATE_FLAG_CLOSEABLE: surface->closeable = state; mask |= KYWC_VIEW_CLOSEABLE; break; case STATE_FLAG_FULLSCREENABLE: surface->fullscreenable = state; mask |= KYWC_VIEW_FULLSCREENABLE; break; case STATE_FLAG_MOVABLE: surface->movable = state; mask |= KYWC_VIEW_MOVABLE; break; case STATE_FLAG_RESIZABLE: surface->resizable = state; mask |= KYWC_VIEW_RESIZABLE; break; case STATE_FLAG_FOCUSABLE: surface->focusable = state; mask |= KYWC_VIEW_FOCUSABLE; break; case STATE_FLAG_ACTIVATABLE: surface->activatable = state; mask |= KYWC_VIEW_ACTIVATABLE; break; case STATE_FLAG_KEEPABOVE: if (surface->view->current_proxy) { kywc_view_set_kept_above(&surface->view->base, state); } break; case STATE_FLAG_KEEPBELOW: if (surface->view->current_proxy) { kywc_view_set_kept_below(&surface->view->base, state); } break; case STATE_FLAG_LAST: break; } if (mask != 0) { view_update_capabilities(surface->view, mask); } } static void handle_set_state(struct wl_client *client, struct wl_resource *resource, uint32_t flags, uint32_t state) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } for (int i = 0; i < STATE_FLAG_LAST; i++) { if (((flags >> i) & 0x1) == 0) { continue; } if (surface->view) { ukui_surface_set_state(surface, i, (state >> i) & 0x1); } else { surface->flags |= 1 << i; if ((state >> i) & 0x1) { surface->state |= 1 << i; } else { surface->state &= ~(1 << i); } } } } static void panel_surface_change_layer(struct ukui_surface *surface) { if (surface->view->base.role != KYWC_VIEW_ROLE_PANEL) { return; } // when ukui panel hide, will show a transparent window, it need above other window enum layer layer = surface->panel_auto_hide ? LAYER_ABOVE : LAYER_DOCK; struct view_layer *view_layer = view_manager_get_layer(layer, false); ky_scene_node_reparent(&surface->view->tree->node, view_layer->tree); } static void ukui_surface_set_usable_area(struct ukui_surface *surface, bool enabled); static void handle_set_panel_auto_hide(struct wl_client *client, struct wl_resource *resource, uint32_t hide) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface || surface->panel_auto_hide == hide) { return; } surface->panel_auto_hide = hide; if (surface->view) { ukui_surface_set_usable_area(surface, true); panel_surface_change_layer(surface); } } static void handle_open_under_cursor(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } if (surface->wlr_surface->mapped) { wl_resource_post_error(resource, UKUI_SURFACE_V1_ERROR_MAPPED, "wl_surface object already mapped"); return; } surface->offset_x = x; surface->offset_y = y; } static struct ukui_keyboard_grab *get_ukui_keyboard_grab_from_seat(struct ukui_surface *surface, struct seat *seat) { struct ukui_keyboard_grab *grab; wl_list_for_each(grab, &surface->ukui_keyboard_grabs, link) { if (grab->keyboard_grab.seat == seat) { return grab; } } return NULL; } static bool keyboard_grab_key(struct seat_keyboard_grab *keyboard_grab, struct keyboard *keyboard, uint32_t time, uint32_t key, bool pressed, uint32_t modifiers) { struct ukui_surface *surface = keyboard_grab->data; struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard; /* don't use notify functions to skip the wlr_seat grab */ wlr_seat_set_keyboard(keyboard_grab->seat->wlr_seat, keyboard->wlr_keyboard); wlr_seat_keyboard_enter(keyboard_grab->seat->wlr_seat, surface->wlr_surface, wlr_keyboard->keycodes, wlr_keyboard->num_keycodes, &wlr_keyboard->modifiers); wlr_seat_keyboard_send_key(keyboard_grab->seat->wlr_seat, time, key, pressed); return true; } static void keyboard_grab_cancel(struct seat_keyboard_grab *keyboard_grab) { struct ukui_surface *surface = keyboard_grab->data; struct ukui_keyboard_grab *grab = get_ukui_keyboard_grab_from_seat(surface, keyboard_grab->seat); wl_list_remove(&grab->link); free(grab); } static const struct seat_keyboard_grab_interface keyboard_grab_impl = { .key = keyboard_grab_key, .cancel = keyboard_grab_cancel, }; static struct ukui_keyboard_grab *ukui_surface_create_keyboard_grab(struct ukui_surface *surface, struct seat *seat) { struct ukui_keyboard_grab *grab = calloc(1, sizeof(*grab)); if (!grab) { return NULL; } grab->keyboard_grab.data = surface; grab->keyboard_grab.interface = &keyboard_grab_impl; wl_list_insert(&surface->ukui_keyboard_grabs, &grab->link); wlr_seat_keyboard_end_grab(seat->wlr_seat); wlr_seat_keyboard_clear_focus(seat->wlr_seat); seat_start_keyboard_grab(seat, &grab->keyboard_grab); return grab; } static void ukui_surface_end_keyboard_grab(struct ukui_keyboard_grab *ukui_keyboard_grab) { struct ukui_surface *surface = ukui_keyboard_grab->keyboard_grab.data; if (surface->view && surface->view->base.role == KYWC_VIEW_ROLE_SWITCHER) { view_manager_set_switcher_shown(false); } seat_end_keyboard_grab(ukui_keyboard_grab->keyboard_grab.seat, &ukui_keyboard_grab->keyboard_grab); wl_list_remove(&ukui_keyboard_grab->link); free(ukui_keyboard_grab); } struct ukui_surface_grab_data { struct ukui_surface *surface; struct seat *seat; // NULL means all }; static bool ukui_surface_start_keyboard_grab(struct seat *seat, int index, void *data) { struct ukui_surface_grab_data *grab_data = data; /* skip seat that is not matched */ if (grab_data->seat && grab_data->seat != seat) { return false; } struct ukui_surface *surface = grab_data->surface; if (!get_ukui_keyboard_grab_from_seat(surface, seat)) { ukui_surface_create_keyboard_grab(surface, seat); } /* return true if seat is specified */ return grab_data->seat; } static void handle_grab_keyboard(struct wl_client *client, struct wl_resource *resource, struct wl_resource *wl_seat) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } struct seat *seat = NULL; if (wl_seat) { seat = seat_from_resource(wl_seat); if (!seat) { return; } } if (surface->view && surface->view->base.role == KYWC_VIEW_ROLE_SWITCHER) { view_manager_set_switcher_shown(true); } struct ukui_surface_grab_data grab_data = { surface, seat }; input_manager_for_each_seat(ukui_surface_start_keyboard_grab, &grab_data); } static void handle_set_icon(struct wl_client *client, struct wl_resource *resource, const char *icon_name) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } if (surface->wlr_surface->mapped) { wl_resource_post_error(resource, UKUI_SURFACE_V1_ERROR_MAPPED, "wl_surface object already mapped"); return; } free(surface->icon_name); surface->icon_name = icon_name ? strdup(icon_name) : NULL; } static void handle_activate(struct wl_client *client, struct wl_resource *resource) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } if (surface->view) { /* activation request */ kywc_view_activate(&surface->view->base); view_set_focus(surface->view, input_manager_get_default_seat()); } } static void handle_set_startup_geometry(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, uint32_t width, uint32_t height) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } if (surface->wlr_surface->mapped) { wl_resource_post_error(resource, UKUI_SURFACE_V1_ERROR_MAPPED, "wl_surface object already mapped"); return; } surface->startup = (struct kywc_box){ x, y, width, height }; } static void handle_set_restore_geometry(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, uint32_t width, uint32_t height) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } if (surface->view) { surface->view->restore_geometry = (struct kywc_box){ x, y, width, height }; return; } surface->restore = (struct kywc_box){ x, y, width, height }; } static void handle_show_tile_flyout(struct wl_client *client, struct wl_resource *resource, struct wl_resource *wl_seat, int32_t x, int32_t y, uint32_t width, uint32_t height) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface || !surface->wlr_surface->mapped) { return; } struct view *view = view_try_from_wlr_surface(surface->wlr_surface); if (!view) { kywc_log(KYWC_DEBUG, "Surface is not a view"); return; } struct seat *seat = wl_seat ? seat_from_resource(wl_seat) : input_manager_get_default_seat(); if (!seat) { return; } struct kywc_box box = { x, y, width, height }; if (!view_show_tile_flyout(view, seat, &box)) { kywc_log(KYWC_ERROR, "Surface show tile flyout failed"); } } static void handle_set_pending_position(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (!surface->wlr_surface) { return; } surface->x = x; surface->y = y; ukui_surface_apply_position(surface, true); } static const struct ukui_surface_v1_interface ukui_surface_impl = { .destroy = handle_destroy, .set_position = handle_set_position, .set_skip_taskbar = handle_set_skip_taskbar, .set_skip_switcher = handle_set_skip_switcher, .set_role = handle_set_role, .set_state = handle_set_state, .set_panel_auto_hide = handle_set_panel_auto_hide, .open_under_cursor = handle_open_under_cursor, .grab_keyboard = handle_grab_keyboard, .set_icon = handle_set_icon, .activate = handle_activate, .set_startup_geometry = handle_set_startup_geometry, .show_tile_flyout = handle_show_tile_flyout, .set_restore_geometry = handle_set_restore_geometry, .set_pending_position = handle_set_pending_position, }; static void ukui_surface_set_usable_area(struct ukui_surface *surface, bool enabled) { bool has_area = enabled && surface->view->base.mapped && surface->view->base.role == KYWC_VIEW_ROLE_PANEL && !surface->panel_auto_hide; view_set_exclusive(surface->view, has_area); } static void surface_handle_view_update_capabilities(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, view_update_capabilities); struct view_update_capabilities_event *event = data; if (event->mask & KYWC_VIEW_MINIMIZABLE) { if (!surface->minimizable) { event->state &= ~KYWC_VIEW_MINIMIZABLE; } } if (event->mask & KYWC_VIEW_MAXIMIZABLE) { if (!surface->maximizable) { event->state &= ~KYWC_VIEW_MAXIMIZABLE; } } if (event->mask & KYWC_VIEW_CLOSEABLE) { if (!surface->closeable) { event->state &= ~KYWC_VIEW_CLOSEABLE; } } if (event->mask & KYWC_VIEW_FULLSCREENABLE) { if (!surface->fullscreenable) { event->state &= ~KYWC_VIEW_FULLSCREENABLE; } } if (event->mask & KYWC_VIEW_MOVABLE) { if (!surface->movable) { event->state &= ~KYWC_VIEW_MOVABLE; } } if (event->mask & KYWC_VIEW_RESIZABLE) { if (!surface->resizable) { event->state &= ~KYWC_VIEW_RESIZABLE; } } if (event->mask & KYWC_VIEW_FOCUSABLE) { if (!surface->focusable) { event->state &= ~KYWC_VIEW_FOCUSABLE; } } if (event->mask & KYWC_VIEW_ACTIVATABLE) { if (!surface->activatable) { event->state &= ~KYWC_VIEW_ACTIVATABLE; } } if (event->mask & KYWC_VIEW_MINIMIZE_BUTTON) { if (!surface->minimizable) { event->state &= ~KYWC_VIEW_MINIMIZE_BUTTON; } } if (event->mask & KYWC_VIEW_MAXIMIZE_BUTTON) { if (!surface->maximizable) { event->state &= ~KYWC_VIEW_MAXIMIZE_BUTTON; } } } static void surface_handle_view_position(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, view_position); ukui_surface_v1_send_position(surface->resource, surface->view->base.geometry.x, surface->view->base.geometry.y); } static void surface_handle_view_destroy(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, view_destroy); wl_list_remove(&surface->view_destroy.link); wl_list_remove(&surface->view_map.link); wl_list_remove(&surface->view_unmap.link); wl_list_remove(&surface->view_update_capabilities.link); wl_list_remove(&surface->view_activate.link); wl_list_remove(&surface->view_minimize.link); wl_list_remove(&surface->view_maximize.link); wl_list_remove(&surface->view_fullscreen.link); wl_list_remove(&surface->view_above.link); wl_list_remove(&surface->view_below.link); wl_list_remove(&surface->view_tile.link); surface->view = NULL; } static void surface_handle_view_map(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, view_map); ukui_surface_set_usable_area(surface, true); surface->view_position.notify = surface_handle_view_position; wl_signal_add(&surface->view->base.events.position, &surface->view_position); ukui_surface_v1_send_position(surface->resource, surface->view->base.geometry.x, surface->view->base.geometry.y); } static void surface_handle_view_unmap(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, view_unmap); wl_list_remove(&surface->view_position.link); ukui_surface_set_usable_area(surface, false); struct ukui_keyboard_grab *grab, *tmp; wl_list_for_each_safe(grab, tmp, &surface->ukui_keyboard_grabs, link) { ukui_surface_end_keyboard_grab(grab); } } static void surface_handle_map(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, surface_map); /* useless once we connect surface and view */ wl_list_remove(&surface->surface_map.link); wl_list_init(&surface->surface_map.link); /* get view from surface */ surface->view = view_try_from_wlr_surface(surface->wlr_surface); if (!surface->view) { kywc_log(KYWC_DEBUG, "Surface is not a toplevel"); surface->buffer = ky_scene_buffer_try_from_surface(surface->wlr_surface); return; } surface->view_update_capabilities.notify = surface_handle_view_update_capabilities; view_add_update_capabilities_listener(surface->view, &surface->view_update_capabilities); struct kywc_view *kywc_view = &surface->view->base; surface->view_activate.notify = surface_handle_view_activate; wl_signal_add(&kywc_view->events.activate, &surface->view_activate); surface->view_minimize.notify = surface_handle_view_minimize; wl_signal_add(&kywc_view->events.minimize, &surface->view_minimize); surface->view_maximize.notify = surface_handle_view_maximize; wl_signal_add(&kywc_view->events.maximize, &surface->view_maximize); surface->view_fullscreen.notify = surface_handle_view_fullscreen; wl_signal_add(&kywc_view->events.fullscreen, &surface->view_fullscreen); surface->view_above.notify = surface_handle_view_above; wl_signal_add(&kywc_view->events.above, &surface->view_above); surface->view_below.notify = surface_handle_view_below; wl_signal_add(&kywc_view->events.below, &surface->view_below); surface->view_tile.notify = surface_handle_view_tile; wl_signal_add(&kywc_view->events.tile, &surface->view_tile); if (surface->skip_taskbar != -1) { kywc_view_set_skip_taskbar(&surface->view->base, surface->skip_taskbar); } if (surface->skip_switcher != -1) { kywc_view_set_skip_switcher(&surface->view->base, surface->skip_switcher); } if (kywc_box_not_empty(&surface->startup)) { surface->view->startup_geometry = surface->startup; surface->startup = (struct kywc_box){ 0 }; } if (kywc_box_not_empty(&surface->restore)) { surface->view->restore_geometry = surface->restore; surface->restore = (struct kywc_box){ 0 }; } if (surface->role != UINT32_MAX) { view_set_role(surface->view, role_from_ukui_surface(surface)); } panel_surface_change_layer(surface); if (surface->flags != 0) { for (int i = 0; i < STATE_FLAG_LAST; i++) { if ((surface->flags >> i) & 0x1) { ukui_surface_set_state(surface, i, (surface->state >> i) & 0x1); } } surface->flags = 0; } if (surface->icon_name) { view_set_icon_name(surface->view, surface->icon_name); } if (surface->offset_x != INT32_MAX || surface->offset_y != INT32_MAX) { struct seat *seat = surface->view->base.focused_seat; int lx = seat->cursor->lx + surface->offset_x; int ly = seat->cursor->ly + surface->offset_y; struct output *output = input_current_output(seat); if (output) { struct kywc_box *geo = &output->geometry; int width = surface->wlr_surface->current.width; int height = surface->wlr_surface->current.height; int min_x = geo->x, max_x = geo->x + geo->width - width; int min_y = geo->y, max_y = geo->y + geo->height - height; lx = CLAMP(lx, min_x, max_x); ly = CLAMP(ly, min_y, max_y); } surface->view->base.has_initial_position = true; view_do_move(surface->view, lx, ly); surface->offset_x = surface->offset_y = INT32_MAX; } /* apply set_position called before map */ if (surface->x != INT32_MAX || surface->y != INT32_MAX) { surface->view->base.has_initial_position = true; ukui_surface_apply_position(surface, false); surface->x = surface->y = INT32_MAX; } surface->view_map.notify = surface_handle_view_map; wl_signal_add(&surface->view->base.events.map, &surface->view_map); surface->view_unmap.notify = surface_handle_view_unmap; wl_signal_add(&surface->view->base.events.unmap, &surface->view_unmap); surface->view_destroy.notify = surface_handle_view_destroy; wl_signal_add(&surface->view->base.events.destroy, &surface->view_destroy); } static void surface_handle_commit(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, surface_commit); if (!surface->view || !surface->view->base.mapped) { return; } if (surface->x != INT32_MAX || surface->y != INT32_MAX) { position_apply_immediately(surface); } } static void surface_handle_destroy(struct wl_listener *listener, void *data) { struct ukui_surface *surface = wl_container_of(listener, surface, surface_destroy); struct ukui_keyboard_grab *grab, *tmp; wl_list_for_each_safe(grab, tmp, &surface->ukui_keyboard_grabs, link) { ukui_surface_end_keyboard_grab(grab); } wl_list_remove(&surface->surface_map.link); wl_list_remove(&surface->surface_commit.link); wl_list_remove(&surface->surface_destroy.link); surface->wlr_surface = NULL; } static void ukui_surface_handle_resource_destroy(struct wl_resource *resource) { struct ukui_surface *surface = wl_resource_get_user_data(resource); if (surface->wlr_surface) { surface_handle_destroy(&surface->surface_destroy, NULL); } if (surface->view) { if (surface->view->base.mapped) { surface_handle_view_unmap(&surface->view_unmap, NULL); } surface_handle_view_destroy(&surface->view_destroy, NULL); } wl_list_remove(&surface->link); free(surface->icon_name); free(surface); } static struct ukui_surface *ukui_surface_from_wlr_surface(struct ukui_shell *shell, struct wlr_surface *wlr_surface) { struct ukui_surface *ukui_surface; wl_list_for_each(ukui_surface, &shell->ukui_surfaces, link) { if (ukui_surface->wlr_surface == wlr_surface) { return ukui_surface; } } return NULL; } static void handle_create_surface(struct wl_client *client, struct wl_resource *shell_resource, uint32_t id, struct wl_resource *surface_resource) { struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface_resource); struct ukui_shell *shell = wl_resource_get_user_data(shell_resource); if (wlr_surface->mapped) { wl_resource_post_error(shell_resource, UKUI_SHELL_V1_ERROR_MAPPED, "surface has already mapped"); return; } struct ukui_surface *surface = ukui_surface_from_wlr_surface(shell, wlr_surface); if (surface) { wl_resource_post_error(shell_resource, UKUI_SHELL_V1_ERROR_SURFACE_EXISTS, "surface has wl_surface object associated"); return; } /* create new ukui surface */ surface = calloc(1, sizeof(*surface)); if (!surface) { wl_client_post_no_memory(client); return; } int version = wl_resource_get_version(shell_resource); struct wl_resource *resource = wl_resource_create(client, &ukui_surface_v1_interface, version, id); if (!resource) { free(surface); wl_client_post_no_memory(client); return; } surface->resource = resource; wl_resource_set_implementation(resource, &ukui_surface_impl, surface, ukui_surface_handle_resource_destroy); surface->x = surface->y = INT32_MAX; surface->offset_x = surface->offset_y = INT32_MAX; surface->role = UINT32_MAX; surface->skip_taskbar = surface->skip_switcher = -1; surface->minimizable = surface->maximizable = surface->fullscreenable = surface->closeable = surface->movable = surface->resizable = surface->focusable = surface->activatable = true; wl_list_init(&surface->ukui_keyboard_grabs); surface->wlr_surface = wlr_surface; surface->surface_map.notify = surface_handle_map; wl_signal_add_next(&wlr_surface->events.map, &surface->surface_map); surface->surface_commit.notify = surface_handle_commit; wl_signal_add_next(&wlr_surface->events.commit, &surface->surface_commit); surface->surface_destroy.notify = surface_handle_destroy; wl_signal_add(&wlr_surface->events.destroy, &surface->surface_destroy); wl_list_insert(&shell->ukui_surfaces, &surface->link); } static void decoration_handle_surface_map(struct wl_listener *listener, void *data) { struct ukui_decoration *decoration = wl_container_of(listener, decoration, surface_map); struct view *view = view_try_from_wlr_surface(decoration->wlr_surface); if (!view) { return; } enum kywc_ssd ssd = view->base.ssd; if (ssd == KYWC_SSD_NONE || decoration->no_titlebar == -1) { return; } if (decoration->no_titlebar) { ssd &= ~KYWC_SSD_TITLE; } else { ssd |= KYWC_SSD_TITLE; } view_set_decoration(view, ssd); } static void decoration_destroy(struct ukui_decoration *decoration) { if (!decoration) { return; } wlr_addon_finish(&decoration->addon); wl_list_remove(&decoration->surface_map.link); wl_resource_set_user_data(decoration->resource, NULL); free(decoration); } static void ukui_decoration_handle_resource_destroy(struct wl_resource *resource) { struct ukui_decoration *decoration = wl_resource_get_user_data(resource); decoration_destroy(decoration); } static void decoration_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void handle_set_no_titlebar(struct wl_client *client, struct wl_resource *resource, uint32_t no_titlebar) { struct ukui_decoration *decoration = wl_resource_get_user_data(resource); if (!decoration) { return; } if (decoration->wlr_surface->mapped) { wl_resource_post_error(resource, UKUI_DECORATION_V1_ERROR_MAPPED, "wl_surface object already mapped"); return; } decoration->no_titlebar = no_titlebar; } static void handle_set_decoration_components(struct wl_client *client, struct wl_resource *resource, uint32_t flags) { struct ukui_decoration *decoration = wl_resource_get_user_data(resource); if (!decoration) { return; } if (decoration->wlr_surface->mapped) { wl_resource_post_error(resource, UKUI_DECORATION_V1_ERROR_MAPPED, "wl_surface object already mapped"); return; } decoration->flags = flags; } static const struct ukui_decoration_v1_interface ukui_decoration_impl = { .set_no_titlebar = handle_set_no_titlebar, .set_decoration_components = handle_set_decoration_components, .destroy = decoration_handle_destroy, }; static void surface_decoration_addon_destroy(struct wlr_addon *addon) { struct ukui_decoration *decoration = wl_container_of(addon, decoration, addon); decoration_destroy(decoration); } static const struct wlr_addon_interface surface_decoration_impl = { .name = "surface_decoration", .destroy = surface_decoration_addon_destroy, }; static struct ukui_decoration *ukui_decoration_from_wlr_surface(struct wlr_surface *surface) { struct ukui_decoration *decoration = NULL; struct wlr_addon *decoration_addon = wlr_addon_find(&surface->addons, surface, &surface_decoration_impl); if (decoration_addon) { decoration = wl_container_of(decoration_addon, decoration, addon); } return decoration; } static void handle_create_decoration(struct wl_client *client, struct wl_resource *shell_resource, uint32_t id, struct wl_resource *surface_resource) { struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface_resource); if (wlr_surface->mapped) { wl_resource_post_error(shell_resource, UKUI_SHELL_V1_ERROR_MAPPED, "surface has already mapped"); return; } struct ukui_decoration *decoration = ukui_decoration_from_wlr_surface(wlr_surface); if (decoration) { wl_resource_post_error(shell_resource, UKUI_SHELL_V1_ERROR_DECORATION_EXISTS, "surface has wl_surface object associated"); return; } /* create new ukui surface */ decoration = calloc(1, sizeof(*decoration)); if (!decoration) { wl_client_post_no_memory(client); return; } int version = wl_resource_get_version(shell_resource); struct wl_resource *resource = wl_resource_create(client, &ukui_decoration_v1_interface, version, id); if (!resource) { free(decoration); wl_client_post_no_memory(client); return; } decoration->resource = resource; wl_resource_set_implementation(resource, &ukui_decoration_impl, decoration, ukui_decoration_handle_resource_destroy); decoration->no_titlebar = -1; decoration->wlr_surface = wlr_surface; decoration->surface_map.notify = decoration_handle_surface_map; wl_signal_add(&wlr_surface->events.map, &decoration->surface_map); /* use addon destroy for surface destroy */ wlr_addon_init(&decoration->addon, &wlr_surface->addons, wlr_surface, &surface_decoration_impl); } static bool send_seat_output(struct seat *seat, int index, void *data) { struct wl_resource *resource = data; struct kywc_output *kywc_output = kywc_output_at_point(seat->cursor->lx, seat->cursor->ly); ukui_shell_v1_send_current_output(resource, kywc_output->name, seat->name); return false; } static void handle_get_current_output(struct wl_client *client, struct wl_resource *shell_resource) { input_manager_for_each_seat(send_seat_output, shell_resource); ukui_shell_v1_send_done(shell_resource); } static const struct ukui_shell_v1_interface ukui_shell_impl = { .create_surface = handle_create_surface, .create_decoration = handle_create_decoration, .get_current_output = handle_get_current_output, }; static void ukui_shell_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &ukui_shell_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } struct ukui_shell *shell = data; wl_resource_set_implementation(resource, &ukui_shell_impl, shell, NULL); input_manager_for_each_seat(send_seat_output, resource); ukui_shell_v1_send_done(resource); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct ukui_shell *shell = wl_container_of(listener, shell, display_destroy); wl_list_remove(&shell->display_destroy.link); wl_global_destroy(shell->global); } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct ukui_shell *shell = wl_container_of(listener, shell, server_destroy); wl_list_remove(&shell->server_destroy.link); free(shell); } bool ukui_shell_create(struct server *server) { struct ukui_shell *shell = calloc(1, sizeof(*shell)); if (!shell) { return false; } shell->global = wl_global_create(server->display, &ukui_shell_v1_interface, UKUI_SHELL_V1_VERSION, shell, ukui_shell_bind); if (!shell->global) { kywc_log(KYWC_WARN, "Ukui-shell-v1 create failed"); free(shell); return false; } wl_list_init(&shell->ukui_surfaces); shell->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &shell->server_destroy); shell->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &shell->display_destroy); return true; } uint32_t ukui_shell_get_surface_decoration_flags(struct wlr_surface *surface) { struct ukui_decoration *decoration = ukui_decoration_from_wlr_surface(surface); return decoration ? decoration->flags : 0; } kylin-wayland-compositor/src/view/tile/0000775000175000017500000000000015160461067017163 5ustar fengfengkylin-wayland-compositor/src/view/tile/meson.build0000664000175000017500000000012515160460057021321 0ustar fengfengwlcom_sources += files( 'tile_assist.c', 'tile_linkage.c', 'tile_manager.c', ) kylin-wayland-compositor/src/view/tile/tile_linkage.c0000664000175000017500000006233315160461067021765 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "input/cursor.h" #include "input/seat.h" #include "output.h" #include "scene/box.h" #include "theme.h" #include "tile_manager_p.h" #include "util/color.h" #include "util/macros.h" #include "view/action.h" #include "view/workspace.h" #define LINKAGE_MIN_VIEWS 2 #define LINKAGE_OFFSET 4 #define LINKAGE_PROXY_VIEW_GAP 8 #define LINKAGE_BAR_WIDTH 24 // Acts as height in horizontal orientation, and as width in vertical orientation #define LINKAGE_BAR_CENTER_DIMENSION_A 10 // Acts as width in horizontal orientation, and as height in vertical orientation #define LINKAGE_BAR_CENTER_DIMENSION_B 97 struct linkage_item { struct wl_list link; struct ky_scene_rect *rect; struct view *view; struct kywc_box geo; int32_t x1, y1, x2, y2; struct wl_listener view_unmap; }; struct bar { struct ky_scene_tree *tree; struct ky_scene_rect *rect; struct ky_scene_rect *center; struct ky_scene_box *border; struct kywc_box geo; }; struct tile_linkage { struct ky_scene_tree *tree; int32_t edges; int32_t min_width, max_width; int32_t min_height, max_height; /* cursor position before resize */ double cursor_x, cursor_y; struct bar bar; struct wl_list items; struct linkage_item *active; struct output *output; /* view position and size before resize */ struct kywc_box geo; struct kywc_box active_pending; struct wl_listener active_view_size; struct wl_listener active_view_position; struct wl_event_source *timer; }; static void linkage_view_min_size(struct view *view, int32_t *width, int32_t *height) { *width = view->base.min_width; *height = view->base.min_height; if (!view->base.unconstrained) { *width = MAX(view->base.min_width, VIEW_MIN_WIDTH); *height = MAX(view->base.min_height, VIEW_MIN_HEIGHT); } } static bool resize_edges_check(struct view *view, uint32_t edges) { switch (view->base.tiled) { case KYWC_TILE_LEFT: return edges == KYWC_EDGE_RIGHT; case KYWC_TILE_RIGHT: return edges == KYWC_EDGE_LEFT; case KYWC_TILE_TOP: return edges == KYWC_EDGE_BOTTOM; case KYWC_TILE_BOTTOM: return edges == KYWC_EDGE_TOP; case KYWC_TILE_TOP_LEFT: return edges == KYWC_EDGE_RIGHT || edges == KYWC_EDGE_BOTTOM; case KYWC_TILE_TOP_RIGHT: return edges == KYWC_EDGE_LEFT || edges == KYWC_EDGE_BOTTOM; case KYWC_TILE_BOTTOM_LEFT: return edges == KYWC_EDGE_RIGHT || edges == KYWC_EDGE_TOP; case KYWC_TILE_BOTTOM_RIGHT: return edges == KYWC_EDGE_LEFT || edges == KYWC_EDGE_TOP; default: return false; } return true; } static void linkage_item_destroy(struct linkage_item *item) { wl_list_remove(&item->link); wl_list_remove(&item->view_unmap.link); free(item); } static void handle_view_unmap(struct wl_listener *listener, void *data) { struct linkage_item *item = wl_container_of(listener, item, view_unmap); linkage_item_destroy(item); } static void linkage_adjust_geo_limit(struct tile_linkage *linkage, struct linkage_item *item, uint32_t edges) { enum kywc_tile tiled = item->view->base.tiled; struct kywc_box *usable = &linkage->output->usable_area; int ux2 = usable->x + usable->width; int uy2 = usable->y + usable->height; int min_width, min_height; linkage_view_min_size(item->view, &min_width, &min_height); min_width += item->view->base.margin.off_width; min_height += item->view->base.margin.off_height; struct wlr_box result; struct wlr_box active_box = { linkage->active->geo.x - LINKAGE_OFFSET / 2, linkage->active->geo.y - LINKAGE_OFFSET / 2, linkage->active->geo.width + LINKAGE_OFFSET, linkage->active->geo.height + LINKAGE_OFFSET }; struct wlr_box item_box = { item->geo.x, item->geo.y, item->geo.width, item->geo.height }; if (!wlr_box_intersection(&result, &item_box, &active_box)) { linkage_item_destroy(item); return; } switch (edges) { case KYWC_EDGE_LEFT: if (tiled == KYWC_TILE_LEFT || tiled == KYWC_TILE_TOP_LEFT || tiled == KYWC_TILE_BOTTOM_LEFT) { if (abs(linkage->active->x1 - item->x2) <= LINKAGE_OFFSET) { int remain = ux2 - item->x1 - min_width - (ux2 - linkage->active->x2); linkage->max_width = MIN(remain, linkage->max_width); return; } } else if (tiled == KYWC_TILE_RIGHT || tiled == KYWC_TILE_TOP_RIGHT || tiled == KYWC_TILE_BOTTOM_RIGHT) { if (abs(linkage->active->x1 - item->x1) <= LINKAGE_OFFSET) { int remain = ux2 - item->x2 + min_width; linkage->min_width = MAX(remain, linkage->min_width); return; } } break; case KYWC_EDGE_RIGHT: if (tiled == KYWC_TILE_RIGHT || tiled == KYWC_TILE_TOP_RIGHT || tiled == KYWC_TILE_BOTTOM_RIGHT) { if (abs(linkage->active->x2 - item->x1) <= LINKAGE_OFFSET) { int remain = item->x2 - min_width - usable->x - (linkage->active->x1 - usable->x); linkage->max_width = MIN(remain, linkage->max_width); return; } } else if (tiled == KYWC_TILE_LEFT || tiled == KYWC_TILE_TOP_LEFT || tiled == KYWC_TILE_BOTTOM_LEFT) { if (abs(linkage->active->x2 - item->x2) <= LINKAGE_OFFSET) { int remain = item->x1 + min_width - usable->x; linkage->min_width = MAX(remain, linkage->min_width); return; } } break; case KYWC_EDGE_TOP: if (tiled == KYWC_TILE_TOP || tiled == KYWC_TILE_TOP_LEFT || tiled == KYWC_TILE_TOP_RIGHT) { if (abs(linkage->active->y1 - item->y2) <= LINKAGE_OFFSET) { int remain = uy2 - item->y1 - min_height - (uy2 - linkage->active->y2); linkage->max_height = MIN(remain, linkage->max_height); return; } } else if (tiled == KYWC_TILE_BOTTOM || tiled == KYWC_TILE_BOTTOM_LEFT || tiled == KYWC_TILE_BOTTOM_RIGHT) { if (abs(linkage->active->y1 - item->y1) <= LINKAGE_OFFSET) { int remain = uy2 - (item->y2 - min_height); linkage->min_height = MAX(remain, linkage->min_height); return; } } break; case KYWC_EDGE_BOTTOM: if (tiled == KYWC_TILE_BOTTOM || tiled == KYWC_TILE_BOTTOM_LEFT || tiled == KYWC_TILE_BOTTOM_RIGHT) { if (abs(linkage->active->y2 - item->y1) <= LINKAGE_OFFSET) { int remain = item->y2 - min_height - usable->y - (linkage->active->y1 - usable->y); linkage->max_height = MIN(remain, linkage->max_height); return; } } else if (tiled == KYWC_TILE_TOP || tiled == KYWC_TILE_TOP_LEFT || tiled == KYWC_TILE_TOP_RIGHT) { if (abs(linkage->active->y2 - item->y2) <= LINKAGE_OFFSET) { int remain = item->y1 - usable->y + min_height; linkage->min_height = MAX(remain, linkage->min_height); return; } } break; default: break; } linkage_item_destroy(item); } static void linkage_item_geometry_update(struct tile_linkage *linkage, struct linkage_item *item, struct kywc_box *pending) { struct view *active = linkage->active->view; enum kywc_tile tiled = item->view->base.tiled; switch (linkage->edges) { case KYWC_EDGE_TOP: if (tiled == KYWC_TILE_TOP || tiled == KYWC_TILE_TOP_LEFT || tiled == KYWC_TILE_TOP_RIGHT) { item->geo.height = (pending->y - active->base.margin.off_y) - item->geo.y; } else if (tiled == KYWC_TILE_BOTTOM || tiled == KYWC_TILE_BOTTOM_LEFT || tiled == KYWC_TILE_BOTTOM_RIGHT) { int item_y2 = item->geo.y + item->geo.height; item->geo.y = pending->y - active->base.margin.off_y; item->geo.height = item_y2 - item->geo.y; } break; case KYWC_EDGE_BOTTOM: if (tiled == KYWC_TILE_BOTTOM || tiled == KYWC_TILE_BOTTOM_LEFT || tiled == KYWC_TILE_BOTTOM_RIGHT) { int item_y2 = item->geo.y + item->geo.height; item->geo.y = pending->y - active->base.margin.off_y + pending->height + active->base.margin.off_height; item->geo.height = item_y2 - item->geo.y; } else if (tiled == KYWC_TILE_TOP || tiled == KYWC_TILE_TOP_LEFT || tiled == KYWC_TILE_TOP_RIGHT) { int pending_y2 = pending->y - active->base.margin.off_y + pending->height + active->base.margin.off_height; item->geo.height = pending_y2 - item->geo.y; } break; case KYWC_EDGE_LEFT: if (tiled == KYWC_TILE_LEFT || tiled == KYWC_TILE_TOP_LEFT || tiled == KYWC_TILE_BOTTOM_LEFT) { item->geo.width = pending->x - active->base.margin.off_x - item->geo.x; } else if (tiled == KYWC_TILE_RIGHT || tiled == KYWC_TILE_TOP_RIGHT || tiled == KYWC_TILE_BOTTOM_RIGHT) { int item_x2 = item->geo.x + item->geo.width; item->geo.x = pending->x - active->base.margin.off_x; item->geo.width = item_x2 - item->geo.x; } break; case KYWC_EDGE_RIGHT: if (tiled == KYWC_TILE_LEFT || tiled == KYWC_TILE_TOP_LEFT || tiled == KYWC_TILE_BOTTOM_LEFT) { int pending_x2 = pending->x - active->base.margin.off_x + pending->width + active->base.margin.off_width; item->geo.width = pending_x2 - item->geo.x; } else if (tiled == KYWC_TILE_RIGHT || tiled == KYWC_TILE_TOP_RIGHT || tiled == KYWC_TILE_BOTTOM_RIGHT) { int item_x2 = item->geo.x + item->geo.width; item->geo.x = pending->x - active->base.margin.off_x + pending->width + active->base.margin.off_width; item->geo.width = item_x2 - item->geo.x; } break; default: break; } ky_scene_rect_set_size(item->rect, item->geo.width - LINKAGE_PROXY_VIEW_GAP * 2, item->geo.height - LINKAGE_PROXY_VIEW_GAP * 2); ky_scene_node_set_position(&item->rect->node, item->geo.x + LINKAGE_PROXY_VIEW_GAP, item->geo.y + LINKAGE_PROXY_VIEW_GAP); } void tile_linkage_destroy(struct tile_linkage *linkage) { struct linkage_item *item, *tmp; wl_list_for_each_safe(item, tmp, &linkage->items, link) { linkage_item_destroy(item); } if (linkage->active) { wl_list_remove(&linkage->active_view_size.link); wl_list_remove(&linkage->active_view_position.link); } if (linkage->timer) { wl_event_source_remove(linkage->timer); } if (linkage->tree) { ky_scene_node_destroy(&linkage->tree->node); } free(linkage); } static void tile_linkage_results_display(struct tile_linkage *linkage, bool cancel) { struct linkage_item *item, *tmp; wl_list_for_each_safe(item, tmp, &linkage->items, link) { if (item == linkage->active || !item->rect) { continue; } if (!cancel) { linkage_item_geometry_update(linkage, item, &linkage->active_pending); struct kywc_box pending = { item->geo.x + item->view->base.margin.off_x, item->geo.y + item->view->base.margin.off_y, item->geo.width - item->view->base.margin.off_width, item->geo.height - item->view->base.margin.off_height, }; view_do_resize(item->view, &pending); } ky_scene_node_set_enabled(&item->rect->node, false); ky_scene_node_set_enabled(&item->view->tree->node, true); } } void tile_linkage_resize_done(struct view *view, bool cancel) { struct tiled_output *tiled_output = tiled_output_from_kywc_output(view->output); if (!tiled_output || !tiled_output->linkage) { return; } tile_linkage_results_display(tiled_output->linkage, cancel); tile_linkage_destroy(tiled_output->linkage); tiled_output->linkage = NULL; } static void paint_linkage_bar(struct tile_linkage *linkage) { if (!linkage->bar.tree->node.enabled) { return; } ky_scene_node_set_input_bypassed(&linkage->tree->node, true); struct kywc_box center = { 0 }; if (linkage->edges == KYWC_EDGE_LEFT || linkage->edges == KYWC_EDGE_RIGHT) { int min_y = linkage->output->usable_area.y + linkage->output->usable_area.height; int max_y = linkage->output->usable_area.y; struct linkage_item *item; wl_list_for_each(item, &linkage->items, link) { min_y = MIN(min_y, item->y1); max_y = MAX(item->y2, max_y); } linkage->bar.geo.width = LINKAGE_BAR_WIDTH; linkage->bar.geo.height = max_y - min_y; linkage->bar.geo.x = linkage->edges == KYWC_EDGE_LEFT ? linkage->active->x1 : linkage->active->x2; linkage->bar.geo.x -= LINKAGE_BAR_WIDTH / 2; linkage->bar.geo.y = min_y; center.width = LINKAGE_BAR_CENTER_DIMENSION_A; center.height = LINKAGE_BAR_CENTER_DIMENSION_B; center.x = (linkage->bar.geo.width - center.width) / 2; center.y = (linkage->bar.geo.height - center.height) / 2; } else if (linkage->edges == KYWC_EDGE_TOP || linkage->edges == KYWC_EDGE_BOTTOM) { int min_x = linkage->output->usable_area.x + linkage->output->usable_area.width; int max_x = linkage->output->usable_area.x; struct linkage_item *item; wl_list_for_each(item, &linkage->items, link) { min_x = MIN(min_x, item->x1); max_x = MAX(item->x2, max_x); } linkage->bar.geo.width = max_x - min_x; linkage->bar.geo.height = LINKAGE_BAR_WIDTH; linkage->bar.geo.x = min_x; linkage->bar.geo.y = linkage->edges == KYWC_EDGE_TOP ? linkage->active->y1 : linkage->active->y2; linkage->bar.geo.y -= LINKAGE_BAR_WIDTH / 2; center.width = LINKAGE_BAR_CENTER_DIMENSION_B; center.height = LINKAGE_BAR_CENTER_DIMENSION_A; center.x = (linkage->bar.geo.width - center.width) / 2; center.y = (linkage->bar.geo.height - center.height) / 2; } struct theme *theme = theme_manager_get_theme(); float color[4]; color_float_pax(color, theme->active_bg_color, theme->opacity / 100.0); linkage->bar.rect = ky_scene_rect_create(linkage->bar.tree, linkage->bar.geo.width, linkage->bar.geo.height, color); /* add blur */ pixman_region32_t region; pixman_region32_init(®ion); ky_scene_node_set_blur_region(&linkage->bar.rect->node, theme->opacity != 100 ? ®ion : NULL); pixman_region32_fini(®ion); color_float_pax(color, theme->active_border_color, theme->opacity / 100.0); linkage->bar.border = ky_scene_box_create(linkage->bar.tree, linkage->bar.geo.width, linkage->bar.geo.height, color, 1); color_float_pa(color, theme->accent_color); linkage->bar.center = ky_scene_rect_create(linkage->bar.tree, center.width, center.height, color); int radius = theme->normal_radius; ky_scene_node_set_radius(&linkage->bar.center->node, (int[4]){ radius, radius, radius, radius }); ky_scene_node_set_position(&linkage->bar.tree->node, linkage->bar.geo.x, linkage->bar.geo.y); ky_scene_node_set_position(&linkage->bar.center->node, center.x, center.y); } static int handle_linkage_bar_show(void *data) { struct tile_linkage *linkage = data; paint_linkage_bar(linkage); return 0; } static struct linkage_item *linkage_item_create(struct view *view) { struct linkage_item *item = calloc(1, sizeof(*item)); if (!item) { return NULL; } item->view = view; item->x1 = view->base.geometry.x - view->base.margin.off_x; item->y1 = view->base.geometry.y - view->base.margin.off_y; item->x2 = item->x1 + view->base.geometry.width + view->base.margin.off_width; item->y2 = item->y1 + view->base.geometry.height + view->base.margin.off_height; struct kywc_box box = { item->x1, item->y1, item->x2 - item->x1, item->y2 - item->y1 }; item->geo = box; item->view_unmap.notify = handle_view_unmap; wl_signal_add(&item->view->base.events.unmap, &item->view_unmap); return item; } static void tile_linkage_geo_update(struct tile_linkage *linkage) { if (!linkage) { return; } struct linkage_item *item; wl_list_for_each(item, &linkage->items, link) { if (item != linkage->active && item->rect) { linkage_item_geometry_update(linkage, item, &linkage->active->view->base.geometry); } } } static void handle_view_size_changed(struct wl_listener *listener, void *data) { struct tile_linkage *linkage = wl_container_of(listener, linkage, active_view_size); tile_linkage_geo_update(linkage); } static void handle_view_position_changed(struct wl_listener *listener, void *data) { struct tile_linkage *linkage = wl_container_of(listener, linkage, active_view_position); tile_linkage_geo_update(linkage); } static bool linkage_add_item(struct tile_linkage *linkage, struct tiled_layer layer) { if (layer.proxy[1] && layer.proxy[1]->item->view->base.tiled == KYWC_TILE_TOP) { layer.proxy[1] = NULL; } if (layer.proxy[2] && layer.proxy[2]->item->view->base.tiled == KYWC_TILE_LEFT) { layer.proxy[2] = NULL; } if (layer.proxy[3] && ((layer.proxy[3]->item->view->base.tiled == KYWC_TILE_RIGHT) || (layer.proxy[3]->item->view->base.tiled == KYWC_TILE_BOTTOM))) { layer.proxy[3] = NULL; } for (int i = 0; i < SLOT_TYPE_COUNT; i++) { if (!layer.proxy[i]) { continue; } struct linkage_item *item = linkage_item_create(layer.proxy[i]->item->view); if (!item) { continue; } if (layer.proxy[i]->item->view == layer.place->operating_view) { linkage->active = item; wl_signal_add(&item->view->base.events.size, &linkage->active_view_size); wl_signal_add(&item->view->base.events.position, &linkage->active_view_position); } wl_list_insert(&linkage->items, &item->link); } return !!linkage->active; } static struct tile_linkage *tile_linkage_create(struct view *view, uint32_t edges, struct tiled_output *tiled_output) { if (!view_need_tile_managerd(view)) { return NULL; } if (!resize_edges_check(view, edges)) { return NULL; } struct tiled_place *place = get_tiled_place(tiled_output, view->current_proxy->workspace); if (!place) { return NULL; } struct item_proxy *item_proxy = item_proxy_from_place(view, place); if (!item_proxy) { return NULL; } struct tiled_layer layer = { 0 }; form_virtual_layer_by_tile(view, place, &layer, view->base.tiled); if (tiled_layer_is_empty(&layer)) { return NULL; } struct tile_linkage *linkage = calloc(1, sizeof(*linkage)); if (!linkage) { return NULL; } linkage->output = output_from_kywc_output(view->output); wl_list_init(&linkage->items); linkage->active_view_size.notify = handle_view_size_changed; linkage->active_view_position.notify = handle_view_position_changed; if (!linkage_add_item(linkage, layer)) { tile_linkage_destroy(linkage); return NULL; } linkage_view_min_size(linkage->active->view, &linkage->min_width, &linkage->min_height); linkage->max_width = linkage->output->usable_area.width; linkage->max_height = linkage->output->usable_area.height; struct linkage_item *item, *tmp; wl_list_for_each_safe(item, tmp, &linkage->items, link) { if (item != linkage->active) { linkage_adjust_geo_limit(linkage, item, edges); } } linkage->max_width -= linkage->active->view->base.margin.off_width; linkage->max_height -= linkage->active->view->base.margin.off_height; struct view_layer *view_layer = view_manager_get_layer(LAYER_POPUP, false); if (wl_list_length(&linkage->items) < LINKAGE_MIN_VIEWS || !view_layer) { tile_linkage_destroy(linkage); return NULL; } tiled_output->linkage = linkage; linkage->edges = edges; linkage->tree = ky_scene_tree_create(view_layer->tree); linkage->bar.tree = ky_scene_tree_create(linkage->tree); return linkage; } static void tile_linkage_bar_hide(struct tile_linkage *linkage) { ky_scene_node_set_enabled(&linkage->bar.tree->node, false); } void tile_linkage_bar_show(struct view *view, uint32_t edges) { struct tiled_output *tiled_output = tiled_output_from_kywc_output(view->output); if (!tiled_output) { return; } struct tile_linkage *linkage = tiled_output->linkage; if (linkage) { if (edges == KYWC_EDGE_NONE || !(linkage->edges & edges)) { tile_linkage_destroy(linkage); tiled_output->linkage = NULL; } return; } linkage = tile_linkage_create(view, edges, tiled_output); if (!linkage) { return; } tiled_output->linkage = linkage; struct seat *seat = input_manager_get_default_seat(); struct wl_event_loop *loop = wl_display_get_event_loop(seat->wlr_seat->display); linkage->timer = wl_event_loop_add_timer(loop, handle_linkage_bar_show, linkage); wl_event_source_timer_update(linkage->timer, 300); } static void tile_linkage_blur_display(struct tile_linkage *linkage, double lx, double ly) { tile_linkage_bar_hide(linkage); linkage->cursor_x = lx; linkage->cursor_y = ly; linkage->geo = linkage->active->view->base.geometry; linkage->active_pending = linkage->geo; struct theme *theme = theme_manager_get_theme(); int radius = theme->window_radius; float color[4]; color_float_pax(color, theme->active_bg_color, theme->opacity / 100.0); struct linkage_item *item; wl_list_for_each(item, &linkage->items, link) { if (item == linkage->active) { continue; } ky_scene_node_set_enabled(&item->view->tree->node, false); item->rect = ky_scene_rect_create(linkage->tree, item->geo.width - LINKAGE_PROXY_VIEW_GAP * 2, item->geo.height - LINKAGE_PROXY_VIEW_GAP * 2, color); ky_scene_node_set_radius(&item->rect->node, (int[4]){ radius, radius, radius, radius }); /* add blur */ pixman_region32_t region; pixman_region32_init(®ion); ky_scene_node_set_blur_region(&item->rect->node, theme->opacity != 100 ? ®ion : NULL); ky_scene_node_set_position(&item->rect->node, item->geo.x + LINKAGE_PROXY_VIEW_GAP, item->geo.y + LINKAGE_PROXY_VIEW_GAP); pixman_region32_fini(®ion); } } static void tile_linkage_view_do_resize(struct tile_linkage *linkage, struct view *view, double lx, double ly) { struct kywc_box view_box = linkage->active->view->base.geometry; double dx = lx - linkage->cursor_x; double dy = ly - linkage->cursor_y; if (linkage->edges & KYWC_EDGE_TOP) { view_box.height = linkage->geo.height - dy; } else if (linkage->edges & KYWC_EDGE_BOTTOM) { view_box.height = linkage->geo.height + dy; } view_box.height = CLAMP(view_box.height, linkage->min_height, linkage->max_height); if (linkage->edges & KYWC_EDGE_LEFT) { view_box.width = linkage->geo.width - dx; } else if (linkage->edges & KYWC_EDGE_RIGHT) { view_box.width = linkage->geo.width + dx; } view_box.width = CLAMP(view_box.width, linkage->min_width, linkage->max_width); if (linkage->edges & KYWC_EDGE_TOP) { /* anchor bottom edge */ view_box.y = linkage->geo.y + linkage->geo.height - view_box.height; } if (linkage->edges & KYWC_EDGE_LEFT) { /* anchor right edge */ view_box.x = linkage->geo.x + linkage->geo.width - view_box.width; } interactive_resize_constraints(view, linkage->output, &view_box, linkage->edges); view_do_resize(linkage->active->view, &view_box); linkage->active_pending = view_box; } bool tile_linkage_view_resize(struct view *view, uint32_t edges, double lx, double ly) { struct tiled_output *tiled_output = tiled_output_from_kywc_output(view->output); if (!tiled_output) { return false; } struct tile_linkage *linkage = tiled_output->linkage; if (!linkage && !edges) { return false; } else if (!linkage) { linkage = tile_linkage_create(view, edges, tiled_output); if (!linkage) { return false; } tiled_output->linkage = linkage; } if (edges) { tile_linkage_blur_display(linkage, lx, ly); } else { tile_linkage_view_do_resize(linkage, view, lx, ly); } return true; } kylin-wayland-compositor/src/view/tile/tile_manager_p.h0000664000175000017500000000537615160460057022313 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _TILE_MANAGER_P_H_ #define _TILE_MANAGER_P_H_ #include "../view_p.h" #define VIEW_MIN_WIDTH 200 #define VIEW_MIN_HEIGHT 100 enum slot_type { SLOT_TYPE_TOP_LEFT = 0, SLOT_TYPE_TOP_RIGHT, SLOT_TYPE_BOTTOM_LEFT, SLOT_TYPE_BOTTOM_RIGHT, SLOT_TYPE_COUNT, }; struct item_proxy { struct wl_list place_link; struct wl_list item_link; struct tiled_item *item; struct tiled_layer *layer; struct tiled_place *place; }; struct tiled_item { struct wl_list item_proxys; struct kywc_output *kywc_output; float ratio_x; float ratio_y; float ratio_w; float ratio_h; struct kywc_box tiled_geo; struct view *view; struct wl_listener view_unmap; struct wl_listener view_minimize; struct wl_listener view_fullscreen; struct wl_listener view_tile; struct wl_listener view_activate; struct wl_listener view_output; struct wl_listener view_size; struct wl_listener view_position; struct wl_listener workspace_enter; struct wl_listener workspace_leave; }; struct tiled_layer { struct wl_list link; // In the layers table of the place bool on_layers; struct tiled_place *place; struct item_proxy *proxy[SLOT_TYPE_COUNT]; }; struct tiled_place { struct wl_list link; struct wl_list item_proxys; struct wl_list layers; struct tiled_output *tiled_output; struct workspace *workspace; struct view *operating_view; struct wl_listener workspace_destroy; }; struct tiled_output { struct wl_list link; struct wl_list tiled_places; struct tile_linkage *linkage; struct seat *assist_seat; bool in_assist; char *output_name; struct kywc_output *kywc_output; struct wl_listener output_destroy; }; void tile_calculate_view_geometry(struct tiled_layer *layer, struct output *output, struct kywc_box *geo, enum kywc_tile tile); struct tiled_output *tiled_output_from_kywc_output(struct kywc_output *kywc_output); struct tiled_place *get_tiled_place(struct tiled_output *tiled_output, struct workspace *workspace); struct item_proxy *item_proxy_from_place(struct view *view, struct tiled_place *place); bool tiled_layer_is_empty(struct tiled_layer *layer); bool view_need_tile_managerd(struct view *view); void form_virtual_layer_by_tile(struct view *view, struct tiled_place *place, struct tiled_layer *layer, enum kywc_tile tile); void view_begin_tile_assist(struct view *view, struct seat *seat, struct output *output, struct tiled_layer *tiled_layer); void tile_linkage_destroy(struct tile_linkage *linkage); #endif /* _TILE_MANAGER_P_H_ */ kylin-wayland-compositor/src/view/tile/tile_assist.c0000664000175000017500000013267015160461067021663 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include "effect/snapshot.h" #include "input/cursor.h" #include "input/seat.h" #include "output.h" #include "painter.h" #include "scene/decoration.h" #include "theme.h" #include "tile_manager_p.h" #include "util/color.h" #include "view/workspace.h" #include "widget/scaled_buffer.h" #include "widget/widget.h" #define GAP 16 #define ITEM_HEIGHT 40 #define ITEM_GAP 8 #define SELECT_BORDER_WIDTH 2 #define RESIZE_BORDER 13 enum split_type { TYPE_HALF_LEFT_OR_RIGHT = 0, TYPE_HALF_TOP_OR_BOTTOM, TYPE_THREE_LEFT, TYPE_THREE_RIGHT, TYPE_THREE_TOP, TYPE_THREE_BOTTOM, TYPE_QUARTER, }; enum split_state { HALF_SCREEN = 2, THREE_SCREEN, QUARTER_SCREEN, }; enum button_state { BUTTON_STATE_NONE = 0, BUTTON_STATE_HOVER, BUTTON_STATE_CLICKED, }; enum assist_part { ASSIST_ROOT = 0, ASSIST_FRAME_RECT, ASSIST_BUTTON_CLOSE, ASSIST_TITLE_ICON, ASSIST_TITLE_TEXT, ASSIST_THUMBNAIL, ASSIST_PART_COUNT, }; struct item_part { struct item *item; struct ky_scene_node *node; enum assist_part part; float scale; }; struct item { struct wl_list link; struct ky_scene_tree *tree; struct widget *title_text; struct item_part part[ASSIST_PART_COUNT]; struct ky_scene_node *selection; struct view *view; struct wl_listener view_unmap; struct snapshot *snapshot; int32_t snapshot_width, snapshot_height; int32_t max_text_width, text_height; }; struct assist { struct view *view; struct wl_listener view_unmap; struct ky_scene_tree *tree; struct ky_scene_rect *assist_area, *background; struct kywc_box box; struct kywc_box full_area; enum kywc_tile tiled; int32_t index_first, current; int32_t total, row, col; }; static struct tile_assist_manager { struct wl_list items; struct ky_scene_tree *tree; struct workspace *workspace; struct seat *seat; struct output *output; struct tile_assist_color *color; struct assist assist[SLOT_TYPE_COUNT]; struct assist *item_page; struct item *selected; struct view *active_view; struct tiled_layer tiled_layer; struct seat_pointer_grab pointer_grab; struct seat_keyboard_grab keyboard_grab; struct seat_touch_grab touch_grab; struct wl_listener new_mapped_view; struct wl_listener configured_output; struct wl_listener seat_destroy; struct wl_listener show_desktop; struct wl_listener workspace_activate; enum split_state state; enum split_type type; int32_t total_items; int32_t fixed_width; // (usable_width - 160) / 4 int32_t fixed_height; // usable_height / 22 * 5 /* listener highlight view */ struct wl_listener highlight; } *manager = NULL; static void title_text_update(struct item *item) { struct theme *theme = theme_manager_get_theme(); widget_set_text(item->title_text, item->view->base.title, JUSTIFY_CENTER, TEXT_ATTR_NONE); widget_set_font(item->title_text, theme->font_name, theme->font_size); widget_set_front_color(item->title_text, theme->active_text_color); widget_set_max_size(item->title_text, item->max_text_width, ITEM_HEIGHT); widget_set_auto_resize(item->title_text, AUTO_RESIZE_ONLY); widget_set_enabled(item->title_text, true); widget_update(item->title_text, true); int32_t text_width, text_height; widget_get_size(item->title_text, &text_width, &text_height); item->text_height = text_height; } static void icon_buffer_update(struct item_part *part, float scale) { struct wlr_buffer *buf = view_get_icon_buffer(part->item->view, scale); if (!buf) { return; } struct ky_scene_buffer *icon_buffer = ky_scene_buffer_from_node(part->node); if (icon_buffer->buffer != buf) { ky_scene_buffer_set_buffer(icon_buffer, buf); } if (icon_buffer->buffer != buf) { return; } struct theme *theme = theme_manager_get_theme(); ky_scene_buffer_set_dest_size(icon_buffer, theme->icon_size, theme->icon_size); } static void set_close_buffer(struct item_part *part, enum button_state state) { enum theme_button_type type = THEME_BUTTON_TYPE_CLOSE; struct theme *theme = theme_manager_get_theme(); /* get actual type by current state */ type += state * 4; struct wlr_fbox src; struct wlr_buffer *buf = theme_button_buffer_load(theme, part->scale, type, &src, true); struct ky_scene_buffer *buffer = ky_scene_buffer_from_node(part->node); if (buffer->buffer != buf) { ky_scene_buffer_set_buffer(buffer, buf); } /* shortcut here if set_buffer triggered scaled buffer update */ if (buffer->buffer != buf) { return; } ky_scene_buffer_set_dest_size(buffer, theme->button_width, theme->button_width); ky_scene_buffer_set_source_box(buffer, &src); } static void item_buffer_update(struct ky_scene_buffer *buffer, float scale, void *data) { struct item_part *item_part = data; item_part->scale = scale; if (item_part->part == ASSIST_BUTTON_CLOSE) { set_close_buffer(item_part, BUTTON_STATE_NONE); } else if (item_part->part == ASSIST_TITLE_ICON) { icon_buffer_update(item_part, scale); } } static void item_buffer_destroy(struct ky_scene_buffer *buffer, void *data) { /* buffers are destroyed in theme */ } static void assist_item_show(void) { struct item *item; wl_list_for_each(item, &manager->items, link) { if (!item->view->base.minimized) { ky_scene_node_set_enabled(&item->view->tree->node, false); } if (!item->tree) { continue; } ky_scene_node_set_enabled(item->selection, false); ky_scene_node_set_enabled(&item->tree->node, false); } int32_t i = 0, col = 0; int32_t node_x = 0; struct assist *assist = manager->item_page; int32_t node_y = (assist->box.height % manager->fixed_height) / 2; wl_list_for_each(item, &manager->items, link) { if (!item->tree) { continue; } if (i < assist->index_first) { i++; continue; } if (i == assist->index_first + assist->total) { break; } if (col != 0 && col % assist->col == 0) { node_y += manager->fixed_height; } node_x = (assist->box.width % manager->fixed_width) / 2 + (col % assist->col) * manager->fixed_width; col++; ky_scene_node_set_position(&item->tree->node, node_x, node_y); ky_scene_node_set_enabled(&item->tree->node, true); if (i == assist->current) { manager->selected = item; ky_scene_node_set_enabled(item->selection, true); } i++; } } static void item_destroy(struct item *item) { wl_list_remove(&item->link); wl_list_remove(&item->view_unmap.link); /* snapshot destroy */ if (item->snapshot) { snapshot_destroy(item->snapshot); } if (item->tree) { manager->total_items--; ky_scene_node_set_enabled(&item->tree->node, false); ky_scene_node_destroy(&item->tree->node); } free(item); } static void tile_assist_done(void); static void handle_view_unmap(struct wl_listener *listener, void *data) { struct item *item = wl_container_of(listener, item, view_unmap); item_destroy(item); if (manager->total_items == 0) { tile_assist_done(); return; } assist_item_show(); } static struct item *item_create(struct view *view, bool flip) { struct item *item = calloc(1, sizeof(*item)); if (!item) { return NULL; } /* The newly opened view is inserted behind the linked list. */ wl_list_insert(flip ? &manager->items : manager->items.prev, &item->link); item->view = view; item->tree = NULL; item->view_unmap.notify = handle_view_unmap; wl_signal_add(&item->view->base.events.unmap, &item->view_unmap); return item; } static void tile_assist_done(void) { struct item *item, *item_tmp; wl_list_for_each_safe(item, item_tmp, &manager->items, link) { if (!item->view->base.minimized && !view_manager_get_highlight()) { ky_scene_node_set_enabled(&item->view->tree->node, true); } item_destroy(item); } for (int32_t i = 0; i < SLOT_TYPE_COUNT; i++) { struct assist *assist = &manager->assist[i]; if (assist->view) { wl_list_remove(&assist->view_unmap.link); } if (assist->tree) { ky_scene_node_destroy(&assist->tree->node); manager->assist[i].tree = NULL; } assist->view = NULL; } if (manager->active_view) { kywc_view_activate(&manager->active_view->base); seat_focus_surface(manager->active_view->base.focused_seat, manager->active_view->surface); } wl_list_remove(&manager->new_mapped_view.link); wl_list_remove(&manager->configured_output.link); wl_list_remove(&manager->seat_destroy.link); wl_list_remove(&manager->show_desktop.link); wl_list_remove(&manager->workspace_activate.link); wl_list_remove(&manager->highlight.link); seat_end_pointer_grab(manager->seat, &manager->pointer_grab); seat_end_keyboard_grab(manager->seat, &manager->keyboard_grab); seat_end_touch_grab(manager->seat, &manager->touch_grab); ky_scene_node_destroy(&manager->tree->node); free(manager); manager = NULL; } static void assist_area_update(struct assist *assist, struct view *top_left, struct view *top_right, struct view *bottom_left, struct view *bottom_right) { if (assist->view) { return; } enum kywc_tile tiled = assist->tiled; struct kywc_box *usable = &manager->output->usable_area; struct kywc_box *full_area = &assist->full_area; struct kywc_box back_box = { 0 }; int remain_w = usable->width - full_area->width; int remain_h = usable->height - full_area->height; bool w_aligned = false, h_aligned = false; if (tiled == KYWC_TILE_LEFT || tiled == KYWC_TILE_RIGHT) { int x = tiled == KYWC_TILE_LEFT ? GAP : usable->width - full_area->width + GAP; assist->box.width = full_area->width - GAP * 2; assist->box.height = full_area->height - GAP * 2; assist->box.x = usable->x + x; assist->box.y = usable->y + GAP; back_box.width = full_area->width - RESIZE_BORDER; back_box.height = full_area->height; back_box.x = tiled == KYWC_TILE_LEFT ? -GAP : -(GAP - RESIZE_BORDER); back_box.y = -GAP; } else if (tiled == KYWC_TILE_TOP || tiled == KYWC_TILE_BOTTOM) { int y = tiled == KYWC_TILE_TOP ? GAP : usable->height - full_area->height + GAP; assist->box.width = full_area->width - GAP * 2; assist->box.height = full_area->height - GAP * 2; assist->box.x = usable->x + GAP; assist->box.y = usable->y + y; back_box.width = full_area->width; back_box.height = full_area->height - RESIZE_BORDER; back_box.x = -GAP; back_box.y = tiled == KYWC_TILE_TOP ? -GAP : -(GAP - RESIZE_BORDER); } else if (tiled == KYWC_TILE_TOP_LEFT) { if (!top_right && bottom_left) { w_aligned = true; if (bottom_right) { w_aligned = bottom_left->base.geometry.height + bottom_left->base.margin.off_height >= bottom_right->base.geometry.height + bottom_right->base.margin.off_height; } } if (top_right && !bottom_left) { h_aligned = true; if (bottom_right) { h_aligned = top_right->base.geometry.width + top_right->base.margin.off_width >= bottom_right->base.geometry.width + bottom_right->base.margin.off_width; } } assist->box.width = w_aligned ? full_area->width - GAP : full_area->width - GAP * 2; assist->box.height = h_aligned ? full_area->height - GAP : full_area->height - GAP * 2; assist->box.x = usable->x + GAP; assist->box.y = usable->y + GAP; back_box.width = w_aligned ? full_area->width : full_area->width - RESIZE_BORDER; back_box.height = h_aligned ? full_area->height : full_area->height - RESIZE_BORDER; back_box.x = -GAP; back_box.y = -GAP; } else if (tiled == KYWC_TILE_TOP_RIGHT) { if (!top_left && bottom_right) { w_aligned = true; if (bottom_left) { w_aligned = bottom_right->base.geometry.height + bottom_right->base.margin.off_height > bottom_left->base.geometry.height + bottom_left->base.margin.off_height; } } if (!bottom_right && top_left) { h_aligned = true; if (bottom_left) { h_aligned = top_left->base.geometry.width + top_left->base.margin.off_width >= bottom_left->base.geometry.width + bottom_left->base.margin.off_width; } } assist->box.width = w_aligned ? full_area->width - GAP : full_area->width - GAP * 2; assist->box.height = h_aligned ? full_area->height - GAP : full_area->height - 2 * GAP; assist->box.x = w_aligned ? usable->x + remain_w : usable->x + remain_w + GAP; assist->box.y = usable->y + GAP; back_box.width = w_aligned ? full_area->width : full_area->width - RESIZE_BORDER; back_box.height = h_aligned ? full_area->height : full_area->height - RESIZE_BORDER; back_box.x = w_aligned ? 0 : -(GAP - RESIZE_BORDER); back_box.y = -GAP; } else if (tiled == KYWC_TILE_BOTTOM_LEFT) { if (!bottom_right && top_left) { w_aligned = true; if (top_right) { w_aligned = top_left->base.geometry.height + top_left->base.margin.off_height >= top_right->base.geometry.height + top_right->base.margin.off_height; } } if (!top_left && bottom_right) { h_aligned = true; if (top_right) { h_aligned = bottom_right->base.geometry.width + bottom_right->base.margin.off_width > top_right->base.geometry.width + top_right->base.margin.off_width; } } assist->box.width = w_aligned ? full_area->width - GAP : full_area->width - GAP * 2; assist->box.height = h_aligned ? full_area->height - GAP : full_area->height - GAP * 2; assist->box.x = usable->x + GAP; assist->box.y = h_aligned ? usable->y + remain_h : usable->y + remain_h + GAP; back_box.width = w_aligned ? full_area->width : full_area->width - RESIZE_BORDER; back_box.height = h_aligned ? full_area->height : full_area->height - RESIZE_BORDER; back_box.x = -GAP; back_box.y = h_aligned ? 0 : -(GAP - RESIZE_BORDER); } else if (tiled == KYWC_TILE_BOTTOM_RIGHT) { if (!bottom_left && top_right) { w_aligned = true; if (top_left) { w_aligned = top_right->base.geometry.height + top_right->base.margin.off_height > top_left->base.geometry.height + top_left->base.margin.off_height; } } if (!top_right && bottom_left) { h_aligned = true; if (top_left) { h_aligned = bottom_left->base.geometry.width + bottom_left->base.margin.off_width > top_left->base.geometry.width + top_left->base.margin.off_width; } } assist->box.width = w_aligned ? full_area->width - GAP : full_area->width - GAP * 2; assist->box.height = h_aligned ? full_area->height - GAP : full_area->height - GAP * 2; assist->box.x = w_aligned ? usable->x + remain_w : usable->x + remain_w + GAP; assist->box.y = h_aligned ? usable->y + remain_h : usable->y + remain_h + GAP; back_box.width = w_aligned ? full_area->width : full_area->width - RESIZE_BORDER; back_box.height = h_aligned ? full_area->height : full_area->height - RESIZE_BORDER; back_box.x = w_aligned ? 0 : -(GAP - RESIZE_BORDER); back_box.y = h_aligned ? 0 : -(GAP - RESIZE_BORDER); } ky_scene_rect_set_size(assist->background, back_box.width, back_box.height); ky_scene_rect_set_size(assist->assist_area, assist->box.width, assist->box.height); ky_scene_node_set_position(&assist->tree->node, assist->box.x, assist->box.y); ky_scene_node_set_position(&assist->background->node, back_box.x, back_box.y); assist->row = assist->box.height / manager->fixed_height; assist->col = assist->box.width / manager->fixed_width; assist->total = assist->row * assist->col; if (!assist->total) { ky_scene_node_set_enabled(&assist->tree->node, false); } } static void item_page_assist_update(void) { struct view *top_left = NULL; struct view *top_right = NULL; struct view *bottom_left = NULL; struct view *bottom_right = NULL; for (uint32_t i = 0; i < manager->state; i++) { if (!manager->assist[i].view) { continue; } enum kywc_tile tiled = manager->assist[i].view->base.tiled; if (tiled == KYWC_TILE_LEFT) { top_left = bottom_left = manager->assist[i].view; } else if (tiled == KYWC_TILE_RIGHT) { top_right = bottom_right = manager->assist[i].view; } else if (tiled == KYWC_TILE_TOP) { top_left = top_right = manager->assist[i].view; } else if (tiled == KYWC_TILE_BOTTOM) { bottom_left = bottom_right = manager->assist[i].view; } else if (tiled == KYWC_TILE_TOP_LEFT) { top_left = manager->assist[i].view; } else if (tiled == KYWC_TILE_TOP_RIGHT) { top_right = manager->assist[i].view; } else if (tiled == KYWC_TILE_BOTTOM_LEFT) { bottom_left = manager->assist[i].view; } else if (tiled == KYWC_TILE_BOTTOM_RIGHT) { bottom_right = manager->assist[i].view; } } for (uint32_t i = 0; i < manager->state; i++) { assist_area_update(&manager->assist[i], top_left, top_right, bottom_left, bottom_right); } for (uint32_t i = 0; i < manager->state; i++) { if (!manager->assist[i].view && manager->assist[i].total) { manager->item_page = &manager->assist[i]; return; } } manager->item_page = NULL; } static void item_page_update(void) { item_page_assist_update(); if (!manager->item_page) { tile_assist_done(); return; } struct item *item; wl_list_for_each(item, &manager->items, link) { if (!item->tree) { continue; } ky_scene_node_reparent(&item->tree->node, manager->item_page->tree); } assist_item_show(); } static void handle_tiled_view_unmap(struct wl_listener *listener, void *data) { tile_assist_done(); } static void assist_area_set_view(struct assist *assist, struct view *view) { assist->view = view; assist->view_unmap.notify = handle_tiled_view_unmap; wl_signal_add(&assist->view->base.events.unmap, &assist->view_unmap); ky_scene_node_set_enabled(&assist->tree->node, false); } static void item_view_set_tiled(struct item *item) { if (!item->view->base.minimized) { ky_scene_node_set_enabled(&item->view->tree->node, true); } manager->active_view = item->view; kywc_view_activate(&item->view->base); int lx, ly; ky_scene_node_coords(item->part[ASSIST_THUMBNAIL].node, &lx, &ly); if (item->view->base.min_width + item->view->base.margin.off_width > manager->item_page->full_area.width || item->view->base.min_height + item->view->base.margin.off_height > manager->item_page->full_area.height) { item->view->tile_start_geometry = (struct kywc_box){ lx, ly, item->snapshot_width, item->snapshot_height }; kywc_view_set_tiled(&item->view->base, manager->item_page->tiled, &manager->output->base); item->view->tile_start_geometry = (struct kywc_box){ 0 }; tile_assist_done(); return; } item->view->tile_start_geometry = (struct kywc_box){ lx, ly, item->snapshot_width, item->snapshot_height }; struct kywc_box geo = { 0 }; geo.x = manager->item_page->full_area.x + item->view->base.margin.off_x; geo.y = manager->item_page->full_area.y + item->view->base.margin.off_y; geo.width = manager->item_page->full_area.width - item->view->base.margin.off_width; geo.height = manager->item_page->full_area.height - item->view->base.margin.off_height; view_do_tiled(item->view, manager->item_page->tiled, &manager->output->base, &geo); item->view->tile_start_geometry = (struct kywc_box){ 0 }; assist_area_set_view(manager->item_page, item->view); item_destroy(item); if (manager->total_items == 0) { tile_assist_done(); return; } item_page_update(); } static int32_t get_last_index(void) { struct assist *assist = manager->item_page; int32_t last_index = 0; if (manager->total_items > assist->total) { int32_t remainder = manager->total_items % assist->total; if (remainder) { int32_t row = remainder % assist->col == 0 ? remainder / assist->col : remainder / assist->col + 1; last_index = manager->total_items - (remainder + (assist->row - row) * assist->col); } else { last_index = manager->total_items - assist->total; } } return last_index; } static void pointer_grab_cancel(struct seat_pointer_grab *pointer_grab) { tile_assist_done(); } static bool pointer_grab_button(struct seat_pointer_grab *pointer_grab, uint32_t time, uint32_t button, bool pressed) { struct seat *seat = pointer_grab->seat; struct input_event_node *inode = input_event_node_from_node(seat->cursor->hover.node); struct ky_scene_node *node = input_event_node_root(inode); if (!node) { struct ky_scene_node *hover = ky_scene_node_at(&seat->scene->tree.node, seat->cursor->lx, seat->cursor->ly, NULL, NULL); inode = input_event_node_from_node(hover); node = input_event_node_root(inode); } if (node == &manager->item_page->tree->node) { inode->impl->click(seat, seat->cursor->hover.node, button, pressed, time, CLICK_STATE_NONE, inode->data); return true; } else if (pressed) { tile_assist_done(); } return !node; } static bool pointer_grab_motion(struct seat_pointer_grab *pointer_grab, uint32_t time, double lx, double ly) { return false; } static bool pointer_grab_axis(struct seat_pointer_grab *pointer_grab, uint32_t time, bool vertical, double value) { struct seat *seat = pointer_grab->seat; struct input_event_node *inode = input_event_node_from_node(seat->cursor->hover.node); struct ky_scene_node *node = input_event_node_root(inode); if (node != &manager->item_page->tree->node) { return false; } static uint32_t last_time = 0; if (time - last_time < 100) { return false; } last_time = time; struct assist *assist = manager->item_page; int32_t last_index = get_last_index(); int32_t next_index = value < 0 ? assist->index_first - assist->col : assist->index_first + assist->col; if (next_index >= 0 && next_index <= last_index) { assist->index_first = next_index; } assist_item_show(); return true; } static bool pointer_grab_frame(struct seat_pointer_grab *pointer_grab) { return false; } static const struct seat_pointer_grab_interface pointer_grab_impl = { .motion = pointer_grab_motion, .button = pointer_grab_button, .axis = pointer_grab_axis, .frame = pointer_grab_frame, .cancel = pointer_grab_cancel, }; static bool touch_grab_touch(struct seat_touch_grab *touch_grab, uint32_t time, bool down) { // FIXME: interactive grab end return pointer_grab_button(&manager->pointer_grab, time, BTN_LEFT, down); } static bool touch_grab_motion(struct seat_touch_grab *touch_grab, uint32_t time, double lx, double ly) { return false; } static void touch_grab_cancel(struct seat_touch_grab *touch_grab) {} static const struct seat_touch_grab_interface touch_grab_impl = { .touch = touch_grab_touch, .motion = touch_grab_motion, .cancel = touch_grab_cancel, }; static bool keyboard_grab_key(struct seat_keyboard_grab *keyboard_grab, struct keyboard *keyboard, uint32_t time, uint32_t key, bool pressed, uint32_t modifiers) { if (!pressed) { return true; } if (key == KEY_LEFTMETA || key == KEY_RIGHTMETA || key == KEY_ESC) { tile_assist_done(); return true; } if (key == KEY_ENTER && manager->selected) { struct item *select = manager->selected; manager->selected = NULL; item_view_set_tiled(select); return true; } if (key != KEY_UP && key != KEY_DOWN && key != KEY_LEFT && key != KEY_RIGHT) { return true; } struct assist *assist = manager->item_page; int32_t current = 0; if (assist->current < 0) { current = 0; } else if (key == KEY_UP) { current = assist->current - assist->col; if (current < 0) { int32_t row = manager->total_items / assist->col; if (assist->current % assist->col < manager->total_items % assist->col) { current = row * assist->col + assist->current % assist->col; } else { current = row * assist->col - (assist->col - assist->current % assist->col); } assist->index_first = get_last_index(); } else if (current < assist->index_first) { current = assist->current - assist->col; assist->index_first = assist->index_first - assist->col; } } else if (key == KEY_DOWN) { current = assist->current + assist->col; if (current >= manager->total_items) { int32_t last_index = get_last_index(); if (assist->index_first < last_index) { assist->index_first = last_index; current = assist->current; } else { assist->index_first = 0; current = assist->current % assist->col; } } else if (current >= assist->index_first + assist->total) { assist->index_first = assist->index_first + assist->col; } } else if (key == KEY_LEFT) { current = assist->current - 1; if (current < 0) { current = manager->total_items - 1; assist->index_first = get_last_index(); } else if (current < assist->index_first) { assist->index_first -= assist->col; } } else if (key == KEY_RIGHT) { current = assist->current + 1; if (current > manager->total_items - 1) { current = 0; assist->index_first = 0; } else if (current >= assist->index_first + assist->total) { assist->index_first = assist->index_first + assist->col; } } assist->current = current; assist_item_show(); return true; } static void keyboard_grab_cancel(struct seat_keyboard_grab *keyboard_grab) { tile_assist_done(); } static bool item_hover(struct seat *seat, struct ky_scene_node *node, double x, double y, uint32_t time, bool first, bool hold, void *data) { if (!data) { return false; } struct item_part *item_part = data; if (item_part->part == ASSIST_BUTTON_CLOSE) { set_close_buffer(item_part, BUTTON_STATE_HOVER); } int32_t i = 0; struct item *item_tmp; wl_list_for_each(item_tmp, &manager->items, link) { if (!item_tmp->tree) { continue; } if (i == manager->item_page->current) { ky_scene_node_set_enabled(item_tmp->selection, false); break; } i++; } ky_scene_node_set_enabled(item_part->item->part[ASSIST_BUTTON_CLOSE].node, true); ky_scene_node_set_enabled(item_part->item->selection, true); i = 0; wl_list_for_each(item_tmp, &manager->items, link) { if (!item_tmp->tree) { continue; } if (item_part->item == item_tmp) { manager->item_page->current = i; break; } i++; } return false; } static void item_leave(struct seat *seat, struct ky_scene_node *node, bool last, void *data) { if (!data) { return; } struct item_part *item_part = data; ky_scene_node_set_enabled(item_part->item->part[ASSIST_BUTTON_CLOSE].node, false); if (item_part->part == ASSIST_BUTTON_CLOSE) { set_close_buffer(item_part, BUTTON_STATE_NONE); } } static void item_click(struct seat *seat, struct ky_scene_node *node, uint32_t button, bool pressed, uint32_t time, enum click_state state, void *data) { if (pressed || button != BTN_LEFT) { return; } if (!data) { tile_assist_done(); return; } struct item_part *item_part = data; if (item_part->part != ASSIST_BUTTON_CLOSE) { item_view_set_tiled(item_part->item); return; } if (LEFT_BUTTON_PRESSED(button, pressed)) { set_close_buffer(item_part, BUTTON_STATE_CLICKED); } if (LEFT_BUTTON_RELEASED(button, pressed)) { kywc_view_close(&item_part->item->view->base); } } static const struct input_event_node_impl item_impl = { .hover = item_hover, .leave = item_leave, .click = item_click, }; static struct ky_scene_node *item_get_root(void *data) { return &manager->item_page->tree->node; } static const struct seat_keyboard_grab_interface keyboard_grab_impl = { .key = keyboard_grab_key, .cancel = keyboard_grab_cancel, }; static struct ky_scene_node *selected_box_paint(struct ky_scene_tree *parent, int w, int h, int border, int radius, float *color) { struct ky_scene_decoration *frame = ky_scene_decoration_create(parent); float select_color[4]; color_float_pa(select_color, color); ky_scene_decoration_set_margin_color(frame, (float[4]){ 0.f, 0.f, 0.f, 0.f }, select_color); ky_scene_decoration_set_surface_size(frame, w, h); ky_scene_decoration_set_margin(frame, 0, border); ky_scene_decoration_set_mask(frame, DECORATION_MASK_ALL); ky_scene_decoration_set_round_corner_radius(frame, (int[4]){ radius, radius, radius, radius }); return ky_scene_node_from_decoration(frame); } static bool item_init(struct item *item) { if (!KYWC_VIEW_IS_RESIZABLE(&item->view->base) || item->view->base.skip_switcher || item->view->parent) { return false; } item->snapshot = snapshot_create_from_view(item->view, 1.0f, SNAPSHOT_RENDER_DISABLE_BLUR); if (!item->snapshot) { item_destroy(item); return false; } int32_t item_w = manager->fixed_width; int32_t item_h = manager->fixed_height; struct theme *theme = theme_manager_get_theme(); item->tree = ky_scene_tree_create(manager->item_page->tree); manager->total_items++; item->selection = selected_box_paint(item->tree, item_w - 2 * SELECT_BORDER_WIDTH, item_h - 2 * SELECT_BORDER_WIDTH, SELECT_BORDER_WIDTH, theme->window_radius, theme->accent_color); ky_scene_node_set_enabled(item->selection, false); ky_scene_node_set_position(item->selection, SELECT_BORDER_WIDTH, SELECT_BORDER_WIDTH); for (int32_t i = ASSIST_ROOT; i < ASSIST_PART_COUNT; i++) { item->part[i].part = i; item->part[i].item = item; item->part[i].scale = 1.0; int32_t x = 0, y = 0; if (i == ASSIST_ROOT) { item->part[i].node = &item->tree->node; } else if (i == ASSIST_BUTTON_CLOSE || i == ASSIST_TITLE_ICON) { struct ky_scene_buffer *buf = scaled_buffer_create(item->tree, item->part[i].scale, item_buffer_update, item_buffer_destroy, &item->part[i]); item->part[i].node = &buf->node; if (i == ASSIST_BUTTON_CLOSE) { ky_scene_node_set_enabled(item->part[i].node, false); set_close_buffer(&item->part[i], BUTTON_STATE_NONE); x = item_w - SELECT_BORDER_WIDTH - ITEM_GAP - theme->button_width; y = SELECT_BORDER_WIDTH + (ITEM_HEIGHT - SELECT_BORDER_WIDTH - theme->button_width) * 0.5; } else { icon_buffer_update(&item->part[i], item->part[i].scale); x = SELECT_BORDER_WIDTH + ITEM_GAP; y = SELECT_BORDER_WIDTH + (ITEM_HEIGHT - SELECT_BORDER_WIDTH - theme->icon_size) * 0.5; } } else if (i == ASSIST_TITLE_TEXT) { item->title_text = widget_create(item->tree); item->part[i].node = ky_scene_node_from_widget(item->title_text); item->max_text_width = item_w - 4 * ITEM_GAP - 2 * SELECT_BORDER_WIDTH - theme->icon_size - theme->button_width; title_text_update(item); x = SELECT_BORDER_WIDTH + theme->icon_size + 2 * ITEM_GAP; y = SELECT_BORDER_WIDTH + (ITEM_HEIGHT - item->text_height) * 0.5; } else if (i == ASSIST_FRAME_RECT) { struct ky_scene_rect *background = ky_scene_rect_create(item->tree, item_w, item_h, (float[4]){ 0.f, 0.f, 0.f, 0.f }); item->part[i].node = &background->node; } else if (i == ASSIST_THUMBNAIL) { int32_t tmp_width = item_w - 2 * SELECT_BORDER_WIDTH; int32_t tmp_height = item_h - ITEM_HEIGHT - 2 * SELECT_BORDER_WIDTH; int32_t view_width = item->view->base.geometry.width; int32_t view_height = item->view->base.geometry.height; x = SELECT_BORDER_WIDTH; y = ITEM_HEIGHT + SELECT_BORDER_WIDTH; float tmp_ratio = (float)tmp_width / tmp_height; float view_ratio = (float)view_width / view_height; if (tmp_ratio < view_ratio) { item->snapshot_width = tmp_width; item->snapshot_height = tmp_width / view_ratio; y += (tmp_height - item->snapshot_height) / 2; } else { item->snapshot_height = tmp_height; item->snapshot_width = tmp_height * view_ratio; x += (tmp_width - item->snapshot_width) / 2; } struct ky_scene_buffer *buffer = snapshot_create_scene_buffer(item->tree, item->snapshot); if (!buffer) { continue; } ky_scene_buffer_set_dest_size(buffer, item->snapshot_width, item->snapshot_height); item->part[i].node = &buffer->node; } ky_scene_node_set_position(item->part[i].node, x, y); input_event_node_create(item->part[i].node, &item_impl, item_get_root, NULL, &item->part[i]); } return true; } static void assist_init(struct assist *assist, enum kywc_tile tile, struct item_proxy *proxy) { assist->tiled = tile; assist->index_first = 0; assist->current = -1; assist->tree = ky_scene_tree_create(manager->tree); assist->background = ky_scene_rect_create(assist->tree, 0, 0, (float[4]){ 0.f, 0.f, 0.f, 0.f }); float color[4]; struct theme *theme = theme_manager_get_theme(); color_float_pax(color, theme->active_bg_color, theme->opacity / 100.0); assist->assist_area = ky_scene_rect_create(assist->tree, 0, 0, color); input_event_node_create(&assist->tree->node, &item_impl, item_get_root, NULL, NULL); int radius = theme->window_radius; ky_scene_node_set_radius(&assist->assist_area->node, (int[4]){ radius, radius, radius, radius }); /* add blur */ pixman_region32_t region; pixman_region32_init(®ion); ky_scene_node_set_blur_region(&assist->assist_area->node, theme->opacity != 100 ? ®ion : NULL); pixman_region32_fini(®ion); if (proxy) { assist_area_set_view(assist, proxy->item->view); } else { tile_calculate_view_geometry(&manager->tiled_layer, manager->output, &assist->full_area, tile); } } static void assist_get_split_type(void) { bool is_half_screen = false; bool is_quarter_screen = false; enum kywc_tile tile = KYWC_TILE_NONE; for (int32_t i = 0; i < SLOT_TYPE_COUNT; i++) { if (!manager->tiled_layer.proxy[i]) { continue; } if (manager->tiled_layer.proxy[i]->item->view->base.tiled == KYWC_TILE_LEFT || manager->tiled_layer.proxy[i]->item->view->base.tiled == KYWC_TILE_RIGHT || manager->tiled_layer.proxy[i]->item->view->base.tiled == KYWC_TILE_TOP || manager->tiled_layer.proxy[i]->item->view->base.tiled == KYWC_TILE_BOTTOM) { is_half_screen = true; tile = manager->tiled_layer.proxy[i]->item->view->base.tiled; } else { is_quarter_screen = true; } } if (is_half_screen && is_quarter_screen) { manager->state = THREE_SCREEN; if (tile == KYWC_TILE_LEFT) { manager->type = TYPE_THREE_LEFT; } else if (tile == KYWC_TILE_RIGHT) { manager->type = TYPE_THREE_RIGHT; } else if (tile == KYWC_TILE_TOP) { manager->type = TYPE_THREE_TOP; } else if (tile == KYWC_TILE_BOTTOM) { manager->type = TYPE_THREE_BOTTOM; } } else if (is_half_screen) { manager->state = HALF_SCREEN; if (tile == KYWC_TILE_LEFT || tile == KYWC_TILE_RIGHT) { manager->type = TYPE_HALF_LEFT_OR_RIGHT; } else if (tile == KYWC_TILE_TOP || tile == KYWC_TILE_BOTTOM) { manager->type = TYPE_HALF_TOP_OR_BOTTOM; } } else { manager->state = QUARTER_SCREEN; manager->type = TYPE_QUARTER; } } static void create_assist_area(void) { assist_get_split_type(); if (manager->type == TYPE_HALF_LEFT_OR_RIGHT) { assist_init(&manager->assist[0], KYWC_TILE_LEFT, manager->tiled_layer.proxy[0]); assist_init(&manager->assist[1], KYWC_TILE_RIGHT, manager->tiled_layer.proxy[1]); } else if (manager->type == TYPE_HALF_TOP_OR_BOTTOM) { assist_init(&manager->assist[0], KYWC_TILE_TOP, manager->tiled_layer.proxy[0]); assist_init(&manager->assist[1], KYWC_TILE_BOTTOM, manager->tiled_layer.proxy[2]); } else if (manager->type == TYPE_THREE_LEFT) { assist_init(&manager->assist[0], KYWC_TILE_LEFT, manager->tiled_layer.proxy[0]); assist_init(&manager->assist[1], KYWC_TILE_TOP_RIGHT, manager->tiled_layer.proxy[1]); assist_init(&manager->assist[2], KYWC_TILE_BOTTOM_RIGHT, manager->tiled_layer.proxy[3]); } else if (manager->type == TYPE_THREE_RIGHT) { assist_init(&manager->assist[0], KYWC_TILE_TOP_LEFT, manager->tiled_layer.proxy[0]); assist_init(&manager->assist[1], KYWC_TILE_BOTTOM_LEFT, manager->tiled_layer.proxy[2]); assist_init(&manager->assist[2], KYWC_TILE_RIGHT, manager->tiled_layer.proxy[1]); } else if (manager->type == TYPE_THREE_TOP) { assist_init(&manager->assist[0], KYWC_TILE_TOP, manager->tiled_layer.proxy[0]); assist_init(&manager->assist[1], KYWC_TILE_BOTTOM_LEFT, manager->tiled_layer.proxy[2]); assist_init(&manager->assist[2], KYWC_TILE_BOTTOM_RIGHT, manager->tiled_layer.proxy[3]); } else if (manager->type == TYPE_THREE_BOTTOM) { assist_init(&manager->assist[0], KYWC_TILE_TOP_LEFT, manager->tiled_layer.proxy[0]); assist_init(&manager->assist[1], KYWC_TILE_TOP_RIGHT, manager->tiled_layer.proxy[1]); assist_init(&manager->assist[2], KYWC_TILE_BOTTOM, manager->tiled_layer.proxy[2]); } else if (manager->type == TYPE_QUARTER) { assist_init(&manager->assist[0], KYWC_TILE_TOP_LEFT, manager->tiled_layer.proxy[0]); assist_init(&manager->assist[1], KYWC_TILE_TOP_RIGHT, manager->tiled_layer.proxy[1]); assist_init(&manager->assist[2], KYWC_TILE_BOTTOM_LEFT, manager->tiled_layer.proxy[2]); assist_init(&manager->assist[3], KYWC_TILE_BOTTOM_RIGHT, manager->tiled_layer.proxy[3]); } } static void handle_new_mapped_view(struct wl_listener *listener, void *data) { struct kywc_view *kywc_view = data; if (kywc_view->role != KYWC_VIEW_ROLE_NORMAL) { return; } manager->active_view = NULL; tile_assist_done(); } static bool tiled_assist_views_check(struct view *view) { for (int i = 0; i < SLOT_TYPE_COUNT; i++) { if (manager->tiled_layer.proxy[i] && manager->tiled_layer.proxy[i]->item->view == view) { return true; } } return false; } static void tiled_assist_show(void) { create_assist_area(); item_page_assist_update(); if (!manager->item_page) { tile_assist_done(); return; } struct tiled_output *tiled_output = tiled_output_from_kywc_output(manager->active_view->output); struct tiled_place *place = get_tiled_place(tiled_output, manager->active_view->current_proxy->workspace); struct item_proxy *proxy; wl_list_for_each_reverse(proxy, &place->item_proxys, place_link) { if (manager->active_view == proxy->item->view) { continue; } if (tiled_assist_views_check(proxy->item->view)) { continue; } struct item *item = item_create(proxy->item->view, true); if (item) { item_init(item); } } if (manager->total_items == 0) { tile_assist_done(); return; } ky_scene_node_set_enabled(&manager->tree->node, true); assist_item_show(); } static void handle_configured_output(struct wl_listener *listener, void *data) { tile_assist_done(); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { tile_assist_done(); } static void handle_show_desktop(struct wl_listener *listener, void *data) { manager->active_view = NULL; tile_assist_done(); } static void handle_workspace_activate(struct wl_listener *listener, void *data) { if (!manager->workspace->activated) { tile_assist_done(); } } static bool views_full_check(struct tiled_layer *layer) { int lenght = 0; for (int i = 0; i < SLOT_TYPE_COUNT; i++) { if (layer->proxy[i]) { lenght++; } } return lenght == SLOT_TYPE_COUNT; } static void handle_highlight_view(struct wl_listener *listener, void *data) { bool enable = !view_manager_get_highlight(); ky_scene_node_set_enabled(&manager->tree->node, enable); if (enable) { for (int32_t i = 0; i < SLOT_TYPE_COUNT; i++) { struct assist *assist = &manager->assist[i]; if (assist->view) { ky_scene_node_set_enabled(&assist->view->tree->node, true); } } struct item *item; wl_list_for_each(item, &manager->items, link) { if (!item->view->base.minimized) { ky_scene_node_set_enabled(&item->view->tree->node, false); } } } } void view_begin_tile_assist(struct view *view, struct seat *seat, struct output *output, struct tiled_layer *tiled_layer) { if (manager || views_full_check(tiled_layer)) { return; } manager = calloc(1, sizeof(*manager)); if (!manager) { return; } wl_list_init(&manager->items); view_do_activate(NULL); seat_focus_surface(seat, NULL); manager->seat = seat; manager->workspace = view->current_proxy->workspace; manager->tiled_layer = *tiled_layer; manager->pointer_grab = (struct seat_pointer_grab){ &pointer_grab_impl, seat, manager }; seat_start_pointer_grab(seat, &manager->pointer_grab); manager->keyboard_grab = (struct seat_keyboard_grab){ &keyboard_grab_impl, seat, manager }; seat_start_keyboard_grab(seat, &manager->keyboard_grab); manager->touch_grab = (struct seat_touch_grab){ &touch_grab_impl, seat, manager }; seat_start_touch_grab(seat, &manager->touch_grab); struct kywc_box *usable_area = &output->usable_area; manager->fixed_width = (usable_area->width - 160) / 4; manager->fixed_height = usable_area->height / 22 * 5; struct view_layer *layer = view_manager_get_layer(LAYER_SWITCHER, false); manager->tree = ky_scene_tree_create(layer->tree); ky_scene_node_set_enabled(&manager->tree->node, false); manager->new_mapped_view.notify = handle_new_mapped_view; kywc_view_add_new_mapped_listener(&manager->new_mapped_view); manager->configured_output.notify = handle_configured_output; output_manager_add_configured_listener(&manager->configured_output); manager->seat_destroy.notify = handle_seat_destroy; wl_signal_add(&seat->events.destroy, &manager->seat_destroy); manager->show_desktop.notify = handle_show_desktop; view_manager_add_show_desktop_listener(&manager->show_desktop); manager->workspace_activate.notify = handle_workspace_activate; wl_signal_add(&manager->workspace->events.activate, &manager->workspace_activate); manager->highlight.notify = handle_highlight_view; view_manager_add_highlight_view_listener(&manager->highlight); manager->output = output; manager->item_page = NULL; manager->selected = NULL; manager->total_items = 0; manager->active_view = view; tiled_assist_show(); } kylin-wayland-compositor/src/view/tile/tile_manager.c0000664000175000017500000011304415160461067021761 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include "output.h" #include "tile_manager_p.h" #include "util/macros.h" #include "view/workspace.h" #define EXTRA_RANGE 1 // calculate offset enum ratio_state { RATIO_NONE = 0, RATIO_POSITER = 1 << 0, RATIO_SIZE = 1 << 1, RATIO_ALL = (1 << 2) - 1, }; static struct tiled_manager { struct wl_list tiled_outputs; struct wl_listener new_mapped_view; struct wl_listener new_output; struct wl_listener server_destroy; } *manager = NULL; static void tile_get_view_geometry(struct view *view, struct kywc_box *geometry, struct kywc_output *kywc_output, enum kywc_tile tile); static void item_ratio_update(struct tiled_item *item, enum ratio_state ratio); static void tile_view_fix_geometry(struct view *view, struct kywc_box *geo, struct kywc_output *kywc_output) { struct kywc_view *kywc_view = &view->base; *geo = kywc_view->geometry; struct tiled_output *tiled_output = tiled_output_from_kywc_output(view->output); if (!tiled_output) { tile_get_view_geometry(view, geo, kywc_output, view->base.tiled); return; } struct tiled_place *place = get_tiled_place(tiled_output, view->current_proxy->workspace); struct item_proxy *proxy = item_proxy_from_place(view, place); if (!proxy) { return; } proxy->item->kywc_output = kywc_output; struct output *new_output = output_from_kywc_output(kywc_output); struct kywc_box *dst_box = &new_output->usable_area; geo->width = round(dst_box->width * proxy->item->ratio_w) - kywc_view->margin.off_width; geo->height = round(dst_box->height * proxy->item->ratio_h) - kywc_view->margin.off_height; geo->x = dst_box->x + round(proxy->item->ratio_x * dst_box->width) + kywc_view->margin.off_x; geo->y = dst_box->y + round(proxy->item->ratio_y * dst_box->height) + kywc_view->margin.off_y; geo->width = MAX(view->base.min_width, geo->width); geo->height = MAX(view->base.min_height, geo->height); geo->x = MIN(geo->x, dst_box->x + dst_box->width - geo->width); geo->y = MIN(geo->y, dst_box->y + dst_box->height - geo->height); } static void get_view_geometry(struct view *view, struct kywc_box *geo); static int calculate_view_width(struct tiled_layer *layer, int slot_1, int slot_2, int slot_3, struct kywc_box *usable, enum kywc_tile tile) { int width = usable->width / 2; struct kywc_box box, diagonal = { 0 }; if (layer->proxy[slot_3] && layer->proxy[slot_3]->item->view->base.tiled != tile) { get_view_geometry(layer->proxy[slot_3]->item->view, &diagonal); if (slot_3 == SLOT_TYPE_TOP_LEFT || slot_3 == SLOT_TYPE_BOTTOM_LEFT) { width = usable->x + usable->width - (diagonal.x + diagonal.width); } else { width = diagonal.x - usable->x; } } if (layer->proxy[slot_1] && layer->proxy[slot_1]->item->view->base.tiled != tile) { get_view_geometry(layer->proxy[slot_1]->item->view, &box); if (diagonal.width == 0 || diagonal.height == 0) { if (slot_1 == SLOT_TYPE_TOP_LEFT || slot_1 == SLOT_TYPE_BOTTOM_LEFT) { width = box.x + box.width - usable->x; } else { width = usable->x + usable->width - box.x; } } else if (box.height < diagonal.height && layer->proxy[slot_2]) { get_view_geometry(layer->proxy[slot_2]->item->view, &box); if (slot_2 == SLOT_TYPE_TOP_LEFT || slot_2 == SLOT_TYPE_BOTTOM_LEFT) { if (usable->x + usable->width - (box.x + box.width) < width) { width = usable->x + usable->width - (box.x + box.width); } } else if (box.x - usable->x < width) { width = box.x - usable->x; } return width; } } if (layer->proxy[slot_2]) { get_view_geometry(layer->proxy[slot_2]->item->view, &box); if (slot_2 == SLOT_TYPE_TOP_LEFT || slot_2 == SLOT_TYPE_BOTTOM_LEFT) { width = usable->x + usable->width - (box.x + box.width); } else { width = box.x - usable->x; } } return width; } static int calculate_view_height(struct tiled_layer *layer, int slot_1, int slot_2, int slot_3, struct kywc_box *usable, enum kywc_tile tile) { int height = usable->height / 2; struct kywc_box box; if (layer->proxy[slot_1]) { get_view_geometry(layer->proxy[slot_1]->item->view, &box); if (slot_1 == SLOT_TYPE_TOP_LEFT || slot_1 == SLOT_TYPE_TOP_RIGHT) { height = usable->y + usable->height - (box.y + box.height); } else { height = box.y - usable->y; } } else if (layer->proxy[slot_2] && layer->proxy[slot_2]->item->view->base.tiled != tile) { get_view_geometry(layer->proxy[slot_2]->item->view, &box); if (slot_2 == SLOT_TYPE_TOP_LEFT || slot_2 == SLOT_TYPE_TOP_RIGHT) { height = usable->y + usable->height - (box.y + box.height); } else { height = box.y - usable->y; } } else if (layer->proxy[slot_3] && layer->proxy[slot_3]->item->view->base.tiled != tile) { get_view_geometry(layer->proxy[slot_3]->item->view, &box); if (slot_3 == SLOT_TYPE_TOP_LEFT || slot_3 == SLOT_TYPE_TOP_RIGHT) { height = box.y + box.height - usable->y; } else { height = usable->y + usable->height - box.y; } } return height; } void tile_calculate_view_geometry(struct tiled_layer *layer, struct output *output, struct kywc_box *geo, enum kywc_tile tile) { struct kywc_box *usable = &output->usable_area; if (tile == KYWC_TILE_LEFT || tile == KYWC_TILE_RIGHT || tile == KYWC_TILE_TOP || tile == KYWC_TILE_BOTTOM) { bool left_or_right = tile == KYWC_TILE_LEFT || tile == KYWC_TILE_RIGHT; geo->width = left_or_right ? usable->width / 2 : usable->width; geo->height = left_or_right ? usable->height : usable->height / 2; geo->x = tile == KYWC_TILE_RIGHT ? usable->x + geo->width : usable->x; geo->y = tile == KYWC_TILE_BOTTOM ? usable->y + geo->height : usable->y; if (tiled_layer_is_empty(layer)) { return; } geo->width = usable->width; geo->height = usable->height; int remain = 0; for (int i = 0; i < SLOT_TYPE_COUNT; i++) { if (!layer->proxy[i]) { continue; } struct kywc_box box; get_view_geometry(layer->proxy[i]->item->view, &box); if (left_or_right) { int u_x2 = usable->x + usable->width; remain = tile == KYWC_TILE_LEFT ? box.x - usable->x : u_x2 - (box.x + box.width); if (remain < geo->width) { geo->width = remain; geo->x = tile == KYWC_TILE_RIGHT ? u_x2 - remain : geo->x; } } else { int u_y2 = usable->y + usable->height; remain = tile == KYWC_TILE_TOP ? box.y - usable->y : u_y2 - (box.y + box.height); if (remain < geo->height) { geo->height = remain; geo->y = tile == KYWC_TILE_BOTTOM ? u_y2 - remain : geo->y; } } } return; } if (tile == KYWC_TILE_TOP_LEFT) { geo->x = usable->x; geo->y = usable->y; geo->width = calculate_view_width(layer, SLOT_TYPE_BOTTOM_LEFT, SLOT_TYPE_TOP_RIGHT, SLOT_TYPE_BOTTOM_RIGHT, usable, KYWC_TILE_BOTTOM); geo->height = calculate_view_height(layer, SLOT_TYPE_BOTTOM_LEFT, SLOT_TYPE_BOTTOM_RIGHT, SLOT_TYPE_TOP_RIGHT, usable, KYWC_TILE_RIGHT); } else if (tile == KYWC_TILE_TOP_RIGHT) { geo->width = calculate_view_width(layer, SLOT_TYPE_BOTTOM_RIGHT, SLOT_TYPE_TOP_LEFT, SLOT_TYPE_BOTTOM_LEFT, usable, KYWC_TILE_BOTTOM); geo->height = calculate_view_height(layer, SLOT_TYPE_BOTTOM_RIGHT, SLOT_TYPE_BOTTOM_LEFT, SLOT_TYPE_TOP_LEFT, usable, KYWC_TILE_LEFT); geo->x = usable->x + usable->width - geo->width; geo->y = usable->y; } else if (tile == KYWC_TILE_BOTTOM_LEFT) { geo->width = calculate_view_width(layer, SLOT_TYPE_TOP_LEFT, SLOT_TYPE_BOTTOM_RIGHT, SLOT_TYPE_TOP_RIGHT, usable, KYWC_TILE_TOP); geo->height = calculate_view_height(layer, SLOT_TYPE_TOP_LEFT, SLOT_TYPE_TOP_RIGHT, SLOT_TYPE_BOTTOM_RIGHT, usable, KYWC_TILE_RIGHT); geo->x = usable->x; geo->y = usable->y + usable->height - geo->height; } else if (tile == KYWC_TILE_BOTTOM_RIGHT) { geo->width = calculate_view_width(layer, SLOT_TYPE_TOP_RIGHT, SLOT_TYPE_BOTTOM_LEFT, SLOT_TYPE_TOP_LEFT, usable, KYWC_TILE_TOP); geo->height = calculate_view_height(layer, SLOT_TYPE_TOP_RIGHT, SLOT_TYPE_TOP_LEFT, SLOT_TYPE_BOTTOM_LEFT, usable, KYWC_TILE_LEFT); geo->x = usable->x + usable->width - geo->width; geo->y = usable->y + usable->height - geo->height; } } static void item_save_tiled_geometry(struct view *view, struct kywc_box *geometry) { struct tiled_output *view_output = tiled_output_from_kywc_output(view->output); if (view_output) { struct tiled_place *place = get_tiled_place(view_output, view->current_proxy->workspace); struct item_proxy *proxy = item_proxy_from_place(view, place); if (proxy) { proxy->item->tiled_geo = *geometry; } } } static void tile_get_view_geometry(struct view *view, struct kywc_box *geometry, struct kywc_output *kywc_output, enum kywc_tile tile) { struct tiled_layer layer = { 0 }; struct tiled_output *tiled_output = tiled_output_from_kywc_output(kywc_output); if (tiled_output) { struct tiled_place *place = get_tiled_place(tiled_output, view->current_proxy->workspace); form_virtual_layer_by_tile(view, place, &layer, tile); } /* When calculating the tile range, if it is still in the layer, it will not be calculated. */ for (int i = 0; i < SLOT_TYPE_COUNT; i++) { if (layer.proxy[i] && layer.proxy[i]->item->view == view) { return; } } struct output *output = output_from_kywc_output(kywc_output); tile_calculate_view_geometry(&layer, output, geometry, tile); int32_t min_width = view->base.min_width + view->base.margin.off_width; int32_t min_height = view->base.min_height + view->base.margin.off_height; bool use_min_width = geometry->width < min_width; bool use_min_height = geometry->height < min_height; geometry->width = MAX(min_width, geometry->width); geometry->height = MAX(min_height, geometry->height); struct kywc_box *usable = &output->usable_area; if (use_min_width && (tile == KYWC_TILE_RIGHT || tile == KYWC_TILE_TOP_RIGHT || tile == KYWC_TILE_BOTTOM_RIGHT)) { geometry->x = usable->x + usable->width - geometry->width; } if (use_min_height && (tile == KYWC_TILE_BOTTOM || tile == KYWC_TILE_BOTTOM_LEFT || tile == KYWC_TILE_BOTTOM_RIGHT)) { geometry->y = usable->y + usable->height - geometry->height; } geometry->x = MAX(geometry->x, usable->x); geometry->y = MAX(geometry->y, usable->y); geometry->x += view->base.margin.off_x; geometry->y += view->base.margin.off_y; geometry->width -= view->base.margin.off_width; geometry->height -= view->base.margin.off_height; item_save_tiled_geometry(view, geometry); } static void remove_all_item_proxy(struct tiled_item *item); static void update_all_item_proxy(struct tiled_item *item); static void tile_view_apply_tiling(struct view *view, enum kywc_tile tile, struct kywc_output *kywc_output) { struct kywc_output *output = kywc_output ? kywc_output : view->output; struct tiled_output *tiled_output = tiled_output_from_kywc_output(output); struct item_proxy *proxy = NULL; if (tiled_output) { struct tiled_place *place = get_tiled_place(tiled_output, view->current_proxy->workspace); proxy = item_proxy_from_place(view, place); if (proxy) { remove_all_item_proxy(proxy->item); } } struct kywc_box geo = { 0 }; view_get_tiled_geometry(view, &geo, output, tile); if (proxy && view->base.tiled == tile && output == view->output && kywc_box_equal(&view->base.geometry, &geo)) { update_all_item_proxy(proxy->item); return; } view_do_tiled(view, tile, output, &geo); } static bool item_need_tile_assist(struct tiled_item *item) { return item->tiled_geo.x == item->view->base.geometry.x && item->tiled_geo.y == item->view->base.geometry.y && item->tiled_geo.width == item->view->base.geometry.width && item->tiled_geo.height == item->view->base.geometry.height; } static void view_try_show_assist(struct view *view) { struct tiled_output *tiled_output = tiled_output_from_kywc_output(view->output); if (!tiled_output || !tiled_output->in_assist || !tiled_output->assist_seat) { return; } struct tiled_place *view_place = get_tiled_place(tiled_output, view->current_proxy->workspace); struct item_proxy *proxy = item_proxy_from_place(view, view_place); if (!proxy) { return; } if (item_need_tile_assist(proxy->item)) { if (proxy->layer) { tiled_output->in_assist = false; struct seat *seat = tiled_output->assist_seat; tiled_output->assist_seat = NULL; view_begin_tile_assist(view, seat, output_from_kywc_output(view->output), proxy->layer); } } } static void tile_view_tiled_assist(struct view *view, struct seat *seat, struct kywc_output *kywc_output) { struct tiled_output *tiled_output = tiled_output_from_kywc_output(kywc_output); if (!tiled_output) { return; } tiled_output->in_assist = true; tiled_output->assist_seat = seat; view_try_show_assist(view); } struct item_proxy *item_proxy_from_place(struct view *view, struct tiled_place *place) { if (!place) { return NULL; } struct item_proxy *proxy; wl_list_for_each(proxy, &place->item_proxys, place_link) { if (proxy->item->view == view) { return proxy; } } return NULL; } struct tiled_output *tiled_output_from_kywc_output(struct kywc_output *kywc_output) { if (!manager) { return NULL; } struct tiled_output *tiled_output; wl_list_for_each(tiled_output, &manager->tiled_outputs, link) { if (tiled_output->kywc_output == kywc_output || !strcmp(tiled_output->output_name, kywc_output->name)) { return tiled_output; } } return NULL; } static void handle_workspace_destroy(struct wl_listener *listener, void *data) { struct tiled_place *place = wl_container_of(listener, place, workspace_destroy); wl_list_remove(&place->workspace_destroy.link); wl_list_remove(&place->link); free(place); } struct tiled_place *get_tiled_place(struct tiled_output *tiled_output, struct workspace *workspace) { if (!tiled_output) { return NULL; } struct tiled_place *place; wl_list_for_each(place, &tiled_output->tiled_places, link) { if (place->workspace == workspace) { return place; } } /* create a new place for this workspace */ place = calloc(1, sizeof(*place)); if (!place) { return NULL; } place->workspace = workspace; place->tiled_output = tiled_output; wl_list_init(&place->item_proxys); wl_list_init(&place->layers); wl_list_insert(&tiled_output->tiled_places, &place->link); place->workspace_destroy.notify = handle_workspace_destroy; wl_signal_add(&workspace->events.destroy, &place->workspace_destroy); return place; } static void get_view_geometry(struct view *view, struct kywc_box *geo) { geo->x = view->base.geometry.x - view->base.margin.off_x; geo->y = view->base.geometry.y - view->base.margin.off_y; geo->width = view->base.geometry.width + view->base.margin.off_width; geo->height = view->base.geometry.height + view->base.margin.off_height; } static void get_min_view_geometry(struct view *view, struct kywc_box *geo, struct kywc_box *usable, int tile) { geo->width = (view->base.min_width ? view->base.min_width : VIEW_MIN_WIDTH) + view->base.margin.off_width; geo->height = (view->base.min_height ? view->base.min_height : VIEW_MIN_HEIGHT) + view->base.margin.off_height; geo->x = usable->x; geo->y = usable->y; if (tile == KYWC_TILE_LEFT) { geo->height = usable->height; } else if (tile == KYWC_TILE_RIGHT) { geo->height = usable->height; geo->x = usable->x + usable->width - geo->width; } else if (tile == KYWC_TILE_TOP) { geo->width = usable->width; } else if (tile == KYWC_TILE_BOTTOM) { geo->width = usable->width; geo->y = usable->y + usable->height - geo->height; } else if (tile == KYWC_TILE_TOP_RIGHT) { geo->x = usable->x + usable->width - geo->width; } else if (tile == KYWC_TILE_BOTTOM_LEFT) { geo->y = usable->y + usable->height - geo->height; } else if (tile == KYWC_TILE_BOTTOM_RIGHT) { geo->x = usable->x + usable->width - geo->width; geo->y = usable->y + usable->height - geo->height; } } static bool item_proxy_is_suit(struct item_proxy *target, struct tiled_place *place, int tile) { struct kywc_box box; get_view_geometry(target->item->view, &box); struct output *output = output_from_kywc_output(place->tiled_output->kywc_output); struct kywc_box *usable = &output->usable_area; struct kywc_box extend_box = { .x = usable->x - EXTRA_RANGE, .y = usable->y - EXTRA_RANGE, .width = usable->width + EXTRA_RANGE * 2, .height = usable->height + EXTRA_RANGE * 2, }; if (!kywc_box_contains_point(&extend_box, box.x, box.y) || !kywc_box_contains_point(&extend_box, box.x + box.width - 1, box.y + box.height - 1)) { return false; } struct wlr_box result_box; struct wlr_box target_box = { box.x, box.y, box.width, box.height }; if (place->operating_view) { struct item_proxy *operating = item_proxy_from_place(place->operating_view, place); if (operating && operating->layer) { get_view_geometry(place->operating_view, &box); } else { get_min_view_geometry(place->operating_view, &box, usable, tile); } struct wlr_box operating_box = { box.x, box.y, box.width, box.height }; if (wlr_box_intersection(&result_box, &target_box, &operating_box)) { return false; } } struct item_proxy *proxy; wl_list_for_each(proxy, &place->item_proxys, place_link) { if (proxy == target) { break; } if (place->operating_view && proxy->item->view == place->operating_view) { continue; } get_view_geometry(proxy->item->view, &box); struct wlr_box item_box = { box.x + EXTRA_RANGE, box.y + EXTRA_RANGE, box.width - EXTRA_RANGE * 2, box.height - EXTRA_RANGE * 2, }; if (wlr_box_intersection(&result_box, &item_box, &target_box)) { return false; } } return true; } static struct item_proxy *item_proxy_from_place_layers(struct tiled_place *place, int slot) { struct tiled_layer *layer; wl_list_for_each(layer, &place->layers, link) { if (layer->proxy[slot]) { return layer->proxy[slot]; } } return NULL; } static void item_proxy_remove_layer(struct item_proxy *proxy); static void layer_set_slot_proxy(struct tiled_layer *layer, struct item_proxy *proxy, int slot) { if (!proxy) { return; } if (!layer->on_layers) { layer->proxy[slot] = proxy; return; } item_proxy_remove_layer(proxy); layer->proxy[slot] = proxy; proxy->layer = layer; } static void tiled_layer_slot_update(struct tiled_layer *layer, int slot, int tile) { layer->proxy[slot] = NULL; struct item_proxy *proxy = item_proxy_from_place_layers(layer->place, slot); if (!proxy) { return; } if (item_proxy_is_suit(proxy, layer->place, tile)) { layer_set_slot_proxy(layer, proxy, slot); } } static void layer_update_related_slots(struct tiled_layer *layer, int tile) { if (tile == KYWC_TILE_LEFT) { tiled_layer_slot_update(layer, SLOT_TYPE_TOP_RIGHT, tile); tiled_layer_slot_update(layer, SLOT_TYPE_BOTTOM_RIGHT, tile); } else if (tile == KYWC_TILE_RIGHT) { tiled_layer_slot_update(layer, SLOT_TYPE_TOP_LEFT, tile); tiled_layer_slot_update(layer, SLOT_TYPE_BOTTOM_LEFT, tile); } else if (tile == KYWC_TILE_TOP) { tiled_layer_slot_update(layer, SLOT_TYPE_BOTTOM_LEFT, tile); tiled_layer_slot_update(layer, SLOT_TYPE_BOTTOM_RIGHT, tile); } else if (tile == KYWC_TILE_BOTTOM) { tiled_layer_slot_update(layer, SLOT_TYPE_TOP_LEFT, tile); tiled_layer_slot_update(layer, SLOT_TYPE_TOP_RIGHT, tile); } else if (tile == KYWC_TILE_TOP_LEFT) { tiled_layer_slot_update(layer, SLOT_TYPE_TOP_RIGHT, tile); tiled_layer_slot_update(layer, SLOT_TYPE_BOTTOM_LEFT, tile); tiled_layer_slot_update(layer, SLOT_TYPE_BOTTOM_RIGHT, tile); } else if (tile == KYWC_TILE_TOP_RIGHT) { tiled_layer_slot_update(layer, SLOT_TYPE_TOP_LEFT, tile); tiled_layer_slot_update(layer, SLOT_TYPE_BOTTOM_LEFT, tile); tiled_layer_slot_update(layer, SLOT_TYPE_BOTTOM_RIGHT, tile); } else if (tile == KYWC_TILE_BOTTOM_LEFT) { tiled_layer_slot_update(layer, SLOT_TYPE_TOP_LEFT, tile); tiled_layer_slot_update(layer, SLOT_TYPE_TOP_RIGHT, tile); tiled_layer_slot_update(layer, SLOT_TYPE_BOTTOM_RIGHT, tile); } else if (tile == KYWC_TILE_BOTTOM_RIGHT) { tiled_layer_slot_update(layer, SLOT_TYPE_TOP_LEFT, tile); tiled_layer_slot_update(layer, SLOT_TYPE_TOP_RIGHT, tile); tiled_layer_slot_update(layer, SLOT_TYPE_BOTTOM_LEFT, tile); } } static void set_place_operating_view(struct tiled_place *place, struct view *view) { place->operating_view = view; } void form_virtual_layer_by_tile(struct view *view, struct tiled_place *place, struct tiled_layer *layer, enum kywc_tile tile) { struct item_proxy *proxy = item_proxy_from_place(view, place); if (proxy && proxy->layer) { memcpy(layer, proxy->layer, sizeof(struct tiled_layer)); } set_place_operating_view(place, view); layer->place = place; layer->on_layers = false; layer_update_related_slots(layer, tile); } static struct tiled_layer *tiled_layer_create(struct item_proxy *proxy) { if (proxy->layer) { return proxy->layer; } struct tiled_layer *layer = calloc(1, sizeof(*layer)); if (!layer) { return NULL; } wl_list_insert(&proxy->place->layers, &layer->link); proxy->layer = layer; layer->on_layers = true; layer->place = proxy->place; set_place_operating_view(proxy->place, proxy->item->view); return layer; } static void tiled_layer_update(struct tiled_layer *layer, struct item_proxy *proxy) { if (!layer) { return; } if (proxy->item->view->base.tiled == KYWC_TILE_LEFT) { layer->proxy[SLOT_TYPE_TOP_LEFT] = proxy; layer->proxy[SLOT_TYPE_BOTTOM_LEFT] = proxy; } else if (proxy->item->view->base.tiled == KYWC_TILE_RIGHT) { layer->proxy[SLOT_TYPE_TOP_RIGHT] = proxy; layer->proxy[SLOT_TYPE_BOTTOM_RIGHT] = proxy; } else if (proxy->item->view->base.tiled == KYWC_TILE_TOP) { layer->proxy[SLOT_TYPE_TOP_LEFT] = proxy; layer->proxy[SLOT_TYPE_TOP_RIGHT] = proxy; } else if (proxy->item->view->base.tiled == KYWC_TILE_BOTTOM) { layer->proxy[SLOT_TYPE_BOTTOM_LEFT] = proxy; layer->proxy[SLOT_TYPE_BOTTOM_RIGHT] = proxy; } else if (proxy->item->view->base.tiled == KYWC_TILE_TOP_LEFT) { layer->proxy[SLOT_TYPE_TOP_LEFT] = proxy; } else if (proxy->item->view->base.tiled == KYWC_TILE_TOP_RIGHT) { layer->proxy[SLOT_TYPE_TOP_RIGHT] = proxy; } else if (proxy->item->view->base.tiled == KYWC_TILE_BOTTOM_LEFT) { layer->proxy[SLOT_TYPE_BOTTOM_LEFT] = proxy; } else if (proxy->item->view->base.tiled == KYWC_TILE_BOTTOM_RIGHT) { layer->proxy[SLOT_TYPE_BOTTOM_RIGHT] = proxy; } layer_update_related_slots(layer, proxy->item->view->base.tiled); } bool tiled_layer_is_empty(struct tiled_layer *layer) { for (int i = 0; i < SLOT_TYPE_COUNT; i++) { if (layer->proxy[i]) { return false; } } return true; } static void tiled_layer_destroy(struct tiled_layer *layer) { wl_list_remove(&layer->link); free(layer); } static void item_proxy_remove_layer(struct item_proxy *proxy) { if (!proxy->layer) { return; } struct tiled_layer *layer = proxy->layer; proxy->layer = NULL; for (int i = 0; i < SLOT_TYPE_COUNT; i++) { if (layer->proxy[i] && layer->proxy[i] == proxy) { layer->proxy[i] = NULL; } } if (tiled_layer_is_empty(layer)) { tiled_layer_destroy(layer); } } static void remove_all_item_proxy(struct tiled_item *item) { struct item_proxy *proxy; wl_list_for_each(proxy, &item->item_proxys, item_link) { item_proxy_remove_layer(proxy); } } static void update_all_item_proxy(struct tiled_item *item) { struct item_proxy *proxy; wl_list_for_each(proxy, &item->item_proxys, item_link) { struct tiled_layer *layer = tiled_layer_create(proxy); tiled_layer_update(layer, proxy); } } bool view_need_tile_managerd(struct view *view) { return view->base.tiled && view->base.tiled <= KYWC_TILE_BOTTOM_RIGHT; } static void item_proxy_destroy(struct item_proxy *proxy); static void tiled_item_destroy(struct tiled_item *item) { struct item_proxy *proxy, *tmp; wl_list_for_each_safe(proxy, tmp, &item->item_proxys, item_link) { item_proxy_destroy(proxy); } wl_list_remove(&item->view_tile.link); wl_list_remove(&item->view_unmap.link); wl_list_remove(&item->view_minimize.link); wl_list_remove(&item->view_fullscreen.link); wl_list_remove(&item->view_activate.link); wl_list_remove(&item->view_output.link); wl_list_remove(&item->view_size.link); wl_list_remove(&item->view_position.link); wl_list_remove(&item->workspace_enter.link); wl_list_remove(&item->workspace_leave.link); free(item); } static void handle_view_unmap(struct wl_listener *listener, void *data) { struct tiled_item *item = wl_container_of(listener, item, view_unmap); tiled_item_destroy(item); } static void handle_view_minimize(struct wl_listener *listener, void *data) { struct tiled_item *item = wl_container_of(listener, item, view_minimize); if (!view_need_tile_managerd(item->view)) { return; } remove_all_item_proxy(item); if (!item->view->base.minimized) { update_all_item_proxy(item); } } static void handle_view_fullscreen(struct wl_listener *listener, void *data) { struct tiled_item *item = wl_container_of(listener, item, view_fullscreen); if (!view_need_tile_managerd(item->view)) { return; } if (item->view->base.fullscreen) { remove_all_item_proxy(item); } else { update_all_item_proxy(item); } } static void handle_view_tile(struct wl_listener *listener, void *data) { struct tiled_item *item = wl_container_of(listener, item, view_tile); remove_all_item_proxy(item); if (!view_need_tile_managerd(item->view)) { struct tiled_output *tiled_output = tiled_output_from_kywc_output(item->view->output); tiled_output->in_assist = false; return; } item_ratio_update(item, RATIO_ALL); update_all_item_proxy(item); view_try_show_assist(item->view); } static void handle_view_activate(struct wl_listener *listener, void *data) { struct tiled_item *item = wl_container_of(listener, item, view_activate); if (!item->view->base.activated) { return; } struct item_proxy *proxy; wl_list_for_each(proxy, &item->item_proxys, item_link) { wl_list_remove(&proxy->place_link); wl_list_insert(&proxy->place->item_proxys, &proxy->place_link); } if (view_need_tile_managerd(item->view)) { remove_all_item_proxy(item); update_all_item_proxy(item); } } static void handle_view_output(struct wl_listener *listener, void *data) { struct tiled_item *item = wl_container_of(listener, item, view_output); struct tiled_output *tiled_output = tiled_output_from_kywc_output(item->view->output); struct item_proxy *proxy; wl_list_for_each(proxy, &item->item_proxys, item_link) { struct tiled_place *new = get_tiled_place(tiled_output, proxy->place->workspace); if (!new) { continue; } wl_list_remove(&proxy->place_link); wl_list_init(&proxy->place_link); wl_list_insert(&new->item_proxys, &proxy->place_link); proxy->place = new; } remove_all_item_proxy(item); item->kywc_output = item->view->output; if (view_need_tile_managerd(item->view)) { update_all_item_proxy(item); } } static void item_ratio_update(struct tiled_item *item, enum ratio_state ratio) { struct kywc_view *view = &item->view->base; struct kywc_box *geo = &view->geometry; struct output *output = output_from_kywc_output(item->view->output); struct kywc_box *usable = &output->usable_area; if (ratio & RATIO_POSITER) { if (usable->width > 0) { item->ratio_x = (float)(geo->x - view->margin.off_x - usable->x) / usable->width; } if (usable->height > 0) { item->ratio_y = (float)(geo->y - view->margin.off_y - usable->y) / usable->height; } } if (ratio & RATIO_SIZE) { if (usable->width > 0) { item->ratio_w = (float)(geo->width + view->margin.off_width) / usable->width; } if (usable->height > 0) { item->ratio_h = (float)(geo->height + view->margin.off_height) / usable->height; } } } static void handle_view_size(struct wl_listener *listener, void *data) { struct tiled_item *item = wl_container_of(listener, item, view_size); if (!view_need_tile_managerd(item->view)) { return; } if (item->kywc_output != item->view->output) { return; } item_ratio_update(item, RATIO_SIZE); view_try_show_assist(item->view); } static void handle_view_position(struct wl_listener *listener, void *data) { struct tiled_item *item = wl_container_of(listener, item, view_position); if (!view_need_tile_managerd(item->view)) { return; } item_ratio_update(item, item->view->pending.configure_action & VIEW_ACTION_RESIZE ? RATIO_POSITER : RATIO_ALL); view_try_show_assist(item->view); } static void item_proxy_create(struct tiled_item *item, struct tiled_place *place) { struct item_proxy *proxy = calloc(1, sizeof(*proxy)); if (!proxy) { return; } proxy->place = place; proxy->item = item; wl_list_insert(&place->item_proxys, &proxy->place_link); wl_list_insert(&item->item_proxys, &proxy->item_link); if (view_need_tile_managerd(item->view)) { struct tiled_layer *layer = tiled_layer_create(proxy); tiled_layer_update(layer, proxy); } } static void item_proxy_destroy(struct item_proxy *proxy) { item_proxy_remove_layer(proxy); wl_list_remove(&proxy->place_link); wl_list_remove(&proxy->item_link); free(proxy); } static void handle_view_workspace_enter(struct wl_listener *listener, void *data) { struct tiled_item *item = wl_container_of(listener, item, workspace_enter); struct workspace *workspace = data; struct tiled_output *tiled_output = tiled_output_from_kywc_output(item->view->output); struct tiled_place *place = get_tiled_place(tiled_output, workspace); if (place && !item_proxy_from_place(item->view, place)) { item_proxy_create(item, place); } } static void handle_view_workspace_leave(struct wl_listener *listener, void *data) { struct tiled_item *item = wl_container_of(listener, item, workspace_leave); struct workspace *workspace = data; struct tiled_output *tiled_output = tiled_output_from_kywc_output(item->view->output); struct tiled_place *place = get_tiled_place(tiled_output, workspace); struct item_proxy *proxy = item_proxy_from_place(item->view, place); if (proxy) { item_proxy_destroy(proxy); } } static void handle_new_mapped_view(struct wl_listener *listener, void *data) { struct kywc_view *kywc_view = data; struct view *view = view_from_kywc_view(kywc_view); if (!view->current_proxy) { return; } struct tiled_item *item = calloc(1, sizeof(*item)); if (!item) { return; } wl_list_init(&item->item_proxys); item->view = view; item->kywc_output = view->output; item->view_unmap.notify = handle_view_unmap; wl_signal_add(&kywc_view->events.unmap, &item->view_unmap); item->view_minimize.notify = handle_view_minimize; wl_signal_add(&kywc_view->events.minimize, &item->view_minimize); item->view_fullscreen.notify = handle_view_fullscreen; wl_signal_add(&kywc_view->events.fullscreen, &item->view_fullscreen); item->view_tile.notify = handle_view_tile; wl_signal_add(&kywc_view->events.tile, &item->view_tile); item->view_activate.notify = handle_view_activate; wl_signal_add(&kywc_view->events.activate, &item->view_activate); item->view_output.notify = handle_view_output; wl_signal_add(&view->events.output, &item->view_output); item->view_size.notify = handle_view_size; wl_signal_add(&kywc_view->events.size, &item->view_size); item->view_position.notify = handle_view_position; wl_signal_add(&view->events.position, &item->view_position); item->workspace_enter.notify = handle_view_workspace_enter; wl_signal_add(&view->events.workspace_enter, &item->workspace_enter); item->workspace_leave.notify = handle_view_workspace_leave; wl_signal_add(&view->events.workspace_leave, &item->workspace_leave); struct tiled_output *tiled_output = tiled_output_from_kywc_output(item->view->output); struct tiled_place *place = get_tiled_place(tiled_output, view->current_proxy->workspace); struct item_proxy *proxy = item_proxy_from_place(item->view, place); if (place && !proxy) { item_proxy_create(item, place); } } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->new_mapped_view.link); wl_list_remove(&manager->server_destroy.link); wl_list_remove(&manager->new_output.link); struct tiled_output *tiled_output, *tmp; wl_list_for_each_safe(tiled_output, tmp, &manager->tiled_outputs, link) { wl_list_remove(&tiled_output->link); if (tiled_output->linkage) { tile_linkage_destroy(tiled_output->linkage); tiled_output->linkage = NULL; } free(tiled_output->output_name); free(tiled_output); } free(manager); manager = NULL; } static void handle_output_destroy(struct wl_listener *listener, void *data) { struct tiled_output *tiled_output = wl_container_of(listener, tiled_output, output_destroy); wl_list_remove(&tiled_output->output_destroy.link); tiled_output->kywc_output = NULL; if (tiled_output->linkage) { tile_linkage_destroy(tiled_output->linkage); tiled_output->linkage = NULL; } } static void handle_new_output(struct wl_listener *listener, void *data) { struct kywc_output *kywc_output = data; struct tiled_output *tiled_output = tiled_output_from_kywc_output(kywc_output); if (!tiled_output) { if (!(tiled_output = calloc(1, sizeof(*tiled_output)))) { return; } wl_list_init(&tiled_output->tiled_places); wl_list_insert(&manager->tiled_outputs, &tiled_output->link); tiled_output->output_name = strdup(kywc_output->name); } tiled_output->kywc_output = kywc_output; tiled_output->output_destroy.notify = handle_output_destroy; wl_signal_add(&kywc_output->events.destroy, &tiled_output->output_destroy); } bool tile_manager_create(struct view_manager *view_manager) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } wl_list_init(&manager->tiled_outputs); view_manager->impl.fix_geometry = tile_view_fix_geometry; view_manager->impl.get_tiled_geometry = tile_get_view_geometry; view_manager->impl.set_tiled = tile_view_apply_tiling; view_manager->impl.show_tile_assist = tile_view_tiled_assist; manager->new_mapped_view.notify = handle_new_mapped_view; kywc_view_add_new_mapped_listener(&manager->new_mapped_view); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(view_manager->server, &manager->server_destroy); manager->new_output.notify = handle_new_output; kywc_output_add_new_listener(&manager->new_output); return true; } kylin-wayland-compositor/src/view/config.c0000664000175000017500000003762715160461067017656 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include "config.h" #include "util/dbus.h" #include "view/workspace.h" #include "view_p.h" static const char *service_path = "/com/kylin/Wlcom/View"; static const char *service_interface = "com.kylin.Wlcom.View"; struct close_window { struct wl_list link; struct view *view; struct wl_listener unmap; /* use unmap, wps is not destroyed */ }; static struct close_windows_context { struct wl_list windows; struct wl_event_source *timer; sd_bus_message *msg; } *context = NULL; static void close_windows_context_destroy(bool ok) { if (context->timer) { wl_event_source_remove(context->timer); } struct close_window *window, *tmp; wl_list_for_each_safe(window, tmp, &context->windows, link) { wl_list_remove(&window->unmap.link); wl_list_remove(&window->link); free(window); } if (context->msg) { sd_bus_reply_method_return(context->msg, "b", ok); sd_bus_message_unref(context->msg); } free(context); context = NULL; } static void handle_view_unmap(struct wl_listener *listener, void *data) { struct close_window *window = wl_container_of(listener, window, unmap); wl_list_remove(&window->unmap.link); wl_list_remove(&window->link); free(window); if (wl_list_empty(&context->windows)) { close_windows_context_destroy(true); } } static int handle_timeout(void *data) { close_windows_context_destroy(false); return 0; } static int close_windows(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { if (context) { close_windows_context_destroy(false); } context = calloc(1, sizeof(*context)); if (!context) { return sd_bus_reply_method_return(m, "b", false); } struct view_manager *manager = userdata; struct workspace *workspace = workspace_manager_get_current(); wl_list_init(&context->windows); struct view *view; wl_list_for_each(view, &manager->views, link) { if (!view->base.mapped || !view->current_proxy) { continue; } /* move all views to current workspace */ view_set_workspace(view, workspace); } struct view_proxy *view_proxy; wl_list_for_each(view_proxy, &workspace->view_proxies, workspace_link) { view = view_proxy->view; if (!view->base.mapped || !view->impl->close) { continue; } struct close_window *window = calloc(1, sizeof(*window)); if (!window) { continue; } window->view = view; wl_list_insert(&context->windows, &window->link); window->unmap.notify = handle_view_unmap; wl_signal_add(&view->base.events.unmap, &window->unmap); /* call close callback to skip view_is_closeable */ view->impl->close(view); } /* no window needs to close, return true */ if (wl_list_empty(&context->windows)) { close_windows_context_destroy(false); return sd_bus_reply_method_return(m, "b", true); } context->timer = wl_event_loop_add_timer(manager->server->event_loop, handle_timeout, context); if (!context->timer) { close_windows_context_destroy(false); return sd_bus_reply_method_return(m, "b", false); } wl_event_source_timer_update(context->timer, 10000); context->msg = sd_bus_message_ref(m); return 1; } static int have_active_fullscreen_view(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct view_manager *manager = userdata; struct view *view = manager->activated.view; if (view && view->base.fullscreen && view->base.role == KYWC_VIEW_ROLE_NORMAL) { struct view_layer *view_layer = view_manager_get_layer_by_node(&view->tree->node, false); if (view_layer->layer == LAYER_ACTIVE) { return sd_bus_reply_method_return(m, "b", true); } } return sd_bus_reply_method_return(m, "b", false); } static int get_view_adsorption(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct view_manager *manager = userdata; return sd_bus_reply_method_return(m, "u", manager->state.view_adsorption); } static int set_view_adsorption(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct view_manager *manager = userdata; uint32_t adsorption; CK(sd_bus_message_read(m, "u", &adsorption)); if (adsorption > VIEW_ADSORPTION_ALL) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid adsorption."); return sd_bus_reply_method_error(m, &error); } manager->state.view_adsorption = adsorption; return sd_bus_reply_method_return(m, NULL); } static int set_csd_round_corner(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct view_manager *manager = userdata; uint32_t enabled; CK(sd_bus_message_read(m, "b", &enabled)); if (manager->state.csd_round_corner != enabled) { manager->state.csd_round_corner = enabled; struct view *view; wl_list_for_each(view, &manager->views, link) { if (view->base.ssd == KYWC_SSD_NONE) { view_update_round_corner(view); } } } return sd_bus_reply_method_return(m, NULL); } static int get_minimize_effect(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct view_manager *manager = userdata; return sd_bus_reply_method_return(m, "u", manager->state.minimize_effect_type); } static int set_minimize_effect(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct view_manager *manager = userdata; uint32_t minimize_effect; CK(sd_bus_message_read(m, "u", &minimize_effect)); if (minimize_effect < MINIMIZE_EFFECT_TYPE_NUM) { manager->state.minimize_effect_type = minimize_effect; } return sd_bus_reply_method_return(m, NULL); } static int set_resize_filter(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct view_manager *manager = userdata; uint32_t resize_filter[2]; CK(sd_bus_message_read(m, "uu", &resize_filter[0], &resize_filter[1])); for (int i = 0; i < 2; i++) { if (resize_filter[i] <= 100) { manager->state.resize_filter[i] = resize_filter[i]; } } return sd_bus_reply_method_return(m, NULL); } static int list_all_views(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct view_manager *manager = userdata; sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(m, &reply)); CK(sd_bus_message_open_container(reply, 'a', "(ssi)")); struct view *view; wl_list_for_each(view, &manager->views, link) { if (!view->base.mapped) { continue; } const char *app_id = view->base.app_id; const char *uuid = view->base.uuid; int pid = view->pid; sd_bus_message_append(reply, "(ssi)", app_id, uuid, pid); } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } static int list_view_states(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *uuid; CK(sd_bus_message_read(m, "s", &uuid)); struct kywc_view *kywc_view = kywc_view_by_uuid(uuid); if (!kywc_view) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid uuid."); return sd_bus_reply_method_error(m, &error); } sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(m, &reply)); CK(sd_bus_message_open_container(reply, 'a', "(sai)")); sd_bus_message_append(reply, "(sai)", "role", 1, kywc_view->role); sd_bus_message_append(reply, "(sai)", "geometry", 4, kywc_view->geometry.x, kywc_view->geometry.y, kywc_view->geometry.width, kywc_view->geometry.height); sd_bus_message_append(reply, "(sai)", "margin", 4, kywc_view->margin.off_x, kywc_view->margin.off_y, kywc_view->margin.off_width, kywc_view->margin.off_height); sd_bus_message_append(reply, "(sai)", "padding", 4, kywc_view->padding.top, kywc_view->padding.bottom, kywc_view->padding.left, kywc_view->padding.right); sd_bus_message_append(reply, "(sai)", "ssd", 1, kywc_view->ssd); sd_bus_message_append(reply, "(sai)", "minimize size", 2, kywc_view->min_width, kywc_view->min_height); sd_bus_message_append(reply, "(sai)", "maximize size", 2, kywc_view->max_width, kywc_view->max_height); sd_bus_message_append(reply, "(sai)", "kept_above", 1, kywc_view->kept_above); sd_bus_message_append(reply, "(sai)", "kept_below", 1, kywc_view->kept_below); sd_bus_message_append(reply, "(sai)", "minimized", 1, kywc_view->minimized); sd_bus_message_append(reply, "(sai)", "maximized", 1, kywc_view->maximized); sd_bus_message_append(reply, "(sai)", "fullscreen", 1, kywc_view->fullscreen); sd_bus_message_append(reply, "(sai)", "activated", 1, kywc_view->activated); sd_bus_message_append(reply, "(sai)", "tiled", 1, kywc_view->tiled); sd_bus_message_append(reply, "(sai)", "modal", 1, kywc_view->modal); sd_bus_message_append(reply, "(sai)", "skip_taskbar", 1, kywc_view->skip_taskbar); sd_bus_message_append(reply, "(sai)", "skip_switcher", 1, kywc_view->skip_switcher); CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } static int list_all_modes(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct view_manager *manager = userdata; sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(m, &reply)); CK(sd_bus_message_open_container(reply, 'a', "(sb)")); struct view_mode *mode; wl_list_for_each(mode, &manager->view_modes, link) { sd_bus_message_append(reply, "(sb)", mode->impl->name, mode == manager->mode); } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } #if 0 static int set_view_mode(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *name; CK(sd_bus_message_read(m, "s", &name)); if (!view_manager_set_view_mode(name)) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid mode name."); return sd_bus_reply_method_error(m, &error); } return sd_bus_reply_method_return(m, NULL); } #endif static const sd_bus_vtable service_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("CloseWindows", "", "b", close_windows, 0), SD_BUS_METHOD("HaveFullScreenActiveWindow", "", "b", have_active_fullscreen_view, 0), SD_BUS_METHOD("GetViewAdsorption", "", "u", get_view_adsorption, 0), SD_BUS_METHOD("SetViewAdsorption", "u", "", set_view_adsorption, 0), SD_BUS_METHOD("SetCSDRoundCorner", "b", "", set_csd_round_corner, 0), SD_BUS_METHOD("GetMinimizeEffect", "", "u", get_minimize_effect, 0), SD_BUS_METHOD("SetMinimizeEffect", "u", "", set_minimize_effect, 0), SD_BUS_METHOD("SetResizeFilter", "uu", "", set_resize_filter, 0), SD_BUS_METHOD("ListAllViews", "", "a(ssi)", list_all_views, 0), SD_BUS_METHOD("ListViewStates", "s", "a(sai)", list_view_states, 0), SD_BUS_METHOD("ListAllModes", "", "a(sb)", list_all_modes, 0), // SD_BUS_METHOD("SetViewMode", "s", "", set_view_mode, 0), SD_BUS_VTABLE_END, }; bool view_manager_config_init(struct view_manager *view_manager) { view_manager->config = config_manager_add_config("Views"); if (!view_manager->config) { return false; } return dbus_register_object(NULL, service_path, service_interface, service_vtable, view_manager); } bool view_read_config(struct view_manager *view_manager) { if (!view_manager->config || !view_manager->config->json) { return false; } json_object *data; if (json_object_object_get_ex(view_manager->config->json, "num_workspaces", &data)) { view_manager->state.num_workspaces = json_object_get_int(data); } if (json_object_object_get_ex(view_manager->config->json, "workspace_names", &data)) { struct array_list *array = json_object_get_array(data); if (array) { for (uint32_t i = 0; i < view_manager->state.num_workspaces; i++) { struct json_object *item = array_list_get_idx(array, i); view_manager->state.workspace_names[i] = json_object_get_string(item); } } } if (json_object_object_get_ex(view_manager->config->json, "view_adsorption", &data)) { view_manager->state.view_adsorption = json_object_get_int(data); } if (json_object_object_get_ex(view_manager->config->json, "csd_round_corner", &data)) { view_manager->state.csd_round_corner = json_object_get_boolean(data); } if (json_object_object_get_ex(view_manager->config->json, "view_mode", &data)) { const char *name = json_object_get_string(data); view_manager->mode = view_manager_mode_from_name(name); } if (json_object_object_get_ex(view_manager->config->json, "minimize_effect", &data)) { int minimize_effect = json_object_get_int(data); if (minimize_effect < MINIMIZE_EFFECT_TYPE_NUM) { view_manager->state.minimize_effect_type = minimize_effect; } } if (json_object_object_get_ex(view_manager->config->json, "resize_filter", &data)) { struct array_list *array = json_object_get_array(data); if (array) { for (uint32_t i = 0; i < 2; i++) { struct json_object *item = array_list_get_idx(array, i); uint32_t resize_filter = json_object_get_int(item); if (resize_filter <= 100) { view_manager->state.resize_filter[i] = resize_filter; } } } } return true; } static bool add_workspaces_arr(struct workspace *workspace, void *data) { struct json_object *arry = (struct json_object *)data; json_object_array_add(arry, workspace->has_custom_name ? json_object_new_string(workspace->name) : json_object_new_string("")); return false; } static struct json_object *get_workspaces_arr(void) { struct json_object *arry = json_object_new_array(); workspace_manager_for_each_workspace(add_workspaces_arr, arry); return arry; } static struct json_object *get_resize_filter_arr(struct view_manager *view_manager) { struct json_object *arry = json_object_new_array(); for (int i = 0; i < 2; i++) { json_object_array_add(arry, json_object_new_int(view_manager->state.resize_filter[i])); } return arry; } void view_write_config(struct view_manager *view_manager) { if (!view_manager->config) { return; } json_object_object_add(view_manager->config->json, "num_workspaces", json_object_new_int(workspace_manager_get_count())); json_object_object_add(view_manager->config->json, "workspace_names", get_workspaces_arr()); json_object_object_add(view_manager->config->json, "view_adsorption", json_object_new_int(view_manager->state.view_adsorption)); json_object_object_add(view_manager->config->json, "csd_round_corner", json_object_new_boolean(view_manager->state.csd_round_corner)); json_object_object_add(view_manager->config->json, "view_mode", json_object_new_string(view_manager->mode->impl->name)); json_object_object_add(view_manager->config->json, "minimize_effect", json_object_new_int(view_manager->state.minimize_effect_type)); json_object_object_add(view_manager->config->json, "resize_filter", get_resize_filter_arr(view_manager)); } kylin-wayland-compositor/src/view/positioner.c0000664000175000017500000006072115160460057020571 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include "output.h" #include "util/hash_table.h" #include "view/workspace.h" #include "view_p.h" #define GRID_GAP_ROW (50) #define GRID_GAP_COLUMN (50) static struct positioner_manager { struct wl_list positioners; struct wl_event_loop *event_loop; struct wl_listener new_output; struct wl_listener server_destroy; } *manager = NULL; /* per output */ struct positioner { struct wl_list link; struct wl_list places; struct wl_list items; char *output_name; struct kywc_output *kywc_output; struct wl_listener output_on; struct wl_listener output_off; struct wl_listener output_usable_area; struct wl_listener output_destroy; struct { struct kywc_box box; struct wl_list positioners; struct wl_list link; } catcher; struct kywc_box usable_area; struct wl_event_source *saved_geometry_timeout; }; /* per workspace in positioner */ struct place { struct wl_list link; struct positioner *positioner; struct workspace *workspace; struct wl_listener workspace_destroy; struct wl_list entries; struct hash_table *groups; }; /* entries group with app_id */ struct group { const char *app_id; struct wl_list entries; // entry.group_link uint32_t hash; }; /* per view insert to place slot */ struct entry { struct wl_list link; struct place *place; struct wl_list group_link; struct group *group; struct kywc_view *view; struct wl_listener view_position; struct wl_listener view_size; struct wl_listener view_unmap; struct wl_listener view_workspace; struct wl_listener view_activate; int lx, ly; bool skip_update; struct kywc_box saved_geometry; bool skip_clear_saved_geometry; }; /* need auto resize when output usable area changed */ struct item { struct wl_list link; struct positioner *positioner; struct kywc_view *view; struct wl_listener view_output; struct wl_listener view_unmap; }; static void positioner_move_views(struct positioner *pos, struct kywc_box *src_box, struct positioner *dst_pos, bool skip_update) { struct place *place; wl_list_for_each(place, &pos->places, link) { struct entry *entry, *tmp; wl_list_for_each_safe(entry, tmp, &place->entries, link) { /* keep entry's place, so we can store when output re-enabled or plugin */ entry->skip_update = skip_update; /* move to dst */ view_move_to_output(view_from_kywc_view(entry->view), src_box, &entry->saved_geometry, dst_pos->kywc_output); } } } static void positioner_handle_output_on(struct wl_listener *listener, void *data) { struct positioner *pos = wl_container_of(listener, pos, output_on); struct output *output = output_from_kywc_output(pos->kywc_output); pos->usable_area = output->usable_area; if (!wl_list_empty(&pos->catcher.link)) { positioner_move_views(pos, &pos->catcher.box, pos, false); wl_list_remove(&pos->catcher.link); wl_list_init(&pos->catcher.link); } } static void positioner_handle_output_off(struct wl_listener *listener, void *data) { struct positioner *src = wl_container_of(listener, src, output_off); struct positioner *pos, *dst = NULL; wl_list_for_each(pos, &manager->positioners, link) { if (pos != src && pos->kywc_output && pos->kywc_output->state.enabled) { dst = pos; break; } } if (!dst) { kywc_log(KYWC_INFO, "No cather for output %s", src->output_name); return; } wl_list_insert(&dst->catcher.positioners, &src->catcher.link); src->catcher.box = dst->usable_area; positioner_move_views(src, &src->usable_area, dst, true); struct positioner *tmp; wl_list_for_each_safe(pos, tmp, &src->catcher.positioners, catcher.link) { wl_list_remove(&pos->catcher.link); wl_list_insert(&dst->catcher.positioners, &pos->catcher.link); pos->catcher.box = dst->usable_area; positioner_move_views(pos, &src->usable_area, dst, true); } } static void positioner_move_items(struct positioner *pos, struct kywc_box *src_box, struct positioner *dst_pos) { struct item *item, *tmp; wl_list_for_each_safe(item, tmp, &pos->items, link) { view_move_to_output(view_from_kywc_view(item->view), src_box, NULL, dst_pos->kywc_output); } } static void entry_save_or_clear_geometry(struct entry *entry, bool save) { if (save && kywc_box_empty(&entry->saved_geometry)) { entry->saved_geometry = entry->view->geometry; return; } if (!save && kywc_box_not_empty(&entry->saved_geometry)) { entry->saved_geometry = (struct kywc_box){ 0 }; } } static int positioner_handle_saved_geometry_timeout(void *data) { struct positioner *pos = data; struct place *place; wl_list_for_each(place, &pos->places, link) { struct entry *entry; wl_list_for_each(entry, &place->entries, link) { entry->skip_clear_saved_geometry = false; } } return 0; } static void positioner_handle_output_usable_area(struct wl_listener *listener, void *data) { struct positioner *pos = wl_container_of(listener, pos, output_usable_area); struct output *output = output_from_kywc_output(pos->kywc_output); if (kywc_box_equal(&pos->usable_area, &output->usable_area)) { return; } struct place *place; wl_list_for_each(place, &pos->places, link) { struct entry *entry; wl_list_for_each(entry, &place->entries, link) { entry->skip_clear_saved_geometry = true; entry_save_or_clear_geometry(entry, true); } } if (!pos->saved_geometry_timeout) { pos->saved_geometry_timeout = wl_event_loop_add_timer( manager->event_loop, positioner_handle_saved_geometry_timeout, pos); } uint32_t timeout = view_manager_get_configure_timeout(NULL); wl_event_source_timer_update(pos->saved_geometry_timeout, timeout); struct kywc_box old = pos->usable_area; pos->usable_area = output->usable_area; positioner_move_views(pos, &old, pos, false); positioner_move_items(pos, &old, pos); struct positioner *tmp; wl_list_for_each(tmp, &pos->catcher.positioners, catcher.link) { tmp->catcher.box = pos->usable_area; positioner_move_views(tmp, &old, pos, true); } } static void positioner_handle_output_destroy(struct wl_listener *listener, void *data) { struct positioner *pos = wl_container_of(listener, pos, output_destroy); if (pos->kywc_output->state.enabled) { positioner_handle_output_off(&pos->output_off, NULL); } if (pos->saved_geometry_timeout) { wl_event_source_remove(pos->saved_geometry_timeout); pos->saved_geometry_timeout = NULL; } wl_list_remove(&pos->output_destroy.link); wl_list_remove(&pos->output_on.link); wl_list_remove(&pos->output_off.link); wl_list_remove(&pos->output_usable_area.link); /* don't destroy positioner */ pos->kywc_output = NULL; } static struct positioner *positioner_from_output(struct kywc_output *kywc_output) { struct positioner *pos; wl_list_for_each(pos, &manager->positioners, link) { /* we don't destroy positioner when output destroy */ if (pos->kywc_output == kywc_output || !strcmp(pos->output_name, kywc_output->name)) { return pos; } } return NULL; } static void handle_new_output(struct wl_listener *listener, void *data) { struct kywc_output *kywc_output = data; struct positioner *pos = positioner_from_output(kywc_output); if (!pos) { if (!(pos = calloc(1, sizeof(*pos)))) { return; } wl_list_init(&pos->places); wl_list_init(&pos->items); wl_list_insert(&manager->positioners, &pos->link); pos->output_name = strdup(kywc_output->name); wl_list_init(&pos->catcher.link); wl_list_init(&pos->catcher.positioners); } pos->kywc_output = kywc_output; pos->output_on.notify = positioner_handle_output_on; wl_signal_add(&kywc_output->events.on, &pos->output_on); pos->output_off.notify = positioner_handle_output_off; wl_signal_add(&kywc_output->events.off, &pos->output_off); struct output *output = output_from_kywc_output(kywc_output); pos->output_usable_area.notify = positioner_handle_output_usable_area; wl_signal_add(&output->events.usable_area, &pos->output_usable_area); pos->output_destroy.notify = positioner_handle_output_destroy; wl_signal_add(&kywc_output->events.destroy, &pos->output_destroy); if (kywc_output->state.enabled) { positioner_handle_output_on(&pos->output_on, NULL); } } static void place_handle_workspace_destroy(struct wl_listener *listener, void *data) { struct place *place = wl_container_of(listener, place, workspace_destroy); wl_list_remove(&place->workspace_destroy.link); wl_list_remove(&place->link); hash_table_destroy(place->groups); free(place); } static struct place *positioner_get_place(struct positioner *pos, struct view *view) { if (!pos || !view->current_proxy) { return NULL; } struct workspace *workspace = view->current_proxy->workspace; /* get place by workspace */ struct place *place; wl_list_for_each(place, &pos->places, link) { if (place->workspace == workspace) { return place; } } /* create a new place for this workspace */ place = calloc(1, sizeof(*place)); if (!place) { return NULL; } place->workspace = workspace; place->positioner = pos; place->groups = hash_table_create_string(place); hash_table_set_max_entries(place->groups, 100); wl_list_init(&place->entries); wl_list_insert(&pos->places, &place->link); place->workspace_destroy.notify = place_handle_workspace_destroy; wl_signal_add(&workspace->events.destroy, &place->workspace_destroy); return place; } static void place_insert_entry(struct place *place, struct entry *entry) { entry->place = place; wl_list_insert(&place->entries, &entry->link); const char *app_id = entry->view->app_id; if (!app_id) { return; } struct hash_entry *hash_entry = hash_table_search(place->groups, app_id); if (!hash_entry) { struct group *group = calloc(1, sizeof(*group)); if (group) { group->app_id = strdup(app_id); wl_list_init(&group->entries); hash_entry = hash_table_insert(place->groups, group->app_id, group); } } if (!hash_entry) { return; } struct group *group = hash_entry->data; group->hash = hash_entry->hash; entry->group = group; wl_list_insert(&group->entries, &entry->group_link); } static void place_remove_entry(struct place *place, struct entry *entry) { struct group *group = entry->group; if (group) { wl_list_remove(&entry->group_link); wl_list_init(&entry->group_link); entry->group = NULL; if (wl_list_empty(&group->entries)) { hash_table_remove_hash(place->groups, group->hash, group->app_id); free((void *)group->app_id); free(group); } } wl_list_remove(&entry->link); entry->place = NULL; } static void place_update_entry(struct place *place, struct entry *entry) { if (place == entry->place) { return; } if (entry->place) { place_remove_entry(entry->place, entry); } place_insert_entry(place, entry); } static void entry_update_place(struct entry *entry) { struct kywc_view *kywc_view = entry->view; entry->lx = kywc_view->geometry.x; entry->ly = kywc_view->geometry.y; if (entry->skip_update) { entry->skip_update = false; return; } struct view *view = view_from_kywc_view(kywc_view); /* the view is on the most output */ struct kywc_output *kywc_output = view->output; int lx = kywc_view->geometry.x - kywc_view->margin.off_x; int ly = kywc_view->geometry.y - kywc_view->margin.off_y; if (!kywc_output_contains_point(kywc_output, lx, ly)) { kywc_output = kywc_output_at_point(lx, ly); } struct positioner *pos = positioner_from_output(kywc_output); struct place *place = positioner_get_place(pos, view); place_update_entry(place, entry); } static void entry_handle_view_size(struct wl_listener *listener, void *data) { struct entry *entry = wl_container_of(listener, entry, view_size); if (!entry->skip_clear_saved_geometry) { entry_save_or_clear_geometry(entry, false); } } static void entry_handle_view_position(struct wl_listener *listener, void *data) { struct entry *entry = wl_container_of(listener, entry, view_position); struct kywc_view *kywc_view = entry->view; if (!kywc_view->mapped) { return; } if (entry->lx == kywc_view->geometry.x && entry->ly == kywc_view->geometry.y) { return; } entry_update_place(entry); } static void entry_handle_view_unmap(struct wl_listener *listener, void *data) { struct entry *entry = wl_container_of(listener, entry, view_unmap); if (entry->place) { place_remove_entry(entry->place, entry); } entry_save_or_clear_geometry(entry, false); wl_list_remove(&entry->view_unmap.link); wl_list_remove(&entry->view_position.link); wl_list_remove(&entry->view_size.link); wl_list_remove(&entry->view_workspace.link); wl_list_remove(&entry->view_activate.link); free(entry); } static void entry_handle_view_workspace(struct wl_listener *listener, void *data) { struct entry *entry = wl_container_of(listener, entry, view_workspace); struct kywc_view *kywc_view = entry->view; struct view *view = view_from_kywc_view(kywc_view); /* view no longer in workspace */ if (!view->current_proxy) { wl_list_remove(&entry->view_position.link); wl_list_init(&entry->view_position.link); wl_list_remove(&entry->view_size.link); wl_list_init(&entry->view_size.link); wl_list_remove(&entry->view_activate.link); wl_list_init(&entry->view_activate.link); if (entry->place) { place_remove_entry(entry->place, entry); } return; } /* view is not mapped, like xwayland shell */ if (!kywc_view->mapped) { return; } /* add to workspace again */ if (!entry->place) { entry->lx = kywc_view->geometry.x; entry->ly = kywc_view->geometry.y; wl_signal_add(&view->events.position, &entry->view_position); wl_signal_add(&kywc_view->events.size, &entry->view_size); wl_signal_add(&kywc_view->events.activate, &entry->view_activate); struct positioner *pos = positioner_from_output(view->output); struct place *place = positioner_get_place(pos, view); place_insert_entry(place, entry); } /* we assume that the position of view is not changed */ struct positioner *pos = entry->place->positioner; struct place *new = positioner_get_place(pos, view); place_update_entry(new, entry); } static void entry_handle_view_activate(struct wl_listener *listener, void *data) { struct entry *entry = wl_container_of(listener, entry, view_activate); if (!entry->view->activated || !entry->group) { return; } wl_list_remove(&entry->group_link); wl_list_insert(&entry->group->entries, &entry->group_link); } static void item_handle_view_output(struct wl_listener *listener, void *data) { struct item *item = wl_container_of(listener, item, view_output); struct kywc_view *kywc_view = item->view; if (!kywc_view->mapped) { return; } struct view *view = view_from_kywc_view(kywc_view); struct kywc_output *kywc_output = view->output; if (kywc_output == item->positioner->kywc_output) { return; } struct positioner *pos = positioner_from_output(kywc_output); if (!pos) { return; } item->positioner = pos; wl_list_remove(&item->link); wl_list_insert(&pos->items, &item->link); } static void item_handle_view_unmap(struct wl_listener *listener, void *data) { struct item *item = wl_container_of(listener, item, view_unmap); wl_list_remove(&item->link); wl_list_remove(&item->view_unmap.link); wl_list_remove(&item->view_output.link); free(item); } static void positioner_add_item(struct positioner *pos, struct view *view) { if (!pos || view->base.role != KYWC_VIEW_ROLE_SYSTEMWINDOW) { return; } struct item *item = calloc(1, sizeof(*item)); if (!item) { return; } item->view = &view->base; item->view_output.notify = item_handle_view_output; wl_signal_add(&view->events.output, &item->view_output); item->view_unmap.notify = item_handle_view_unmap; wl_signal_add(&view->base.events.unmap, &item->view_unmap); item->positioner = pos; wl_list_insert(&pos->items, &item->link); } static void entry_fix_geometry(struct entry *entry, struct kywc_box *box, bool has_parent) { struct kywc_view *kywc_view = entry->view; struct view *view = view_from_kywc_view(kywc_view); struct kywc_output *kywc_output = view->output; if (has_parent) { kywc_output = view->parent->output; } else if (kywc_view->has_initial_position) { kywc_output = kywc_output_at_point(view->pending.geometry.x, view->pending.geometry.y); /* use the default output if position is not in the layout */ if (!kywc_output_contains_point(kywc_output, view->pending.geometry.x, view->pending.geometry.y)) { kywc_output = view->output; } } struct output *output = output_from_kywc_output(kywc_output); struct kywc_box *usable_area = &output->usable_area; bool need_resize = false; /* show view in the center of output or parent */ struct kywc_box geo = { .width = kywc_view->geometry.width + kywc_view->margin.off_width, .height = kywc_view->geometry.height + kywc_view->margin.off_height, }; if (kywc_view->has_initial_position) { geo.x = view->pending.geometry.x; geo.y = view->pending.geometry.y; } else { geo.x = box->x + (box->width - kywc_view->geometry.width) / 2 - kywc_view->margin.off_x; geo.y = box->y + (box->height - kywc_view->geometry.height) / 2 - kywc_view->margin.off_y; } /* make sure the view is in the usable area of output */ if (geo.width > usable_area->width) { geo.x = usable_area->x; geo.width = usable_area->width; need_resize = true; } else if (geo.x < usable_area->x) { geo.x = usable_area->x; } else if (geo.x + geo.width > usable_area->x + usable_area->width) { geo.x = usable_area->x + usable_area->width - geo.width; } if (geo.height > usable_area->height) { geo.y = usable_area->y; geo.height = usable_area->height; need_resize = true; } else if (geo.y < usable_area->y) { geo.y = usable_area->y; } else if (geo.y + geo.height > usable_area->y + usable_area->height) { geo.y = usable_area->y + usable_area->height - geo.height; } geo.x += kywc_view->margin.off_x; geo.y += kywc_view->margin.off_y; if (need_resize) { geo.width -= kywc_view->margin.off_width; geo.height -= kywc_view->margin.off_height; view_do_resize(view, &geo); } else { view_do_move(view, geo.x, geo.y); } } static struct entry *group_get_last_entry(struct group *group) { struct entry *entry; wl_list_for_each(entry, &group->entries, group_link) { if (!entry->view->minimized) { return entry; } } return NULL; } static void entry_fix_position(struct entry *entry, struct kywc_box *box, struct group *group) { struct entry *last = group_get_last_entry(group); if (!last) { entry_fix_geometry(entry, box, false); return; } struct kywc_view *kywc_view = entry->view; struct view *view = view_from_kywc_view(kywc_view); int width = kywc_view->geometry.width + kywc_view->margin.off_width; int height = kywc_view->geometry.height + kywc_view->margin.off_height; /* fallback to the fix_geometry */ if (width > box->width || height > box->height) { entry_fix_geometry(entry, box, false); return; } /* find a suitable position for the entry */ int lx = last->lx - kywc_view->margin.off_x; int ly = last->ly - kywc_view->margin.off_y; for (int i = 0; i < 2; i++) { if (lx < box->x) { lx = box->x; } if (ly < box->y) { ly = box->y; } if (lx + width > box->x + box->width) { lx = box->x; ly = box->y; } if (ly + height > box->y + box->height) { ly = box->y; } if (i == 0) { lx += GRID_GAP_COLUMN; ly += GRID_GAP_ROW; } } lx += kywc_view->margin.off_x; ly += kywc_view->margin.off_y; view_do_move(view, lx, ly); } void positioner_add_new_view(struct view *view) { if (!manager) { return; } assert(view->base.mapped == false); struct positioner *pos = positioner_from_output(view->output); struct place *place = positioner_get_place(pos, view); /* no output or workspace */ if (!place) { positioner_add_item(pos, view); return; } struct entry *entry = calloc(1, sizeof(*entry)); if (!entry) { return; } wl_list_init(&entry->link); wl_list_init(&entry->group_link); struct kywc_view *kywc_view = &view->base; entry->view = kywc_view; entry->lx = kywc_view->geometry.x; entry->ly = kywc_view->geometry.y; /* view alwayas has a workspace in view_init or xdg_activation */ entry->view_position.notify = entry_handle_view_position; wl_signal_add(&view->events.position, &entry->view_position); entry->view_size.notify = entry_handle_view_size; wl_signal_add(&kywc_view->events.size, &entry->view_size); entry->view_activate.notify = entry_handle_view_activate; wl_signal_add(&view->base.events.activate, &entry->view_activate); entry->view_unmap.notify = entry_handle_view_unmap; wl_signal_add(&kywc_view->events.unmap, &entry->view_unmap); entry->view_workspace.notify = entry_handle_view_workspace; wl_signal_add(&view->events.workspace, &entry->view_workspace); if (kywc_view->maximized || kywc_view->minimized || kywc_view->fullscreen) { place_insert_entry(place, entry); return; } bool has_parent = view->parent && view->parent->base.mapped; struct kywc_box *box = has_parent ? &view->parent->base.geometry : &pos->usable_area; if (has_parent) { entry_fix_geometry(entry, box, true); } else if (kywc_view->has_initial_position) { entry_fix_geometry(entry, box, false); } else { struct hash_entry *hash_entry = NULL; if (kywc_view->app_id) { hash_entry = hash_table_search(place->groups, kywc_view->app_id); } if (hash_entry && hash_entry->data) { struct group *group = hash_entry->data; assert(!wl_list_empty(&group->entries)); entry_fix_position(entry, box, group); } else { entry_fix_geometry(entry, box, false); } } /* view is not mapped current, entry->place must be NULL */ assert(entry->place == NULL); place_insert_entry(place, entry); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->server_destroy.link); wl_list_remove(&manager->new_output.link); struct positioner *pos, *tmp; wl_list_for_each_safe(pos, tmp, &manager->positioners, link) { wl_list_remove(&pos->link); free(pos->output_name); free(pos); } free(manager); manager = NULL; } bool positioner_manager_create(struct view_manager *view_manager) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } wl_list_init(&manager->positioners); manager->event_loop = view_manager->server->event_loop; manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(view_manager->server, &manager->server_destroy); manager->new_output.notify = handle_new_output; kywc_output_add_new_listener(&manager->new_output); return true; } kylin-wayland-compositor/src/view/tile_flyout.c0000664000175000017500000004717015160460057020740 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "effect/fade.h" #include "input/cursor.h" #include "output.h" #include "scene/decoration.h" #include "scene/linear_gradient.h" #include "theme.h" #include "util/color.h" #include "util/macros.h" #include "view_p.h" #define FLYOUT_WIDTH (204) #define FLYOUT_HEIGHT (80) #define FLYOUT_GAP (8) #define FLYOUT_LAYOUT_GAP (4) #define FLYOUT_ITEM_GAP (2) #define FLYOUT_BORDER (1) #define FLYOUT_OFFSET (2) enum tile_layout_mode { TILE_LAYOUT_MODE_HALF = 0, TILE_LAYOUT_MODE_QUARTER, TILE_LAYOUT_MODE_COUNT, }; struct tile_flyout_item { struct wl_list link; struct tile_flyout_layout *layout; struct ky_scene_linear_gradient *gradient; enum kywc_tile tile_type; }; struct tile_flyout_layout { struct wl_list link; struct tile_flyout *flyout; struct ky_scene_tree *tree; struct ky_scene_decoration *deco; struct wl_list items; enum tile_layout_mode mode; }; struct tile_flyout { struct wl_list link; struct seat *seat; struct wl_listener seat_destroy; struct view *view; struct wl_listener view_unmap; struct ky_scene_tree *tree; struct ky_scene_decoration *deco; struct wl_list layouts; struct seat_pointer_grab pointer_grab; struct kywc_box box; struct wl_event_source *timer; bool timer_enabled, entered; }; static struct tile_flyout_manager { struct wl_list flyouts; struct wl_listener theme_update; struct wl_listener server_destroy; } *manager = NULL; static void tile_flyout_item_set_gradient(struct tile_flyout_item *item, struct theme_gradient *gradient) { struct ky_scene_linear_gradient *linear_gradient = item->gradient; ky_scene_linear_gradient_set_linear(linear_gradient, gradient->angle); float color[4]; color_float_pa(color, gradient->background); ky_scene_linear_gradient_set_background_color(linear_gradient, color); color_float_pa(color, gradient->start); ky_scene_linear_gradient_set_color_stop_0(linear_gradient, 0, color); color_float_pa(color, gradient->stop); ky_scene_linear_gradient_set_color_stop_1(linear_gradient, 1, color); } static void tile_flyout_update(struct tile_flyout *flyout) { struct theme *theme = theme_manager_get_theme(); float null_color[4] = { 0, 0, 0, 0 }; ky_scene_decoration_set_shadow_count(flyout->deco, theme->menu_shadow_color.num_layers); for (int i = 0; i < theme->menu_shadow_color.num_layers; i++) { struct theme_shadow_layer *shadow = &theme->menu_shadow_color.layers[i]; float shadow_color[4]; color_float_pa(shadow_color, shadow->color); ky_scene_decoration_set_shadow(flyout->deco, i, shadow->off_x, shadow->off_y, shadow->spread, shadow->blur, shadow_color); } float border_color[4]; color_float_pa(border_color, theme->active_border_color); ky_scene_decoration_set_margin_color(flyout->deco, null_color, border_color); float bg_color[4]; color_float_pax(bg_color, theme->active_bg_color, theme->opacity / 100.0); ky_scene_decoration_set_surface_color(flyout->deco, bg_color); int r = theme->menu_radius; ky_scene_decoration_set_round_corner_radius(flyout->deco, (int[4]){ r, r, r, r }); ky_scene_decoration_set_surface_blurred(flyout->deco, theme->opacity != 100); r = theme->normal_radius; struct tile_flyout_layout *layout; wl_list_for_each(layout, &flyout->layouts, link) { ky_scene_decoration_set_margin_color(layout->deco, null_color, border_color); ky_scene_decoration_set_round_corner_radius(layout->deco, (int[4]){ r, r, r, r }); struct tile_flyout_item *item; wl_list_for_each(item, &layout->items, link) { tile_flyout_item_set_gradient(item, &theme->normal_state_color); } } } static void tile_flyout_set_timer_enabled(struct tile_flyout *flyout, bool enabled) { if (flyout->timer_enabled == enabled) { return; } flyout->timer_enabled = enabled; wl_event_source_timer_update(flyout->timer, enabled ? 250 : 0); } static void tile_flyout_set_enabled(struct tile_flyout *flyout, bool enabled, bool force) { if (flyout->tree->node.enabled == enabled) { return; } if (!enabled) { if (!force) { tile_flyout_set_timer_enabled(flyout, true); return; } tile_flyout_set_timer_enabled(flyout, false); seat_end_pointer_grab(flyout->seat, &flyout->pointer_grab); wl_list_remove(&flyout->view_unmap.link); wl_list_init(&flyout->view_unmap.link); struct cursor *cursor = flyout->seat->cursor; bool need_rebase = flyout->view == NULL; if (!need_rebase) { struct kywc_box *geo = &flyout->view->base.geometry; struct wlr_box box = { geo->x + flyout->box.x, geo->y + flyout->box.y, flyout->box.width, flyout->box.height }; need_rebase = !wlr_box_contains_point(&box, cursor->lx, cursor->ly); } if (need_rebase) { cursor_rebase(flyout->seat->cursor); } flyout->view = NULL; flyout->entered = false; popup_add_fade_effect(&flyout->tree->node, FADE_OUT, true, false, 1.0); ky_scene_node_set_enabled(&flyout->tree->node, false); return; } if (!flyout->view) { return; } wl_list_remove(&flyout->view_unmap.link); wl_signal_add(&flyout->view->base.events.unmap, &flyout->view_unmap); seat_start_pointer_grab(flyout->seat, &flyout->pointer_grab); tile_flyout_set_timer_enabled(flyout, false); struct kywc_box *geo = &flyout->view->base.geometry; int lx = geo->x + flyout->box.x - (FLYOUT_WIDTH - flyout->box.width) / 2; int ly = geo->y + flyout->box.y + flyout->box.height - FLYOUT_OFFSET; struct kywc_output *output = kywc_output_at_point(lx, ly); geo = &output_from_kywc_output(output)->geometry; int min_x = geo->x, max_x = geo->x + geo->width - FLYOUT_WIDTH; lx = CLAMP(lx, min_x, max_x); ky_scene_node_set_position(&flyout->tree->node, lx, ly); ky_scene_node_raise_to_top(&flyout->tree->node); ky_scene_node_set_enabled(&flyout->tree->node, true); popup_add_fade_effect(&flyout->tree->node, FADE_IN, true, false, output->state.scale); } static bool tile_flyout_item_hover(struct seat *seat, struct ky_scene_node *node, double x, double y, uint32_t time, bool first, bool hold, void *data) { if (!first) { return false; } struct tile_flyout_item *item = data; struct theme *theme = theme_manager_get_theme(); tile_flyout_item_set_gradient(item, &theme->hover_state_color); return false; } static void tile_flyout_item_leave(struct seat *seat, struct ky_scene_node *node, bool last, void *data) { struct tile_flyout_item *item = data; struct theme *theme = theme_manager_get_theme(); tile_flyout_item_set_gradient(item, &theme->normal_state_color); } static void tile_flyout_item_click(struct seat *seat, struct ky_scene_node *node, uint32_t button, bool pressed, uint32_t time, enum click_state state, void *data) { struct tile_flyout_item *item = data; struct theme *theme = theme_manager_get_theme(); if (pressed) { tile_flyout_item_set_gradient(item, &theme->click_state_color); return; } struct view *view = item->layout->flyout->view; struct output *output = output_from_kywc_output(view->output); kywc_view_set_tiled(&view->base, item->tile_type, &output->base); view_manager_show_tile_assist(view, seat, &output->base); tile_flyout_item_set_gradient(item, &theme->normal_state_color); } static struct ky_scene_node *get_tile_flyout_item_root(void *data) { struct tile_flyout_item *item = data; return &item->layout->flyout->tree->node; } static const struct input_event_node_impl tile_flyout_item_impl = { .hover = tile_flyout_item_hover, .leave = tile_flyout_item_leave, .click = tile_flyout_item_click, }; static struct tile_flyout_item *tile_flyout_item_create(struct tile_flyout_layout *layout, int width, int height, enum kywc_tile tile_type) { struct tile_flyout_item *item = calloc(1, sizeof(*item)); if (!item) { return NULL; } item->tile_type = tile_type; item->layout = layout; wl_list_insert(&layout->items, &item->link); /* color is set in tile_flyout_update */ item->gradient = ky_scene_linear_gradient_create(layout->tree, width, height, (float[4]){ 0, 0, 0, 0 }); struct ky_scene_node *node = ky_scene_node_from_linear_gradient(item->gradient); ky_scene_node_set_radius(node, (int[4]){ 4, 4, 4, 4 }); input_event_node_create(node, &tile_flyout_item_impl, get_tile_flyout_item_root, NULL, item); return item; } static struct tile_flyout_layout *tile_flyout_layout_create(struct tile_flyout *flyout, int width, int height, enum tile_layout_mode mode) { struct tile_flyout_layout *layout = calloc(1, sizeof(*layout)); if (!layout) { return NULL; } layout->mode = mode; wl_list_init(&layout->items); layout->flyout = flyout; wl_list_insert(&flyout->layouts, &layout->link); layout->tree = ky_scene_tree_create(flyout->tree); layout->deco = ky_scene_decoration_create(layout->tree); struct ky_scene_node *deco_node = ky_scene_node_from_decoration(layout->deco); ky_scene_node_set_input_bypassed(deco_node, true); ky_scene_decoration_set_mask(layout->deco, DECORATION_MASK_ALL); ky_scene_decoration_set_margin(layout->deco, 0, FLYOUT_BORDER); ky_scene_decoration_set_surface_size(layout->deco, width, height); int item_count = 0; int item_width = (width - 2 * FLYOUT_LAYOUT_GAP - FLYOUT_ITEM_GAP) / 2; int item_height = height - 2 * FLYOUT_LAYOUT_GAP; if (mode == TILE_LAYOUT_MODE_HALF) { item_count = 2; } else if (mode == TILE_LAYOUT_MODE_QUARTER) { item_count = 4; item_height = (item_height - FLYOUT_ITEM_GAP) / 2; } for (int i = 0; i < item_count; i++) { if (mode == TILE_LAYOUT_MODE_HALF) { struct tile_flyout_item *item = tile_flyout_item_create(layout, item_width, item_height, i + KYWC_TILE_LEFT); int x = FLYOUT_LAYOUT_GAP + i * (item_width + FLYOUT_ITEM_GAP) + FLYOUT_BORDER; int y = FLYOUT_LAYOUT_GAP + FLYOUT_BORDER; ky_scene_node_set_position(ky_scene_node_from_linear_gradient(item->gradient), x, y); } else if (mode == TILE_LAYOUT_MODE_QUARTER) { struct tile_flyout_item *item = tile_flyout_item_create(layout, item_width, item_height, i + KYWC_TILE_TOP_LEFT); int x = FLYOUT_LAYOUT_GAP + i / 2 * (item_width + FLYOUT_ITEM_GAP) + FLYOUT_BORDER; int y = FLYOUT_LAYOUT_GAP + i % 2 * (item_height + FLYOUT_ITEM_GAP) + FLYOUT_BORDER; ky_scene_node_set_position(ky_scene_node_from_linear_gradient(item->gradient), x, y); } } return layout; } static void pointer_grab_cancel(struct seat_pointer_grab *pointer_grab) { struct tile_flyout *flyout = pointer_grab->data; tile_flyout_set_enabled(flyout, false, true); } static bool pointer_grab_button(struct seat_pointer_grab *pointer_grab, uint32_t time, uint32_t button, bool pressed) { struct tile_flyout *flyout = pointer_grab->data; struct seat *seat = pointer_grab->seat; // support click in flyout->box struct input_event_node *inode = input_event_node_from_node(seat->cursor->hover.node); struct ky_scene_node *node = input_event_node_root(inode); if (node == &flyout->tree->node) { inode->impl->click(seat, seat->cursor->hover.node, button, pressed, time, CLICK_STATE_NONE, inode->data); return true; } else if (pressed) { tile_flyout_set_enabled(flyout, false, true); } return false; } static bool pointer_grab_motion(struct seat_pointer_grab *pointer_grab, uint32_t time, double lx, double ly) { struct tile_flyout *flyout = pointer_grab->data; struct seat *seat = pointer_grab->seat; struct kywc_box *geo = &flyout->view->base.geometry; struct wlr_box box = { geo->x + flyout->box.x, geo->y + flyout->box.y, flyout->box.width, flyout->box.height }; struct ky_scene_node *hover = ky_scene_node_at(&seat->scene->tree.node, lx, ly, NULL, NULL); struct input_event_node *inode = input_event_node_from_node(hover); struct ky_scene_node *node = input_event_node_root(inode); bool hoverd = node == &flyout->tree->node; if (!flyout->entered && hoverd) { flyout->entered = true; } if (hoverd || (!flyout->entered && wlr_box_contains_point(&box, lx, ly))) { // leak to cursor motion to generate leave event tile_flyout_set_timer_enabled(flyout, false); return false; } tile_flyout_set_enabled(flyout, false, false); return true; } static bool pointer_grab_axis(struct seat_pointer_grab *pointer_grab, uint32_t time, bool vertical, double value) { return true; } static const struct seat_pointer_grab_interface pointer_grab_impl = { .motion = pointer_grab_motion, .button = pointer_grab_button, .axis = pointer_grab_axis, .cancel = pointer_grab_cancel, }; static void flyout_handle_view_unmap(struct wl_listener *listener, void *data) { struct tile_flyout *flyout = wl_container_of(listener, flyout, view_unmap); flyout->view = NULL; tile_flyout_set_enabled(flyout, false, true); } static void flyout_handle_seat_destroy(struct wl_listener *listener, void *data) { struct tile_flyout *flyout = wl_container_of(listener, flyout, seat_destroy); wl_list_remove(&flyout->seat_destroy.link); wl_list_remove(&flyout->view_unmap.link); wl_list_remove(&flyout->link); struct tile_flyout_layout *layout, *layout_tmp; wl_list_for_each_safe(layout, layout_tmp, &flyout->layouts, link) { struct tile_flyout_item *item, *item_tmp; wl_list_for_each_safe(item, item_tmp, &layout->items, link) { ky_scene_node_destroy(ky_scene_node_from_linear_gradient(item->gradient)); free(item); } ky_scene_node_destroy(&layout->tree->node); free(layout); } ky_scene_node_destroy(&flyout->tree->node); seat_end_pointer_grab(flyout->seat, &flyout->pointer_grab); wl_event_source_remove(flyout->timer); free(flyout); } static bool tile_flyout_hover(struct seat *seat, struct ky_scene_node *node, double x, double y, uint32_t time, bool first, bool hold, void *data) { return false; } static void tile_flyout_leave(struct seat *seat, struct ky_scene_node *node, bool last, void *data) { // do nothing } static void tile_flyout_click(struct seat *seat, struct ky_scene_node *node, uint32_t button, bool pressed, uint32_t time, enum click_state state, void *data) { // do nothing } static struct ky_scene_node *get_tile_flyout_root(void *data) { struct tile_flyout *flyout = data; return &flyout->tree->node; } static const struct input_event_node_impl tile_flyout_impl = { .hover = tile_flyout_hover, .leave = tile_flyout_leave, .click = tile_flyout_click, }; static int handle_timeout(void *data) { struct tile_flyout *flyout = data; tile_flyout_set_enabled(flyout, false, true); return 0; } static struct tile_flyout *tile_flyout_create(struct seat *seat) { struct tile_flyout *flyout = calloc(1, sizeof(*flyout)); if (!flyout) { return NULL; } flyout->seat = seat; flyout->seat_destroy.notify = flyout_handle_seat_destroy; wl_signal_add(&seat->events.destroy, &flyout->seat_destroy); flyout->view_unmap.notify = flyout_handle_view_unmap; wl_list_init(&flyout->view_unmap.link); wl_list_init(&flyout->layouts); wl_list_insert(&manager->flyouts, &flyout->link); struct wl_event_loop *loop = wl_display_get_event_loop(seat->wlr_seat->display); flyout->timer = wl_event_loop_add_timer(loop, handle_timeout, flyout); flyout->pointer_grab.seat = seat; flyout->pointer_grab.data = flyout; flyout->pointer_grab.interface = &pointer_grab_impl; struct view_layer *layer = view_manager_get_layer(LAYER_POPUP, false); flyout->tree = ky_scene_tree_create(layer->tree); ky_scene_node_set_enabled(&flyout->tree->node, false); flyout->deco = ky_scene_decoration_create(flyout->tree); ky_scene_decoration_set_mask(flyout->deco, DECORATION_MASK_ALL); ky_scene_decoration_set_margin(flyout->deco, 0, FLYOUT_BORDER); ky_scene_decoration_set_surface_size(flyout->deco, FLYOUT_WIDTH, FLYOUT_HEIGHT); struct ky_scene_node *deco_node = ky_scene_node_from_decoration(flyout->deco); input_event_node_create(deco_node, &tile_flyout_impl, &get_tile_flyout_root, NULL, flyout); int layout_width = (FLYOUT_WIDTH - 3 * FLYOUT_GAP) / TILE_LAYOUT_MODE_COUNT; int layout_height = FLYOUT_HEIGHT - 2 * FLYOUT_GAP; for (int i = 0; i < TILE_LAYOUT_MODE_COUNT; i++) { struct tile_flyout_layout *layout = tile_flyout_layout_create( flyout, layout_width, layout_height, i + TILE_LAYOUT_MODE_HALF); int x = FLYOUT_GAP + i * (layout_width + FLYOUT_GAP); int y = FLYOUT_GAP; ky_scene_node_set_position(&layout->tree->node, x, y); } tile_flyout_update(flyout); return flyout; } static struct tile_flyout *tile_flyout_get_or_create(struct seat *seat) { struct tile_flyout *flyout; wl_list_for_each(flyout, &manager->flyouts, link) { if (flyout->seat == seat) { return flyout; } } return tile_flyout_create(seat); } void tile_flyout_show(struct view *view, struct seat *seat, struct kywc_box *box) { if (!manager) { return; } struct tile_flyout *flyout = tile_flyout_get_or_create(seat); if (!flyout) { return; } /* disable current flyout if needed */ tile_flyout_set_enabled(flyout, false, true); flyout->view = view; flyout->box = *box; tile_flyout_set_enabled(flyout, true, true); } static void handle_theme_update(struct wl_listener *listener, void *data) { struct theme_update_event *update_event = data; uint32_t allowed_mask = THEME_UPDATE_MASK_STATE_COLOR | THEME_UPDATE_MASK_OPACITY | THEME_UPDATE_MASK_BORDER_COLOR | THEME_UPDATE_MASK_BACKGROUND_COLOR | THEME_UPDATE_MASK_CORNER_RADIUS | THEME_UPDATE_MASK_SHADOW_COLOR; if (!(update_event->update_mask & allowed_mask)) { return; } struct tile_flyout *flyout; wl_list_for_each(flyout, &manager->flyouts, link) { tile_flyout_update(flyout); } } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->server_destroy.link); wl_list_remove(&manager->theme_update.link); free(manager); } bool tile_flyout_manager_create(struct view_manager *view_manager) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } wl_list_init(&manager->flyouts); manager->theme_update.notify = handle_theme_update; theme_manager_add_update_listener(&manager->theme_update, false); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(view_manager->server, &manager->server_destroy); return true; } kylin-wayland-compositor/src/view/global_authentication.c0000664000175000017500000000710015160461067022727 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "effect/shake_view.h" #include "input/event.h" #include "output.h" #include "view_p.h" static float authentication_color[4] = { 18.0 / 255, 18.0 / 255, 18.0 / 255, 128.0 / 255 }; struct global_authentication { struct view *view; struct ky_scene_rect *box; struct ky_scene_tree *tree; struct wl_listener view_unmap; struct wl_listener configured_output; }; static bool authentication_hover(struct seat *seat, struct ky_scene_node *node, double x, double y, uint32_t time, bool first, bool hold, void *data) { return false; } static void authentication_leave(struct seat *seat, struct ky_scene_node *node, bool last, void *data) { } static void authentication_click(struct seat *seat, struct ky_scene_node *node, uint32_t button, bool pressed, uint32_t time, enum click_state state, void *data) { if (!pressed) { return; } struct global_authentication *authentication = data; /* active current view */ kywc_view_activate(&authentication->view->base); view_add_shake_effect(authentication->view); } static struct ky_scene_node *authentication_get_root(void *data) { struct global_authentication *authentication = data; return &authentication->box->node; } static const struct input_event_node_impl authentication_impl = { .hover = authentication_hover, .leave = authentication_leave, .click = authentication_click, }; static void handle_view_unmap(struct wl_listener *listener, void *data) { struct global_authentication *authentication = wl_container_of(listener, authentication, view_unmap); wl_list_remove(&authentication->view_unmap.link); wl_list_remove(&authentication->configured_output.link); ky_scene_node_destroy(&authentication->tree->node); free(authentication); } static void handle_configured_output(struct wl_listener *listener, void *data) { struct global_authentication *authentication = wl_container_of(listener, authentication, configured_output); int width = 0, height = 0; output_layout_get_size(&width, &height); ky_scene_rect_set_size(authentication->box, width, height); } void global_authentication_create(struct view *view) { if (view_manager_get_global_authentication()) { kywc_log(KYWC_DEBUG, "Open multiple global authentication views at the same time"); return; } struct global_authentication *authentication = calloc(1, sizeof(*authentication)); if (!authentication) { return; } view_manager_set_global_authentication(view); authentication->view = view; struct view_layer *layer = view_manager_get_layer(LAYER_CRITICAL_NOTIFICATION, false); authentication->tree = ky_scene_tree_create(layer->tree); int width = 0, height = 0; output_layout_get_size(&width, &height); authentication->box = ky_scene_rect_create(authentication->tree, width, height, authentication_color); ky_scene_node_lower_to_bottom(&authentication->tree->node); input_event_node_create(&authentication->box->node, &authentication_impl, authentication_get_root, NULL, authentication); authentication->view_unmap.notify = handle_view_unmap; wl_signal_add(&view->base.events.unmap, &authentication->view_unmap); authentication->configured_output.notify = handle_configured_output; output_manager_add_configured_listener(&authentication->configured_output); } kylin-wayland-compositor/src/view/xdg_shell.c0000664000175000017500000005710715160461067020355 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "input/event.h" #include "output.h" #include "scene/xdg_shell.h" #include "util/macros.h" #include "view/action.h" #include "view_p.h" struct xdg_view { struct view view; struct wlr_xdg_surface *wlr_xdg_surface; struct wl_listener commit; struct wl_listener map; struct wl_listener unmap; struct wl_listener destroy; struct wl_listener new_popup; struct wl_listener ping_timeout; struct wl_listener request_move; struct wl_listener request_resize; struct wl_listener request_minimize; struct wl_listener request_maximize; struct wl_listener request_fullscreen; struct wl_listener request_show_window_menu; struct wl_listener set_parent; struct wl_listener set_title; struct wl_listener set_app_id; struct wl_list icon_buffers; // xdg_icon_buffer.link from xdg toplevel icon }; struct xdg_icon_buffer { struct wl_list link; int scale; struct wlr_buffer *wlr_buffer; }; static bool xdg_view_hover(struct seat *seat, struct ky_scene_node *node, double x, double y, uint32_t time, bool first, bool hold, void *data) { struct wlr_surface *surface = wlr_surface_try_from_node(node); struct xdg_view *xdg_view = data; if (first) { view_hover(seat, &xdg_view->view); kywc_log(KYWC_DEBUG, "First hover surface %p (%f %f)", surface, x, y); } if (!hold) { seat_notify_motion(seat, surface, time, x, y, first); return false; } struct kywc_box *geometry = &xdg_view->view.base.geometry; double sx = x - geometry->x; double sy = y - geometry->y; // sx = sx < 0 ? 0 : (sx > geometry->width ? geometry->width : sx); // sy = sy < 0 ? 0 : (sy > geometry->height ? geometry->height : sy); sx += xdg_view->wlr_xdg_surface->current.geometry.x; sy += xdg_view->wlr_xdg_surface->current.geometry.y; seat_notify_motion(seat, surface, time, sx, sy, first); return true; } static void xdg_view_click(struct seat *seat, struct ky_scene_node *node, uint32_t button, bool pressed, uint32_t time, enum click_state state, void *data) { seat_notify_button(seat, time, button, pressed); /* only do activated when button pressed */ if (!pressed) { return; } struct xdg_view *xdg_view = data; view_click(seat, &xdg_view->view, button, pressed, state); } static void xdg_view_leave(struct seat *seat, struct ky_scene_node *node, bool last, void *data) { /* so surface will call set_cursor when enter again */ struct wlr_surface *surface = wlr_surface_try_from_node(node); seat_notify_leave(seat, surface); } static struct ky_scene_node *xdg_view_get_root(void *data) { struct xdg_view *xdg_view = data; return &xdg_view->view.tree->node; } static struct wlr_surface *xdg_view_get_toplevel(void *data) { struct xdg_view *xdg_view = data; return xdg_view->view.surface; } static const struct input_event_node_impl xdg_view_event_node_impl = { .hover = xdg_view_hover, .click = xdg_view_click, .leave = xdg_view_leave, }; static struct xdg_view *xdg_view_from_view(struct view *view) { struct xdg_view *xdg_view = wl_container_of(view, xdg_view, view); return xdg_view; } static void xdg_view_close(struct view *view) { struct xdg_view *xdg_view = xdg_view_from_view(view); wlr_xdg_toplevel_send_close(xdg_view->wlr_xdg_surface->toplevel); } static void xdg_view_ping(struct view *view) { struct xdg_view *xdg_view = xdg_view_from_view(view); struct wlr_xdg_surface *wlr_xdg_surface = xdg_view->wlr_xdg_surface; wlr_xdg_surface_ping(wlr_xdg_surface); } static void xdg_view_configure(struct view *view) { struct xdg_view *xdg_view = xdg_view_from_view(view); struct wlr_xdg_toplevel *wlr_xdg_toplevel = xdg_view->wlr_xdg_surface->toplevel; struct kywc_view *kywc_view = &xdg_view->view.base; /* do nothing when minimize */ if (view->pending.action & VIEW_ACTION_MINIMIZE) { view->pending.action &= ~VIEW_ACTION_MINIMIZE; } /* ignore activate configure serial */ if (view->pending.action & VIEW_ACTION_ACTIVATE) { view->pending.action &= ~VIEW_ACTION_ACTIVATE; wlr_xdg_toplevel_set_activated(wlr_xdg_toplevel, kywc_view->activated); } /* direct move when not changed size */ if (view->pending.action & VIEW_ACTION_MOVE) { view->pending.action &= ~VIEW_ACTION_MOVE; if (!view_action_change_size(view->pending.configure_action)) { view_helper_move(view, view->pending.geometry.x, view->pending.geometry.y); } else { kywc_log(KYWC_DEBUG, "Skip move when pending configure action 0x%x", view->pending.configure_action); } } if (view->pending.action == VIEW_ACTION_NOP) { return; } /* now, only changed size action left */ assert(view_action_change_size(view->pending.action)); uint32_t serial = 0; if (view->pending.action & VIEW_ACTION_FULLSCREEN) { serial = wlr_xdg_toplevel_set_fullscreen(wlr_xdg_toplevel, kywc_view->fullscreen); } if (view->pending.action & VIEW_ACTION_MAXIMIZE) { serial = wlr_xdg_toplevel_set_maximized(wlr_xdg_toplevel, kywc_view->maximized); } if (view->pending.action & VIEW_ACTION_TILE) { serial = wlr_xdg_toplevel_set_tiled(wlr_xdg_toplevel, kywc_view->tiled ? 0xf : 0); } struct kywc_box *current = &view->base.geometry; struct kywc_box *pending = &view->pending.geometry; /* If no need to resizing, process the move immediately */ if (!view_action_change_size(view->pending.configure_action) && current->width == pending->width && current->height == pending->height) { view->pending.action &= ~VIEW_ACTION_RESIZE; view_helper_move(view, pending->x, pending->y); view_configure(view, serial); return; } serial = wlr_xdg_toplevel_set_size(wlr_xdg_toplevel, pending->width, pending->height); view_configure(view, serial); } static void xdg_view_close_popups(struct view *view) { struct xdg_view *xdg_view = xdg_view_from_view(view); struct wlr_xdg_popup *popup, *tmp; wl_list_for_each_safe(popup, tmp, &xdg_view->wlr_xdg_surface->popups, link) { wlr_xdg_popup_destroy(popup); } } static void xdg_view_destroy(struct view *view) { struct xdg_view *xdg_view = xdg_view_from_view(view); free(xdg_view); } static void xdg_view_update_usable_area(struct view *view, struct kywc_output *output, struct kywc_box *usable_area, enum kywc_edges edge) { struct kywc_box geo; kywc_output_effective_geometry(output, &geo); struct kywc_box *view_geo = &view->base.geometry; if (edge == KYWC_EDGE_TOP) { int y = view_geo->y + view_geo->height; geo.height -= y - geo.y; geo.y = y; } else if (edge == KYWC_EDGE_BOTTOM) { geo.height = view_geo->y - geo.y; } else if (edge == KYWC_EDGE_LEFT) { int x = view_geo->width + view_geo->x; geo.width -= x - geo.x; geo.x = x; } else if (edge == KYWC_EDGE_RIGHT) { geo.width = view_geo->x - geo.x; } /* intersect usable_area and geo */ usable_area->x = MAX(geo.x, usable_area->x); usable_area->y = MAX(geo.y, usable_area->y); usable_area->width = MIN(geo.width, usable_area->width); usable_area->height = MIN(geo.height, usable_area->height); } static struct wlr_buffer *xdg_view_get_icon_buffer(struct view *view, int size, float scale) { struct xdg_view *xdg_view = xdg_view_from_view(view); if (wl_list_empty(&xdg_view->icon_buffers)) { return NULL; } float scale_width = size * scale; float min_abs = FLT_MAX; float tmp_abs; struct xdg_icon_buffer *icon_buffer_similar = NULL; struct xdg_icon_buffer *icon_buffer; wl_list_for_each(icon_buffer, &xdg_view->icon_buffers, link) { // maybe equal to size if ((icon_buffer->wlr_buffer->width == size && FLOAT_EQUAL(icon_buffer->scale, scale)) || (icon_buffer->wlr_buffer->width == scale_width)) { return icon_buffer->wlr_buffer; } tmp_abs = fabs(icon_buffer->wlr_buffer->width - scale_width); if (tmp_abs < min_abs) { min_abs = tmp_abs; icon_buffer_similar = icon_buffer; } } return icon_buffer_similar->wlr_buffer; } static const struct view_impl xdg_surface_impl = { .ping = xdg_view_ping, .configure = xdg_view_configure, .close_popups = xdg_view_close_popups, .close = xdg_view_close, .destroy = xdg_view_destroy, .update_usable_area = xdg_view_update_usable_area, .get_icon_buffer = xdg_view_get_icon_buffer, }; static void xdg_view_update_geometry(struct xdg_view *xdg_view) { struct wlr_xdg_surface *wlr_xdg_surface = xdg_view->wlr_xdg_surface; struct wlr_surface *wlr_surface = wlr_xdg_surface->surface; struct wlr_box *geo = &wlr_xdg_surface->current.geometry; int width = geo->width, height = geo->height; if (!width || !height) { width = wlr_surface->current.width; height = wlr_surface->current.height; } struct wlr_xdg_toplevel_state *current = &wlr_xdg_surface->toplevel->current; view_update_size(&xdg_view->view, width, height, current->min_width, current->min_height, current->max_width, current->max_height); struct kywc_view *kywc_view = &xdg_view->view.base; /* padding if used CSD with drop-shadow */ if (kywc_view->ssd == KYWC_SSD_NONE) { kywc_view->padding.left = geo->x; kywc_view->padding.right = wlr_surface->current.width - geo->x - width; kywc_view->padding.top = geo->y; kywc_view->padding.bottom = wlr_surface->current.height - geo->y - height; } } static void xdg_view_handle_commit(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, commit); struct wlr_xdg_surface *wlr_xdg_surface = xdg_view->wlr_xdg_surface; struct view *view = &xdg_view->view; if (wlr_xdg_surface->initial_commit) { wlr_xdg_surface_schedule_configure(wlr_xdg_surface); return; } if (!wlr_xdg_surface->surface->mapped) { return; } xdg_view_update_geometry(xdg_view); enum view_action pending_action = view->pending.configure_action; uint32_t pending_serial = view->pending.configure_serial; if (pending_action == VIEW_ACTION_NOP && pending_serial == 0) { return; } assert(view_action_change_size(pending_action)); struct kywc_box *current = &view->base.geometry; struct kywc_box *pending = &view->pending.configure_geometry; int x = pending->x, y = pending->y; uint32_t current_serial = xdg_view->wlr_xdg_surface->current.configure_serial; /* pending configure has not been acked yet, fix wobbling when resize */ if (pending_serial >= current_serial && pending_action & VIEW_ACTION_RESIZE) { uint32_t resize_edges = xdg_view->view.current_resize_edges; if (resize_edges & KYWC_EDGE_LEFT) { x += pending->width - current->width; } if (resize_edges & KYWC_EDGE_TOP) { y += pending->height - current->height; } if (pending_serial > current_serial) { view_helper_move(view, x, y); } } /* last configure has been acked */ if (current_serial >= pending_serial) { view_helper_move(view, x, y); view_configured(&xdg_view->view, true); } } static void xdg_view_handle_ping_timeout(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, ping_timeout); view_show_hang_window(&xdg_view->view); } static void xdg_view_handle_new_popup(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, new_popup); struct view *view = &xdg_view->view; struct wlr_xdg_popup *wlr_xdg_popup = data; struct view_layer *view_layer = view_manager_get_layer_by_node(&view->tree->node, false); enum layer layer = view_layer->layer > LAYER_POPUP ? view_layer->layer : LAYER_POPUP; struct view_layer *popup_layer = view_manager_get_layer(layer, false); xdg_popup_create(wlr_xdg_popup, xdg_view->view.surface_tree, popup_layer, view->base.role == KYWC_VIEW_ROLE_PANEL); } static void xdg_view_handle_request_move(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, request_move); struct wlr_xdg_toplevel_move_event *event = data; struct seat *seat = seat_from_wlr_seat(event->seat->seat); if (view_validate_move_or_resize_request(&xdg_view->view, seat)) { window_begin_move(&xdg_view->view, seat); } } static void xdg_view_handle_request_resize(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, request_resize); struct wlr_xdg_toplevel_resize_event *event = data; struct seat *seat = seat_from_wlr_seat(event->seat->seat); if (view_validate_move_or_resize_request(&xdg_view->view, seat)) { window_begin_resize(&xdg_view->view, event->edges, seat); } } static void xdg_view_handle_request_minimize(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, request_minimize); struct wlr_xdg_toplevel *toplevel = xdg_view->wlr_xdg_surface->toplevel; kywc_view_set_minimized(&xdg_view->view.base, toplevel->requested.minimized); } static void xdg_view_handle_request_maximize(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, request_maximize); struct wlr_xdg_toplevel *toplevel = xdg_view->wlr_xdg_surface->toplevel; kywc_view_set_maximized(&xdg_view->view.base, toplevel->requested.maximized, NULL); } static void xdg_view_handle_request_fullscreen(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, request_fullscreen); struct wlr_xdg_toplevel *toplevel = xdg_view->wlr_xdg_surface->toplevel; struct wlr_xdg_toplevel_requested *requested = &toplevel->requested; struct kywc_view *kywc_view = &xdg_view->view.base; struct kywc_output *kywc_output = NULL; if (requested->fullscreen_output) { struct output *output = output_from_wlr_output(requested->fullscreen_output); if (output && !output->base.destroying && output->base.state.enabled) { kywc_output = &output->base; } } kywc_view_set_fullscreen(kywc_view, requested->fullscreen, kywc_output); } static void xdg_view_handle_show_window_menu(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, request_show_window_menu); struct wlr_xdg_toplevel_show_window_menu_event *event = data; struct kywc_view *kywc_view = &xdg_view->view.base; struct seat *seat = seat_from_wlr_seat(event->seat->seat); int off_x = kywc_view->geometry.x - xdg_view->wlr_xdg_surface->current.geometry.x; int off_y = kywc_view->geometry.y - xdg_view->wlr_xdg_surface->current.geometry.y; view_show_window_menu(&xdg_view->view, seat, off_x + event->x, off_y + event->y); } static void xdg_view_handle_set_parent(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, set_parent); struct wlr_xdg_toplevel *parent = xdg_view->wlr_xdg_surface->toplevel->parent; struct xdg_view *parent_xdg_view = parent ? parent->base->data : NULL; struct view *parent_view = parent_xdg_view ? &parent_xdg_view->view : NULL; view_set_parent(&xdg_view->view, parent_view); } static void xdg_view_handle_set_title(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, set_title); const char *title = xdg_view->wlr_xdg_surface->toplevel->title; view_set_title(&xdg_view->view, title); } static void xdg_view_handle_set_app_id(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, set_app_id); const char *app_id = xdg_view->wlr_xdg_surface->toplevel->app_id; view_set_app_id(&xdg_view->view, app_id); } static void xdg_view_handle_map(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, map); struct wlr_xdg_surface *wlr_xdg_surface = xdg_view->wlr_xdg_surface; struct wlr_surface *wlr_surface = wlr_xdg_surface->surface; struct wlr_xdg_toplevel *toplevel = wlr_xdg_surface->toplevel; struct kywc_view *kywc_view = &xdg_view->view.base; xdg_view_update_geometry(xdg_view); kywc_log(KYWC_DEBUG, "Kywc_view %p padding: (← %d), (↑ %d), (→ %d), (↓ %d)", kywc_view, kywc_view->padding.left, kywc_view->padding.top, kywc_view->padding.right, kywc_view->padding.bottom); /* all states are ready when map */ view_set_app_id(&xdg_view->view, toplevel->app_id); view_set_title(&xdg_view->view, toplevel->title); xdg_view_handle_set_parent(&xdg_view->set_parent, NULL); xdg_view_handle_request_minimize(&xdg_view->request_minimize, NULL); xdg_view_handle_request_maximize(&xdg_view->request_maximize, NULL); xdg_view_handle_request_fullscreen(&xdg_view->request_fullscreen, NULL); /* some requesets before map are cached in toplevel */ xdg_view->request_move.notify = xdg_view_handle_request_move; wl_signal_add(&toplevel->events.request_move, &xdg_view->request_move); xdg_view->request_resize.notify = xdg_view_handle_request_resize; wl_signal_add(&toplevel->events.request_resize, &xdg_view->request_resize); xdg_view->request_minimize.notify = xdg_view_handle_request_minimize; wl_signal_add(&toplevel->events.request_minimize, &xdg_view->request_minimize); xdg_view->request_maximize.notify = xdg_view_handle_request_maximize; wl_signal_add(&toplevel->events.request_maximize, &xdg_view->request_maximize); xdg_view->request_fullscreen.notify = xdg_view_handle_request_fullscreen; wl_signal_add(&toplevel->events.request_fullscreen, &xdg_view->request_fullscreen); xdg_view->request_show_window_menu.notify = xdg_view_handle_show_window_menu; wl_signal_add(&toplevel->events.request_show_window_menu, &xdg_view->request_show_window_menu); xdg_view->set_parent.notify = xdg_view_handle_set_parent; wl_signal_add(&toplevel->events.set_parent, &xdg_view->set_parent); xdg_view->set_title.notify = xdg_view_handle_set_title; wl_signal_add(&toplevel->events.set_title, &xdg_view->set_title); xdg_view->set_app_id.notify = xdg_view_handle_set_app_id; wl_signal_add(&toplevel->events.set_app_id, &xdg_view->set_app_id); xdg_view->ping_timeout.notify = xdg_view_handle_ping_timeout; wl_signal_add(&wlr_xdg_surface->events.ping_timeout, &xdg_view->ping_timeout); xdg_view->new_popup.notify = xdg_view_handle_new_popup; wl_signal_add(&wlr_xdg_surface->events.new_popup, &xdg_view->new_popup); struct wl_client *client = wl_resource_get_client(wlr_surface->resource); wl_client_get_credentials(client, &xdg_view->view.pid, NULL, NULL); view_map(&xdg_view->view); } static void xdg_view_handle_unmap(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, unmap); wl_list_remove(&xdg_view->ping_timeout.link); wl_list_remove(&xdg_view->new_popup.link); wl_list_remove(&xdg_view->request_move.link); wl_list_remove(&xdg_view->request_minimize.link); wl_list_remove(&xdg_view->request_maximize.link); wl_list_remove(&xdg_view->request_fullscreen.link); wl_list_remove(&xdg_view->request_resize.link); wl_list_remove(&xdg_view->request_show_window_menu.link); wl_list_remove(&xdg_view->set_parent.link); wl_list_remove(&xdg_view->set_title.link); wl_list_remove(&xdg_view->set_app_id.link); view_unmap(&xdg_view->view); } static void xdg_view_handle_destroy(struct wl_listener *listener, void *data) { struct xdg_view *xdg_view = wl_container_of(listener, xdg_view, destroy); wl_list_remove(&xdg_view->destroy.link); wl_list_remove(&xdg_view->map.link); wl_list_remove(&xdg_view->unmap.link); wl_list_remove(&xdg_view->commit.link); xdg_view_clear_icon_buffer(xdg_view->wlr_xdg_surface); xdg_view->wlr_xdg_surface->surface->data = NULL; /* scene tree destroy will be called before by scene */ view_destroy(&xdg_view->view); } static void handle_new_xdg_toplevel(struct wl_listener *listener, void *data) { struct wlr_xdg_toplevel *wlr_xdg_toplevel = data; struct xdg_view *xdg_view = calloc(1, sizeof(*xdg_view)); if (!xdg_view) { wl_resource_post_no_memory(wlr_xdg_toplevel->resource); return; } struct wlr_xdg_surface *wlr_xdg_surface = wlr_xdg_toplevel->base; xdg_view->view.surface = wlr_xdg_surface->surface; view_init(&xdg_view->view, &xdg_surface_impl, xdg_view); xdg_view->wlr_xdg_surface = wlr_xdg_surface; wlr_xdg_surface->data = xdg_view; wlr_xdg_surface->surface->data = &xdg_view->view; wl_list_init(&xdg_view->icon_buffers); xdg_view->unmap.notify = xdg_view_handle_unmap; wl_signal_add(&wlr_xdg_surface->surface->events.unmap, &xdg_view->unmap); /* create tree for surface and all sub-surfaces */ xdg_view->view.surface_tree = ky_scene_xdg_surface_create(xdg_view->view.tree, wlr_xdg_surface); /* event node will be destroyed when xdg_surface destroy */ input_event_node_create(&xdg_view->view.surface_tree->node, &xdg_view_event_node_impl, xdg_view_get_root, xdg_view_get_toplevel, xdg_view); /* others will add in map and remove in unmap */ xdg_view->map.notify = xdg_view_handle_map; wl_signal_add(&wlr_xdg_surface->surface->events.map, &xdg_view->map); xdg_view->destroy.notify = xdg_view_handle_destroy; wl_signal_add(&wlr_xdg_toplevel->events.destroy, &xdg_view->destroy); xdg_view->commit.notify = xdg_view_handle_commit; wl_signal_add(&wlr_xdg_surface->surface->events.commit, &xdg_view->commit); } void xdg_view_add_icon_buffer(struct wlr_xdg_surface *wlr_xdg_surface, int scale, struct wlr_buffer *wlr_buffer) { struct xdg_view *xdg_view = wlr_xdg_surface->data; if (!xdg_view) { return; } if (scale <= 0 || !wlr_buffer) { return; } struct xdg_icon_buffer *icon_buffer = calloc(1, sizeof(*icon_buffer)); if (!icon_buffer) { return; } icon_buffer->scale = scale; icon_buffer->wlr_buffer = wlr_buffer; wlr_buffer_lock(icon_buffer->wlr_buffer); wl_list_insert(&xdg_view->icon_buffers, &icon_buffer->link); } void xdg_view_clear_icon_buffer(struct wlr_xdg_surface *wlr_xdg_surface) { struct xdg_view *xdg_view = wlr_xdg_surface->data; if (!xdg_view) { return; } struct xdg_icon_buffer *icon_buffer, *tmp; wl_list_for_each_safe(icon_buffer, tmp, &xdg_view->icon_buffers, link) { wlr_buffer_unlock(icon_buffer->wlr_buffer); wl_list_remove(&icon_buffer->link); free(icon_buffer); } } bool xdg_shell_init(struct view_manager *view_manager) { struct server *server = view_manager->server; struct wlr_xdg_shell *xdg_shell = wlr_xdg_shell_create(server->display, 5); if (!xdg_shell) { kywc_log(KYWC_ERROR, "Unable to create xdg shell"); return false; } view_manager->new_xdg_toplevel.notify = handle_new_xdg_toplevel; wl_signal_add(&xdg_shell->events.new_toplevel, &view_manager->new_xdg_toplevel); return true; } kylin-wayland-compositor/src/view/window_menu.c0000664000175000017500000004151015160461067020726 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "input/seat.h" #include "nls.h" #include "output.h" #include "view/action.h" #include "view/workspace.h" #include "view_p.h" #include "widget/menu.h" #define MAX_OUTPUTS (16) struct desktop_item { struct window_menu *window_menu; struct menu_item *item; struct workspace *workspace; }; struct screen_item { struct window_menu *window_menu; struct menu_item *item; struct kywc_output *output; }; /* window menu per seat */ struct window_menu { struct wl_list link; struct menu *root; struct menu *more; struct menu *screen; struct screen_item screen_items[MAX_OUTPUTS]; struct menu *desktop; struct desktop_item add_items[MAX_WORKSPACES]; struct menu_item *add_to; struct menu_item *move_to; struct menu_item *maximize; struct menu_item *minimize; struct menu_item *keep_above; struct menu_item *keep_below; struct menu_item *move; struct menu_item *resize; struct seat *seat; struct wl_listener seat_destroy; struct view *view; struct wl_listener view_destroy; int x, y; bool enabled; }; struct desktop { struct wl_list link; struct workspace *workspace; struct wl_listener workspace_destroy; }; struct window_menu_manager { struct ky_scene_tree *tree; struct wl_list menus; struct wl_list desktops; struct wl_listener new_workspace; struct wl_listener output_configured; struct wl_listener server_destroy; }; static struct window_menu_manager *manager = NULL; static bool window_menu_action(struct menu_item *item, uint32_t key, void *data) { struct window_menu *window_menu = data; struct menu *menu = item->menu; enum window_action action = WINDOW_ACTION_NONE; if (menu == window_menu->root) { if (key == KEY_N) { action = WINDOW_ACTION_MINIMIZE; } else if (key == KEY_X) { action = WINDOW_ACTION_MAXIMIZE; } else if (key == KEY_C) { action = WINDOW_ACTION_CLOSE; } else if (key == KEY_T) { action = WINDOW_ACTION_CAPTURE; } } else if (menu == window_menu->more) { if (key == KEY_M) { action = WINDOW_ACTION_MOVE; } else if (key == KEY_R) { action = WINDOW_ACTION_RESIZE; } else if (key == KEY_A) { action = WINDOW_ACTION_KEEP_ABOVE; } else if (key == KEY_B) { action = WINDOW_ACTION_KEEP_BELOW; } } else if (menu == window_menu->desktop) { struct view *view = window_menu->view; if (key == KEY_A) { if (view->base.sticky) { view_set_workspace(view, workspace_manager_get_current()); } else { view_add_all_workspace(view); } } else if (key == KEY_N) { struct workspace *workspace = workspace_create(NULL, workspace_manager_get_count()); if (workspace) { view_add_workspace(view, workspace); } } else if (key == KEY_M) { struct workspace *workspace = workspace_create(NULL, workspace_manager_get_count()); if (workspace) { view_set_workspace(view, workspace); } } return true; } if (action != WINDOW_ACTION_NONE) { window_action(window_menu->view, window_menu->seat, action); return true; } return false; } static bool add_desktop_action(struct menu_item *item, uint32_t key, void *data) { struct desktop_item *desktop = data; struct view *view = desktop->window_menu->view; if (item->checked) { view_remove_workspace(view, desktop->workspace); } else { view_add_workspace(view, desktop->workspace); } return true; } static void window_menu_update_desktop_item(struct window_menu *window_menu) { uint32_t count = workspace_manager_get_count(); bool activated = count == MAX_WORKSPACES ? false : true; menu_item_set_activated(window_menu->add_to, activated); menu_item_set_activated(window_menu->move_to, activated); menu_item_lower_to_bottom(window_menu->add_to); menu_item_lower_to_bottom(window_menu->move_to); } static void window_menu_update_desktop(struct window_menu *window_menu) { uint32_t count = workspace_manager_get_count(); struct desktop_item *desktop; char name[256] = { 0 }; for (uint32_t i = 0; i < MAX_WORKSPACES; i++) { uint32_t key = i < 9 ? KEY_1 + i : 0; desktop = &window_menu->add_items[i]; if (i >= count) { if (desktop->item) { menu_item_set_enabled(desktop->item, false); } continue; } desktop->window_menu = window_menu; desktop->workspace = workspace_by_position(i); if (desktop->workspace->has_custom_name) { snprintf(name, 256, "%s%d %s", i < 9 ? "_" : "", i + 1, desktop->workspace->name); } else { snprintf(name, 256, "%s%d %s %d", i < 9 ? "_" : "", i + 1, tr("Desktop"), i + 1); } if (!desktop->item) { desktop->item = menu_add_item(window_menu->desktop, name, key, add_desktop_action, desktop); } else { menu_item_update_text(desktop->item, name); } menu_item_set_enabled(desktop->item, true); menu_item_set_checked(desktop->item, false); menu_item_set_separator(desktop->item, i == 0); menu_item_lower_to_bottom(desktop->item); } struct view_proxy *view_proxy; wl_list_for_each(view_proxy, &window_menu->view->view_proxies, view_link) { desktop = &window_menu->add_items[view_proxy->workspace->position]; menu_item_set_checked(desktop->item, true); } window_menu_update_desktop_item(window_menu); } static bool move_screen_action(struct menu_item *item, uint32_t key, void *data) { struct screen_item *screen = data; if (!item->checked) { view_move_to_output(screen->window_menu->view, NULL, NULL, screen->output); sub_view_move_to_output(screen->window_menu->view, screen->output); } return true; } static bool screen_update(struct kywc_output *output, int index, void *data) { assert(index < MAX_OUTPUTS); struct window_menu *window_menu = data; char name[64] = { 0 }; struct screen_item *screen = &window_menu->screen_items[index]; snprintf(name, 64, "%s %s%d (%s)", tr("Screen"), index < 9 ? "_" : "", index + 1, output->name); uint32_t key = index < 9 ? KEY_1 + index : 0; if (!screen->item) { screen->item = menu_add_item(window_menu->screen, name, key, move_screen_action, screen); } else { menu_item_update_text(screen->item, name); } menu_item_set_enabled(screen->item, true); menu_item_set_checked(screen->item, window_menu->view->output == output); screen->window_menu = window_menu; screen->output = output; return false; } static void window_menu_update_screen(struct window_menu *window_menu) { uint32_t count = output_manager_for_each_output(screen_update, true, window_menu); /* hide screen item when only one output */ bool enable = !output_state_is_mirror_mode() && count > 1; menu_item_set_enabled(window_menu->screen->parent, enable); menu_item_set_activated(window_menu->screen->parent, !view_has_modal_property(window_menu->view)); struct screen_item *screen; for (uint32_t i = count; i < MAX_OUTPUTS; i++) { screen = &window_menu->screen_items[i]; if (screen->item) { menu_item_set_enabled(screen->item, false); } } } static void window_menu_update_view_action(struct window_menu *window_menu) { struct kywc_view *kywc_view = &window_menu->view->base; menu_item_set_activated(window_menu->maximize, KYWC_VIEW_IS_MAXIMIZABLE(kywc_view)); menu_item_set_activated(window_menu->minimize, KYWC_VIEW_IS_MINIMIZABLE(kywc_view)); menu_item_set_activated(window_menu->move, KYWC_VIEW_IS_MOVABLE(kywc_view)); menu_item_set_activated(window_menu->resize, KYWC_VIEW_IS_RESIZABLE(kywc_view)); menu_item_set_activated(window_menu->keep_above, KYWC_VIEW_IS_ABOVEABLE(kywc_view)); menu_item_set_activated(window_menu->keep_below, KYWC_VIEW_IS_BELOWABLE(kywc_view)); menu_item_set_checked(window_menu->maximize, kywc_view->maximized); menu_item_set_checked(window_menu->minimize, kywc_view->minimized); menu_item_set_checked(window_menu->keep_above, kywc_view->kept_above); menu_item_set_checked(window_menu->keep_below, kywc_view->kept_below); } static void window_menu_set_enabled(struct window_menu *window_menu, bool enabled) { if (window_menu->enabled == enabled) { return; } window_menu->enabled = enabled; if (!enabled) { wl_list_remove(&window_menu->view_destroy.link); window_menu->view = NULL; menu_hide_root(window_menu->root); return; } ky_scene_node_raise_to_top(&manager->tree->node); wl_signal_add(&window_menu->view->base.events.destroy, &window_menu->view_destroy); window_menu_update_view_action(window_menu); window_menu_update_screen(window_menu); window_menu_update_desktop(window_menu); menu_show_root(window_menu->root, window_menu->seat, window_menu->x, window_menu->y); } static void window_menu_handle_view_destroy(struct wl_listener *listener, void *data) { struct window_menu *window_menu = wl_container_of(listener, window_menu, view_destroy); window_menu_set_enabled(window_menu, false); } static void window_menu_handle_seat_destroy(struct wl_listener *listener, void *data) { struct window_menu *window_menu = wl_container_of(listener, window_menu, seat_destroy); /* don't destroy the window menu, reuse it */ window_menu->seat = NULL; wl_list_remove(&window_menu->seat_destroy.link); window_menu_set_enabled(window_menu, false); } static struct window_menu *window_menu_create(struct seat *seat) { struct window_menu *window_menu = calloc(1, sizeof(*window_menu)); if (!window_menu) { return NULL; } wl_list_insert(&manager->menus, &window_menu->link); window_menu->seat = seat; window_menu->view_destroy.notify = window_menu_handle_view_destroy; window_menu->seat_destroy.notify = window_menu_handle_seat_destroy; wl_signal_add(&seat->events.destroy, &window_menu->seat_destroy); /* create the root menu: items and submenus */ window_menu->root = menu_create(manager->tree, NULL); menu_set_fade_enabled(window_menu->root, true); menu_add_item(window_menu->root, tr("_Take Screenshot"), KEY_T, window_menu_action, window_menu); struct menu_item *desktop = menu_add_item(window_menu->root, tr("_Desktop"), KEY_D, NULL, NULL); window_menu->desktop = menu_create(manager->tree, desktop); menu_set_fade_enabled(window_menu->desktop, true); menu_add_item(window_menu->desktop, tr("_All Desktop"), KEY_A, window_menu_action, window_menu); window_menu->add_to = menu_add_item(window_menu->desktop, tr("Add To _New Desktop"), KEY_N, window_menu_action, window_menu); menu_item_set_separator(window_menu->add_to, true); window_menu->move_to = menu_add_item(window_menu->desktop, tr("_Move To New Desktop"), KEY_M, window_menu_action, window_menu); window_menu->maximize = menu_add_item(window_menu->root, tr("Ma_ximize"), KEY_X, window_menu_action, window_menu); menu_item_add_shortcut(window_menu->maximize, "Alt+F10"); struct menu_item *screen = menu_add_item(window_menu->root, tr("Move To _Screen"), KEY_S, NULL, NULL); window_menu->screen = menu_create(manager->tree, screen); menu_set_fade_enabled(window_menu->screen, true); window_menu->minimize = menu_add_item(window_menu->root, tr("Mi_nimize"), KEY_N, window_menu_action, window_menu); menu_item_add_shortcut(window_menu->minimize, "Alt+F9"); /* create the more action submenu */ struct menu_item *more = menu_add_item(window_menu->root, tr("_More"), KEY_M, NULL, NULL); window_menu->more = menu_create(manager->tree, more); menu_set_fade_enabled(window_menu->more, true); window_menu->move = menu_add_item(window_menu->more, tr("_Move"), KEY_M, window_menu_action, window_menu); window_menu->resize = menu_add_item(window_menu->more, tr("_Resize"), KEY_R, window_menu_action, window_menu); window_menu->keep_above = menu_add_item(window_menu->more, tr("Keep-_Above"), KEY_A, window_menu_action, window_menu); window_menu->keep_below = menu_add_item(window_menu->more, tr("Keep-_Below"), KEY_B, window_menu_action, window_menu); struct menu_item *close = menu_add_item(window_menu->root, tr("_Close"), KEY_C, window_menu_action, window_menu); menu_item_add_shortcut(close, "Alt+F4"); return window_menu; } static struct window_menu *window_menu_by_seat(struct seat *seat) { struct window_menu *window_menu, *empty_menu = NULL; wl_list_for_each(window_menu, &manager->menus, link) { if (window_menu->seat == seat) { return window_menu; } if (!empty_menu && !window_menu->seat) { empty_menu = window_menu; } } if (empty_menu) { empty_menu->seat = seat; return empty_menu; } return window_menu_create(seat); } void window_menu_show(struct view *view, struct seat *seat, int x, int y) { if (!manager) { return; } /* create or find a window menu for this seat */ struct window_menu *window_menu = window_menu_by_seat(seat); if (!window_menu) { return; } window_menu_set_enabled(window_menu, false); window_menu->view = view; window_menu->x = x; window_menu->y = y; window_menu_set_enabled(window_menu, true); } static void handle_output_configured(struct wl_listener *listener, void *data) { /* disable all window menus when output configured */ struct window_menu *window_menu; wl_list_for_each(window_menu, &manager->menus, link) { window_menu_set_enabled(window_menu, false); } } static void desktop_handle_workspace_destroy(struct wl_listener *listener, void *data) { struct desktop *desktop = wl_container_of(listener, desktop, workspace_destroy); wl_list_remove(&desktop->workspace_destroy.link); wl_list_remove(&desktop->link); free(desktop); struct window_menu *window_menu; wl_list_for_each(window_menu, &manager->menus, link) { if (window_menu->desktop->enabled) { window_menu_set_enabled(window_menu, false); } else if (window_menu->enabled) { window_menu_update_desktop(window_menu); } } } static void handle_new_workspace(struct wl_listener *listener, void *data) { struct window_menu *window_menu; wl_list_for_each(window_menu, &manager->menus, link) { if (window_menu->desktop->enabled) { window_menu_set_enabled(window_menu, false); } else if (window_menu->enabled) { window_menu_update_desktop(window_menu); } } struct desktop *desktop = calloc(1, sizeof(*desktop)); if (!desktop) { return; } struct workspace *workspace = data; desktop->workspace = workspace; wl_list_insert(&manager->desktops, &desktop->link); desktop->workspace_destroy.notify = desktop_handle_workspace_destroy; wl_signal_add(&workspace->events.destroy, &desktop->workspace_destroy); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->server_destroy.link); wl_list_remove(&manager->output_configured.link); wl_list_remove(&manager->new_workspace.link); struct window_menu *menu, *tmp; wl_list_for_each_safe(menu, tmp, &manager->menus, link) { wl_list_remove(&menu->link); free(menu); } /* free all menus by tree node destroy */ ky_scene_node_destroy(&manager->tree->node); free(manager); manager = NULL; } bool window_menu_manager_create(struct view_manager *view_manager) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(view_manager->server, &manager->server_destroy); wl_list_init(&manager->menus); manager->tree = ky_scene_tree_create(view_manager->layers[LAYER_POPUP].tree); manager->output_configured.notify = handle_output_configured; output_manager_add_configured_listener(&manager->output_configured); wl_list_init(&manager->desktops); manager->new_workspace.notify = handle_new_workspace; workspace_manager_add_new_listener(&manager->new_workspace); /* create the default seat one */ window_menu_create(input_manager_get_default_seat()); return true; } kylin-wayland-compositor/src/widget/0000775000175000017500000000000015160461067016537 5ustar fengfengkylin-wayland-compositor/src/widget/menu.c0000664000175000017500000010252615160461067017655 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include "effect/fade.h" #include "input/cursor.h" #include "input/event.h" #include "output.h" #include "painter.h" #include "theme.h" #include "util/color.h" #include "util/macros.h" #include "widget/menu.h" #define SUB_MENU_GAP (2) #define SUB_MENU_OVERLAP (4) #define MENU_FLIP_HEIGHT (12) static void menu_update_shown_item(struct menu *menu, int offset); static void menu_draw_item(struct menu_item *item, bool force) { if (!force && !item->redraw) { return; } item->redraw = false; struct theme *theme = theme_manager_get_theme(); uint32_t text_attrs = item->checked ? TEXT_ATTR_CHECKED : TEXT_ATTR_NONE; text_attrs |= item->submenu ? TEXT_ATTR_SUBMENU : TEXT_ATTR_NONE; text_attrs |= item->key ? TEXT_ATTR_ACCEL : TEXT_ATTR_NONE; if (item->item_type == ITEM_TYPE_FLIP_UP || item->item_type == ITEM_TYPE_FLIP_DOWN) { widget_set_text(item->content, item->text, TEXT_ALIGN_CENTER, text_attrs); widget_set_size(item->content, item->menu->width, MENU_FLIP_HEIGHT); } else { widget_set_text(item->content, item->text, theme->text_is_right_align ? TEXT_ALIGN_RIGHT : TEXT_ALIGN_LEFT, text_attrs); widget_set_size(item->content, item->menu->width, item->menu->item_height); widget_set_hovered_color(item->content, theme->accent_color, theme->normal_radius); } widget_set_shortcut(item->content, item->shortcut); widget_set_font(item->content, theme->font_name, theme->font_size); widget_set_layout(item->content, theme->layout_is_right_to_left); float *front_color = item->activated ? theme->active_text_color : theme->inactive_text_color; widget_set_front_color(item->content, front_color); uint32_t border_mask = item->separator ? BORDER_MASK_TOP : BORDER_MASK_NONE; widget_set_border(item->content, theme->active_border_color, border_mask, theme->border_width); widget_update(item->content, true); } static void menu_set_clip_item(struct menu *menu, struct menu_item *item) { if (!menu || !item || !menu->exceed_output) { return; } struct ky_scene_node *node = NULL; int height = menu->clip_item_height; bool is_bottom = menu->shown_start + menu->shown_item == menu->item_count; if (menu->clip_item) { node = ky_scene_node_from_widget(menu->clip_item->content); pixman_region32_clear(&node->clip_region); pixman_region32_clear(&node->input_region); menu->clip_item = NULL; } /* do not show item if flip item shown and clip_item_height is 0 */ if (menu->shown_start && !is_bottom && !menu->clip_item_height) { item->shown = false; ky_scene_node_set_enabled(&item->tree->node, false); return; } if (is_bottom || !menu->shown_start) { height += MENU_FLIP_HEIGHT; } pixman_region32_t region; pixman_region32_init_rect(®ion, 0, is_bottom ? menu->item_height - height : 0, menu->width, height); node = ky_scene_node_from_widget(item->content); ky_scene_node_set_clip_region(node, ®ion); ky_scene_node_set_input_region(node, ®ion); pixman_region32_fini(®ion); menu->clip_item = item; } static void menu_not_exceed_output(struct menu *menu) { if (menu->flip_up) { menu->flip_up->shown = false; } if (menu->flip_down) { menu->flip_down->shown = false; } if (menu->clip_item) { struct ky_scene_node *node = ky_scene_node_from_widget(menu->clip_item->content); pixman_region32_clear(&node->clip_region); pixman_region32_clear(&node->input_region); menu->clip_item = NULL; } menu->exceed_output = false; ky_scene_decoration_set_surface_size(menu->deco, menu->width, menu->height); } static void menu_exceed_output(struct menu *menu, int output_height) { if (!menu->flip_up) { menu->flip_up = menu_add_item(menu, "▴", 0, NULL, menu); menu->flip_up->item_type = ITEM_TYPE_FLIP_UP; widget_set_enabled(menu->flip_up->content, true); menu_draw_item(menu->flip_up, true); } if (!menu->flip_down) { menu->flip_down = menu_add_item(menu, "▾", 0, NULL, menu); menu->flip_down->item_type = ITEM_TYPE_FLIP_DOWN; widget_set_enabled(menu->flip_down->content, true); menu_draw_item(menu->flip_down, true); } menu->exceed_output = true; ky_scene_node_set_position(&menu->flip_up->tree->node, 0, 0); ky_scene_node_set_position(&menu->flip_down->tree->node, 0, output_height - MENU_FLIP_HEIGHT); ky_scene_decoration_set_surface_size(menu->deco, menu->width, output_height); } static void menu_adjust_exceed_output(struct menu *menu) { struct output *output = menu->root->output; if (!output) { return; } struct theme *theme = theme_manager_get_theme(); int usable_height = output->geometry.height - 2 * theme->border_width; menu->clip_item_height = (usable_height - 2 * MENU_FLIP_HEIGHT) % menu->item_height; menu->shown_item = (usable_height - 2 * MENU_FLIP_HEIGHT) / menu->item_height + 1; if (menu->shown_item > menu->item_count) { menu->shown_item = menu->item_count; } if (menu->height <= usable_height) { menu_not_exceed_output(menu); } else if (menu->height > usable_height) { menu_exceed_output(menu, usable_height); } } static void menu_render_items(struct menu *menu, bool force) { if (wl_list_empty(&menu->items) || (!force && !menu->redraw)) { return; } menu->redraw = false; struct theme *theme = theme_manager_get_theme(); int max_width = 0, max_height = 0, max_shortcut_width = 0; int width = 0, height = 0, shortcut_width = 0, shortcut_height = 0; int ascent = 0, descent = 0; int item_count = 0; struct menu_item *item, *last_item = NULL; wl_list_for_each_reverse(item, &menu->items, link) { if (item->item_type != ITEM_TYPE_NORMAL) { continue; } painter_get_text_size(item->text, theme->font_name, theme->font_size, &width, &height); if (width > max_width) { max_width = width; } if (height > max_height) { max_height = height; } if (item->enabled) { item->first = item_count == 0; last_item = item; item_count++; } if (!item->shortcut) { continue; } painter_get_text_size(item->shortcut, theme->font_name, theme->font_size, &shortcut_width, &shortcut_height); if (shortcut_width > max_shortcut_width) { max_shortcut_width = shortcut_width; } if (shortcut_height > max_height) { max_height = shortcut_height; } } if (last_item) { last_item->last = true; } painter_get_text_metrics(theme->font_name, theme->font_size, &ascent, &descent); if (max_height > ascent + descent) { height = max_height + 18; } else { height = ascent + 18 + descent * 0.35; } width = max_width + max_shortcut_width + height * 1.75; if (width != menu->width) { menu->width = width; force = true; } if (height != menu->item_height) { menu->item_height = height; force = true; } menu->height = menu->item_height * item_count; menu->item_count = item_count; /* update all enable item */ wl_list_for_each_reverse(item, &menu->items, link) { if (!item->enabled) { continue; } menu_draw_item(item, force); } } static void menu_set_enabled(struct menu *menu, bool enabled) { if (menu->enabled == enabled) { return; } struct menu_item *item = NULL; menu->current = NULL; menu->hovered = NULL; menu->enabled = enabled; menu->shown_start = 0; ky_scene_node_set_enabled(&menu->tree->node, enabled); if (enabled) { ky_scene_node_raise_to_top(&menu->tree->node); menu_render_items(menu, false); menu_adjust_exceed_output(menu); if (menu->exceed_output) { menu->flip_up->shown = false; menu->flip_down->shown = true; } } else { wl_list_for_each_reverse(item, &menu->items, link) { if (item->enabled && item->submenu) { menu_set_enabled(item->submenu, false); } } } if (menu->fade_enabled) { struct output *output = menu->root ? menu->root->output : NULL; popup_add_fade_effect(&menu->tree->node, enabled, !menu->parent, !menu->parent, output ? output->base.state.scale : 1.0f); } int index = 0; wl_list_for_each_reverse(item, &menu->items, link) { if (!item->enabled) { continue; } if (item->item_type != ITEM_TYPE_NORMAL) { ky_scene_node_set_enabled(&item->tree->node, item->shown); continue; } item->shown = index >= menu->shown_start && index < menu->shown_start + menu->shown_item; if (item->shown) { ky_scene_node_set_position(&item->tree->node, 0, index * menu->item_height); } ky_scene_node_set_enabled(&item->tree->node, item->shown); widget_set_hovered(item->content, false); widget_set_enabled(item->content, enabled); widget_update(item->content, true); index++; if (menu->exceed_output && index == menu->shown_start + menu->shown_item) { menu_set_clip_item(menu, item); } } if (menu->parent) { return; } /* clear grab when disable a root-menu */ if (!enabled) { seat_end_pointer_grab(menu->seat, &menu->pointer_grab); seat_end_keyboard_grab(menu->seat, &menu->keyboard_grab); seat_end_touch_grab(menu->seat, &menu->touch_grab); } else { seat_start_pointer_grab(menu->seat, &menu->pointer_grab); seat_start_keyboard_grab(menu->seat, &menu->keyboard_grab); seat_start_touch_grab(menu->seat, &menu->touch_grab); } } static void menu_set_position(struct menu *menu, int x, int y) { struct output *output = menu->root->output; if (!output) { return; } /* keep menu visible in the output */ struct kywc_box *geo = &output->geometry; int max_x = geo->x + geo->width; int max_y = geo->y + geo->height; struct theme *theme = theme_manager_get_theme(); int offset = theme->border_width; int extend = theme->border_width * 2; /* use (x, y) when root-menu */ int lx = x, ly = y; /* otherwise use parent item pos */ struct menu_item *parent = menu->parent; if (parent) { ky_scene_node_coords(&parent->tree->node, &lx, &ly); lx -= offset, ly -= offset; } int width = menu->width + extend; int height = menu->height + extend; if (!parent) { if (theme->layout_is_right_to_left) { x = CLAMP(x - width, geo->x, max_x - width); } else { x = CLAMP(x, geo->x, max_x - width); } if (menu->exceed_output) { y = geo->y; } else { y = CLAMP(y, geo->y, max_y - height); } } else { int parent_width = parent->menu->width + extend; if (theme->layout_is_right_to_left) { if (lx - geo->x < width) { x = parent_width - SUB_MENU_OVERLAP; } else { x = -width - SUB_MENU_GAP; } } else { if (lx + parent_width + width > max_x) { x = -width + SUB_MENU_OVERLAP; } else { x = parent_width + SUB_MENU_GAP; } } int off_y = ly + height - max_y; if (menu->exceed_output) { y = geo->y - ly; } else if (off_y > 0) { y -= off_y; } x += lx, y += ly; } ky_scene_node_set_position(&menu->tree->node, x + offset, y + offset); } static bool menu_item_action(struct menu_item *item) { if (item->action) { return item->action(item, item->key, item->data); } return false; } static void menu_shown_by_current_item(struct menu_item *item) { struct menu *menu = item->menu; if (!menu->exceed_output) { return; } int index = 0; struct menu_item *tmp; wl_list_for_each_reverse(tmp, &menu->items, link) { if (!tmp->enabled || tmp->item_type != ITEM_TYPE_NORMAL) { continue; } if (tmp == item) { break; } index++; } if (index < menu->shown_start || index > menu->shown_start + menu->shown_item - 2) { menu_update_shown_item(menu, index < menu->shown_start ? index - menu->shown_start : index - menu->shown_start - menu->shown_item + 2); } } static void menu_item_set_hovered(struct menu_item *item) { struct menu_item *hovered = item->menu->hovered; if (hovered == item) { return; } if (hovered) { widget_set_hovered(hovered->content, false); widget_update(hovered->content, true); if (hovered->submenu) { menu_set_enabled(hovered->submenu, false); } } widget_set_hovered(item->content, true); widget_update(item->content, true); item->menu->hovered = item; } static struct menu_item *menu_first_item(struct menu *menu) { struct menu_item *item; wl_list_for_each_reverse(item, &menu->items, link) { if (item->enabled && item->activated && item->item_type == ITEM_TYPE_NORMAL) { return item; } } return NULL; } static struct menu_item *menu_prev_or_next_item(struct menu *menu, struct wl_list *link, bool next) { struct wl_list *node = next ? link->prev : link->next; /* skip list head */ if (node == &menu->items) { node = next ? menu->items.prev : menu->items.next; } struct menu_item *item = wl_container_of(node, item, link); bool skip = !item->enabled || !item->activated || item->item_type == ITEM_TYPE_FLIP_UP || item->item_type == ITEM_TYPE_FLIP_DOWN; return skip ? menu_prev_or_next_item(menu, node, next) : item; } static void menu_hover_prev_or_next(struct menu *menu, bool next) { if (wl_list_empty(&menu->items)) { return; } struct menu_item *item = menu->hovered ? menu_prev_or_next_item(menu, &menu->hovered->link, next) : menu_first_item(menu); if (item) { menu_item_set_hovered(item); menu_shown_by_current_item(item); } } static void submenu_show(struct menu *menu, bool hovered) { menu_set_enabled(menu, true); menu_set_position(menu, 0, 0); menu->root->current = menu; if (hovered) { menu_hover_prev_or_next(menu, true); } } static void menu_update_shown_item(struct menu *menu, int offset) { if (!menu || !menu->exceed_output) { return; } int shown_start = CLAMP(menu->shown_start + offset, 0, menu->item_count - menu->shown_item); if (shown_start == menu->shown_start) { return; } menu->shown_start = shown_start; int position_y = 0; bool is_bottom = menu->shown_start + menu->shown_item == menu->item_count; if (menu->shown_start) { position_y = MENU_FLIP_HEIGHT; } if (is_bottom) { position_y = MENU_FLIP_HEIGHT - (menu->item_height - menu->clip_item_height - MENU_FLIP_HEIGHT); } menu->flip_up->shown = menu->shown_start ? true : false; menu->flip_down->shown = is_bottom ? false : true; int index = 0; struct menu_item *item, *record_item = NULL; wl_list_for_each_reverse(item, &menu->items, link) { if (!item->enabled) { continue; } if (item->item_type != ITEM_TYPE_NORMAL) { ky_scene_node_set_enabled(&item->tree->node, item->shown); continue; } item->shown = index >= menu->shown_start && index < menu->shown_start + menu->shown_item; if (item->shown) { if (is_bottom && index == menu->shown_start) { record_item = item; } ky_scene_node_set_position( &item->tree->node, 0, (index - menu->shown_start) * menu->item_height + position_y); } ky_scene_node_set_enabled(&item->tree->node, item->shown); index++; if (index == menu->shown_start + menu->shown_item && !record_item) { record_item = item; } } menu_set_clip_item(menu, record_item); } static bool menu_item_hover(struct seat *seat, struct ky_scene_node *node, double x, double y, uint32_t time, bool first, bool hold, void *data) { struct menu_item *item = data; if (!item->activated) { return false; } if (item->item_type == ITEM_TYPE_FLIP_UP || item->item_type == ITEM_TYPE_FLIP_DOWN) { menu_update_shown_item(item->menu, item->item_type == ITEM_TYPE_FLIP_UP ? -1 : 1); return false; } if (first) { cursor_set_image(seat->cursor, CURSOR_DEFAULT); } else if (item->menu->hovered == item) { return false; } menu_item_set_hovered(item); item->menu->root->current = item->menu; if (item->submenu) { submenu_show(item->submenu, false); } /* make sure parent item is hovered */ if (item->menu->parent) { menu_item_set_hovered(item->menu->parent); } return false; } static void menu_item_leave(struct seat *seat, struct ky_scene_node *node, bool last, void *data) { struct menu_item *item = data; /* don't if submenu is enabled */ if (!item->action || (item->submenu && item->submenu->enabled)) { return; } if (item->item_type == ITEM_TYPE_FLIP_UP || item->item_type == ITEM_TYPE_FLIP_DOWN) { return; } if (item->menu->hovered == item) { widget_set_hovered(item->content, false); widget_update(item->content, true); item->menu->hovered = NULL; } } static void menu_item_click(struct seat *seat, struct ky_scene_node *node, uint32_t button, bool pressed, uint32_t time, enum click_state state, void *data) { struct menu_item *item = data; /* do actions when released */ if (!item->activated || !LEFT_BUTTON_RELEASED(button, pressed)) { return; } if (item->item_type == ITEM_TYPE_FLIP_UP || item->item_type == ITEM_TYPE_FLIP_DOWN) { return; } if (menu_item_action(item)) { menu_hide_root(item->menu->root); } } static const struct input_event_node_impl menu_item_impl = { .hover = menu_item_hover, .leave = menu_item_leave, .click = menu_item_click, }; static struct ky_scene_node *menu_item_get_root(void *data) { struct menu_item *item = data; struct menu *menu = item->menu; while (menu->parent) { menu = menu->parent->menu; } return &menu->tree->node; } static bool menu_shortcut(struct menu *menu, uint32_t key) { struct menu_item *item; wl_list_for_each(item, &menu->items, link) { if (!item->enabled || !item->activated || item->key != key) { continue; } if (menu_item_action(item)) { return true; } else if (item->submenu) { submenu_show(item->submenu, true); menu_item_set_hovered(item); } break; } return false; } static bool keyboard_grab_key(struct seat_keyboard_grab *keyboard_grab, struct keyboard *keyboard, uint32_t time, uint32_t key, bool pressed, uint32_t modifiers) { static bool left_alt = false, right_alt = false; if (!pressed) { left_alt = key == KEY_LEFTALT ? false : left_alt; right_alt = key == KEY_RIGHTALT ? false : right_alt; return true; } struct menu *root = keyboard_grab->data; if (!root->current) { root->current = root; } struct menu *menu = root->current; switch (key) { case KEY_UP: menu_hover_prev_or_next(menu, false); break; case KEY_DOWN: menu_hover_prev_or_next(menu, true); break; case KEY_LEFTALT: if (left_alt) { break; } left_alt = true; // fallthrough to right alt key case KEY_RIGHTALT: if (key == KEY_RIGHTALT) { if (right_alt) { break; } right_alt = true; } // fallthrough to esc key case KEY_ESC: if (!menu->parent) { menu_hide_root(root); left_alt = right_alt = false; break; } // fallthrough to left key case KEY_LEFT: if (menu->parent) { menu_set_enabled(menu, false); root->current = menu->parent->menu; } break; case KEY_ENTER: if (menu->hovered) { if (menu_item_action(menu->hovered)) { menu_hide_root(root); break; } } // fallthrough to right key case KEY_RIGHT: if (menu->hovered && menu->hovered->submenu) { submenu_show(menu->hovered->submenu, true); } break; default: if (menu_shortcut(menu, key)) { menu_hide_root(root); } break; } return true; } static void keyboard_grab_cancel(struct seat_keyboard_grab *keyboard_grab) { struct menu *root = keyboard_grab->data; menu_hide_root(root); } static const struct seat_keyboard_grab_interface keyboard_grab_impl = { .key = keyboard_grab_key, .cancel = keyboard_grab_cancel, }; static void pointer_grab_cancel(struct seat_pointer_grab *pointer_grab) { struct menu *root = pointer_grab->data; menu_hide_root(root); } static bool pointer_grab_button(struct seat_pointer_grab *pointer_grab, uint32_t time, uint32_t button, bool pressed) { struct menu *root = pointer_grab->data; struct seat *seat = pointer_grab->seat; /* check current hover node in the window menu tree */ struct input_event_node *inode = input_event_node_from_node(seat->cursor->hover.node); struct ky_scene_node *node = input_event_node_root(inode); if (node == &root->tree->node) { inode->impl->click(seat, seat->cursor->hover.node, button, pressed, time, CLICK_STATE_NONE, inode->data); return true; } else if (pressed) { menu_hide_root(root); } return false; } static bool pointer_grab_motion(struct seat_pointer_grab *pointer_grab, uint32_t time, double lx, double ly) { return false; } static bool pointer_grab_axis(struct seat_pointer_grab *pointer_grab, uint32_t time, bool vertical, double value) { struct menu *root = pointer_grab->data; struct input_event_node *inode = input_event_node_from_node(root->seat->cursor->hover.node); struct ky_scene_node *node = input_event_node_root(inode); if (node != &root->tree->node) { return false; } struct menu_item *item = inode->data; static uint32_t last_time = 0; if (time - last_time < 100) { return false; } last_time = time; menu_update_shown_item(item->menu, value < 0 ? -1 : 1); return true; } static const struct seat_pointer_grab_interface pointer_grab_impl = { .motion = pointer_grab_motion, .button = pointer_grab_button, .axis = pointer_grab_axis, .cancel = pointer_grab_cancel, }; static bool touch_grab_touch(struct seat_touch_grab *touch_grab, uint32_t time, bool down) { // FIXME: interactive grab end struct menu *root = touch_grab->data; return pointer_grab_button(&root->pointer_grab, time, BTN_LEFT, down); } static bool touch_grab_motion(struct seat_touch_grab *touch_grab, uint32_t time, double lx, double ly) { return false; } static void touch_grab_cancel(struct seat_touch_grab *touch_grab) { struct menu *root = touch_grab->data; menu_hide_root(root); } static const struct seat_touch_grab_interface touch_grab_impl = { .touch = touch_grab_touch, .motion = touch_grab_motion, .cancel = touch_grab_cancel, }; void menu_item_set_enabled(struct menu_item *item, bool enabled) { if (item->enabled == enabled) { return; } item->enabled = enabled; item->menu->redraw = true; // TODO: update when menu is enabled } // TODO: update when item is enabled void menu_item_set_checked(struct menu_item *item, bool checked) { if (item->checked == checked) { return; } item->checked = checked; item->redraw = true; item->menu->redraw = true; } void menu_item_set_separator(struct menu_item *item, bool separator) { if (item->separator == separator) { return; } item->separator = separator; item->redraw = true; item->menu->redraw = true; } void menu_item_set_activated(struct menu_item *item, bool activated) { if (item->activated == activated) { return; } item->activated = activated; item->redraw = true; item->menu->redraw = true; } void menu_item_update_text(struct menu_item *item, const char *text) { if (strcmp(text, item->text) == 0) { return; } free(item->text); item->text = strdup(text); item->redraw = true; item->menu->redraw = true; } void menu_item_place_above(struct menu_item *item, struct menu_item *sibling) { assert(item != sibling); assert(item->menu == sibling->menu); if (item->link.prev == &sibling->link) { return; } wl_list_remove(&item->link); wl_list_insert(&sibling->link, &item->link); item->menu->redraw = true; } void menu_item_place_below(struct menu_item *item, struct menu_item *sibling) { assert(item != sibling); assert(item->menu == sibling->menu); if (item->link.next == &sibling->link) { return; } wl_list_remove(&item->link); wl_list_insert(sibling->link.prev, &item->link); item->menu->redraw = true; } void menu_item_raise_to_top(struct menu_item *item) { struct menu_item *top = wl_container_of(item->menu->items.prev, top, link); if (item == top) { return; } menu_item_place_above(item, top); } void menu_item_lower_to_bottom(struct menu_item *item) { struct menu_item *bottom = wl_container_of(item->menu->items.next, bottom, link); if (item == bottom) { return; } menu_item_place_below(item, bottom); } static void item_handle_destroy(struct wl_listener *listener, void *data) { struct menu_item *item = wl_container_of(listener, item, destroy); wl_list_remove(&item->destroy.link); wl_list_remove(&item->link); free(item->text); free(item->shortcut); free(item); } void menu_item_add_shortcut(struct menu_item *item, const char *text) { if ((!text && !item->shortcut) || (item->shortcut && text && strcmp(item->shortcut, text) == 0)) { return; } free(item->shortcut); item->shortcut = strdup(text); item->redraw = true; item->menu->redraw = true; } struct menu_item *menu_add_item(struct menu *menu, const char *text, uint32_t key, bool (*action)(struct menu_item *item, uint32_t key, void *data), void *data) { struct menu_item *item = calloc(1, sizeof(*item)); if (!item) { return NULL; } item->menu = menu; wl_list_insert(&menu->items, &item->link); item->menu->redraw = true; item->redraw = true; item->enabled = true; item->activated = true; item->shown = true; item->data = data; item->text = strdup(text); item->key = key; item->action = action; item->tree = ky_scene_tree_create(menu->tree); item->destroy.notify = item_handle_destroy; /* tree destroy event is before node destroy */ wl_signal_add(&item->tree->node.events.destroy, &item->destroy); /* use widget to create a scene buffer */ item->content = widget_create(item->tree); input_event_node_create(ky_scene_node_from_widget(item->content), &menu_item_impl, menu_item_get_root, NULL, item); return item; } static void menu_handle_destroy(struct wl_listener *listener, void *data) { struct menu *menu = wl_container_of(listener, menu, destroy); wl_list_remove(&menu->destroy.link); wl_list_remove(&menu->output_disable.link); wl_list_remove(&menu->theme_update.link); struct menu_item *item, *tmp; wl_list_for_each_safe(item, tmp, &menu->items, link) { wl_list_remove(&item->link); wl_list_init(&item->link); } free(menu); } static void menu_handle_output_disable(struct wl_listener *listener, void *data) { struct menu *menu = wl_container_of(listener, menu, output_disable); menu_hide_root(menu); } static void menu_update_decoration(struct menu *menu) { struct theme *theme = theme_manager_get_theme(); int r = theme->menu_radius, border = theme->border_width; ky_scene_decoration_set_shadow_count(menu->deco, theme->menu_shadow_color.num_layers); for (int i = 0; i < theme->menu_shadow_color.num_layers; i++) { struct theme_shadow_layer *shadow = &theme->menu_shadow_color.layers[i]; float shadow_color[4]; color_float_pa(shadow_color, shadow->color); ky_scene_decoration_set_shadow(menu->deco, i, shadow->off_x, shadow->off_y, shadow->spread, shadow->blur, shadow_color); } ky_scene_decoration_set_margin(menu->deco, 0, border); ky_scene_decoration_set_round_corner_radius(menu->deco, (int[4]){ r, r, r, r }); ky_scene_node_set_position(ky_scene_node_from_decoration(menu->deco), -border, -border); ky_scene_decoration_set_surface_blurred(menu->deco, theme->opacity != 100); float border_color[4]; color_float_pa(border_color, theme->active_border_color); float bg_color[4]; color_float_pax(bg_color, theme->active_bg_color, theme->opacity / 100.0); ky_scene_decoration_set_surface_color(menu->deco, bg_color); ky_scene_decoration_set_margin_color(menu->deco, bg_color, border_color); } static void menu_handle_theme_update(struct wl_listener *listener, void *data) { struct menu *menu = wl_container_of(listener, menu, theme_update); struct theme_update_event *update_event = data; uint32_t allowed_mask = THEME_UPDATE_MASK_FONT | THEME_UPDATE_MASK_BACKGROUND_COLOR | THEME_UPDATE_MASK_ACCENT_COLOR | THEME_UPDATE_MASK_CORNER_RADIUS | THEME_UPDATE_MASK_OPACITY | THEME_UPDATE_MASK_BORDER_COLOR | THEME_UPDATE_MASK_SHADOW_COLOR; if (update_event->update_mask & allowed_mask) { /* force update all items */ menu_render_items(menu, true); menu_update_decoration(menu); } } struct menu *menu_create(struct ky_scene_tree *parent, struct menu_item *parent_item) { assert(parent); struct menu *menu = calloc(1, sizeof(*menu)); if (!menu) { return NULL; } menu->tree = ky_scene_tree_create(parent); ky_scene_node_set_enabled(&menu->tree->node, false); menu->destroy.notify = menu_handle_destroy; wl_signal_add(&menu->tree->node.events.destroy, &menu->destroy); menu->output_disable.notify = menu_handle_output_disable; wl_list_init(&menu->output_disable.link); wl_list_init(&menu->items); menu->parent = parent_item; menu->redraw = true; if (parent_item) { // is a submenu parent_item->submenu = menu; menu->root = parent_item->menu->root; } else { menu->root = menu; menu->pointer_grab.data = menu; menu->pointer_grab.interface = &pointer_grab_impl; menu->keyboard_grab.data = menu; menu->keyboard_grab.interface = &keyboard_grab_impl; menu->touch_grab.data = menu; menu->touch_grab.interface = &touch_grab_impl; } /* create shadow and blur support */ menu->deco = ky_scene_decoration_create(menu->tree); ky_scene_decoration_set_mask(menu->deco, DECORATION_MASK_ALL); menu_update_decoration(menu); menu->theme_update.notify = menu_handle_theme_update; theme_manager_add_update_listener(&menu->theme_update, false); return menu; } void menu_destroy(struct menu *menu) { menu_set_enabled(menu, false); if (menu->parent) { menu->parent->submenu = NULL; } ky_scene_node_destroy(&menu->tree->node); } void menu_set_fade_enabled(struct menu *menu, bool enabled) { menu->fade_enabled = enabled; } void menu_show_root(struct menu *menu, struct seat *seat, int x, int y) { assert(menu->parent == NULL); /* update root menu with the new seat and position */ if (menu->enabled) { menu_set_enabled(menu, false); } struct kywc_output *kywc_output = kywc_output_at_point(x, y); struct output *output = output_from_kywc_output(kywc_output); wl_list_remove(&menu->output_disable.link); wl_signal_add(&output->events.disable, &menu->output_disable); menu->output = output; menu->seat = seat; menu_set_enabled(menu, true); menu_set_position(menu, x, y); } void menu_hide_root(struct menu *menu) { assert(menu->parent == NULL); menu_set_enabled(menu, false); menu->seat = NULL; wl_list_remove(&menu->output_disable.link); wl_list_init(&menu->output_disable.link); menu->output = NULL; } kylin-wayland-compositor/src/widget/meson.build0000664000175000017500000000011115160460057020670 0ustar fengfengwlcom_sources += files( 'menu.c', 'scaled_buffer.c', 'widget.c', ) kylin-wayland-compositor/src/widget/scaled_buffer.c0000664000175000017500000001273515160461067021477 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "output.h" #include "widget/scaled_buffer.h" struct scaled_buffer { struct ky_scene_buffer *base; struct wl_listener node_destroy; /* output links */ struct wl_list outputs; struct wl_listener output_enter; struct wl_listener output_leave; scaled_buffer_update_func_t update; scaled_buffer_destroy_func_t destroy; void *data; /* current buffer scale */ float scale; }; struct scaled_output { struct wl_list link; struct scaled_buffer *buffer; struct wlr_output *wlr_output; struct wl_listener output_scale; }; static void scaled_buffer_update(struct scaled_buffer *buffer) { if (wl_list_empty(&buffer->outputs)) { return; } float scale = 0.0; struct scaled_output *output; /* find max scale in current output list */ wl_list_for_each(output, &buffer->outputs, link) { if (scale < output->wlr_output->scale) { scale = output->wlr_output->scale; } } /* no need to redraw if max scale not changed. * wlr_scene_buffer_set_buffer will emit output_enter, so loop: * update_buffer -> wlr_scene_buffer_set_buffer -> * output_enter -> update_buffer -> return early here */ if (scale == buffer->scale) { return; } buffer->scale = scale; if (buffer->update) { buffer->update(buffer->base, buffer->scale, buffer->data); } } static void handle_output_scale(struct wl_listener *listener, void *data) { struct scaled_output *output = wl_container_of(listener, output, output_scale); scaled_buffer_update(output->buffer); } static struct scaled_output *scaled_output_create(struct scaled_buffer *buffer, struct wlr_output *wlr_output) { /* create output and insert to output list */ struct scaled_output *output = calloc(1, sizeof(*output)); if (!output) { return NULL; } output->buffer = buffer; output->wlr_output = wlr_output; wl_list_insert(&buffer->outputs, &output->link); /* update buffer if output scale changed */ struct kywc_output *kywc_output = &output_from_wlr_output(wlr_output)->base; output->output_scale.notify = handle_output_scale; wl_signal_add(&kywc_output->events.scale, &output->output_scale); return output; } static void scaled_output_destroy(struct scaled_output *output) { wl_list_remove(&output->link); wl_list_remove(&output->output_scale.link); free(output); } static void handle_output_enter(struct wl_listener *listener, void *data) { struct scaled_buffer *buffer = wl_container_of(listener, buffer, output_enter); struct ky_scene_output *scene_output = data; if (scaled_output_create(buffer, scene_output->output)) { scaled_buffer_update(buffer); } } static struct scaled_output *scaled_output_from_wlr_output(struct scaled_buffer *buffer, struct wlr_output *wlr_output) { struct scaled_output *output; wl_list_for_each(output, &buffer->outputs, link) { if (output->wlr_output == wlr_output) { return output; } } return NULL; } /* if buffer leave this output, or this output is plug-out or disabled */ static void handle_output_leave(struct wl_listener *listener, void *data) { struct scaled_buffer *buffer = wl_container_of(listener, buffer, output_leave); struct ky_scene_output *scene_output = data; struct scaled_output *output = scaled_output_from_wlr_output(buffer, scene_output->output); if (!output) { return; } scaled_output_destroy(output); scaled_buffer_update(buffer); } static void handle_node_destroy(struct wl_listener *listener, void *data) { struct scaled_buffer *buffer = wl_container_of(listener, buffer, node_destroy); wl_list_remove(&buffer->node_destroy.link); wl_list_remove(&buffer->output_enter.link); wl_list_remove(&buffer->output_leave.link); /* destroy outputs */ struct scaled_output *output, *tmp; wl_list_for_each_safe(output, tmp, &buffer->outputs, link) { scaled_output_destroy(output); } if (buffer->destroy) { buffer->destroy(buffer->base, buffer->data); } /* scene_buffer is destroyed in node_destroy */ free(buffer); } struct ky_scene_buffer *scaled_buffer_create(struct ky_scene_tree *parent, float scale, scaled_buffer_update_func_t update, scaled_buffer_destroy_func_t destroy, void *data) { struct scaled_buffer *buffer = calloc(1, sizeof(*buffer)); if (!buffer) { return NULL; } buffer->scale = scale; buffer->update = update; buffer->destroy = destroy; buffer->data = data; wl_list_init(&buffer->outputs); buffer->base = ky_scene_buffer_create(parent, NULL); /* add output enter and leave listener */ buffer->output_enter.notify = handle_output_enter; wl_signal_add(&buffer->base->events.output_enter, &buffer->output_enter); buffer->output_leave.notify = handle_output_leave; wl_signal_add(&buffer->base->events.output_leave, &buffer->output_leave); /* do something when node destroy */ buffer->node_destroy.notify = handle_node_destroy; wl_signal_add(&buffer->base->node.events.destroy, &buffer->node_destroy); return buffer->base; } kylin-wayland-compositor/src/widget/widget.c0000664000175000017500000003723315160461067020176 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include "painter.h" #include "util/macros.h" #include "widget/scaled_buffer.h" #include "widget/widget.h" #include "widget_p.h" // TODO: support buffers for different scales ? static struct wlr_buffer *widget_paint_buffer(struct widget *widget, float scale, bool redraw_only) { struct draw_info info = { .width = widget->auto_resize ? widget->max_width : widget->width, .height = widget->auto_resize ? widget->max_height : widget->height, .scale = scale, .solid_rgba = COLOR_VALID(widget->background_color) ? widget->background_color : NULL, .hover_rgba = COLOR_VALID(widget->hovered_color) ? widget->hovered_color : NULL, .border_rgba = COLOR_VALID(widget->border_color) ? widget->border_color : NULL, .border_mask = widget->border_mask, .border_width = widget->border_width, .corner_mask = widget->corner_mask, .corner_radius = widget->corner_radius, .hover_radius = widget->hover_radius, .text = widget->text, .shortcut = widget->shortcut, .font_rgba = COLOR_VALID(widget->front_color) ? widget->front_color : NULL, .font_name = widget->font_name, .font_size = widget->font_size, .align = widget->text_align, .auto_resize = widget->auto_resize, .text_attrs = widget->text_attrs, }; struct wlr_buffer *buffer = widget->content.buffer->buffer; if (buffer && redraw_only) { painter_buffer_redraw(buffer, &info); } else { buffer = painter_draw_buffer(&info); if (!buffer) { return NULL; } } if (widget->auto_resize) { int width; painter_buffer_get_dest_size(buffer, &width, NULL); widget->text_truncated = width == widget->max_width; } return buffer; } static void widget_buffer_get_size(struct widget *widget, struct wlr_buffer *buffer, struct wlr_fbox *src, int *width, int *height) { int w, h, scaled_w = buffer->width, scaled_h = buffer->height; painter_buffer_get_dest_size(buffer, &w, &h); src->x = 0; src->y = widget->hovered ? scaled_h / 2 : 0; src->width = scaled_w; src->height = widget->hoverable ? scaled_h / 2 : scaled_h; *width = w; *height = widget->hoverable ? h / 2 : h; } static void widget_set_buffer(struct widget *widget, struct wlr_buffer *buffer, bool redraw_only) { struct ky_scene_buffer *scene_buffer = widget->content.buffer; if (!buffer) { int width = widget->width != 0 ? widget->width : widget->max_width; int height = widget->height != 0 ? widget->height : widget->max_height; assert(width != 0 && height != 0); ky_scene_buffer_set_dest_size(scene_buffer, width, height); return; } struct wlr_buffer *old_buffer = scene_buffer->buffer; if (old_buffer != buffer) { wlr_buffer_drop(old_buffer); } if (old_buffer != buffer || redraw_only) { ky_scene_buffer_set_buffer(scene_buffer, buffer); } /* shortcut here if set_buffer triggered scaled buffer update */ if (scene_buffer->buffer != buffer) { return; } struct wlr_fbox src; int width, height; widget_buffer_get_size(widget, buffer, &src, &width, &height); ky_scene_buffer_set_source_box(scene_buffer, &src); ky_scene_buffer_set_dest_size(scene_buffer, width, height); } static void widget_do_update(struct widget *widget) { if (widget->pending_cause & WIDGET_UPDATE_CAUSE_ENABLED) { widget->pending_cause &= ~WIDGET_UPDATE_CAUSE_ENABLED; ky_scene_node_set_enabled(widget->content.node, widget->enabled); } /* skip update widget if widget is disabled */ if (!widget->enabled) { return; } /* draw it in scaled buffer update at the first time */ if (widget->scale == 0.0) { widget_set_buffer(widget, NULL, false); if (widget->content.buffer->buffer) { widget->pending_cause = WIDGET_UPDATE_CAUSE_NONE; return; } // fallback to draw widget in 1.0 widget->scale = 1.0; widget->pending_cause |= WIDGET_UPDATE_CAUSE_SCALE; } /* we need paint a new buffer if content and scale changed */ if (widget->pending_cause & (WIDGET_UPDATE_CAUSE_CONTENT | WIDGET_UPDATE_CAUSE_SCALE)) { /* only redraw content if size or scale not changed */ bool redraw_only = !widget->auto_resize && !(widget->pending_cause & (WIDGET_UPDATE_CAUSE_SIZE | WIDGET_UPDATE_CAUSE_SCALE)); struct wlr_buffer *buf = widget_paint_buffer(widget, widget->scale, redraw_only); if (buf) { widget_set_buffer(widget, buf, redraw_only); } widget->pending_cause = WIDGET_UPDATE_CAUSE_NONE; return; } struct wlr_buffer *buffer = widget->content.buffer->buffer; if (!buffer) { return; } if (widget->pending_cause & WIDGET_UPDATE_CAUSE_SIZE) { int width; painter_buffer_get_dest_size(buffer, &width, NULL); /* skip paint buffer if text not truncated when auto-resized */ if (widget->auto_resize && !widget->text_truncated && width <= widget->max_width) { widget->pending_cause &= ~WIDGET_UPDATE_CAUSE_SIZE; } else { struct wlr_buffer *buf = widget_paint_buffer(widget, widget->scale, false); if (buf) { widget_set_buffer(widget, buf, false); } widget->pending_cause = WIDGET_UPDATE_CAUSE_NONE; return; } } /* hovered changed only */ if (widget->pending_cause & WIDGET_UPDATE_CAUSE_HOVERED) { widget->pending_cause = WIDGET_UPDATE_CAUSE_NONE; struct wlr_fbox src; int width, height; widget_buffer_get_size(widget, buffer, &src, &width, &height); ky_scene_buffer_set_source_box(widget->content.buffer, &src); ky_scene_buffer_set_dest_size(widget->content.buffer, width, height); return; } if (widget->pending_cause & WIDGET_UPDATE_CAUSE_FORCE) { widget->pending_cause = WIDGET_UPDATE_CAUSE_NONE; struct ky_scene_buffer *scene_buffer = widget->content.buffer; ky_scene_buffer_set_buffer(scene_buffer, scene_buffer->buffer); return; } } void widget_update(struct widget *widget, bool partial) { /* force update when partial update is not enabled */ if (!partial) { widget->pending_cause |= WIDGET_UPDATE_CAUSE_FORCE; } if (widget->pending_cause == WIDGET_UPDATE_CAUSE_NONE || (!widget->enabled && !(widget->pending_cause & WIDGET_UPDATE_CAUSE_ENABLED))) { return; } widget_do_update(widget); } void widget_set_round_corner(struct widget *widget, uint32_t mask, float radius) { if (widget->corner_mask == mask && widget->corner_radius == radius) { return; } widget->corner_mask = mask; widget->corner_radius = radius; widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; if (!widget->blurred) { return; } int round_radius[4] = { mask & CORNER_MASK_BOTTOM_RIGHT ? radius : 0, mask & CORNER_MASK_TOP_RIGHT ? radius : 0, mask & CORNER_MASK_BOTTOM_LEFT ? radius : 0, mask & CORNER_MASK_TOP_LEFT ? radius : 0, }; ky_scene_node_set_radius(widget->content.node, round_radius); } void widget_set_opacity(struct widget *widget, float opacity) { /* no need to redraw the buffer */ ky_scene_buffer_set_opacity(widget->content.buffer, opacity); } void widget_set_blurred(struct widget *widget, bool blurred) { if (widget->blurred == blurred) { return; } widget->blurred = blurred; if (!blurred) { ky_scene_node_set_blur_region(widget->content.node, NULL); ky_scene_node_set_radius(widget->content.node, (int[4]){ 0, 0, 0, 0 }); return; } pixman_region32_t region; pixman_region32_init(®ion); ky_scene_node_set_blur_region(widget->content.node, ®ion); pixman_region32_fini(®ion); /* update radius by widget corner_mask and corner_radius */ int round_radius[4] = { widget->corner_mask & CORNER_MASK_BOTTOM_RIGHT ? widget->corner_radius : 0, widget->corner_mask & CORNER_MASK_TOP_RIGHT ? widget->corner_radius : 0, widget->corner_mask & CORNER_MASK_BOTTOM_LEFT ? widget->corner_radius : 0, widget->corner_mask & CORNER_MASK_TOP_LEFT ? widget->corner_radius : 0, }; ky_scene_node_set_radius(widget->content.node, round_radius); } void widget_set_border(struct widget *widget, const float color[static 4], uint32_t mask, float width) { if (memcmp(widget->border_color, color, sizeof(widget->border_color))) { memcpy(widget->border_color, color, sizeof(widget->border_color)); widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; } if (widget->border_mask != mask) { widget->border_mask = mask; widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; } if (widget->border_width != width) { widget->border_width = width; widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; } } void widget_set_background_color(struct widget *widget, const float color[static 4]) { if (memcmp(widget->background_color, color, sizeof(widget->background_color)) == 0) { return; } memcpy(widget->background_color, color, sizeof(widget->background_color)); widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; } void widget_set_front_color(struct widget *widget, const float color[static 4]) { if (memcmp(widget->front_color, color, sizeof(widget->front_color)) == 0) { return; } memcpy(widget->front_color, color, sizeof(widget->front_color)); widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; } void widget_set_hovered_color(struct widget *widget, const float color[static 4], float radius) { if (widget->hover_radius != radius) { widget->hover_radius = radius; widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; } if (memcmp(widget->hovered_color, color, sizeof(widget->hovered_color)) == 0) { return; } memcpy(widget->hovered_color, color, sizeof(widget->hovered_color)); widget->hoverable = COLOR_VALID(widget->hovered_color); widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; } void widget_set_auto_resize(struct widget *widget, int auto_resize) { if (widget->auto_resize == auto_resize) { return; } widget->auto_resize = auto_resize; widget->pending_cause |= WIDGET_UPDATE_CAUSE_SIZE; } void widget_set_size(struct widget *widget, int width, int height) { if (widget->max_width != 0 && width > widget->max_width) { width = widget->max_width; } if (widget->max_height != 0 && height > widget->max_height) { height = widget->max_height; } if (widget->width == width && widget->height == height) { return; } widget->width = width; widget->height = height; widget->pending_cause |= WIDGET_UPDATE_CAUSE_SIZE; } void widget_set_max_size(struct widget *widget, int width, int height) { if (widget->max_width == width && widget->max_height == height) { return; } widget->max_width = width; widget->max_height = height; widget->pending_cause |= WIDGET_UPDATE_CAUSE_SIZE; if (widget->width > widget->max_width) { widget->width = widget->max_width; } if (widget->height > widget->max_height) { widget->height = widget->max_height; } } void widget_set_enabled(struct widget *widget, bool enabled) { if (widget->enabled == enabled) { return; } widget->enabled = enabled; widget->pending_cause |= WIDGET_UPDATE_CAUSE_ENABLED; } void widget_set_hovered(struct widget *widget, bool hovered) { if (!widget->hoverable || widget->hovered == hovered) { return; } widget->hovered = hovered; widget->pending_cause |= WIDGET_UPDATE_CAUSE_HOVERED; } void widget_set_font(struct widget *widget, const char *name, int size) { if (widget->font_size != size) { widget->font_size = size; widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; } if ((!widget->font_name && !name) || (widget->font_name && name && strcmp(widget->font_name, name) == 0)) { return; } free((void *)widget->font_name); widget->font_name = name ? strdup(name) : NULL; widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; } void widget_set_layout(struct widget *widget, bool right_to_left) { if ((widget->text_attrs & TEXT_ATTR_RTL) == right_to_left) { return; } if (right_to_left) { widget->text_attrs |= TEXT_ATTR_RTL; } else { widget->text_attrs &= ~TEXT_ATTR_RTL; } widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; } void widget_set_text(struct widget *widget, const char *text, int align, uint32_t attrs) { if (widget->text_attrs != attrs) { widget->text_attrs = attrs; widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; } if (widget->text_align != align) { widget->text_align = align; widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; } if (widget->text && text && strcmp(widget->text, text) == 0) { return; } free((void *)widget->text); widget->text = text ? strdup(text) : NULL; widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; } void widget_set_shortcut(struct widget *widget, const char *text) { if ((!text && !widget->shortcut) || (widget->shortcut && text && strcmp(widget->shortcut, text) == 0)) { return; } free((void *)widget->shortcut); widget->shortcut = text ? strdup(text) : NULL; widget->pending_cause |= WIDGET_UPDATE_CAUSE_CONTENT; } void widget_destroy(struct widget *widget) { /* widget_destroy_buffer is called */ ky_scene_node_destroy(widget->content.node); } /* called when widget scene node destroyed */ static void widget_destroy_buffer(struct ky_scene_buffer *buffer, void *data) { struct widget *widget = data; wlr_buffer_drop(buffer->buffer); free((void *)widget->text); free((void *)widget->shortcut); free((void *)widget->font_name); free(widget); } static void widget_update_buffer(struct ky_scene_buffer *buffer, float scale, void *data) { struct widget *widget = data; if (widget->scale == scale) { return; } widget->scale = scale; if (!widget->enabled) { widget->pending_cause |= WIDGET_UPDATE_CAUSE_SCALE; return; } struct wlr_buffer *buf = widget_paint_buffer(widget, scale, false); if (buf) { widget_set_buffer(widget, buf, false); } } struct ky_scene_node *ky_scene_node_from_widget(struct widget *widget) { return widget->content.node; } struct widget *widget_create(struct ky_scene_tree *parent) { struct widget *widget = calloc(1, sizeof(*widget)); if (!widget) { return NULL; } widget->scale = 0.0; widget->content.buffer = scaled_buffer_create(parent, widget->scale, widget_update_buffer, widget_destroy_buffer, widget); widget->content.node = &widget->content.buffer->node; ky_scene_node_set_enabled(widget->content.node, false); return widget; } void widget_get_size(struct widget *widget, int *width, int *height) { *width = 0; *height = 0; struct wlr_buffer *buffer = widget->content.buffer->buffer; if (!buffer) { return; } painter_buffer_get_dest_size(buffer, width, height); } float widget_get_scale(struct widget *widget) { return widget->scale; } kylin-wayland-compositor/src/widget/widget_p.h0000664000175000017500000000261115160460057020510 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _WIDGET_P_H_ #define _WIDGET_P_H_ #include #include enum widget_update_cause { WIDGET_UPDATE_CAUSE_NONE = 0, WIDGET_UPDATE_CAUSE_SIZE = 1 << 0, WIDGET_UPDATE_CAUSE_CONTENT = 1 << 1, WIDGET_UPDATE_CAUSE_HOVERED = 1 << 2, WIDGET_UPDATE_CAUSE_ENABLED = 1 << 3, WIDGET_UPDATE_CAUSE_SCALE = 1 << 4, WIDGET_UPDATE_CAUSE_FORCE = 1 << 5, }; struct widget { struct { struct ky_scene_buffer *buffer; struct ky_scene_node *node; } content; // struct wl_listener output_frame; float scale; /* current content in widget */ const char *text; const char *shortcut; const char *font_name; int font_size; int text_align; bool text_truncated; uint32_t text_attrs; /* color in this widget */ float background_color[4]; float front_color[4]; float hovered_color[4]; float border_color[4]; uint32_t border_mask; float border_width; uint32_t corner_mask; float corner_radius; float hover_radius; int width, height; int max_width, max_height; uint32_t pending_cause; /* auto resize to content, clamp to max size */ int auto_resize; /* widget support hover state */ bool hoverable; bool enabled, hovered, blurred; }; #endif /* _WIDGET_P_H_ */ kylin-wayland-compositor/src/util/0000775000175000017500000000000015160461067016231 5ustar fengfengkylin-wayland-compositor/src/util/meson.build0000664000175000017500000000112715160461067020374 0ustar fengfengutil_sources = files( 'boxes.c', 'event.c', 'file.c', 'hash_table.c', 'identifier.c', 'limit.c', 'logger.c', 'queue.c', 'spawn.c', 'string.c', 'sysfs.c', 'time.c', ) util_static = static_library( 'util_static', util_sources, include_directories: wlcom_inc, c_args: '-DUTIL_USED_IN_CLIENT', build_by_default : false, ) util = declare_dependency( link_with: util_static, ) crypto = dependency('openssl') wlcom_deps += crypto wlcom_sources += util_sources wlcom_sources += files( 'dbus.c', 'debug.c', 'matrix.c', 'quirks.c', 'vec2.c', 'wayland.c', ) kylin-wayland-compositor/src/util/vec2.c0000664000175000017500000000512515160460057017235 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "util/vec2.h" float ky_vec2_length2(struct ky_vec2 *vec) { return vec->x * vec->x + vec->y * vec->y; } float ky_vec2_length(struct ky_vec2 *vec) { return sqrtf(vec->x * vec->x + vec->y * vec->y); } float ky_vec2_distance2(struct ky_vec2 *a_vec, struct ky_vec2 *b_vec) { float x = b_vec->x - a_vec->x; float y = b_vec->y - a_vec->y; return x * x + y * y; } float ky_vec2_distance(struct ky_vec2 *a_vec, struct ky_vec2 *b_vec) { float x = b_vec->x - a_vec->x; float y = b_vec->y - a_vec->y; return sqrtf(x * x + y * y); } void ky_vec2_normalize(struct ky_vec2 *vec) { float length = ky_vec2_length(vec); vec->x /= length; vec->y /= length; } void ky_vec2_normalize_to(struct ky_vec2 *vec, struct ky_vec2 *result) { float length = ky_vec2_length(vec); result->x = vec->x / length; result->y = vec->y / length; } void ky_vec2_perpendicular(struct ky_vec2 *vec, struct ky_vec2 *result) { result->x = vec->y; result->y = -vec->x; } void ky_vec2_add(struct ky_vec2 *a_vec, struct ky_vec2 *b_vec, struct ky_vec2 *result) { result->x = a_vec->x + b_vec->x; result->y = a_vec->y + b_vec->y; } void ky_vec2_sub(struct ky_vec2 *a_vec, struct ky_vec2 *b_vec, struct ky_vec2 *result) { result->x = a_vec->x - b_vec->x; result->y = a_vec->y - b_vec->y; } void ky_vec2_mul(struct ky_vec2 *a_vec, struct ky_vec2 *b_vec, struct ky_vec2 *result) { result->x = a_vec->x * b_vec->x; result->y = a_vec->y * b_vec->y; } void ky_vec2_div(struct ky_vec2 *a_vec, struct ky_vec2 *b_vec, struct ky_vec2 *result) { result->x = a_vec->x / b_vec->x; result->y = a_vec->y / b_vec->y; } void ky_vec2_muls(struct ky_vec2 *vec, float s) { vec->x *= s; vec->y *= s; } void ky_vec2_divs(struct ky_vec2 *vec, float s) { vec->x /= s; vec->y /= s; } void ky_vec2_muls_to(struct ky_vec2 *vec, float s, struct ky_vec2 *result) { result->x = vec->x * s; result->y = vec->y * s; } void ky_vec2_divs_to(struct ky_vec2 *vec, float s, struct ky_vec2 *result) { result->x = vec->x / s; result->y = vec->y / s; } void ky_vec2_min(struct ky_vec2 *a_vec, struct ky_vec2 *b_vec, struct ky_vec2 *result) { result->x = a_vec->x < b_vec->x ? a_vec->x : b_vec->x; result->y = a_vec->y < b_vec->y ? a_vec->y : b_vec->y; } void ky_vec2_max(struct ky_vec2 *a_vec, struct ky_vec2 *b_vec, struct ky_vec2 *result) { result->x = a_vec->x > b_vec->x ? a_vec->x : b_vec->x; result->y = a_vec->y > b_vec->y ? a_vec->y : b_vec->y; } kylin-wayland-compositor/src/util/sysfs.c0000664000175000017500000000437015160461067017550 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "util/sysfs.h" bool sysfs_read_uint64(const char *filename, uint64_t *val) { FILE *fp = fopen(filename, "r"); if (!fp) { kywc_log(KYWC_ERROR, "Reading %s failed", filename); return false; } char buffer[64] = { 0 }; if (fgets(buffer, sizeof(buffer), fp) == NULL) { kywc_log(KYWC_ERROR, "Couldn't read from '%s'", filename); fclose(fp); return false; } fclose(fp); buffer[strcspn(buffer, "\n")] = '\0'; char *endptr = NULL; errno = 0; unsigned long long result = strtoull(buffer, &endptr, 10); if (endptr == buffer) { kywc_log(KYWC_ERROR, "No digits found in '%s'", filename); return false; } if (errno == ERANGE) { kywc_log(KYWC_ERROR, "Value out of range in '%s'", filename); return false; } if (*endptr != '\0') { kywc_log(KYWC_ERROR, "Invalid characters in number '%s': '%s'", filename, endptr); return false; } if (result > UINT64_MAX) { kywc_log(KYWC_ERROR, "Value too large for uint64_t in '%s'", filename); return false; } *val = (uint64_t)result; return true; } bool sysfs_write_uint64(const char *filename, uint64_t val) { FILE *fp = fopen(filename, "w"); if (!fp) { kywc_log(KYWC_ERROR, "Writing %s failed", filename); return false; } if (fprintf(fp, "%" PRIu64, val) < 0) { kywc_log(KYWC_ERROR, "Couldn't write an unsigned integer to '%s'", filename); fclose(fp); return false; } fclose(fp); return true; } size_t sysfs_read_data(const char *filename, void *buf, size_t size) { if (!filename || !buf || size == 0) { kywc_log(KYWC_ERROR, "Invalid parameters for reading file"); return 0; } FILE *fp = fopen(filename, "r"); if (!fp) { kywc_log(KYWC_ERROR, "Reading %s failed", filename); return 0; } size_t len = fread(buf, 1, size, fp); if (ferror(fp)) { kywc_log(KYWC_ERROR, "Error occurred while reading '%s'", filename); len = 0; } fclose(fp); return len; } kylin-wayland-compositor/src/util/identifier.c0000664000175000017500000001002215160461067020512 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "util/string.h" const char *kywc_identifier_generate(const char *format, ...) { if (!format) { return NULL; } va_list args; va_start(args, format); char *identifier = string_create_vargs(format, args); va_end(args); string_strip_space(identifier); string_replace_unprintable(identifier); return identifier; } const char *kywc_identifier_time_generate(const char *prefix, const char *suffix) { size_t len = (prefix ? strlen(prefix) : 0) + (suffix ? strlen(suffix) : 0) + 20; char *identifier = malloc(len); if (!identifier) { return NULL; } time_t timer = time(NULL); struct tm *tm = localtime(&timer); snprintf(identifier, len, "%s%04d-%02d-%02d_%02d-%02d-%02d%s", prefix ? prefix : "", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, suffix ? suffix : ""); return identifier; } const char *kywc_identifier_rand_generate(char *data, int suffixlen) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); long r = ts.tv_nsec; char *buf = data + strlen(data) - 6 - suffixlen; for (int i = 0; i < 6; ++i) { buf[i] = 'A' + (r & 15) + (r & 16) * 2; r >>= 5; } return data; } #ifndef UTIL_USED_IN_CLIENT static char hexchar(int x) { static const char table[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', }; return table[x & 15]; } const char *kywc_identifier_uuid_generate(void) { unsigned char uuid[16]; RAND_bytes(uuid, sizeof(uuid)); /* Set UUID version to 4 --- truly uuid generation */ uuid[6] = (uuid[6] & 0x0F) | 0x40; /* Set the UUID variant to DCE */ uuid[8] = (uuid[8] & 0x3F) | 0x80; char *uuid_str = malloc(37); size_t k = 0; for (size_t n = 0; n < 16; n++) { if (n == 4 || n == 6 || n == 8 || n == 10) { uuid_str[k++] = '-'; } uuid_str[k++] = hexchar(uuid[n] >> 4); uuid_str[k++] = hexchar(uuid[n] & 0xF); } uuid_str[k] = 0; return uuid_str; } const char *kywc_identifier_md5_generate(const void *data, unsigned int len) { unsigned char md5[16] = { 0 }; EVP_Digest(data, len, md5, NULL, EVP_md5(), NULL); char *md5_str = malloc(UUID_SIZE); size_t k = 0; for (size_t n = 0; n < 16; n++) { md5_str[k++] = hexchar(md5[n] >> 4); md5_str[k++] = hexchar(md5[n] & 0xF); } md5_str[k] = 0; return md5_str; } void kywc_identifier_md5_generate_ex(const void *data, unsigned int len, char *md5_str, unsigned int str_size) { unsigned char md5[16] = { 0 }; EVP_Digest(data, len, md5, NULL, EVP_md5(), NULL); size_t k = 0; for (size_t n = 0; n < str_size / 2; n++) { md5_str[k++] = hexchar(md5[n] >> 4); md5_str[k++] = hexchar(md5[n] & 0xF); } md5_str[str_size - 1] = 0; } const char *kywc_identifier_md5_generate_uuid(const void *data, unsigned int len) { unsigned char md5[16] = { 0 }; EVP_Digest(data, len, md5, NULL, EVP_md5(), NULL); char *uuid_str = malloc(UUID_SIZE); size_t k = 0; for (size_t n = 0; n < 16; n++) { if (n == 4 || n == 6 || n == 8 || n == 10) { uuid_str[k++] = '-'; } uuid_str[k++] = hexchar(md5[n] >> 4); uuid_str[k++] = hexchar(md5[n] & 0xF); } uuid_str[k] = 0; return uuid_str; } const char *kywc_identifier_base64_generate(const void *data, unsigned int len) { /* every 3 bytes to 4 bytes and 1 NUL terminator */ size_t size = (len + 2) / 3 * 4 + 1; char *base64 = malloc(size); if (!base64) { return NULL; } EVP_EncodeBlock((unsigned char *)base64, data, len); return base64; } #endif kylin-wayland-compositor/src/util/logger.c0000664000175000017500000001712715160461067017664 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include "util/file.h" #include "util/logger.h" #include "util/macros.h" #include "util/string.h" #include "util/time.h" #define MAX_FILE_SIZE (1024 * 1024) // 1M static FILE *log_fp = NULL; static enum kywc_log_level log_level = KYWC_WARN; static bool log_in_realtime = true; // log ratelimit static uint32_t ratelimit_interval = 5000; // ms static uint32_t ratelimit_burst = 10; // TLS for thread static __thread struct { uint32_t start; // ms uint32_t printed; uint32_t missed; } ratelimit_state = { 0 }; static const char *level_colors[] = { [KYWC_SILENT] = "", [KYWC_FATAL] = "\x1B[7;31m", [KYWC_ERROR] = "\x1B[1;31m", [KYWC_WARN] = "\x1B[1;35m", [KYWC_INFO] = "\x1B[1;34m", [KYWC_DEBUG] = "\x1B[1;90m", }; static const char *level_headers[] = { [KYWC_SILENT] = "", [KYWC_FATAL] = "[FATAL]", [KYWC_ERROR] = "[ERROR]", [KYWC_WARN] = "[WARN]", [KYWC_INFO] = "[INFO]", [KYWC_DEBUG] = "[DEBUG]", }; static inline bool ratelimit_allow(void) { /* log is disabled when burst is 0 */ if (ratelimit_burst == 0) { return false; } /* rate limit is disabled when interval is 0 */ if (ratelimit_interval == 0) { return true; } uint32_t now = current_time_msec(); if (ratelimit_state.start == 0) { ratelimit_state.start = now; } if (now - ratelimit_state.start >= ratelimit_interval) { if (ratelimit_state.missed > 0) { fprintf(log_fp, "[Log Ratelimited] : %d messages suppressed in %d ms\n", ratelimit_state.missed, ratelimit_interval); fflush(log_fp); } ratelimit_state.start = now; ratelimit_state.printed = 0; ratelimit_state.missed = 0; } if (ratelimit_state.printed < ratelimit_burst) { ratelimit_state.printed++; return true; } else { ratelimit_state.missed++; return false; } } static void log_file(enum kywc_log_level level, const char *fmt, va_list args, bool limited) { if (!log_fp || level > log_level) { return; } if (limited && !ratelimit_allow()) { return; } char *buffer; size_t size; FILE *fp = open_memstream(&buffer, &size); if (!fp) { return; } bool log_color = log_fp == stdout; if (log_in_realtime) { time_t time_log = time(NULL); struct tm *tm_log = localtime(&time_log); fprintf(fp, "[%04d-%02d-%02d %02d:%02d:%02d] %s: ", tm_log->tm_year + 1900, tm_log->tm_mon + 1, tm_log->tm_mday, tm_log->tm_hour, tm_log->tm_min, tm_log->tm_sec, log_color ? level_colors[level] : level_headers[level]); } else { struct timespec ts = { 0 }; clock_gettime(CLOCK_MONOTONIC, &ts); fprintf(fp, "[%02d:%02d:%02d.%06ld] %s: ", (int)(ts.tv_sec / 60 / 60), (int)(ts.tv_sec / 60 % 60), (int)(ts.tv_sec % 60), ts.tv_nsec / 1000, log_color ? level_colors[level] : level_headers[level]); } vfprintf(fp, fmt, args); if (log_color) { fprintf(fp, "\x1B[0m"); } fprintf(fp, "\n"); fclose(fp); fprintf(log_fp, "%s", buffer); /* fflush is needed when log to file */ fflush(log_fp); free(buffer); } void kywc_log(enum kywc_log_level level, const char *format, ...) { va_list args; va_start(args, format); log_file(level, format, args, false); va_end(args); } void kywc_vlog(enum kywc_log_level level, const char *format, va_list args) { log_file(level, format, args, false); } void kywc_log_ratelimited(enum kywc_log_level level, const char *format, ...) { va_list args; va_start(args, format); log_file(level, format, args, true); va_end(args); } void kywc_vlog_ratelimited(enum kywc_log_level level, const char *format, va_list args) { log_file(level, format, args, true); } static enum kywc_log_level detect_log_level(enum kywc_log_level level) { /* set logger log_level from env */ const char *level_str = getenv("KYWC_LOG_LEVEL"); if (!level_str) { return level; } if (strcmp(level_str, "DEBUG") == 0) { return KYWC_DEBUG; } else if (strcmp(level_str, "INFO") == 0) { return KYWC_INFO; } else if (strcmp(level_str, "WARN") == 0) { return KYWC_WARN; } else if (strcmp(level_str, "ERROR") == 0) { return KYWC_ERROR; } else if (strcmp(level_str, "FATAL") == 0) { return KYWC_FATAL; } else if (strcmp(level_str, "SILENT") == 0) { return KYWC_SILENT; } else { fprintf(stderr, "logger: env error, support (DEBUG|INFO|WARN|ERROR|FATAL|SILENT)\n"); return level; } } static void rotate_file_if_needed(int dir_fd, const char *file) { struct stat st; if (fstatat(dir_fd, file, &st, AT_SYMLINK_NOFOLLOW) != 0) { return; } if (!S_ISREG(st.st_mode)) { fprintf(stderr, "logger: %s not a regular file\n", file); return; } if (st.st_size < MAX_FILE_SIZE) { return; } char timestamp[32]; time_t now = time(NULL); struct tm *tm_info = localtime(&now); strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", tm_info); char *new_name = string_create("%s.%s", file, timestamp); if (!new_name) { return; } int ret = renameat(dir_fd, file, dir_fd, new_name); free(new_name); if (ret != 0) { fprintf(stderr, "logger: rename file failed: %s\n", strerror(errno)); } } void logger_init(enum kywc_log_level level, const char *file, bool realtime) { log_in_realtime = realtime; log_level = detect_log_level(level); fprintf(stdout, "logger: current log level is %s\n", level_headers[log_level]); /* don't log to file */ if (STRING_INVALID(file)) { log_fp = stdout; return; } char *log_dir = string_expand_path("~/.log"); if (!log_dir) { fprintf(stderr, "get log dir failed\n"); return; } if (!file_exists(log_dir)) { fprintf(stdout, "logger: %s not exist, create it\n", log_dir); int ret = mkdir(log_dir, S_IRWXU | S_IRWXG); if (ret) { fprintf(stderr, "create log dir failed: %s\n", strerror(errno)); free(log_dir); return; } } int dir_fd = open(log_dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); if (dir_fd == -1) { fprintf(stderr, "logger: cannot open log directory '%s': %s\n", log_dir, strerror(errno)); free(log_dir); return; } // check log file size rotate_file_if_needed(dir_fd, file); close(dir_fd); char *log_path = string_join_path(log_dir, NULL, file); free(log_dir); fprintf(stdout, "logger: path is %s\n", log_path); log_fp = fopen(log_path, "a"); // appending mode free(log_path); if (!log_fp) { fprintf(stderr, "logger: create log file failed: %s\n", strerror(errno)); return; } } enum kywc_log_level kywc_log_get_level(void) { return log_level; } void logger_set_level(enum kywc_log_level level) { if (level == log_level) { return; } log_level = level; kywc_log(KYWC_SILENT, "Logger: current log level is %s", level_headers[level]); } void logger_set_rate_limit(unsigned interval, unsigned burst) { ratelimit_interval = MIN(interval, 10000); ratelimit_burst = MIN(burst, 1000); } void logger_finish(void) { if (!log_fp) { return; } fflush(log_fp); fclose(log_fp); } kylin-wayland-compositor/src/util/boxes.c0000664000175000017500000000516515160461067017524 0ustar fengfeng// SPDX-FileCopyrightText: 2022 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include bool kywc_fbox_equal(const struct kywc_fbox *a, const struct kywc_fbox *b) { if (kywc_fbox_empty(a)) { a = NULL; } if (kywc_fbox_empty(b)) { b = NULL; } if (a == NULL || b == NULL) { return a == b; } return a->x == b->x && a->y == b->y && a->width == b->width && a->height == b->height; } bool kywc_box_intersection(struct kywc_box *dest, const struct kywc_box *box_a, const struct kywc_box *box_b) { bool a_empty = kywc_box_empty(box_a); bool b_empty = kywc_box_empty(box_b); if (a_empty || b_empty) { *dest = (struct kywc_box){ 0 }; return false; } int x1 = fmax(box_a->x, box_b->x); int y1 = fmax(box_a->y, box_b->y); int x2 = fmin(box_a->x + box_a->width, box_b->x + box_b->width); int y2 = fmin(box_a->y + box_a->height, box_b->y + box_b->height); dest->x = x1; dest->y = y1; dest->width = x2 - x1; dest->height = y2 - y1; return !kywc_box_empty(dest); } void kywc_box_transform(struct kywc_box *dest, const struct kywc_box *box, enum wl_output_transform transform, int width, int height) { struct kywc_box src = { 0 }; if (box != NULL) { src = *box; } if (transform % 2 == 0) { dest->width = src.width; dest->height = src.height; } else { dest->width = src.height; dest->height = src.width; } switch (transform) { case WL_OUTPUT_TRANSFORM_NORMAL: dest->x = src.x; dest->y = src.y; break; case WL_OUTPUT_TRANSFORM_90: dest->x = height - src.y - src.height; dest->y = src.x; break; case WL_OUTPUT_TRANSFORM_180: dest->x = width - src.x - src.width; dest->y = height - src.y - src.height; break; case WL_OUTPUT_TRANSFORM_270: dest->x = src.y; dest->y = width - src.x - src.width; break; case WL_OUTPUT_TRANSFORM_FLIPPED: dest->x = width - src.x - src.width; dest->y = src.y; break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: dest->x = src.y; dest->y = src.x; break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: dest->x = src.x; dest->y = height - src.y - src.height; break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: dest->x = height - src.y - src.height; dest->y = width - src.x - src.width; break; } } kylin-wayland-compositor/src/util/matrix.c0000664000175000017500000001460015160461067017702 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "util/matrix.h" #define M_PI (3.14159265358979323846) void ky_mat3_identity(struct ky_mat3 *mat3) { static const float identity[9] = { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; memcpy(mat3, identity, sizeof(identity)); } void ky_mat3_init_scale(struct ky_mat3 *mat3, float sx, float sy) { mat3->matrix[0] = sx; mat3->matrix[1] = 0.f; mat3->matrix[2] = 0.f; mat3->matrix[3] = 0.f; mat3->matrix[4] = sy; mat3->matrix[5] = 0.f; mat3->matrix[6] = 0.f; mat3->matrix[7] = 0.f; mat3->matrix[8] = 1.f; } void ky_mat3_init_translate(struct ky_mat3 *mat3, float tx, float ty) { mat3->matrix[0] = 1.f; mat3->matrix[1] = 0.f; mat3->matrix[2] = 0.f; mat3->matrix[3] = 0.f; mat3->matrix[4] = 1.f; mat3->matrix[5] = 0.f; mat3->matrix[6] = tx; mat3->matrix[7] = ty; mat3->matrix[8] = 1.f; } void ky_mat3_init_scale_translate(struct ky_mat3 *mat3, float sx, float sy, float tx, float ty) { mat3->matrix[0] = sx; mat3->matrix[1] = 0.f; mat3->matrix[2] = 0.f; mat3->matrix[3] = 0.f; mat3->matrix[4] = sy; mat3->matrix[5] = 0.f; mat3->matrix[6] = tx; mat3->matrix[7] = ty; mat3->matrix[8] = 1.f; } void ky_mat3_multiply(const struct ky_mat3 *a_mat, const struct ky_mat3 *b_mat, struct ky_mat3 *result) { float *r = result->matrix; const float *a = a_mat->matrix; const float *b = b_mat->matrix; r[0] = a[0] * b[0] + a[3] * b[1] + a[6] * b[2]; r[1] = a[1] * b[0] + a[4] * b[1] + a[7] * b[2]; r[2] = a[2] * b[0] + a[5] * b[1] + a[8] * b[2]; r[3] = a[0] * b[3] + a[3] * b[4] + a[6] * b[5]; r[4] = a[1] * b[3] + a[4] * b[4] + a[7] * b[5]; r[5] = a[2] * b[3] + a[5] * b[4] + a[8] * b[5]; r[6] = a[0] * b[6] + a[3] * b[7] + a[6] * b[8]; r[7] = a[1] * b[6] + a[4] * b[7] + a[7] * b[8]; r[8] = a[2] * b[6] + a[5] * b[7] + a[8] * b[8]; } void ky_mat3_translate(struct ky_mat3 *mat3, float x, float y) { struct ky_mat3 translate; ky_mat3_identity(&translate); translate.matrix[6] = x; translate.matrix[7] = y; struct ky_mat3 mat = *mat3; ky_mat3_multiply(&translate, &mat, mat3); } void ky_mat3_scale(struct ky_mat3 *mat3, float x, float y) { struct ky_mat3 scale; ky_mat3_identity(&scale); scale.matrix[0] = x; scale.matrix[4] = y; struct ky_mat3 mat = *mat3; ky_mat3_multiply(&scale, &mat, mat3); } void ky_mat3_rotate(struct ky_mat3 *mat3, float rad) { struct ky_mat3 rotate; ky_mat3_identity(&rotate); rotate.matrix[0] = cosf(rad); rotate.matrix[1] = -sinf(rad); rotate.matrix[3] = sinf(rad); rotate.matrix[4] = cosf(rad); struct ky_mat3 mat = *mat3; ky_mat3_multiply(&rotate, &mat, mat3); } void ky_mat3_flip_x(struct ky_mat3 *mat3) { struct ky_mat3 flip; ky_mat3_identity(&flip); flip.matrix[0] = -1; struct ky_mat3 mat = *mat3; ky_mat3_multiply(&flip, &mat, mat3); } void ky_mat3_flip_y(struct ky_mat3 *mat3) { struct ky_mat3 flip; ky_mat3_identity(&flip); flip.matrix[4] = -1; struct ky_mat3 mat = *mat3; ky_mat3_multiply(&flip, &mat, mat3); } void ky_mat3_framebuffer_to_ndc(struct ky_mat3 *mat3, int width, int height) { // ndc [-1 ~ 1]. 1/pixel * 2 - 1 ky_mat3_identity(mat3); float *mat = mat3->matrix; // scale mat[0] = 1.0f / width * 2.0f; mat[4] = 1.0f / height * 2.0f; // translate mat[6] = -1.0f; mat[7] = -1.0f; } void ky_mat3_logic_to_ndc(struct ky_mat3 *mat, int width, int height, enum wl_output_transform transform) { // logic ndc [-1 ~ 1] ky_mat3_identity(mat); ky_mat3_scale(mat, 1.0f / width * 2.0f, 1.0f / height * 2.0f); ky_mat3_translate(mat, -1.0f, -1.0f); // flip and rotate switch (transform) { case WL_OUTPUT_TRANSFORM_NORMAL: // do nothing break; case WL_OUTPUT_TRANSFORM_90: ky_mat3_rotate(mat, M_PI * 0.5f); break; case WL_OUTPUT_TRANSFORM_180: ky_mat3_rotate(mat, M_PI); break; case WL_OUTPUT_TRANSFORM_270: ky_mat3_rotate(mat, M_PI * 1.5f); break; case WL_OUTPUT_TRANSFORM_FLIPPED: ky_mat3_flip_x(mat); break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: ky_mat3_flip_x(mat); ky_mat3_rotate(mat, M_PI * 0.5f); break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: ky_mat3_flip_x(mat); ky_mat3_rotate(mat, M_PI); break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: ky_mat3_flip_x(mat); ky_mat3_rotate(mat, M_PI * 1.5f); break; } } void ky_mat3_invert_output_transform(struct ky_mat3 *mat, enum wl_output_transform transform) { ky_mat3_identity(mat); // rotate center at (0,0) ky_mat3_translate(mat, -0.5, -0.5); switch (transform) { case WL_OUTPUT_TRANSFORM_NORMAL: // do nothing break; case WL_OUTPUT_TRANSFORM_90: ky_mat3_rotate(mat, -M_PI * 0.5f); break; case WL_OUTPUT_TRANSFORM_180: ky_mat3_rotate(mat, -M_PI); break; case WL_OUTPUT_TRANSFORM_270: ky_mat3_rotate(mat, -M_PI * 1.5f); break; case WL_OUTPUT_TRANSFORM_FLIPPED: ky_mat3_flip_x(mat); break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: ky_mat3_rotate(mat, -M_PI * 0.5f); ky_mat3_flip_x(mat); break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: ky_mat3_rotate(mat, -M_PI); ky_mat3_flip_x(mat); break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: ky_mat3_rotate(mat, -M_PI * 1.5f); ky_mat3_flip_x(mat); break; } ky_mat3_translate(mat, 0.5, 0.5); } void ky_mat3_uvofbox_to_ndc(struct ky_mat3 *uv2ndc, int buffer_w, int buffer_h, float rotation_angle, const struct kywc_box *dst_box) { struct ky_mat3 projection; ky_mat3_framebuffer_to_ndc(&projection, buffer_w, buffer_h); struct ky_mat3 uv2pos; ky_mat3_identity(&uv2pos); ky_mat3_scale(&uv2pos, dst_box->width, dst_box->height); ky_mat3_translate(&uv2pos, dst_box->x, dst_box->y); ky_mat3_translate(&uv2pos, -buffer_w / 2.0, -buffer_h / 2.0); ky_mat3_rotate(&uv2pos, -rotation_angle * M_PI / 180); ky_mat3_translate(&uv2pos, buffer_w / 2.0, buffer_h / 2.0); ky_mat3_multiply(&projection, &uv2pos, uv2ndc); } kylin-wayland-compositor/src/util/quirks.c0000664000175000017500000000634515160461067017723 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "util/macros.h" #include "util/quirks.h" /* graphics quirks */ struct quirks { const char *name; /* dc name */ const char *vendor; /* gpu egl-vendor */ uint32_t mask; }; static const struct quirks quirks_table[] = { { "hisi", "ARM", QUIRKS_MASK_MASTER_FD | QUIRKS_MASK_EGL_WAYLAND }, { "hisi-dpu", "HUAWEI", QUIRKS_MASK_MASTER_FD | QUIRKS_MASK_EGL_WAYLAND }, { "ftd330", NULL, QUIRKS_MASK_EXPLICIT_SYNC | QUIRKS_MASK_YUYV_MODE }, { "ftv310", "Phytium Technology Co., Ltd.", QUIRKS_MASK_EGL_WAYLAND }, { "ftg340", "Phytium Technology Co., Ltd.", QUIRKS_MASK_EGL_WAYLAND | QUIRKS_MASK_PREFER_OPENGL }, { "nvidia-drm", "NVIDIA", QUIRKS_MASK_SOFTWARE_CURSOR | QUIRKS_MASK_EXPLICIT_SYNC | QUIRKS_MASK_PREFER_OPENGL }, { "nouveau", NULL, QUIRKS_MASK_SOFTWARE_CURSOR | QUIRKS_MASK_DISABLE_CTM }, { "virtio_gpu", "Mesa Project", QUIRKS_MASK_SOFTWARE_CURSOR | QUIRKS_MASK_EXPLICIT_SYNC }, { "vmwgfx", NULL, QUIRKS_MASK_SOFTWARE_CURSOR }, { "mtgpu", "MTT Mesa Client", QUIRKS_MASK_NO_MODIFIERS | QUIRKS_MASK_DISABLE_DIRECT_SCANOUT | QUIRKS_MASK_EXPLICIT_SYNC }, { "mwv207", "Mesa Project", QUIRKS_MASK_NO_MODIFIERS | QUIRKS_MASK_DISABLE_GAMMA | QUIRKS_MASK_DISABLE_CTM }, { "ljm", "Ljmicro Corporation", QUIRKS_MASK_EGL_WAYLAND | QUIRKS_MASK_NO_MODIFIERS | QUIRKS_MASK_EXPLICIT_SYNC }, { "arise", "Mesa Project", QUIRKS_MASK_DISABLE_CTM }, { "linlondp", "ARM", QUIRKS_MASK_SOFTWARE_CURSOR | QUIRKS_MASK_EXPLICIT_SYNC }, { "cx4", "Shanghai Zhaoxin Semiconductor Co. Ltd", QUIRKS_MASK_DISABLE_CTM }, { "zx", "Shanghai Zhaoxin Semiconductor Co., Ltd", QUIRKS_MASK_DISABLE_CTM }, }; static bool quirks_by_env(uint32_t *mask) { char *env = getenv("KYWC_FORCE_QUIRKS"); if (!env) { return false; } *mask = atoi(env); return true; } uint32_t quirks_by_backend(int drm_fd) { uint32_t mask; if (quirks_by_env(&mask)) { return mask; } drmVersion *version = drmGetVersion(drm_fd); if (!version) { return 0; } const struct quirks *quirks = NULL; for (size_t i = 0; i < ARRAY_SIZE(quirks_table); ++i) { if (!quirks_table[i].name || strcmp(quirks_table[i].name, version->name)) { continue; } quirks = &quirks_table[i]; break; } drmFreeVersion(version); return quirks ? quirks->mask : 0; } uint32_t quirks_by_renderer(int drm_fd, const char *vendor_name) { uint32_t mask; if (quirks_by_env(&mask)) { return mask; } drmVersion *version = drmGetVersion(drm_fd); if (!version) { return 0; } const struct quirks *quirks = NULL; for (size_t i = 0; i < ARRAY_SIZE(quirks_table); ++i) { if (!quirks_table[i].vendor || strcmp(vendor_name, quirks_table[i].vendor)) { continue; } if (!quirks_table[i].name || strcmp(quirks_table[i].name, version->name)) { continue; } quirks = &quirks_table[i]; break; } drmFreeVersion(version); return quirks ? quirks->mask : 0; } kylin-wayland-compositor/src/util/event.c0000664000175000017500000000524715160461067017526 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include "util/event.h" struct event_source { struct event_source *prev, *next; event_func_t func; void *user_data; }; struct event_context { int epoll_fd; struct event_source source; }; /* stolen from wayland */ static int set_cloexec_or_close(int fd) { if (fd == -1) { return -1; } long flags = fcntl(fd, F_GETFD); if (flags == -1) { goto err; } if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { goto err; } return fd; err: close(fd); return -1; } static int create_epoll(void) { int fd; #ifdef EPOLL_CLOEXEC fd = epoll_create1(EPOLL_CLOEXEC); if (fd >= 0) { return fd; } if (errno != EINVAL) { return -1; } #endif fd = epoll_create(1); return set_cloexec_or_close(fd); } struct event_context *event_loop_create(void) { struct event_context *ctx = calloc(1, sizeof(*ctx)); if (!ctx) { return NULL; } ctx->epoll_fd = create_epoll(); if (ctx->epoll_fd == -1) { free(ctx); return NULL; } ctx->source.prev = ctx->source.next = &ctx->source; return ctx; } void event_loop_destroy(struct event_context *ctx) { if (!ctx) { return; } struct event_source *head = &ctx->source, *temp; while (head->next != &ctx->source) { temp = head->next; head->next = temp->next; free(temp); } free(ctx); } int event_loop_add_source(struct event_context *ctx, int fd, event_func_t func, void *data) { struct event_source *source = malloc(sizeof *source); if (!source) { return -1; } struct epoll_event ep = { .events = EPOLLIN, .data.ptr = source, }; if (epoll_ctl(ctx->epoll_fd, EPOLL_CTL_ADD, fd, &ep) < 0) { free(source); return -1; } struct event_source *head = &ctx->source; source->prev = head; source->next = head->next; head->next->prev = source; head->next = source; source->func = func; source->user_data = data; return 0; } int event_loop_dispatch(struct event_context *ctx, int timeout) { struct epoll_event ep[32]; int count = epoll_wait(ctx->epoll_fd, ep, 32, timeout); if (count < 0 && errno != EINTR && errno != EAGAIN) { return -1; } struct event_source *source; for (int i = 0; i < count; i++) { source = ep[i].data.ptr; if (source->func(source->user_data) != 0) { return -1; } } return 0; } kylin-wayland-compositor/src/util/wayland.c0000664000175000017500000000455215160460057020040 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "util/wayland.h" /** * stolen from wlroots. */ struct destroy_global_data { struct wl_global *global; struct wl_event_source *event_source; struct wl_listener display_destroy; }; static void destroy_global(struct destroy_global_data *data) { wl_list_remove(&data->display_destroy.link); wl_global_destroy(data->global); wl_event_source_remove(data->event_source); free(data); } static int handle_timer_event(void *data) { destroy_global(data); return 0; } static void handle_display_destroy(struct wl_listener *listener, void *_data) { struct destroy_global_data *data = wl_container_of(listener, data, display_destroy); destroy_global(data); } void wl_global_destroy_safe(struct wl_global *global) { // Don't destroy the global immediately. If the global has been created // recently, clients might try to bind to it after we've destroyed it. // Instead, remove the global so that clients stop seeing it and wait an // arbitrary amount of time before destroying the global as a workaround. // See: https://gitlab.freedesktop.org/wayland/wayland/issues/10 wl_global_remove(global); wl_global_set_user_data(global, NULL); // safety net struct wl_display *display = wl_global_get_display(global); struct wl_event_loop *event_loop = wl_display_get_event_loop(display); struct destroy_global_data *data = calloc(1, sizeof(*data)); if (data == NULL) { wl_global_destroy(global); return; } data->global = global; data->event_source = wl_event_loop_add_timer(event_loop, handle_timer_event, data); if (data->event_source == NULL) { free(data); wl_global_destroy(global); return; } wl_event_source_timer_update(data->event_source, 5000); data->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(display, &data->display_destroy); } void wl_signal_emit_oneshot(struct wl_signal *signal, void *data) { struct wl_list *listeners = &signal->listener_list; if (wl_list_empty(listeners)) { return; } struct wl_listener *listener = wl_container_of(listeners->next, listener, link); listener->notify(listener, data); } kylin-wayland-compositor/src/util/file.c0000664000175000017500000001713515160460057017321 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _DEFAULT_SOURCE #include #include #include #include #include #include #include #include #include #include "util/file.h" #include "util/macros.h" #include "util/string.h" struct pair { const char *key, *value; }; struct file { char *data, *last; size_t size; const char *delim, *needle; size_t needle_len; struct wl_array pairs; }; static bool pair_parse(struct file *file, char *pair, file_parse_func_t parse, void *data) { if (!*pair || *pair == '#') { return false; } if (file->needle_len == 0) { char *key = string_skip_space(pair); return parse(file, key, NULL, data); } char *value = strstr(pair, file->needle); if (!value) { return false; } *value = '\0'; char *key = string_skip_space(pair); value = string_skip_space(value + file->needle_len); return parse(file, key, value, data); } void file_parse(struct file *file, file_parse_func_t parse, void *data) { if (!file->last || !file->delim) { return; } char *save, *pair = strtok_r(file->last, file->delim, &save); while (pair) { file->last = pair + strlen(pair) + 1; pair = string_skip_space(pair); if (pair_parse(file, pair, parse, data)) { break; } pair = strtok_r(NULL, file->delim, &save); } if (!pair || file->last >= file->data + file->size) { file->last = NULL; } } static bool get_value(struct file *file, const char *key, const char *value, void *data) { struct pair *pair = wl_array_add(&file->pairs, sizeof(*pair)); if (pair) { *pair = (struct pair){ key, value }; } const char **need = data; if (strcmp(key, *need) == 0) { *need = value; return true; } return false; } const char *file_get_value(struct file *file, const char *key) { if (!file->delim) { return NULL; } struct pair *pair; wl_array_for_each(pair, &file->pairs) { if (strcmp(pair->key, key) == 0) { return pair->value; } } const char *value = key; file_parse(file, get_value, &value); return value == key ? NULL : value; } char *file_get_value_copy(struct file *file, const char *key) { const char *value = file_get_value(file, key); return value ? strdup(value) : NULL; } struct file *file_open(const char *name, const char *delim, const char *needle) { struct file *file = calloc(1, sizeof(*file)); if (!file) { return NULL; } int fd = open(name, O_RDONLY); if (fd == -1) { free(file); return NULL; } struct stat st; if (fstat(fd, &st) == -1 || st.st_size == 0) { close(fd); free(file); return NULL; } file->size = st.st_size; /* read and copy it if needs parsing */ if (STRING_VALID(delim)) { if ((file->data = malloc(file->size + 1))) { if (read(fd, file->data, file->size) == (ssize_t)file->size) { /* make sure file->data is a valid string */ file->data[file->size] = '\0'; } else { free(file->data); file->data = NULL; } } } else { file->data = mmap(NULL, file->size, PROT_READ, MAP_PRIVATE, fd, 0); if (file->data == MAP_FAILED) { file->data = NULL; } } /* fd is not used anymore, close it */ close(fd); if (file->data == NULL) { free(file); return NULL; } file->last = file->data; wl_array_init(&file->pairs); if (STRING_VALID(delim)) { file->delim = strdup(delim); } if (STRING_VALID(needle)) { file->needle = strdup(needle); file->needle_len = strlen(file->needle); } return file; } const char *file_get_data(struct file *file, size_t *size) { if (size) { *size = file->size; } return file->data; } void file_close(struct file *file) { if (!file) { return; } if (file->delim) { free(file->data); } else { munmap(file->data, file->size); } wl_array_release(&file->pairs); free((void *)file->needle); free((void *)file->delim); free(file); } static bool is_directory(const char *path) { struct stat st; if (stat(path, &st) == 0) { return S_ISDIR(st.st_mode); } return false; } static bool is_regular_file(const char *path) { struct stat st; if (stat(path, &st) == 0) { return S_ISREG(st.st_mode); } return false; } static bool for_each(const char *path, int len, file_iterator_func_t iterator, void *data, const char *orig_path, const char *orig_subdir) { int offset = (int)strlen(path) - len; const char *subpath = offset > 0 ? path + len : NULL; DIR *dir = opendir(path); if (!dir) { // the orig path and subdir do not exist if (!subpath && errno == ENOENT) { iterator(orig_path, orig_subdir, DIR_NOT_EXIST, data); } return false; } if (iterator(path, subpath, NULL, data)) { closedir(dir); // return false to skip the subpath but not the iterator return false; } bool need_break = false; for (struct dirent *ent = readdir(dir); ent; ent = readdir(dir)) { if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) { continue; } char *fullpath = string_join_path(path, NULL, ent->d_name); if (!fullpath) { continue; } if (ent->d_type == DT_DIR || (ent->d_type == DT_UNKNOWN && is_directory(fullpath))) { need_break = for_each(fullpath, len, iterator, data, orig_path, orig_subdir); } else if ((ent->d_type == DT_REG || ent->d_type == DT_LNK) || (ent->d_type == DT_UNKNOWN && is_regular_file(fullpath))) { need_break = iterator(fullpath, subpath, ent->d_name, data); } free(fullpath); if (need_break) { break; } } closedir(dir); return need_break; } void file_for_each(const char *dirs, const char *subdir, file_iterator_func_t iterator, void *data) { size_t len = 0; char **paths = string_split(dirs, ":", &len); if (!paths) { return; } for (size_t i = 0; i < len; i++) { char *path = string_expand_path(paths[i]); if (!path) { continue; } char *fullpath = string_join_path(path, subdir, NULL); if (!fullpath) { free(path); continue; } bool need_break = for_each(fullpath, strlen(fullpath) + 1, iterator, data, path, subdir); free(fullpath); free(path); if (need_break) { break; } } string_free_split(paths); } bool file_exists(const char *path) { return path && access(path, F_OK) != -1; } const char *file_get_xdg_pictures_dir(void) { struct file *file = NULL; const char *dir = NULL; char *user_dirs_file = NULL, *pictures_dir; user_dirs_file = string_expand_path("~/.config/user-dirs.dirs"); if (!user_dirs_file) { goto out; } file = file_open(user_dirs_file, "\n", "="); if (!file) { goto out; } dir = file_get_value(file, "XDG_PICTURES_DIR"); if (!dir) { goto out; } out: pictures_dir = string_expand_path(dir ? dir : "~"); file_close(file); free(user_dirs_file); return pictures_dir; } kylin-wayland-compositor/src/util/spawn.c0000664000175000017500000001634015160460057017527 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "util/limit.h" #include "util/spawn.h" bool spawn_shell(char *command) { wordexp_t p = { 0 }; if (wordexp(command, &p, 0) || !p.we_wordv[0]) { kywc_log(KYWC_ERROR, "Command illegal: %s", command); wordfree(&p); return false; } pid_t child = fork(); switch (child) { case -1: kywc_log_errno(KYWC_ERROR, "Unable to fork()"); wordfree(&p); return false; case 0: setsid(); /* do not give our signal mask to the new process */ sigset_t set; sigfillset(&set); sigprocmask(SIG_UNBLOCK, &set, NULL); limit_unset_nofile(); pid_t grandchild = fork(); if (grandchild == 0) { char *argv[] = { "/bin/sh", "-c", command, NULL }; execvp(argv[0], argv); kywc_log_errno(KYWC_ERROR, "execvp failed"); _exit(1); } else if (grandchild < 0) { kywc_log(KYWC_ERROR, "Unable to fork()"); } _exit(0); default: break; } waitpid(child, NULL, 0); wordfree(&p); return true; } bool spawn_invoke(const char *command) { wordexp_t p = { 0 }; if (wordexp(command, &p, 0) || !p.we_wordv[0]) { kywc_log(KYWC_ERROR, "Command illegal: %s", command); wordfree(&p); return false; } pid_t child = fork(); switch (child) { case -1: kywc_log(KYWC_ERROR, "Unable to fork()"); wordfree(&p); return false; case 0: setsid(); /* do not give our signal mask to the new process */ sigset_t set; sigfillset(&set); sigprocmask(SIG_UNBLOCK, &set, NULL); limit_unset_nofile(); pid_t grandchild = fork(); if (grandchild == 0) { /* close stdout/stderr */ int devnull = open("/dev/null", O_WRONLY | O_CREAT | O_CLOEXEC, 0660); if (devnull < 0) { kywc_log_errno(KYWC_ERROR, "failed to open /dev/null"); _exit(1); } if (kywc_log_get_level() < KYWC_DEBUG) { dup2(devnull, STDOUT_FILENO); dup2(devnull, STDERR_FILENO); } char **argv = p.we_wordv; execvp(argv[0], argv); kywc_log_errno(KYWC_ERROR, "execvp failed"); close(devnull); _exit(1); } else if (grandchild < 0) { kywc_log(KYWC_ERROR, "Unable to fork()"); } _exit(0); default: break; } waitpid(child, NULL, 0); wordfree(&p); return true; } #ifndef UTIL_USED_IN_CLIENT #include #include pid_t spawn_session(const char *session) { wordexp_t p = { 0 }; if (wordexp(session, &p, 0) || !p.we_wordv[0]) { kywc_log(KYWC_ERROR, "Session illegal: %s", session); wordfree(&p); return -1; } pid_t child = fork(); switch (child) { case -1: kywc_log(KYWC_ERROR, "Unable to fork()"); wordfree(&p); return -1; case 0: /* child */ setsid(); /* do not give our signal mask to the new process */ sigset_t set; sigfillset(&set); sigprocmask(SIG_UNBLOCK, &set, NULL); limit_unset_nofile(); /* close stdout/stderr */ int devnull = open("/dev/null", O_WRONLY | O_CREAT | O_CLOEXEC, 0660); if (devnull < 0) { kywc_log_errno(KYWC_ERROR, "failed to open /dev/null"); _exit(1); } if (kywc_log_get_level() < KYWC_DEBUG) { dup2(devnull, STDOUT_FILENO); dup2(devnull, STDERR_FILENO); } char **argv = p.we_wordv; execvp(argv[0], argv); kywc_log_errno(KYWC_ERROR, "execvp failed"); close(devnull); _exit(1); default: wordfree(&p); return child; } } void spawn_wait(pid_t pid) { waitpid(pid, NULL, WNOHANG); } static bool set_cloexec(int fd, bool cloexec) { int flags = fcntl(fd, F_GETFD); if (flags == -1) { kywc_log_errno(KYWC_ERROR, "fcntl failed"); return false; } if (cloexec) { flags = flags | FD_CLOEXEC; } else { flags = flags & ~FD_CLOEXEC; } if (fcntl(fd, F_SETFD, flags) == -1) { kywc_log_errno(KYWC_ERROR, "fcntl failed"); return false; } return true; } struct wl_client *spawn_client(struct wl_display *display, const char *command) { wordexp_t p = { 0 }; if (wordexp(command, &p, 0) || !p.we_wordv[0]) { kywc_log(KYWC_ERROR, "Command illegal: %s", command); wordfree(&p); return NULL; } int fds[2] = { -1, -1 }; /* create a socket pair with cloexec */ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) { kywc_log_errno(KYWC_ERROR, "socketpair failed"); goto error; } if (!set_cloexec(fds[0], true) || !set_cloexec(fds[1], true)) { kywc_log(KYWC_ERROR, "Failed to set O_CLOEXEC on socket"); goto error; } struct wl_client *client = wl_client_create(display, fds[0]); if (!client) { kywc_log_errno(KYWC_ERROR, "wl_client_create failed"); goto error; } fds[0] = -1; /* not ours anymore */ pid_t child = fork(); switch (child) { case -1: kywc_log(KYWC_ERROR, "Unable to fork()"); goto error; case 0: setsid(); /* do not give our signal mask to the new process */ sigset_t set; sigfillset(&set); sigprocmask(SIG_UNBLOCK, &set, NULL); limit_unset_nofile(); pid_t grandchild = fork(); if (grandchild == 0) { if (!set_cloexec(fds[1], false)) { kywc_log(KYWC_ERROR, "Failed to unset CLOEXEC on FD"); _exit(1); } char wayland_socket_str[16]; snprintf(wayland_socket_str, sizeof(wayland_socket_str), "%d", fds[1]); setenv("WAYLAND_SOCKET", wayland_socket_str, true); kywc_log(KYWC_ERROR, "Run %s in %s", command, wayland_socket_str); /* close stdout/stderr */ int devnull = open("/dev/null", O_WRONLY | O_CREAT | O_CLOEXEC, 0660); if (devnull < 0) { kywc_log_errno(KYWC_ERROR, "failed to open /dev/null"); _exit(1); } if (kywc_log_get_level() < KYWC_DEBUG) { dup2(devnull, STDOUT_FILENO); dup2(devnull, STDERR_FILENO); } char **argv = p.we_wordv; execvp(argv[0], argv); kywc_log_errno(KYWC_ERROR, "execvp failed"); close(devnull); _exit(1); } else if (grandchild < 0) { kywc_log(KYWC_ERROR, "Unable to fork()"); } _exit(0); default: break; } waitpid(child, NULL, 0); wordfree(&p); close(fds[1]); return client; error: if (fds[0] >= 0) { close(fds[0]); } if (fds[1] >= 0) { close(fds[1]); } wordfree(&p); return NULL; } #endif kylin-wayland-compositor/src/util/string.c0000664000175000017500000000677415160461067017721 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _DEFAULT_SOURCE #include #include #include #include #include #include "util/string.h" char *string_create_vargs(const char *format, va_list args) { if (!format) { return NULL; } va_list args_copy; va_copy(args_copy, args); int len = vsnprintf(NULL, 0, format, args) + 1; char *str = malloc(len); if (str == NULL) { va_end(args_copy); return NULL; } vsnprintf(str, len, format, args_copy); va_end(args_copy); return str; } char *string_create(const char *format, ...) { va_list args; va_start(args, format); char *str = string_create_vargs(format, args); va_end(args); return str; } char *string_skip_space(char *str) { while (isspace(*str)) { str++; } if (*str == '\0') { return str; } char *end = str + strlen(str) - 1; while (end > str && isspace(*end)) { end--; } *(end + 1) = '\0'; return str; } void string_strip_space(char *str) { size_t len = strlen(str); size_t start = strspn(str, " \f\n\r\t\v"); memmove(str, &str[start], len + 1 - start); if (*str) { for (len -= start + 1; isspace(str[len]); --len) { } str[len + 1] = '\0'; } } void string_replace_unprintable(char *str) { for (char *p = str; *p; ++p) { if (*p == ' ' || !isprint(*p)) { *p = '_'; } } } char **string_split(const char *str, const char *delim, size_t *len) { size_t alloc = 16, length = 0; char **split = malloc(sizeof(char *) * alloc); if (!split) { return NULL; } char *copy = strdup(str); char *save, *token = strtok_r(copy, delim, &save); while (token) { if (length >= alloc) { alloc *= 2; split = realloc(split, sizeof(char *) * alloc); } split[length++] = token; token = strtok_r(NULL, delim, &save); } if (len) { *len = length; } return split; } void string_free_split(char **split) { if (!split) { return; } free(split[0]); free(split); } char *string_expand_path(const char *path) { wordexp_t p = { 0 }; if (wordexp(path, &p, WRDE_UNDEF) != 0 || !p.we_wordv[0]) { wordfree(&p); return NULL; } char *realpath = strdup(p.we_wordv[0]); wordfree(&p); return realpath; } char *string_join_path(const char *dir, const char *subdir, const char *file) { size_t dir_len = strlen(dir); size_t subdir_len = subdir ? strlen(subdir) : 0; size_t file_len = file ? strlen(file) : 0; size_t sep_count = (subdir_len > 0) + (file_len > 0); size_t total_len = dir_len + subdir_len + file_len + sep_count + 1; // +1 for '\0' char *path = malloc(total_len); if (!path) { return NULL; } char *p = path; memcpy(p, dir, dir_len); p += dir_len; if (subdir_len > 0) { *p++ = '/'; memcpy(p, subdir, subdir_len); p += subdir_len; } if (file_len > 0) { *p++ = '/'; memcpy(p, file, file_len); p += file_len; } *p = '\0'; return path; } void string_strip_path(char *str) { const char *start = strrchr(str, '/'); start = start ? start + 1 : str; const char *end = strchr(start, ' '); size_t len = end ? (size_t)(end - start) : strlen(start); memmove(str, start, len); str[len] = '\0'; } kylin-wayland-compositor/src/util/hash_table.c0000664000175000017500000002720115160460057020467 0ustar fengfeng// SPDX-FileCopyrightText: 2009,2012 Intel Corporation // SPDX-FileCopyrightText: 1988-2004 Keith Packard and Bart Massey. // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat /** * hash table adapted from mesa hash_table.c * * Copyright © 2009,2012 Intel Corporation * Copyright © 1988-2004 Keith Packard and Bart Massey. * * Authors: * Eric Anholt * Keith Packard */ #include #include #include "util/hash_table.h" #include "util/macros.h" #define DELETED_KEY_VALUE (void *)(uintptr_t)(-1) struct hash_size { uint32_t max_entries; uint32_t size, rehash; uint64_t size_magic, rehash_magic; }; struct hash_table { struct hash_entry *table; hash_func_t hash; equal_func_t equal; void *data; uint32_t size_index; struct hash_size hash_size; uint32_t entries; uint32_t deleted_entries; }; static inline uint32_t fast_urem32(uint32_t n, uint32_t d, uint64_t magic) { uint64_t lowbits = magic * n; #ifdef __SIZEOF_INT128__ return ((__uint128_t)lowbits * d) >> 64; #else uint32_t b0 = (uint32_t)lowbits; uint32_t b1 = lowbits >> 32; return ((((uint64_t)d * b0) >> 32) + (uint64_t)d * b1) >> 32; #endif } /** * From Knuth -- a good choice for hash/rehash values is p, p-2 where * p and p-2 are both prime. These tables are sized to have an extra 10% * free to avoid exponential performance degradation as the hash table fills */ static const struct hash_size hash_sizes[] = { #define REMAINDER_MAGIC(divisor) ((uint64_t)~0ull / (divisor) + 1) #define ENTRY(max_entries, size, rehash) \ { max_entries, size, rehash, REMAINDER_MAGIC(size), REMAINDER_MAGIC(rehash) } ENTRY(2, 5, 3), ENTRY(4, 7, 5), ENTRY(8, 13, 11), ENTRY(16, 19, 17), ENTRY(32, 43, 41), ENTRY(64, 73, 71), ENTRY(128, 151, 149), ENTRY(256, 283, 281), ENTRY(512, 571, 569), ENTRY(1024, 1153, 1151), ENTRY(2048, 2269, 2267), ENTRY(4096, 4519, 4517), ENTRY(8192, 9013, 9011), ENTRY(16384, 18043, 18041), ENTRY(32768, 36109, 36107), ENTRY(65536, 72091, 72089), ENTRY(131072, 144409, 144407), ENTRY(262144, 288361, 288359), ENTRY(524288, 576883, 576881), ENTRY(1048576, 1153459, 1153457), ENTRY(2097152, 2307163, 2307161), ENTRY(4194304, 4613893, 4613891), ENTRY(8388608, 9227641, 9227639), ENTRY(16777216, 18455029, 18455027), ENTRY(33554432, 36911011, 36911009), ENTRY(67108864, 73819861, 73819859), ENTRY(134217728, 147639589, 147639587), ENTRY(268435456, 295279081, 295279079), ENTRY(536870912, 590559793, 590559791), ENTRY(1073741824, 1181116273, 1181116271), ENTRY(2147483648ul, 2362232233ul, 2362232231ul) }; static int entry_is_free(const struct hash_entry *entry) { return entry->key == NULL; } static int entry_is_deleted(const struct hash_table *ht, struct hash_entry *entry) { return entry->key == DELETED_KEY_VALUE; } static int entry_is_present(const struct hash_table *ht, struct hash_entry *entry) { return entry->key != NULL && entry->key != DELETED_KEY_VALUE; } static void hash_table_insert_rehash(struct hash_table *ht, uint32_t hash, const void *key, void *data) { uint32_t size = ht->hash_size.size; uint32_t start_hash_address = fast_urem32(hash, size, ht->hash_size.size_magic); uint32_t double_hash = 1 + fast_urem32(hash, ht->hash_size.rehash, ht->hash_size.rehash_magic); uint32_t hash_address = start_hash_address; do { struct hash_entry *entry = ht->table + hash_address; if (entry->key == NULL) { entry->hash = hash; entry->key = key; entry->data = data; return; } hash_address += double_hash; if (hash_address >= size) { hash_address -= size; } } while (true); } static void hash_table_rehash(struct hash_table *ht, uint32_t new_size_index) { if (ht->size_index == new_size_index && ht->deleted_entries == ht->hash_size.max_entries) { memset(ht->table, 0, sizeof(struct hash_entry) * hash_sizes[ht->size_index].size); ht->entries = ht->deleted_entries = 0; return; } if (new_size_index >= ARRAY_SIZE(hash_sizes)) { return; } struct hash_entry *table = calloc(hash_sizes[new_size_index].size, sizeof(struct hash_entry)); if (!table) { return; } struct hash_table old_ht = *ht; ht->table = table; ht->size_index = new_size_index; ht->hash_size = hash_sizes[ht->size_index]; ht->entries = ht->deleted_entries = 0; struct hash_entry *entry; hash_table_for_each(entry, &old_ht) { hash_table_insert_rehash(ht, entry->hash, entry->key, entry->data); } ht->entries = old_ht.entries; free(old_ht.table); } struct hash_table *hash_table_create(hash_func_t hash, equal_func_t equal, void *data) { struct hash_table *ht = calloc(1, sizeof(*ht)); if (!ht) { return NULL; } ht->size_index = 0; ht->hash_size = hash_sizes[ht->size_index]; ht->table = calloc(ht->hash_size.size, sizeof(struct hash_entry)); if (!ht->table) { free(ht); return NULL; } ht->hash = hash; ht->equal = equal; ht->data = data; ht->entries = ht->deleted_entries = 0; return ht; } bool hash_table_set_max_entries(struct hash_table *ht, uint32_t max_entries) { if (max_entries < ht->hash_size.max_entries) { return true; } for (uint32_t i = ht->size_index + 1; i < ARRAY_SIZE(hash_sizes); i++) { if (hash_sizes[i].max_entries >= max_entries) { hash_table_rehash(ht, i); break; } } return ht->hash_size.max_entries >= max_entries; } uint32_t hash_table_get_entries(const struct hash_table *ht) { return ht->entries; } void hash_table_destroy(struct hash_table *ht) { if (!ht) { return; } free(ht->table); free(ht); } struct hash_entry *hash_table_search_hash(const struct hash_table *ht, uint32_t hash, const void *key) { uint32_t size = ht->hash_size.size; uint32_t start_hash_address = fast_urem32(hash, size, ht->hash_size.size_magic); uint32_t double_hash = 1 + fast_urem32(hash, ht->hash_size.rehash, ht->hash_size.rehash_magic); uint32_t hash_address = start_hash_address; do { struct hash_entry *entry = ht->table + hash_address; if (entry_is_free(entry)) { return NULL; } else if (entry_is_present(ht, entry) && entry->hash == hash) { if (ht->equal(key, entry->key, ht->data)) { return entry; } } hash_address += double_hash; if (hash_address >= size) { hash_address -= size; } } while (hash_address != start_hash_address); return NULL; } struct hash_entry *hash_table_search(const struct hash_table *ht, const void *key) { return hash_table_search_hash(ht, ht->hash(key, ht->data), key); } static struct hash_entry *hash_table_get_entry(struct hash_table *ht, uint32_t hash, const void *key) { if (ht->entries >= ht->hash_size.max_entries) { hash_table_rehash(ht, ht->size_index + 1); } else if (ht->deleted_entries + ht->entries >= ht->hash_size.max_entries) { hash_table_rehash(ht, ht->size_index); } uint32_t size = ht->hash_size.size; uint32_t start_hash_address = fast_urem32(hash, size, ht->hash_size.size_magic); uint32_t double_hash = 1 + fast_urem32(hash, ht->hash_size.rehash, ht->hash_size.rehash_magic); uint32_t hash_address = start_hash_address; struct hash_entry *available_entry = NULL; do { struct hash_entry *entry = ht->table + hash_address; if (!entry_is_present(ht, entry)) { /* Stash the first available entry we find */ if (available_entry == NULL) { available_entry = entry; } if (entry_is_free(entry)) { break; } } /* already in the table */ if (!entry_is_deleted(ht, entry) && entry->hash == hash && ht->equal(key, entry->key, ht->data)) { return entry; } hash_address += double_hash; if (hash_address >= size) { hash_address -= size; } } while (hash_address != start_hash_address); if (available_entry) { if (entry_is_deleted(ht, available_entry)) { ht->deleted_entries--; } available_entry->hash = hash; ht->entries++; return available_entry; } return NULL; } struct hash_entry *hash_table_insert_hash(struct hash_table *ht, uint32_t hash, const void *key, void *data) { struct hash_entry *entry = hash_table_get_entry(ht, hash, key); if (entry) { entry->key = key; entry->data = data; } return entry; } struct hash_entry *hash_table_insert(struct hash_table *ht, const void *key, void *data) { return hash_table_insert_hash(ht, ht->hash(key, ht->data), key, data); } void hash_table_remove(struct hash_table *ht, struct hash_entry *entry) { if (!entry) { return; } entry->key = DELETED_KEY_VALUE; ht->entries--; ht->deleted_entries++; } void hash_table_remove_hash(struct hash_table *ht, uint32_t hash, const void *key) { hash_table_remove(ht, hash_table_search_hash(ht, hash, key)); } void hash_table_remove_key(struct hash_table *ht, const void *key) { hash_table_remove_hash(ht, ht->hash(key, ht->data), key); } struct hash_entry *hash_table_next_entry(struct hash_table *ht, struct hash_entry *entry) { /* early return if table is empty */ if (ht->entries == 0) { return NULL; } if (entry == NULL) { entry = ht->table; } else { entry = entry + 1; } for (; entry != ht->table + ht->hash_size.size; entry++) { if (entry_is_present(ht, entry)) { return entry; } } return NULL; } static uint32_t hash_pointer(const void *pointer, void *data) { uintptr_t num = (uintptr_t)pointer; return (uint32_t)((num >> 2) ^ (num >> 6) ^ (num >> 10) ^ (num >> 14)); } static bool hash_key_pointer_equal(const void *a, const void *b, void *data) { return a == b; } struct hash_table *hash_table_create_pointer(void *data) { return hash_table_create(hash_pointer, hash_key_pointer_equal, data); } uint32_t hash_string_with_length(const void *string, uint32_t length) { uint32_t hash = 0x811C9DC5; const char *str = string; // Iterate over each character in the string for (uint32_t i = 0; i < length; i++) { // XOR the current byte with the hash value hash ^= (uint8_t)str[i]; // Multiply by the FNV prime hash *= 0x01000193; } return hash; } static uint32_t hash_string(const void *string, void *data) { return hash_string_with_length(string, strlen(string)); } static bool hash_key_string_equal(const void *a, const void *b, void *data) { return strcmp(a, b) == 0; } struct hash_table *hash_table_create_string(void *data) { return hash_table_create(hash_string, hash_key_string_equal, data); } static uint32_t hash_int(const void *key, void *data) { uint32_t hash = 0x811C9DC5; hash ^= (uintptr_t)key; hash *= 16777619; return hash; } static bool hash_key_int_equal(const void *a, const void *b, void *data) { return (uintptr_t)a == (uintptr_t)b; } struct hash_table *hash_table_create_int(void *data) { return hash_table_create(hash_int, hash_key_int_equal, data); } kylin-wayland-compositor/src/util/debug.c0000664000175000017500000000617015160460057017465 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _GNU_SOURCE #include #include #include "util/debug.h" #if HAVE_LIBUNWIND #define UNW_LOCAL_ONLY #include void debug_backtrace(void) { unw_context_t context; int ret = unw_getcontext(&context); if (ret) { kywc_log(KYWC_ERROR, "Unw_getcontext failed: %s [%d]", unw_strerror(ret), ret); return; } unw_cursor_t cursor; ret = unw_init_local(&cursor, &context); if (ret) { kywc_log(KYWC_ERROR, "Unw_init_local failed: %s [%d]", unw_strerror(ret), ret); return; } kywc_log(KYWC_SILENT, "Backtrace:"); unw_proc_info_t pip = { .unwind_info = NULL }; unw_word_t off, ip; Dl_info dlinfo; char procname[256]; const char *filename; int i = 0; ret = unw_step(&cursor); while (ret > 0) { ret = unw_get_proc_info(&cursor, &pip); if (ret) { kywc_log(KYWC_ERROR, "Unw_get_proc_info failed: %s [%d]", unw_strerror(ret), ret); break; } off = 0; ret = unw_get_proc_name(&cursor, procname, 256, &off); if (ret && ret != -UNW_ENOMEM) { if (ret != -UNW_EUNSPEC) { kywc_log(KYWC_ERROR, "Unw_get_proc_name failed: %s [%d]", unw_strerror(ret), ret); } procname[0] = '?'; procname[1] = 0; } if (unw_get_reg(&cursor, UNW_REG_IP, &ip) < 0) { ip = pip.start_ip + off; } if (dladdr((void *)(uintptr_t)(ip), &dlinfo) && dlinfo.dli_fname && *dlinfo.dli_fname) { filename = dlinfo.dli_fname; } else { filename = "?"; } if (unw_is_signal_frame(&cursor)) { kywc_log(KYWC_SILENT, " %u: ", i++); } else { kywc_log(KYWC_SILENT, " %u: %s (%s%s+0x%x) [%p]", i++, filename, procname, ret == -UNW_ENOMEM ? "..." : "", (int)off, (void *)(uintptr_t)(ip)); } ret = unw_step(&cursor); if (ret < 0) { kywc_log(KYWC_ERROR, "Unw_step failed: %s [%d]", unw_strerror(ret), ret); } } } #else /* HAVE_LIBUNWIND */ #include #define BACKTRACE_SIZE (256) void debug_backtrace(void) { void *array[BACKTRACE_SIZE]; int size = backtrace(array, BACKTRACE_SIZE); kywc_log(KYWC_SILENT, "Backtrace:"); Dl_info info; const char *mod; for (int i = 0; i < size; i++) { if (dladdr(array[i], &info) == 0) { kywc_log(KYWC_SILENT, " %u: ?? [%p]", i, array[i]); continue; } mod = (info.dli_fname && *info.dli_fname) ? info.dli_fname : "(vdso)"; if (info.dli_saddr) { kywc_log(KYWC_SILENT, " %u: %s (%s+0x%x) [%p]", i, mod, info.dli_sname, (unsigned int)((char *)array[i] - (char *)info.dli_saddr), array[i]); } else { kywc_log(KYWC_SILENT, " %u: %s (%p+0x%x) [%p]", i, mod, info.dli_fbase, (unsigned int)((char *)array[i] - (char *)info.dli_fbase), array[i]); } } } #endif kylin-wayland-compositor/src/util/dbus.c0000664000175000017500000002320515160461067017334 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "server.h" #include "util/dbus.h" struct dbus_object { struct wl_list link; sd_bus_slot *slot; }; struct dbus_context { /* system bus */ sd_bus *system; struct wl_event_source *system_event; /* user bus */ sd_bus *user; struct wl_event_source *user_event; struct wl_list objects; struct wl_display *display; struct wl_listener display_destroy; struct wl_listener server_destroy; }; static struct dbus_context *context = NULL; static int user_dbus_process(int fd, uint32_t mask, void *data) { if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { kywc_log(KYWC_FATAL, "User dbus eventfd failed"); wl_event_source_remove(context->user_event); context->user_event = NULL; sd_bus_flush_close_unref(context->user); context->user = NULL; wl_display_terminate(context->display); return 0; } sd_bus *bus = data; while (sd_bus_process(bus, NULL) > 0) { ; } return 0; } static int system_dbus_process(int fd, uint32_t mask, void *data) { if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { kywc_log(KYWC_FATAL, "System dbus eventfd failed"); wl_event_source_remove(context->system_event); context->system_event = NULL; sd_bus_flush_close_unref(context->system); context->system = NULL; wl_display_terminate(context->display); return 0; } sd_bus *bus = data; while (sd_bus_process(bus, NULL) > 0) { ; } return 0; } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&context->server_destroy.link); struct dbus_object *object, *tmp; wl_list_for_each_safe(object, tmp, &context->objects, link) { dbus_unregister_object(object); } sd_bus_flush_close_unref(context->system); sd_bus_flush_close_unref(context->user); free(context); context = NULL; } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&context->display_destroy.link); if (context->user_event) { wl_event_source_remove(context->user_event); } if (context->system_event) { wl_event_source_remove(context->system_event); } } bool dbus_context_create(struct server *server) { context = calloc(1, sizeof(*context)); if (!context) { return false; } int ret = sd_bus_open_system(&context->system); if (ret < 0) { kywc_log(KYWC_WARN, "Failed to connect to system bus: %s", strerror(-ret)); } ret = sd_bus_open_user(&context->user); if (ret < 0) { kywc_log(KYWC_WARN, "Failed to connect to user bus: %s", strerror(-ret)); } if (context->system) { int fd = sd_bus_get_fd(context->system); context->system_event = wl_event_loop_add_fd(server->event_loop, fd, WL_EVENT_READABLE, system_dbus_process, context->system); wl_event_source_check(context->system_event); } if (context->user) { ret = sd_bus_request_name(context->user, "com.kylin.Wlcom", 0); if (ret < 0) { kywc_log(KYWC_ERROR, "Failed to acquire service name: %s", strerror(-ret)); if (ret == -EEXIST) { kywc_log(KYWC_ERROR, "Is a Kylin-Wlcom already running?"); } sd_bus_flush_close_unref(context->user); context->user = NULL; } else { int fd = sd_bus_get_fd(context->user); context->user_event = wl_event_loop_add_fd(server->event_loop, fd, WL_EVENT_READABLE, user_dbus_process, context->user); wl_event_source_check(context->user_event); } } if (!context->system && !context->user) { kywc_log(KYWC_ERROR, "Failed to init system and user bus"); free(context); return false; } wl_list_init(&context->objects); context->display = server->display; context->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &context->display_destroy); context->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &context->server_destroy); return true; } sd_bus *dbus_context_get_bus(bool system) { if (!context) { return NULL; } return system ? context->system : context->user; } struct dbus_object *dbus_register_object(const char *name, const char *path, const char *interface, const sd_bus_vtable *vtable, void *data) { if (!context || !context->user) { return NULL; } struct dbus_object *object = calloc(1, sizeof(*object)); if (!object) { return NULL; } if (name) { sd_bus_request_name(context->user, name, 0); } if (path && interface && vtable) { int ret = sd_bus_add_object_vtable(context->user, &object->slot, path, interface, vtable, data); if (ret < 0) { kywc_log(KYWC_ERROR, "Interface %s vtable add failed: %d", interface, ret); free(object); return NULL; } } wl_list_insert(&context->objects, &object->link); return object; } void dbus_unregister_object(struct dbus_object *object) { if (!object) { return; } if (object->slot) { sd_bus_slot_unref(object->slot); } wl_list_remove(&object->link); free(object); } bool dbus_call_method(const char *service, const char *path, const char *interface, const char *method, sd_bus_message_handler_t callback, void *data) { if (!context || !context->user) { return false; } return sd_bus_call_method_async(context->user, NULL, service, path, interface, method, callback, data, NULL) >= 0; } bool dbus_call_methodv(const char *service, const char *path, const char *interface, const char *method, sd_bus_message_handler_t callback, void *data, const char *types, ...) { if (!context || !context->user) { return false; } va_list args; va_start(args, types); int ret = sd_bus_call_method_asyncv(context->user, NULL, service, path, interface, method, callback, data, types, args); va_end(args); return ret >= 0; } bool dbus_call_system_method(const char *service, const char *path, const char *interface, const char *method, sd_bus_message_handler_t callback, void *data) { if (!context || !context->system) { return false; } return sd_bus_call_method_async(context->system, NULL, service, path, interface, method, callback, data, NULL) >= 0; } bool dbus_call_system_methodv(const char *service, const char *path, const char *interface, const char *method, sd_bus_message_handler_t callback, void *data, const char *types, ...) { if (!context || !context->system) { return false; } va_list args; va_start(args, types); int ret = sd_bus_call_method_asyncv(context->system, NULL, service, path, interface, method, callback, data, types, args); va_end(args); return ret >= 0; } void dbus_notify(const char *name, const char *summary, const char *body, const char *icon) { if (!context || !context->user) { return; } sd_bus_call_method_async(context->user, NULL, "org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", "Notify", NULL, NULL, "susssasa{sv}i", name, 0, icon ? icon : "", summary, body, 0, 0, 5000); } int dbus_add_match(const char *match, sd_bus_message_handler_t callback, void *data) { if (!context || !context->user) { return false; } int ret = sd_bus_add_match(context->user, NULL, match, callback, data); if (ret < 0) { kywc_log(KYWC_ERROR, "Failed to add user match: %s", match); return false; } return true; } bool dbus_match_signal(const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *data) { if (!context || !context->user) { return false; } int ret = sd_bus_match_signal(context->user, NULL, sender, path, interface, member, callback, data); if (ret < 0) { kywc_log(KYWC_ERROR, "Failed to add user signal match: %s %s", interface, member); return false; } return true; } bool dbus_match_system_signal(const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *data) { if (!context || !context->system) { return false; } int ret = sd_bus_match_signal(context->system, NULL, sender, path, interface, member, callback, data); if (ret < 0) { kywc_log(KYWC_ERROR, "Failed to add system signal match: %s %s", interface, member); return false; } return true; } void dbus_emit_signal(const char *path, const char *interface, const char *member, const char *types, ...) { if (!context || !context->user) { return; } va_list args; va_start(args, types); sd_bus_emit_signalv(context->user, path, interface, member, types, args); va_end(args); } kylin-wayland-compositor/src/util/time.c0000664000175000017500000000116015160460057017327 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include "util/time.h" uint32_t current_time_msec(void) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); return now.tv_sec * 1000 + now.tv_nsec / 1000000; } int64_t timespec_diff_usec(const struct timespec *a, const struct timespec *b) { struct timespec r; r.tv_sec = a->tv_sec - b->tv_sec; r.tv_nsec = a->tv_nsec - b->tv_nsec; if (r.tv_nsec < 0) { r.tv_sec--; r.tv_nsec += 1000000000; } return r.tv_sec * 1000000 + r.tv_nsec / 1000; } kylin-wayland-compositor/src/util/queue.c0000664000175000017500000002124715160461067017527 0ustar fengfeng// SPDX-FileCopyrightText: 2016 Advanced Micro Devices, Inc. // SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat /* Job queue with execution in a separate thread. * Copied from mesa u_queue.c. */ #include #include #include #include "util/queue.h" struct queue_job { void *job; void *global_data; struct queue_fence *fence; queue_execute_func execute; queue_execute_func cleanup; }; struct queue { pthread_mutex_t lock; pthread_cond_t has_queued_cond; pthread_cond_t has_space_cond; struct queue_job *jobs; int max_jobs; int num_queued; int write_idx, read_idx; pthread_t *threads; unsigned num_threads; void *global_data; }; struct thread_input { struct queue *queue; unsigned thread_index; }; void queue_fence_init(struct queue_fence *fence) { memset(fence, 0, sizeof(*fence)); pthread_mutex_init(&fence->mutex, NULL); pthread_cond_init(&fence->cond, NULL); fence->signalled = true; } void queue_fence_wait(struct queue_fence *fence) { if (fence->signalled) { return; } pthread_mutex_lock(&fence->mutex); while (!fence->signalled) { pthread_cond_wait(&fence->cond, &fence->mutex); } pthread_mutex_unlock(&fence->mutex); } void queue_fence_finish(struct queue_fence *fence) { assert(fence->signalled); pthread_mutex_lock(&fence->mutex); pthread_mutex_unlock(&fence->mutex); pthread_cond_destroy(&fence->cond); pthread_mutex_destroy(&fence->mutex); } static void queue_fence_signal(struct queue_fence *fence) { pthread_mutex_lock(&fence->mutex); fence->signalled = true; pthread_cond_broadcast(&fence->cond); pthread_mutex_unlock(&fence->mutex); } static void *queue_thread_func(void *input) { struct queue *queue = ((struct thread_input *)input)->queue; unsigned thread_index = ((struct thread_input *)input)->thread_index; free(input); while (1) { struct queue_job job; pthread_mutex_lock(&queue->lock); assert(queue->num_queued >= 0 && queue->num_queued <= queue->max_jobs); /* wait if the queue is empty */ while (thread_index < queue->num_threads && queue->num_queued == 0) { pthread_cond_wait(&queue->has_queued_cond, &queue->lock); } /* only kill threads that are above "num_threads" */ if (thread_index >= queue->num_threads) { pthread_mutex_unlock(&queue->lock); break; } job = queue->jobs[queue->read_idx]; memset(&queue->jobs[queue->read_idx], 0, sizeof(struct queue_job)); queue->read_idx = (queue->read_idx + 1) % queue->max_jobs; queue->num_queued--; pthread_cond_signal(&queue->has_space_cond); pthread_mutex_unlock(&queue->lock); if (job.job) { job.execute(job.job, job.global_data, thread_index); if (job.fence) { queue_fence_signal(job.fence); } if (job.cleanup) { job.cleanup(job.job, job.global_data, thread_index); } } } pthread_mutex_lock(&queue->lock); if (queue->num_threads == 0) { for (int i = queue->read_idx; i != queue->write_idx; i = (i + 1) % queue->max_jobs) { if (queue->jobs[i].job) { if (queue->jobs[i].fence) { queue_fence_signal(queue->jobs[i].fence); } queue->jobs[i].job = NULL; } } queue->read_idx = queue->write_idx; queue->num_queued = 0; } pthread_mutex_unlock(&queue->lock); return NULL; } static bool queue_create_thread(struct queue *queue, unsigned index) { struct thread_input *input = malloc(sizeof(*input)); input->queue = queue; input->thread_index = index; if (pthread_create(queue->threads + index, NULL, queue_thread_func, input) != 0) { free(input); return false; } return true; } struct queue *queue_create(unsigned max_jobs, unsigned num_threads, void *global_data) { struct queue *queue = calloc(1, sizeof(*queue)); if (!queue) { return NULL; } queue->max_jobs = max_jobs; queue->global_data = global_data; queue->num_threads = num_threads; queue->num_queued = 0; pthread_mutex_init(&queue->lock, NULL); pthread_cond_init(&queue->has_queued_cond, NULL); pthread_cond_init(&queue->has_space_cond, NULL); queue->jobs = calloc(max_jobs, sizeof(struct queue_job)); if (!queue->jobs) { goto fail; } queue->threads = calloc(queue->num_threads, sizeof(pthread_t)); if (!queue->threads) { goto fail; } /* start threads */ for (unsigned i = 0; i < queue->num_threads; i++) { if (!queue_create_thread(queue, i)) { if (i == 0) { /* no threads created, fail */ goto fail; } else { /* at least one thread created, so use it */ queue->num_threads = i; break; } } } return queue; fail: free(queue->threads); if (queue->jobs) { pthread_cond_destroy(&queue->has_space_cond); pthread_cond_destroy(&queue->has_queued_cond); pthread_mutex_destroy(&queue->lock); free(queue->jobs); } free(queue); return NULL; } static void queue_kill_threads(struct queue *queue) { /* Signal all threads to terminate. */ pthread_mutex_lock(&queue->lock); unsigned old_num_threads = queue->num_threads; /* Setting num_threads is what causes the threads to terminate. * Then cnd_broadcast wakes them up and they will exit their function. */ queue->num_threads = 0; pthread_cond_broadcast(&queue->has_queued_cond); pthread_mutex_unlock(&queue->lock); for (unsigned i = 0; i < old_num_threads; i++) { pthread_join(queue->threads[i], NULL); } } void queue_destroy(struct queue *queue) { if (!queue || !queue->threads) { return; } queue_kill_threads(queue); pthread_cond_destroy(&queue->has_space_cond); pthread_cond_destroy(&queue->has_queued_cond); pthread_mutex_destroy(&queue->lock); free(queue->jobs); free(queue->threads); } bool queue_add_job(struct queue *queue, void *job, struct queue_fence *fence, queue_execute_func execute, queue_execute_func cleanup) { if (!queue || !queue->threads) { return false; } pthread_mutex_lock(&queue->lock); if (queue->num_threads == 0) { pthread_mutex_unlock(&queue->lock); return false; } if (fence) { queue_fence_reset(fence); } assert(queue->num_queued >= 0 && queue->num_queued <= queue->max_jobs); if (queue->num_queued == queue->max_jobs) { unsigned new_max_jobs = queue->max_jobs + 8; struct queue_job *jobs = calloc(new_max_jobs, sizeof(struct queue_job)); assert(jobs); /* Copy all queued jobs into the new list. */ int num_jobs = 0; int i = queue->read_idx; do { jobs[num_jobs++] = queue->jobs[i]; i = (i + 1) % queue->max_jobs; } while (i != queue->write_idx); assert(num_jobs == queue->num_queued); free(queue->jobs); queue->jobs = jobs; queue->read_idx = 0; queue->write_idx = num_jobs; queue->max_jobs = new_max_jobs; } struct queue_job *ptr = &queue->jobs[queue->write_idx]; assert(ptr->job == NULL); ptr->job = job; ptr->global_data = queue->global_data; ptr->fence = fence; ptr->execute = execute; ptr->cleanup = cleanup; queue->write_idx = (queue->write_idx + 1) % queue->max_jobs; queue->num_queued++; pthread_cond_signal(&queue->has_queued_cond); pthread_mutex_unlock(&queue->lock); return true; } void queue_drop_job(struct queue *queue, struct queue_fence *fence) { if (!queue || !queue->threads) { return; } if (queue_fence_is_signalled(fence)) { return; } bool removed = false; pthread_mutex_lock(&queue->lock); for (int i = queue->read_idx; i != queue->write_idx; i = (i + 1) % queue->max_jobs) { if (queue->jobs[i].fence == fence) { if (queue->jobs[i].cleanup) { queue->jobs[i].cleanup(queue->jobs[i].job, queue->global_data, -1); } /* Just clear it. The threads will treat as a no-op job. */ memset(&queue->jobs[i], 0, sizeof(queue->jobs[i])); removed = true; break; } } pthread_mutex_unlock(&queue->lock); if (removed) { queue_fence_signal(fence); } else { queue_fence_wait(fence); } } kylin-wayland-compositor/src/util/limit.c0000664000175000017500000000211615160460057017511 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "util/limit.h" static struct rlimit original_nofile_rlimit = { 0 }; void limit_set_nofile(void) { if (getrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) { kywc_log_errno(KYWC_ERROR, "Failed to get max open files limit"); return; } struct rlimit new_rlimit = original_nofile_rlimit; new_rlimit.rlim_cur = new_rlimit.rlim_max; if (setrlimit(RLIMIT_NOFILE, &new_rlimit) != 0) { kywc_log_errno(KYWC_ERROR, "Failed to set max open files limit"); kywc_log(KYWC_INFO, "Running with %ld max open files", original_nofile_rlimit.rlim_cur); return; } kywc_log(KYWC_INFO, "Running with %ld max open files", new_rlimit.rlim_cur); } void limit_unset_nofile(void) { if (original_nofile_rlimit.rlim_cur == 0) { return; } if (setrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) { kywc_log_errno(KYWC_ERROR, "Failed to restore max open files limit"); } } kylin-wayland-compositor/src/output/0000775000175000017500000000000015160461067016614 5ustar fengfengkylin-wayland-compositor/src/output/layout.c0000664000175000017500000001771015160461067020303 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "output_p.h" #include "util/macros.h" static void output_edge_position(struct output *output, enum layout_edge edge, int *lx, int *ly) { struct kywc_box *geo = &output->geometry; switch (edge) { case LAYOUT_EDGE_TOP: *lx = geo->x + geo->width / 2; *ly = geo->y - 1; break; case LAYOUT_EDGE_BOTTOM: *lx = geo->x + geo->width / 2; *ly = geo->y + geo->height; break; case LAYOUT_EDGE_LEFT: *lx = geo->x - 1; *ly = geo->y + geo->height / 2; break; case LAYOUT_EDGE_RIGHT: *lx = geo->x + geo->width; *ly = geo->y + geo->height / 2; break; } } bool output_at_layout_edge(struct output *output, enum layout_edge edge) { int lx = 0, ly = 0; output_edge_position(output, edge, &lx, &ly); struct wlr_output_layout *layout = output->manager->server->layout; return !wlr_output_layout_contains_point(layout, NULL, lx, ly); } static struct output *output_get_prev_usable_output(struct output *output) { struct output *find_output = NULL; wl_list_for_each_reverse(find_output, &output->link, link) { if (&find_output->link == &output_manager->outputs) { continue; } if (find_output->base.state.enabled) { return find_output; } } return NULL; } static struct output *output_get_next_usable_output(struct output *output) { struct output *find_output = NULL; wl_list_for_each(find_output, &output->link, link) { if (&find_output->link == &output_manager->outputs) { continue; } if (find_output->base.state.enabled) { return find_output; } } return NULL; } struct output *output_find_specified_output(struct output *output, enum layout_edge edge) { struct output *find_output = NULL; switch (edge) { case LAYOUT_EDGE_LEFT: find_output = output_get_prev_usable_output(output); break; case LAYOUT_EDGE_RIGHT: find_output = output_get_next_usable_output(output); break; case LAYOUT_EDGE_TOP: case LAYOUT_EDGE_BOTTOM: break; } return find_output; } bool output_state_is_mirror_mode(void) { if (wl_list_length(&output_manager->outputs) < 2) { return false; } struct kywc_box *geometry = NULL; struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { if (!output->base.state.enabled) { continue; } if (!geometry) { geometry = &output->geometry; continue; } if (!kywc_box_equal(geometry, &output->geometry)) { return false; } } return true; } struct kywc_output *kywc_output_at_point(double lx, double ly) { struct wlr_output_layout *layout = output_manager->server->layout; struct wlr_output *wlr_output = NULL; double closest_x, closest_y; wlr_output_layout_closest_point(layout, wlr_output, lx, ly, &closest_x, &closest_y); wlr_output = wlr_output_layout_output_at(layout, closest_x, closest_y); return wlr_output ? &output_from_wlr_output(wlr_output)->base : NULL; } void output_layout_get_size(int *width, int *height) { struct wlr_box box; wlr_output_layout_get_box(output_manager->server->layout, NULL, &box); if (width) { *width = box.width; } if (height) { *height = box.height; } } void output_layout_get_workarea(struct wlr_box *box) { wlr_output_layout_get_box(output_manager->server->layout, NULL, box); /* layout edges */ int layout_left = MAX(0, box->x); int layout_right = MAX(0, box->x + box->width); int layout_top = MAX(0, box->y); int layout_bottom = MAX(0, box->y + box->height); /* workarea edges */ int workarea_left = layout_left; int workarea_right = layout_right; int workarea_top = layout_top; int workarea_bottom = layout_bottom; struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { if (!output->base.state.enabled) { continue; } /* output edges */ int output_left = output->geometry.x; int output_right = output_left + output->geometry.width; int output_top = output->geometry.y; int output_bottom = output_top + output->geometry.height; /* output usable edges */ int usable_left = output->usable_area.x; int usable_right = usable_left + output->usable_area.width; int usable_top = output->usable_area.y; int usable_bottom = usable_top + output->usable_area.height; /** * Only adjust workarea edges for output edges that are * aligned with outer edges of layout */ if (output_left == layout_left) { workarea_left = MAX(workarea_left, usable_left); } if (output_right == layout_right) { workarea_right = MIN(workarea_right, usable_right); } if (output_top == layout_top) { workarea_top = MAX(workarea_top, usable_top); } if (output_bottom == layout_bottom) { workarea_bottom = MIN(workarea_bottom, usable_bottom); } } box->x = workarea_left; box->y = workarea_top; box->width = workarea_right - workarea_left; box->height = workarea_bottom - workarea_top; } static bool output_manager_is_sorted(void) { int prev_x = 0, prev_y = 0; struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { int distance_x = output->geometry.x - prev_x; int distance_y = output->geometry.y - prev_y; if (distance_x < 0 || (distance_x == 0 && distance_y < 0)) { return false; } prev_x = output->geometry.x; prev_y = output->geometry.y; } return true; } void output_manager_sort_outputs(void) { if (output_manager_is_sorted()) { return; } struct output *output, *prev_output, *tmp; wl_list_for_each_safe(output, tmp, &output_manager->outputs, link) { wl_list_for_each_reverse(prev_output, &output->link, link) { if (&prev_output->link == &output_manager->outputs) { wl_list_remove(&output->link); wl_list_insert(&output_manager->outputs, &output->link); break; } // sort int distance_x = output->geometry.x - prev_output->geometry.x; int distance_y = output->geometry.y - prev_output->geometry.y; if (distance_x < 0 || (distance_x == 0 && distance_y < 0)) { continue; } if (distance_x > 0 || (distance_x == 0 && distance_y > 0)) { wl_list_remove(&output->link); wl_list_insert(&prev_output->link, &output->link); break; } } } } void output_set_layout(struct output *output) { struct server *server = output_manager->server; struct wlr_output *wlr_output = output->wlr_output; struct kywc_output_state *state = &output->pending_state; /* if output disabled, skip output_layout_add */ if (!state->enabled) { if (output->layout_output) { wlr_output_layout_remove(server->layout, wlr_output); output->layout_output = NULL; } return; } /* force to reconfigure even if have loutput */ if (state->lx == -1 || state->ly == -1) { output->layout_output = wlr_output_layout_add_auto(server->layout, wlr_output); if (output->layout_output) { output->layout_output->auto_configured = false; } } else { if (!output->layout_output || output->layout_output->x != state->lx || output->layout_output->y != state->ly) { output->layout_output = wlr_output_layout_add(server->layout, wlr_output, state->lx, state->ly); } } } kylin-wayland-compositor/src/output/meson.build0000664000175000017500000000071115160461067020755 0ustar fengfengwlcom_sources += files( 'config.c', 'configure.c', 'layout.c', 'modes.c', 'output.c', 'xdg_output.c', 'ky_output.c', ) if have_kde_output wlcom_sources += files( 'kde_output.c', ) endif if have_wlr_output wlcom_sources += files( 'wlr_output.c', ) endif if have_ukui_output wlcom_sources += files( 'ukui_output.c', ) endif if have_ukui_output_config wlcom_sources += files( 'ukui_output_config.c', ) endif kylin-wayland-compositor/src/output/ukui_output.c0000664000175000017500000002410615160461067021360 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "ukui-output-v1-protocol.h" #include "output_p.h" #include "server.h" #define UKUI_OUTPUT_VERSION 1 struct ukui_output_management { struct wl_global *global; struct wl_event_loop *event_loop; struct wl_event_source *idle_source; struct wl_list resources; struct wl_list outputs; struct wl_listener new_enabled_output; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct ukui_output { struct ukui_output_management *management; struct wl_list link; struct wl_list clients; // ukui_output_client.link struct kywc_output *kywc_output; struct wl_listener disable; struct wl_listener usable_area_changed; }; struct ukui_output_client { struct wl_resource *resource; struct wl_list link; struct ukui_output *ukui_output; }; static void send_output_initial_state(struct ukui_output_client *ukui_client) { ukui_output_v1_send_name(ukui_client->resource, ukui_client->ukui_output->kywc_output->name); struct output *output = output_from_kywc_output(ukui_client->ukui_output->kywc_output); ukui_output_v1_send_usable_area(ukui_client->resource, output->usable_area.x, output->usable_area.y, output->usable_area.width, output->usable_area.height); kywc_log(KYWC_DEBUG, "Ukui output usable area: %s, %d, %d, %d x %d", output->base.name, output->usable_area.x, output->usable_area.y, output->usable_area.width, output->usable_area.height); } static void management_idle_send_done(void *data) { struct ukui_output_management *management = data; struct wl_resource *resource; wl_resource_for_each(resource, &management->resources) { ukui_output_management_v1_send_done(resource); } management->idle_source = NULL; } static void management_update_idle_source(struct ukui_output_management *management) { if (management->idle_source || wl_list_empty(&management->resources)) { return; } management->idle_source = wl_event_loop_add_idle(management->event_loop, management_idle_send_done, management); } static void handle_ukui_output_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void ukui_output_resource_destroy(struct wl_resource *resource) { struct ukui_output_client *ukui_client = wl_resource_get_user_data(resource); if (!ukui_client) { return; } wl_list_remove(&ukui_client->link); free(ukui_client); } static const struct ukui_output_v1_interface ukui_output_impl = { .destroy = handle_ukui_output_destroy, }; static struct ukui_output_client * create_client_for_resource(struct ukui_output *ukui_output, struct wl_resource *management_resource) { struct ukui_output_client *ukui_client = calloc(1, sizeof(*ukui_client)); if (!ukui_client) { return NULL; } struct wl_client *client = wl_resource_get_client(management_resource); struct wl_resource *resource = wl_resource_create( client, &ukui_output_v1_interface, wl_resource_get_version(management_resource), 0); if (!resource) { wl_client_post_no_memory(client); free(ukui_client); return NULL; } ukui_client->resource = resource; ukui_client->ukui_output = ukui_output; wl_list_insert(&ukui_output->clients, &ukui_client->link); wl_resource_set_implementation(resource, &ukui_output_impl, ukui_client, ukui_output_resource_destroy); ukui_output_management_v1_send_output(management_resource, resource); return ukui_client; } static void handle_output_disable(struct wl_listener *listener, void *data) { struct ukui_output *ukui_output = wl_container_of(listener, ukui_output, disable); struct ukui_output_client *ukui_client, *tmp; wl_list_for_each_safe(ukui_client, tmp, &ukui_output->clients, link) { ukui_output_v1_send_finished(ukui_client->resource); wl_resource_set_user_data(ukui_client->resource, NULL); wl_list_remove(&ukui_client->link); free(ukui_client); } wl_list_remove(&ukui_output->link); wl_list_remove(&ukui_output->usable_area_changed.link); wl_list_remove(&ukui_output->disable.link); free(ukui_output); } static void handle_usable_area_changed(struct wl_listener *listener, void *data) { struct ukui_output *ukui_output = wl_container_of(listener, ukui_output, usable_area_changed); struct output *output = output_from_kywc_output(ukui_output->kywc_output); struct ukui_output_client *ukui_client; wl_list_for_each(ukui_client, &ukui_output->clients, link) { ukui_output_v1_send_usable_area(ukui_client->resource, output->usable_area.x, output->usable_area.y, output->usable_area.width, output->usable_area.height); } management_update_idle_source(ukui_output->management); } static void handle_new_enabled_output(struct wl_listener *listener, void *data) { struct kywc_output *kywc_output = data; struct output *output = output_from_kywc_output(kywc_output); struct ukui_output_management *management = wl_container_of(listener, management, new_enabled_output); struct ukui_output *ukui_output = calloc(1, sizeof(*ukui_output)); if (!ukui_output) { return; } ukui_output->kywc_output = kywc_output; ukui_output->management = management; wl_list_init(&ukui_output->clients); wl_list_insert(&management->outputs, &ukui_output->link); ukui_output->disable.notify = handle_output_disable; wl_signal_add(&output->events.disable, &ukui_output->disable); ukui_output->usable_area_changed.notify = handle_usable_area_changed; wl_signal_add(&output->events.usable_area, &ukui_output->usable_area_changed); /* send output usable_area */ struct wl_resource *resource; struct ukui_output_client *ukui_client; wl_resource_for_each(resource, &management->resources) { ukui_client = create_client_for_resource(ukui_output, resource); if (ukui_client) { send_output_initial_state(ukui_client); } ukui_output_management_v1_send_done(resource); } } static struct ukui_output_client *ukui_output_client_from_client(struct ukui_output *ukui_output, struct wl_client *client) { struct ukui_output_client *ukui_client; wl_list_for_each(ukui_client, &ukui_output->clients, link) { if (client == wl_resource_get_client(ukui_client->resource)) { return ukui_client; } } return NULL; } static void handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void ukui_output_management_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static const struct ukui_output_management_v1_interface ukui_output_management_impl = { .destroy = handle_destroy, }; static void ukui_output_management_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct ukui_output_management *management = data; struct wl_resource *resource = wl_resource_create(client, &ukui_output_management_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &ukui_output_management_impl, management, ukui_output_management_resource_destroy); wl_list_insert(&management->resources, wl_resource_get_link(resource)); struct ukui_output *ukui_output; wl_list_for_each(ukui_output, &management->outputs, link) { create_client_for_resource(ukui_output, resource); } struct ukui_output_client *ukui_client; wl_list_for_each_reverse(ukui_output, &management->outputs, link) { ukui_client = ukui_output_client_from_client(ukui_output, client); if (ukui_client) { send_output_initial_state(ukui_client); } } ukui_output_management_v1_send_done(resource); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct ukui_output_management *management = wl_container_of(listener, management, display_destroy); wl_list_remove(&management->display_destroy.link); wl_list_remove(&management->new_enabled_output.link); if (management->idle_source) { wl_event_source_remove(management->idle_source); } wl_global_destroy(management->global); } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct ukui_output_management *management = wl_container_of(listener, management, server_destroy); wl_list_remove(&management->server_destroy.link); free(management); } bool ukui_output_management_create(struct server *server) { struct ukui_output_management *management = calloc(1, sizeof(*management)); if (!management) { return false; } management->global = wl_global_create(server->display, &ukui_output_management_v1_interface, UKUI_OUTPUT_VERSION, management, ukui_output_management_bind); if (!management->global) { kywc_log(KYWC_WARN, "Ukui output management create failed"); free(management); return false; } wl_list_init(&management->resources); wl_list_init(&management->outputs); management->event_loop = wl_display_get_event_loop(server->display); management->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &management->server_destroy); management->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &management->display_destroy); management->new_enabled_output.notify = handle_new_enabled_output; output_manager_add_new_enabled_listener(&management->new_enabled_output); return true; } kylin-wayland-compositor/src/output/ukui_output_config.c0000664000175000017500000002536415160461067022714 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "output_p.h" #include "util/macros.h" #define HASH_SIZE 33 static bool output_read_usd_config(struct output *output, json_object *config_json, struct kywc_output_state *state) { if (!config_json) { return false; } json_object *config = json_object_object_get(config_json, output->base.prop.edid_hash); if (!config) { // try to get config form output name config = json_object_object_get(config_json, output->base.name); } if (!config) { kywc_log(KYWC_WARN, "%s config not found", output->base.name); return false; } json_object *data; if (json_object_object_get_ex(config, "enable", &data)) { state->enabled = state->power = json_object_get_boolean(data); } if (!state->enabled) { return true; } if (json_object_object_get_ex(config, "primary", &data)) { if (json_object_get_boolean(data)) { output_manager_set_pending_primary(output); } } if (json_object_object_get_ex(config, "width", &data)) { state->width = json_object_get_int(data); } if (json_object_object_get_ex(config, "height", &data)) { state->height = json_object_get_int(data); } if (json_object_object_get_ex(config, "refresh", &data)) { state->refresh = json_object_get_int(data); } if (json_object_object_get_ex(config, "rotation", &data)) { state->transform = json_object_get_int(data); } if (json_object_object_get_ex(config, "scale", &data)) { state->scale = json_object_get_double(data); } if (json_object_object_get_ex(config, "x", &data)) { state->lx = json_object_get_int(data); } if (json_object_object_get_ex(config, "y", &data)) { state->ly = json_object_get_int(data); } return true; } static json_object *get_usd_layout_config_object(struct output_manager *output_manager) { char layout_name[128]; const char *path = "/etc/ukui/usd/config/screen/wlcom"; snprintf(layout_name, sizeof(layout_name), "%s/%s", path, output_manager->outputs_layout); kywc_log(KYWC_DEBUG, "Sync outputs config from USD: %s", layout_name); json_object *config_json = json_object_from_file(layout_name); return config_json; } static int compare_modes(struct kywc_output_mode *mode1, struct kywc_output_mode *mode2) { return (mode1->width != mode2->width) ? (mode1->width - mode2->width) : (mode1->height - mode2->height); } static bool is_mode_supported_for_other_outputs(struct kywc_output *current_output, struct kywc_output_mode *current_mode) { struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { struct kywc_output *kywc_output = &output->base; if (kywc_output == output_manager_get_fallback() || kywc_output == current_output) { continue; } struct kywc_output_mode *mode; wl_list_for_each(mode, &kywc_output->prop.modes, link) { if (mode->width == current_mode->width && mode->height == current_mode->height) { return true; } } } return false; } static float get_min_preferred_scale(struct kywc_output *current_output, int32_t width, int32_t height) { float min_scale = output_preferred_scale(current_output, width, height); struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { struct kywc_output *kywc_output = &output->base; if (kywc_output == output_manager_get_fallback() || kywc_output == current_output) { continue; } float preferred_scale = output_preferred_scale(kywc_output, width, height); if (preferred_scale < min_scale) { min_scale = preferred_scale; } } return min_scale; } static int32_t output_get_mode_max_refresh(struct output *output, int width, int height) { struct kywc_output *kywc_output = &output->base; struct kywc_output_mode *mode; int32_t max_refresh = 0; wl_list_for_each(mode, &kywc_output->prop.modes, link) { if (mode->width == width && mode->height == height && mode->refresh > max_refresh) { max_refresh = mode->refresh; } } return max_refresh; } static void output_fill_mirror_mode_state(struct output *output, int32_t width, int32_t height, float scale) { struct kywc_output_state *pending = &output->pending_state; *pending = output->base.state; pending->enabled = pending->power = true; pending->width = width; pending->height = height; pending->refresh = output_get_mode_max_refresh(output, width, height); pending->scale = scale; pending->lx = pending->ly = 0; pending->transform = WL_OUTPUT_TRANSFORM_NORMAL; pending->brightness = pending->brightness == 0 ? 100 : CLAMP(pending->brightness, 10, 100); pending->color_temp = pending->color_temp == 0 ? 6500 : CLAMP(pending->color_temp, 1000, 25000); } static bool output_manager_add_mirror_mode_state(struct output_manager *output_manager) { struct kywc_output *first_output = NULL; struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { struct kywc_output *kywc_output = &output->base; if (kywc_output == output_manager_get_fallback()) { continue; } first_output = kywc_output; break; } if (!first_output) { return false; } struct kywc_output_mode *mode, *max_mode = NULL; wl_list_for_each(mode, &first_output->prop.modes, link) { if (!is_mode_supported_for_other_outputs(first_output, mode)) { continue; } if (!max_mode || compare_modes(mode, max_mode) > 0) { max_mode = mode; } } if (!max_mode) { return false; } float scale = get_min_preferred_scale(first_output, max_mode->width, max_mode->height); kywc_log(KYWC_DEBUG, "Add mirror mode: %dx%d scale: %f", max_mode->width, max_mode->height, scale); wl_list_for_each(output, &output_manager->outputs, link) { struct kywc_output *kywc_output = &output->base; if (kywc_output == output_manager_get_fallback()) { continue; } output_fill_mirror_mode_state(output, max_mode->width, max_mode->height, scale); } return true; } struct output_hash { const char *name, *hash; }; static int compare_output_hash(const void *p1, const void *p2) { const char *v1 = ((struct output_hash *)p1)->hash; const char *v2 = ((struct output_hash *)p2)->hash; return strcmp(v1, v2); } static void output_manager_generate_usd_layout(struct output_manager *output_manager, char *layout_hash) { struct output_hash *o_hashs = NULL; int actual_cnt = 0; size_t hash_chunk_size = HASH_SIZE - 1; struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { struct kywc_output *kywc_output = &output->base; if (kywc_output == output_manager_get_fallback()) { continue; } actual_cnt++; } if (!actual_cnt) { return; } o_hashs = malloc(actual_cnt * sizeof(struct output_hash)); if (!o_hashs) { return; } int index = 0; wl_list_for_each(output, &output_manager->outputs, link) { struct kywc_output *kywc_output = &output->base; if (kywc_output == output_manager_get_fallback()) { continue; } o_hashs[index].name = kywc_output->name; o_hashs[index].hash = kywc_output->prop.edid_hash; index++; } if (actual_cnt == 1) { const char *hash = kywc_identifier_md5_generate((void *)o_hashs[0].hash, hash_chunk_size); if (hash) { memcpy(layout_hash, hash, hash_chunk_size); layout_hash[hash_chunk_size] = '\0'; free((void *)hash); } free(o_hashs); return; } qsort(o_hashs, actual_cnt, sizeof(struct output_hash), compare_output_hash); uint8_t *hashs = malloc(actual_cnt * hash_chunk_size); if (!hashs) { free(o_hashs); return; } for (int i = 0; i < actual_cnt; ++i) { memcpy(hashs + i * hash_chunk_size, o_hashs[i].hash, hash_chunk_size); } free(o_hashs); const char *hash = kywc_identifier_md5_generate(hashs, actual_cnt * hash_chunk_size); if (hash) { memcpy(layout_hash, hash, hash_chunk_size); layout_hash[hash_chunk_size] = '\0'; free((void *)hash); } free(hashs); } static void output_manager_get_usd_configs(bool output_removed) { if (output_manager->server->terminate || !output_manager->server->start || output_manager->initial_configured) { return; } if (!output_manager_has_actual_outputs()) { struct output *fallback_output = output_from_kywc_output(output_manager_get_fallback()); output_manager_add_pending_state(fallback_output, &fallback_output->pending_state); return; } bool fill_mirror_state = false; /* update current outputs layout */ output_manager_generate_usd_layout(output_manager, output_manager->outputs_layout); json_object *usd_config = get_usd_layout_config_object(output_manager); if (!usd_config) { fill_mirror_state = output_manager_add_mirror_mode_state(output_manager); kywc_log(KYWC_WARN, "USD configuration missing try to fill mirror mode"); } else { kywc_log(KYWC_INFO, "Configure outputs from USD layout %s", output_manager->outputs_layout); } /* get all outputs configuration */ struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { struct kywc_output *kywc_output = &output->base; if (kywc_output == output_manager_get_fallback()) { continue; } if (!fill_mirror_state) { output_fill_preferred_state(output); } kywc_log(KYWC_INFO, "output %s edid_hash: %s", kywc_output->name, kywc_output->prop.edid_hash); output_read_usd_config(output, usd_config, &output->pending_state); output_manager_add_pending_state(output, &output->pending_state); } if (usd_config) { json_object_put(usd_config); } } bool ukui_output_config_init(struct output_manager *output_manager) { if (output_manager->has_layout_manager) { return false; } output_manager->impl.get_configures = output_manager_get_usd_configs; return true; } kylin-wayland-compositor/src/output/xdg_output.c0000664000175000017500000002600115160460057021157 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "output_p.h" #include "util/wayland.h" #include "xdg-output-unstable-v1-protocol.h" #include "xwayland.h" #define OUTPUT_MANAGER_VERSION 3 #define OUTPUT_DONE_DEPRECATED_SINCE_VERSION 3 #define OUTPUT_DESCRIPTION_MUTABLE_SINCE_VERSION 3 struct xdg_output_v1 { struct wl_list resources; struct wl_list link; /* resource by xwayland client */ struct wl_resource *xwayland_resource; struct output *output; struct wl_listener geometry; struct wl_listener disable; struct wl_listener description; }; struct xdg_output_manager_v1 { struct wl_global *global; struct wl_list outputs; struct wl_listener new_enabled_output; struct wl_listener max_scale; struct wl_listener display_destroy; struct wl_listener server_destroy; }; static void output_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct zxdg_output_v1_interface output_implementation = { .destroy = output_handle_destroy, }; static void output_handle_resource_destroy(struct wl_resource *resource) { struct xdg_output_v1 *output = wl_resource_get_user_data(resource); if (output && output->xwayland_resource == resource) { output->xwayland_resource = NULL; } wl_list_remove(wl_resource_get_link(resource)); } static void output_send_details(struct xdg_output_v1 *xdg_output, struct wl_resource *resource) { struct kywc_box *geo = &xdg_output->output->geometry; int32_t x = geo->x, y = geo->y; int32_t width = geo->width, height = geo->height; if (xdg_output->xwayland_resource == resource) { float scale = output_manager_get_max_scale(); x *= scale, y *= scale; width *= scale, height *= scale; } zxdg_output_v1_send_logical_position(resource, x, y); zxdg_output_v1_send_logical_size(resource, width, height); if (wl_resource_get_version(resource) < OUTPUT_DONE_DEPRECATED_SINCE_VERSION) { zxdg_output_v1_send_done(resource); } } static void xdg_output_destroy(struct xdg_output_v1 *output) { struct wl_resource *resource, *tmp; wl_resource_for_each_safe(resource, tmp, &output->resources) { wl_list_remove(wl_resource_get_link(resource)); wl_list_init(wl_resource_get_link(resource)); wl_resource_set_user_data(resource, NULL); } wl_list_remove(&output->geometry.link); wl_list_remove(&output->disable.link); wl_list_remove(&output->description.link); wl_list_remove(&output->link); free(output); } static const struct zxdg_output_manager_v1_interface output_manager_implementation; static void output_manager_handle_get_xdg_output(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *output_resource) { assert(wl_resource_instance_of(resource, &zxdg_output_manager_v1_interface, &output_manager_implementation)); struct wl_resource *xdg_output_resource = wl_resource_create( client, &zxdg_output_v1_interface, wl_resource_get_version(resource), id); if (!xdg_output_resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(xdg_output_resource, &output_implementation, NULL, output_handle_resource_destroy); struct wlr_output *wlr_output = wlr_output_from_resource(output_resource); if (!wlr_output) { wl_list_init(wl_resource_get_link(xdg_output_resource)); return; } struct output *output = output_from_wlr_output(wlr_output); assert(output); struct xdg_output_manager_v1 *manager = wl_resource_get_user_data(resource); struct xdg_output_v1 *_xdg_output, *xdg_output = NULL; wl_list_for_each(_xdg_output, &manager->outputs, link) { if (_xdg_output->output == output) { xdg_output = _xdg_output; break; } } assert(xdg_output); if (xwayland_check_client(client)) { xdg_output->xwayland_resource = xdg_output_resource; } wl_resource_set_user_data(xdg_output_resource, xdg_output); wl_list_insert(&xdg_output->resources, wl_resource_get_link(xdg_output_resource)); // Name and description should only be sent once per output uint32_t xdg_version = wl_resource_get_version(xdg_output_resource); if (xdg_version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { zxdg_output_v1_send_name(xdg_output_resource, wlr_output->name); } if (xdg_version >= ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION && wlr_output->description != NULL) { zxdg_output_v1_send_description(xdg_output_resource, wlr_output->description); } output_send_details(xdg_output, xdg_output_resource); uint32_t wl_version = wl_resource_get_version(output_resource); if (wl_version >= WL_OUTPUT_DONE_SINCE_VERSION && xdg_version >= OUTPUT_DONE_DEPRECATED_SINCE_VERSION) { wl_output_send_done(output_resource); } } static void output_manager_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct zxdg_output_manager_v1_interface output_manager_implementation = { .destroy = output_manager_handle_destroy, .get_xdg_output = output_manager_handle_get_xdg_output, }; static void output_manager_bind(struct wl_client *wl_client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(wl_client, &zxdg_output_manager_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(wl_client); return; } struct xdg_output_manager_v1 *manager = data; wl_resource_set_implementation(resource, &output_manager_implementation, manager, NULL); } static void handle_output_geometry(struct wl_listener *listener, void *data) { struct xdg_output_v1 *output = wl_container_of(listener, output, geometry); struct wl_resource *resource; wl_resource_for_each(resource, &output->resources) { output_send_details(output, resource); } wlr_output_schedule_done(output->output->wlr_output); } static void handle_output_disable(struct wl_listener *listener, void *data) { struct xdg_output_v1 *output = wl_container_of(listener, output, disable); xdg_output_destroy(output); } static void handle_output_description(struct wl_listener *listener, void *data) { struct xdg_output_v1 *xdg_output = wl_container_of(listener, xdg_output, description); struct wlr_output *output = xdg_output->output->wlr_output; if (!output->description) { return; } struct wl_resource *resource; wl_resource_for_each(resource, &xdg_output->resources) { if (wl_resource_get_version(resource) >= OUTPUT_DESCRIPTION_MUTABLE_SINCE_VERSION) { zxdg_output_v1_send_description(resource, output->description); } } } static void handle_new_enabled_output(struct wl_listener *listener, void *data) { struct xdg_output_manager_v1 *manager = wl_container_of(listener, manager, new_enabled_output); struct kywc_output *kywc_output = data; struct output *output = output_from_kywc_output(kywc_output); struct xdg_output_v1 *xdg_output = calloc(1, sizeof(*xdg_output)); if (!xdg_output) { return; } wl_list_init(&xdg_output->resources); wl_list_insert(&manager->outputs, &xdg_output->link); xdg_output->output = output; xdg_output->geometry.notify = handle_output_geometry; wl_signal_add(&output->events.geometry, &xdg_output->geometry); xdg_output->disable.notify = handle_output_disable; wl_signal_add(&output->events.disable, &xdg_output->disable); xdg_output->description.notify = handle_output_description; wl_signal_add(&output->wlr_output->events.description, &xdg_output->description); } static void handle_max_scale(struct wl_listener *listener, void *data) { struct xdg_output_manager_v1 *manager = wl_container_of(listener, manager, max_scale); /* update xdg_output pos and size to xwayland */ struct xdg_output_v1 *output; wl_list_for_each(output, &manager->outputs, link) { struct kywc_output *kywc_output = &output->output->base; if (kywc_output->destroying || !kywc_output->state.enabled) { continue; } if (!output->xwayland_resource) { continue; } output_send_details(output, output->xwayland_resource); /* send wl_output.done to apply the geometry in xwayland */ struct wl_resource *resource = wl_resource_find_for_client(&output->output->wlr_output->resources, wl_resource_get_client(output->xwayland_resource)); if (resource && wl_resource_get_version(resource) >= WL_OUTPUT_DONE_SINCE_VERSION) { wl_output_send_done(resource); } } } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct xdg_output_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); struct xdg_output_v1 *output, *tmp; wl_list_for_each_safe(output, tmp, &manager->outputs, link) { xdg_output_destroy(output); } wl_list_remove(&manager->display_destroy.link); wl_list_remove(&manager->new_enabled_output.link); wl_list_remove(&manager->max_scale.link); wl_global_destroy_safe(manager->global); } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct xdg_output_manager_v1 *manager = wl_container_of(listener, manager, server_destroy); wl_list_remove(&manager->server_destroy.link); free(manager); } bool xdg_output_manager_v1_create(struct server *server) { struct xdg_output_manager_v1 *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->global = wl_global_create(server->display, &zxdg_output_manager_v1_interface, OUTPUT_MANAGER_VERSION, manager, output_manager_bind); if (!manager->global) { kywc_log(KYWC_WARN, "Failed to create %s global", zxdg_output_manager_v1_interface.name); free(manager); return false; } wl_list_init(&manager->outputs); manager->new_enabled_output.notify = handle_new_enabled_output; output_manager_add_new_enabled_listener(&manager->new_enabled_output); manager->max_scale.notify = handle_max_scale; #if HAVE_XWAYLAND output_manager_add_max_scale_listener(&manager->max_scale); #else wl_list_init(&manager->max_scale.link); #endif manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &manager->display_destroy); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); return true; } kylin-wayland-compositor/src/output/output_p.h0000664000175000017500000000711015160461067020643 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _OUTPUT_P_H_ #define _OUTPUT_P_H_ #include #include #include "output.h" #include "server.h" struct output_manager_interface { void (*get_configures)(bool output_removed); }; struct output_manager { struct server *server; struct wl_list outputs; struct wl_list configure_outputs; /* output configure_link */ struct kywc_output *fallback_output; struct kywc_output *primary_output; struct kywc_output *pending_primary; float max_scale; struct { struct wl_signal new_output; struct wl_signal new_enabled_output; struct wl_signal primary_output; struct wl_signal configured; struct wl_signal layout_damage; struct wl_signal max_scale; } events; struct config *config; struct config *layout_config; struct output_manager_interface impl; struct wl_listener new_output; struct wl_listener configured; struct wl_listener backend_destroy; struct wl_listener server_start; struct wl_listener server_destroy; struct wl_listener server_suspend; struct wl_listener server_resume; char outputs_layout[UUID_SIZE]; bool has_layout_manager; bool initial_configured; }; extern struct output_manager *output_manager; /* output.c */ void output_log_state(enum kywc_log_level level, struct output *output, const char *desc, const struct kywc_output_state *state); uint32_t output_get_state_changes(struct output *output, const struct kywc_output_state *state); void output_update_state(struct output *output); bool output_manager_has_actual_outputs(void); /* config.c */ bool output_manager_config_init(struct output_manager *output_manager); bool output_read_config(struct output *output, struct kywc_output_state *state); void output_write_config(struct output *output); /* configure.c */ void output_fill_preferred_state(struct output *output); float output_preferred_scale(struct kywc_output *kywc_output, int width, int height); void output_manager_set_pending_primary(struct output *output); void output_manager_add_pending_state(struct output *output, struct kywc_output_state *state); bool output_manager_configure_outputs(struct output *destroying_output); /* modes.c */ void output_add_custom_modes(struct output *output); void output_add_mode(struct output *output, struct wlr_output_mode *mode, bool check); /* layout.c */ void output_manager_sort_outputs(void); void output_set_layout(struct output *output); /* output protocols */ bool xdg_output_manager_v1_create(struct server *server); bool ky_output_manager_create(struct server *server); #if HAVE_KDE_OUTPUT bool kde_output_management_create(struct server *server); #else static __attribute__((unused)) inline bool kde_output_management_create(struct server *server) { return false; } #endif #if HAVE_WLR_OUTPUT bool wlr_output_management_create(struct server *server); #else static __attribute__((unused)) inline bool wlr_output_management_create(struct server *server) { return false; } #endif #if HAVE_UKUI_OUTPUT bool ukui_output_management_create(struct server *server); #else static __attribute__((unused)) inline bool ukui_output_management_create(struct server *server) { return false; } #endif #if HAVE_UKUI_OUTPUT_CONFIG bool ukui_output_config_init(struct output_manager *output_manager); #else static __attribute__((unused)) inline bool ukui_output_config_init(struct output_manager *output_manager) { return false; } #endif #endif /* _OUTPUT_P_H_ */ kylin-wayland-compositor/src/output/modes.c0000664000175000017500000000646715160461067020104 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "backend/drm.h" #include "output_p.h" struct custom_mode { int32_t width, height; int32_t refresh; // in mHz }; static const struct custom_mode custom_landscape_modes[] = { { 800, 600, 60000 }, { 1024, 768, 60000 }, { 1280, 800, 60000 }, { 1280, 1024, 60000 }, { 1366, 768, 59093 }, { 1440, 900, 60000 }, { 1440, 960, 60000 }, { 1600, 1200, 60000 }, { 1680, 1050, 60000 }, { 1680, 1120, 60000 }, { 1920, 1080, 60000 }, { 1920, 1280, 60000 }, { 2160, 1440, 60000 }, }; static const struct custom_mode custom_portrait_modes[] = { { 600, 800, 60000 }, { 768, 1024, 60000 }, { 800, 1280, 60000 }, { 768, 1366, 59093 }, { 900, 1440, 60000 }, { 960, 1440, 60000 }, { 1200, 1600, 60000 }, { 1050, 1680, 60000 }, { 1120, 1680, 60000 }, { 1080, 1920, 60000 }, { 1280, 1920, 60000 }, { 1440, 2160, 60000 }, }; void output_add_custom_modes(struct output *output) { struct wlr_output *wlr_output = output->wlr_output; if (!wlr_backend_is_drm(wlr_output->backend)) { return; } /* add more modes for eDP LVDS panel */ if (strncmp(output->base.name, "eDP", 3) && strncmp(output->base.name, "LVDS", 4)) { return; } int width = 0, height = 0; struct wlr_output_mode *wlr_mode; /* only one same resolution continue to add custom modes */ wl_list_for_each(wlr_mode, &wlr_output->modes, link) { if (width == 0 && height == 0) { width = wlr_mode->width; height = wlr_mode->height; continue; } if (width != wlr_mode->width && height != wlr_mode->height) { return; } } const struct custom_mode *modes; size_t mode_lens = 0; if (width > height) { modes = custom_landscape_modes; mode_lens = sizeof(custom_landscape_modes) / sizeof(custom_landscape_modes[0]); } else { modes = custom_portrait_modes; mode_lens = sizeof(custom_portrait_modes) / sizeof(custom_portrait_modes[0]); } for (size_t i = 0; i < mode_lens; ++i) { const struct custom_mode *mode = &modes[i]; if (mode->width > width || mode->height > height || (mode->width == width && mode->height == height)) { continue; } if (!wlr_output_add_mode(wlr_output, mode->width, mode->height, mode->refresh)) { kywc_log(KYWC_ERROR, "Output %s: add custom mode(%dx%d@%f) failed", output->base.name, mode->width, mode->height, mode->refresh * 0.001); } } } void output_add_mode(struct output *output, struct wlr_output_mode *mode, bool check) { struct kywc_output_prop *prop = &output->base.prop; if (check) { struct kywc_output_mode *_mode; wl_list_for_each(_mode, &prop->modes, link) { if (_mode->width == mode->width && _mode->height == mode->height && _mode->refresh == mode->refresh) { return; } } } struct kywc_output_mode *new = calloc(1, sizeof(*new)); if (!new) { return; } new->width = mode->width; new->height = mode->height; new->refresh = mode->refresh; new->preferred = mode->preferred; wl_list_insert(&prop->modes, &new->link); } kylin-wayland-compositor/src/output/wlr_output.c0000664000175000017500000001526715160461067021217 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "output_p.h" struct wlr_output_management { struct wlr_output_manager_v1 *output_manager; struct wl_listener output_apply; struct wlr_output_power_manager_v1 *power_manager; struct wl_listener power_set_mode; struct wl_list heads; struct wl_listener new_output; struct wl_listener configured; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct output_head { struct kywc_output *kywc_output; struct wl_list link; struct wl_listener destroy; }; static struct wlr_output_management *management = NULL; static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&management->server_destroy.link); free(management); management = NULL; } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&management->display_destroy.link); wl_list_remove(&management->new_output.link); wl_list_remove(&management->configured.link); wl_list_remove(&management->output_apply.link); wl_list_remove(&management->power_set_mode.link); } static void manager_update_configuration(void) { struct wlr_output_configuration_v1 *config = wlr_output_configuration_v1_create(); struct output_head *head; wl_list_for_each(head, &management->heads, link) { struct wlr_output *wlr_output = output_from_kywc_output(head->kywc_output)->wlr_output; if (head->kywc_output->destroying) { continue; } if (head->kywc_output == output_manager_get_fallback() && !head->kywc_output->state.enabled) { continue; } struct wlr_output_configuration_head_v1 *head_v1 = wlr_output_configuration_head_v1_create(config, wlr_output); head_v1->state.enabled = head->kywc_output->state.enabled; head_v1->state.x = head->kywc_output->state.lx; head_v1->state.y = head->kywc_output->state.ly; } wlr_output_manager_v1_set_configuration(management->output_manager, config); } static void handle_output_destroy(struct wl_listener *listener, void *data) { struct output_head *head = wl_container_of(listener, head, destroy); wl_list_remove(&head->destroy.link); wl_list_remove(&head->link); free(head); } static void handle_new_output(struct wl_listener *listener, void *data) { struct kywc_output *kywc_output = data; struct output_head *head = calloc(1, sizeof(*head)); if (!head) { return; } head->kywc_output = kywc_output; wl_list_insert(&management->heads, &head->link); head->destroy.notify = handle_output_destroy; wl_signal_add(&kywc_output->events.destroy, &head->destroy); manager_update_configuration(); } static void handle_configured(struct wl_listener *listener, void *data) { manager_update_configuration(); } static void handle_output_apply(struct wl_listener *listener, void *data) { kywc_log(KYWC_DEBUG, "Wlr output manager apply"); struct wlr_output_configuration_v1 *config = data; struct kywc_output *primary_output = kywc_output_get_primary(); if (wl_list_empty(&management->heads) || !primary_output) { kywc_log(KYWC_WARN, "Configuration cannot be applied"); wlr_output_configuration_v1_send_failed(config); wlr_output_configuration_v1_destroy(config); return; } struct wlr_output_configuration_head_v1 *head_v1; wl_list_for_each(head_v1, &config->heads, link) { struct kywc_output_state pending = { .enabled = head_v1->state.enabled, .power = head_v1->state.enabled, .width = head_v1->state.mode ? head_v1->state.mode->width : head_v1->state.custom_mode.width, .height = head_v1->state.mode ? head_v1->state.mode->height : head_v1->state.custom_mode.height, .refresh = head_v1->state.mode ? head_v1->state.mode->refresh : head_v1->state.custom_mode.refresh, .scale = head_v1->state.scale, .transform = head_v1->state.transform, .lx = head_v1->state.x, .ly = head_v1->state.y, .vrr_policy = head_v1->state.adaptive_sync_enabled ? KYWC_OUTPUT_VRR_POLICY_ALWAYS : KYWC_OUTPUT_VRR_POLICY_NEVER, }; struct output *output = output_from_wlr_output(head_v1->state.output); // fixup brightness and color temp pending.brightness = output->base.state.brightness; pending.color_temp = output->base.state.color_temp; output_manager_add_pending_state(output, &pending); } output_manager_set_pending_primary(output_from_kywc_output(primary_output)); if (!output_manager_configure_outputs(NULL)) { wlr_output_configuration_v1_send_failed(config); wlr_output_configuration_v1_destroy(config); return; } wlr_output_configuration_v1_send_succeeded(config); wlr_output_configuration_v1_destroy(config); } static void handle_power_set_mode(struct wl_listener *listener, void *data) { struct wlr_output_power_v1_set_mode_event *event = data; struct output *output = output_from_wlr_output(event->output); output_set_power(&output->base, event->mode != ZWLR_OUTPUT_POWER_V1_MODE_OFF); } bool wlr_output_management_create(struct server *server) { management = calloc(1, sizeof(*management)); if (!management) { return false; } wl_list_init(&management->heads); management->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &management->server_destroy); management->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &management->display_destroy); management->new_output.notify = handle_new_output; kywc_output_add_new_listener(&management->new_output); management->configured.notify = handle_configured; output_manager_add_configured_listener(&management->configured); management->output_manager = wlr_output_manager_v1_create(server->display); management->output_apply.notify = handle_output_apply; wl_signal_add(&management->output_manager->events.apply, &management->output_apply); management->power_manager = wlr_output_power_manager_v1_create(server->display); management->power_set_mode.notify = handle_power_set_mode; wl_signal_add(&management->power_manager->events.set_mode, &management->power_set_mode); return true; } kylin-wayland-compositor/src/output/output.c0000664000175000017500000010226215160461067020323 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include "backend/drm.h" #include "backend/fbdev.h" #include "effect/output_transform.h" #include "output_p.h" #include "render/profile.h" #include "util/debug.h" #include "util/quirks.h" #include "xwayland.h" #define FALLBACK_OUTPUT "FALLBACK" struct output_manager *output_manager = NULL; struct kywc_output *kywc_output_by_name(const char *name) { struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { if (strcmp(name, output->base.name) == 0) { return &output->base; } } return NULL; } struct kywc_output *kywc_output_by_uuid(const char *uuid) { struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { if (strcmp(uuid, output->base.uuid) == 0) { return &output->base; } } return NULL; } void kywc_output_add_new_listener(struct wl_listener *listener) { wl_signal_add(&output_manager->events.new_output, listener); } struct kywc_output *kywc_output_get_primary(void) { return output_manager->primary_output; } void kywc_output_add_primary_listener(struct wl_listener *listener) { wl_signal_add(&output_manager->events.primary_output, listener); } void kywc_output_effective_geometry(struct kywc_output *kywc_output, struct kywc_box *box) { struct output *output = output_from_kywc_output(kywc_output); *box = output->geometry; } bool kywc_output_contains_point(struct kywc_output *kywc_output, int x, int y) { struct output *output = output_from_kywc_output(kywc_output); return kywc_box_contains_point(&output->geometry, x, y); } struct output *output_from_kywc_output(struct kywc_output *kywc_output) { struct output *output = wl_container_of(kywc_output, output, base); return output; } struct output *output_from_wlr_output(struct wlr_output *wlr_output) { return wlr_output->data; } struct output *output_from_resource(struct wl_resource *resource) { struct wlr_output *wlr_output = wl_resource_get_user_data(resource); return output_from_wlr_output(wlr_output); } void output_schedule_frame(struct wlr_output *wlr_output) { if (output_manager->server->active) { wlr_output_schedule_frame(wlr_output); } } void output_add_update_usable_area_listener(struct kywc_output *kywc_output, struct wl_listener *listener, bool late) { struct output *output = output_from_kywc_output(kywc_output); if (late) { wl_signal_add(&output->events.update_late_usable_area, listener); } else { wl_signal_add(&output->events.update_usable_area, listener); } } uint32_t output_manager_for_each_output(output_iterator_func_t iterator, bool enabled, void *data) { int index = 0; struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { if (output->base.state.enabled == enabled) { if (iterator(&output->base, index++, data)) { break; } } } return index; } bool output_manager_has_actual_outputs(void) { struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { if (&output->base != output_manager->fallback_output) { return true; } } return false; } float output_manager_get_max_scale(void) { return output_manager->max_scale; } void output_manager_add_max_scale_listener(struct wl_listener *listener) { wl_signal_add(&output_manager->events.max_scale, listener); } struct kywc_output *output_manager_get_fallback(void) { return output_manager->fallback_output; } void output_manager_add_new_enabled_listener(struct wl_listener *listener) { wl_signal_add(&output_manager->events.new_enabled_output, listener); } void output_manager_add_configured_listener(struct wl_listener *listener) { wl_signal_add(&output_manager->events.configured, listener); } void output_manager_add_layout_damage_listener(struct wl_listener *listener) { wl_signal_add(&output_manager->events.layout_damage, listener); } void output_log_state(enum kywc_log_level level, struct output *output, const char *desc, const struct kywc_output_state *state) { if (kywc_log_get_level() < level || !state) { return; } kywc_log(level, "%s(%s): mode (%d x %d @ %d) scale %f pos (%d, %d) transform %d %s brightness %d " "colortemp %d colorfilter %d %s", output->base.name, desc ? desc : "", state->width, state->height, state->refresh, state->scale, state->lx, state->ly, state->transform, state->enabled ? "enabled" : "disabled", state->brightness, state->color_temp, state->color_filter, output_manager->pending_primary == &output->base ? "primary" : ""); } static void output_log_prop(enum kywc_log_level level, struct output *output, const char *desc, const struct kywc_output_prop *prop) { if (kywc_log_get_level() < level || !prop) { return; } static char *backend[] = { "none", "drm", "headless", "fbdev" }; kywc_log(level, "%s(%s): %s backend, [%s]: [%s, %s, %s] (%d x %d)", output->base.name, desc ? desc : "", backend[prop->backend], prop->desc, prop->make, prop->model, prop->serial ? prop->serial : "", prop->phys_width, prop->phys_height); } static const char *output_get_edid(struct wlr_output *wlr_output) { size_t len = 0; const void *edid_raw = wlr_output_get_edid(wlr_output, &len); if (!edid_raw) { return NULL; } const char *edid = kywc_identifier_base64_generate(edid_raw, len); return edid; } static const char *output_get_edid_hash(struct wlr_output *wlr_output) { size_t len = 0; const void *edid = wlr_output_get_edid(wlr_output, &len); return edid ? kywc_identifier_md5_generate(edid, len) : kywc_identifier_md5_generate(wlr_output->name, strlen(wlr_output->name)); } static void output_get_prop(struct output *output, struct kywc_output_prop *prop) { struct wlr_output *wlr_output = output->wlr_output; static char *unknown = " "; if (wlr_output_is_drm(wlr_output)) { prop->backend = KYWC_OUTPUT_BACKEND_DRM; } else if (wlr_output_is_headless(wlr_output)) { prop->backend = KYWC_OUTPUT_BACKEND_HEADLESS; } else if (wlr_output_is_fbdev(wlr_output)) { prop->backend = KYWC_OUTPUT_BACKEND_FBDEV; } else { prop->backend = KYWC_OUTPUT_BACKEND_NONE; } prop->phys_width = wlr_output->phys_width; prop->phys_height = wlr_output->phys_height; prop->make = wlr_output->make ? wlr_output->make : wlr_output->name; prop->model = wlr_output->model ? wlr_output->model : unknown; prop->serial = wlr_output->serial; // May be NULL prop->desc = wlr_output->description ? wlr_output->description : wlr_output->name; prop->edid = output_get_edid(wlr_output); prop->edid_hash = output_get_edid_hash(wlr_output); prop->capabilities = KYWC_OUTPUT_CAPABILITY_POWER; if (prop->backend == KYWC_OUTPUT_BACKEND_DRM) { prop->capabilities |= KYWC_OUTPUT_CAPABILITY_BRIGHTNESS | KYWC_OUTPUT_CAPABILITY_COLOR_TEMP | KYWC_OUTPUT_CAPABILITY_COLOR_FILTER; } if (wlr_output_get_vrr_capable(wlr_output)) { prop->capabilities |= KYWC_OUTPUT_CAPABILITY_VRR; } if (wlr_output_get_rgb_range(wlr_output, &output->base.state.rgb_range)) { prop->capabilities |= KYWC_OUTPUT_CAPABILITY_RGB_RANGE; } if (wlr_output_get_overscan(wlr_output, &output->base.state.overscan)) { prop->capabilities |= KYWC_OUTPUT_CAPABILITY_OVERSCAN; } if (wlr_output_get_scaling_mode(wlr_output, &output->base.state.scaling_mode)) { prop->capabilities |= KYWC_OUTPUT_CAPABILITY_SCALING_MODE; } /* fix zero mode in some backend, like wayland */ if (wl_list_empty(&wlr_output->modes)) { struct wlr_output_mode mode = { .width = wlr_output->width, .height = wlr_output->height, .refresh = wlr_output->refresh, .preferred = true, }; output_add_mode(output, &mode, false); } else { struct wlr_output_mode *mode; wl_list_for_each(mode, &wlr_output->modes, link) { output_add_mode(output, mode, false); } } } static void output_get_state(struct output *output, struct kywc_output_state *state) { struct wlr_output *wlr_output = output->wlr_output; state->enabled = wlr_output->enabled; state->power = wlr_output->enabled; state->width = wlr_output->width; state->height = wlr_output->height; state->refresh = wlr_output->refresh; state->transform = wlr_output->transform; state->scale = wlr_output->scale; struct wlr_output_layout_output *layout_output; layout_output = wlr_output_layout_get(output_manager->server->layout, wlr_output); if (layout_output) { state->lx = layout_output->x; state->ly = layout_output->y; } else { state->lx = state->ly = -1; } state->brightness = output->brightness; state->color_temp = output->color_temp; state->color_filter = output->color_filter; state->color_feature = output->color_feature; state->vrr_policy = output->vrr_policy; wlr_output_get_rgb_range(wlr_output, &state->rgb_range); wlr_output_get_overscan(wlr_output, &state->overscan); wlr_output_get_scaling_mode(wlr_output, &state->scaling_mode); } uint32_t output_get_state_changes(struct output *output, const struct kywc_output_state *state) { struct kywc_output_prop *prop = &output->base.prop; struct kywc_output_state *current = &output->base.state; uint32_t mask = 0; if (current->enabled != state->enabled) { mask |= KYWC_OUTPUT_STATE_ENABLE; } if (prop->capabilities & KYWC_OUTPUT_CAPABILITY_POWER && current->power != state->power) { mask |= KYWC_OUTPUT_STATE_POWER; } if (current->width != state->width || current->height != state->height || current->refresh != state->refresh) { mask |= KYWC_OUTPUT_STATE_MODE; } if (current->transform != state->transform) { mask |= KYWC_OUTPUT_STATE_TRANSFORM; } if (current->scale != state->scale) { mask |= KYWC_OUTPUT_STATE_SCALE; } if (current->vrr_policy != state->vrr_policy) { mask |= KYWC_OUTPUT_STATE_VRR_POLICY; } if (current->lx != state->lx || current->ly != state->ly) { mask |= KYWC_OUTPUT_STATE_POSITION; } if (prop->capabilities & KYWC_OUTPUT_CAPABILITY_BRIGHTNESS && current->brightness != state->brightness) { mask |= KYWC_OUTPUT_STATE_BRIGHTNESS; } if (prop->capabilities & KYWC_OUTPUT_CAPABILITY_COLOR_TEMP && current->color_temp != state->color_temp) { mask |= KYWC_OUTPUT_STATE_COLOR_TEMP; } if (current->overscan != state->overscan) { mask |= KYWC_OUTPUT_STATE_OVERSCAN; } if (current->color_feature != state->color_feature) { mask |= KYWC_OUTPUT_STATE_COLOR_FEATURE; } if (prop->capabilities & KYWC_OUTPUT_CAPABILITY_COLOR_FILTER && current->color_filter != state->color_filter) { mask |= KYWC_OUTPUT_STATE_COLOR_FILTER; } if (current->scaling_mode != state->scaling_mode) { mask |= KYWC_OUTPUT_STATE_SCALING_MODE; } if (current->rgb_range != state->rgb_range) { mask |= KYWC_OUTPUT_STATE_RGB_RANGE; } return mask; } static void output_manager_update_max_scale(struct kywc_output *kywc_output) { float scale = 1.0; struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { struct kywc_output *kywc_output = &output->base; if (kywc_output->destroying || !kywc_output->state.enabled) { continue; } if (kywc_output->state.scale > scale) { scale = kywc_output->state.scale; } } if (scale != output_manager->max_scale) { output_manager->max_scale = scale; wl_signal_emit_mutable(&output_manager->events.max_scale, kywc_output); } } bool output_use_hardware_color(struct output *output) { return output->hardware_color; } bool output_state_attempt_vrr(struct output *output, struct wlr_output_state *state, bool fullscreen) { if (!(output->base.prop.capabilities & KYWC_OUTPUT_CAPABILITY_VRR)) { return false; } if (output->vrr_policy == KYWC_OUTPUT_VRR_POLICY_NEVER || (output->vrr_policy == KYWC_OUTPUT_VRR_POLICY_AUTO && !fullscreen)) { if (output->wlr_output->adaptive_sync_status) { wlr_output_state_set_adaptive_sync_enabled(state, false); } return true; } if (!output->wlr_output->adaptive_sync_status) { wlr_output_state_set_adaptive_sync_enabled(state, true); } return true; } bool output_state_attempt_tearing(struct output *output, struct wlr_output_state *state, bool is_tearing) { if (!is_tearing) { return false; } state->tearing_page_flip = true; if (!wlr_output_test_state(output->wlr_output, state)) { state->tearing_page_flip = false; return false; } return true; } static void output_state_get_geometry(struct kywc_output_state *state, struct kywc_box *box) { if (state->transform % 2 == 0) { box->width = state->width; box->height = state->height; } else { box->width = state->height; box->height = state->width; } box->x = state->lx; box->y = state->ly; box->width = ceil(box->width / state->scale); box->height = ceil(box->height / state->scale); } static void output_do_update_usable_area(struct output *output, struct kywc_box *usable) { *usable = output->geometry; wl_signal_emit_mutable(&output->events.update_usable_area, usable); wl_signal_emit_mutable(&output->events.update_late_usable_area, usable); } static void output_emit_usable_area(struct output *output) { if (!output->state_usable) { return; } kywc_log(KYWC_DEBUG, "Output %s usable area is (%d, %d) %d x %d", output->base.name, output->usable_area.x, output->usable_area.y, output->usable_area.width, output->usable_area.height); xwayland_update_workarea(); wl_signal_emit_mutable(&output->events.usable_area, NULL); } void output_update_usable_area(struct kywc_output *kywc_output) { /* no need to update usable area when disabled or destroyed */ if (!kywc_output || kywc_output->destroying || !kywc_output->state.enabled) { return; } struct output *output = output_from_kywc_output(kywc_output); struct kywc_box usable_area; output_do_update_usable_area(output, &usable_area); if (kywc_box_equal(&output->usable_area, &usable_area)) { return; } output->usable_area = usable_area; output_emit_usable_area(output); } void output_update_state(struct output *output) { struct kywc_output *kywc_output = &output->base; /* update current state */ struct kywc_output_state *current = &kywc_output->state; struct kywc_output_state old = kywc_output->state; output_get_state(output, current); // fix current.enabled for dpms power current->enabled = output->pending_state.enabled; if (!kywc_output->destroying) { output_log_state(KYWC_INFO, output, "current", current); } uint32_t changes = output_get_state_changes(output, &old); /* update geometry and usable area before all signals */ bool geometry_changed = false; bool usable_area_changed = false; if (current->enabled) { struct kywc_box geo = output->geometry; output_state_get_geometry(&output->base.state, &output->geometry); geometry_changed = !kywc_box_equal(&geo, &output->geometry); /* only update usable area when geometry changed */ if (!old.enabled || geometry_changed) { output_do_update_usable_area(output, &geo); if (!kywc_box_equal(&geo, &output->usable_area)) { output->usable_area = geo; usable_area_changed = true; } } } /* usable area may changed in ky_scene_node_update_outputs */ struct kywc_box usable_area = output->usable_area; struct server *server = output->manager->server; if (output->layout_output && !output->scene_output) { output->scene_output = ky_scene_output_create(server->scene, output->wlr_output); } else if (!output->layout_output && output->scene_output) { ky_scene_output_destroy(output->scene_output); output->scene_output = NULL; } assert(!!output->scene_output == !!output->layout_output); /* sort output manager outputs */ if (geometry_changed || current->width != old.width || current->height != old.height || current->transform != old.transform || current->scale != old.scale || current->lx != old.lx || current->ly != old.ly) { output_manager_sort_outputs(); } if (changes & (KYWC_OUTPUT_STATE_ENABLE | KYWC_OUTPUT_STATE_SCALE)) { output_manager_update_max_scale(kywc_output); } if (!kywc_output->destroying) { output_write_config(output); } struct output_configure_event event = { .output = output, .changes = changes, .geometry_changed = geometry_changed, }; wl_signal_emit_mutable(&output->events.configure, &event); usable_area_changed |= !kywc_box_equal(&usable_area, &output->usable_area); /* used to sync signals */ output->state_usable = current->enabled; /* check state changes */ if (changes & KYWC_OUTPUT_STATE_ENABLE) { if (!current->enabled) { wl_signal_emit_mutable(&kywc_output->events.off, NULL); wl_signal_emit_mutable(&output->events.disable, NULL); return; } else { wl_signal_emit_mutable(&kywc_output->events.on, NULL); wl_signal_emit_mutable(&output_manager->events.new_enabled_output, kywc_output); } } if (geometry_changed) { kywc_log(KYWC_DEBUG, "Output %s geometry is (%d, %d) %d x %d", output->base.name, output->geometry.x, output->geometry.y, output->geometry.width, output->geometry.height); wl_signal_emit_mutable(&output->events.geometry, NULL); } if (usable_area_changed) { output_emit_usable_area(output); } if (changes & KYWC_OUTPUT_STATE_POWER) { wl_signal_emit_mutable(&kywc_output->events.power, NULL); } if (changes & KYWC_OUTPUT_STATE_BRIGHTNESS) { wl_signal_emit_mutable(&kywc_output->events.brightness, NULL); } if (changes & KYWC_OUTPUT_STATE_COLOR_TEMP) { wl_signal_emit_mutable(&kywc_output->events.color_temp, NULL); } if (changes & KYWC_OUTPUT_CAPABILITY_COLOR_FILTER) { wl_signal_emit_mutable(&kywc_output->events.color_filter, NULL); } if (changes & KYWC_OUTPUT_STATE_MODE) { wl_signal_emit_mutable(&kywc_output->events.mode, NULL); } if (changes & KYWC_OUTPUT_STATE_TRANSFORM) { output_add_transform_effect(kywc_output, &old, current); wl_signal_emit_mutable(&kywc_output->events.transform, NULL); } if (changes & KYWC_OUTPUT_STATE_SCALE) { wl_signal_emit_mutable(&kywc_output->events.scale, NULL); } if (changes & KYWC_OUTPUT_STATE_POSITION) { wl_signal_emit_mutable(&kywc_output->events.position, NULL); } } static void output_init_quirks(struct output *output) { if (!wlr_output_is_drm(output->wlr_output)) { return; } int drm_fd = drm_backend_get_non_master_fd(output->wlr_output->backend); output->quirks = quirks_by_backend(drm_fd); /* using software curosr, depending on the quirks mask */ if (output->quirks & QUIRKS_MASK_SOFTWARE_CURSOR) { wlr_output_lock_software_cursors(output->wlr_output, true); } close(drm_fd); } static void output_uuid_generate(struct kywc_output *kywc_output) { char description[128]; snprintf(description, sizeof(description), "%s %s%s%s (%s)", kywc_output->prop.make, kywc_output->prop.model, kywc_output->prop.serial ? " " : "", kywc_output->prop.serial ? kywc_output->prop.serial : "", kywc_output->name); kywc_output->uuid = kywc_identifier_md5_generate_uuid((void *)description, strlen(description)); } static void handle_output_destroy(struct wl_listener *listener, void *data) { struct output *output = wl_container_of(listener, output, destroy); wl_list_remove(&output->destroy.link); wl_list_remove(&output->frame.link); wl_list_remove(&output->present.link); wl_list_remove(&output->commit.link); wl_list_remove(&output->request_state.link); struct kywc_output *kywc_output = &output->base; wl_list_remove(&output->link); kywc_output->destroying = true; /* get configure outputs */ output_manager->impl.get_configures(true); /* disable output if output is enabled */ if (kywc_output->state.enabled) { output->pending_state.enabled = false; output_manager_add_pending_state(output, &output->pending_state); } output_manager_configure_outputs(output); wl_list_remove(&output->configure_link); wl_signal_emit_mutable(&output->events.disable, NULL); wl_signal_emit_mutable(&kywc_output->events.destroy, NULL); assert(wl_list_empty(&output->events.configure.listener_list)); assert(wl_list_empty(&output->events.disable.listener_list)); assert(wl_list_empty(&output->events.geometry.listener_list)); assert(wl_list_empty(&output->events.usable_area.listener_list)); assert(wl_list_empty(&output->events.update_usable_area.listener_list)); assert(wl_list_empty(&output->events.update_late_usable_area.listener_list)); assert(wl_list_empty(&kywc_output->events.on.listener_list)); assert(wl_list_empty(&kywc_output->events.off.listener_list)); assert(wl_list_empty(&kywc_output->events.scale.listener_list)); assert(wl_list_empty(&kywc_output->events.transform.listener_list)); assert(wl_list_empty(&kywc_output->events.mode.listener_list)); assert(wl_list_empty(&kywc_output->events.position.listener_list)); assert(wl_list_empty(&kywc_output->events.power.listener_list)); assert(wl_list_empty(&kywc_output->events.brightness.listener_list)); assert(wl_list_empty(&kywc_output->events.color_temp.listener_list)); assert(wl_list_empty(&kywc_output->events.destroy.listener_list)); struct kywc_output_mode *mode, *tmp_mode; wl_list_for_each_safe(mode, tmp_mode, &kywc_output->prop.modes, link) { wl_list_remove(&mode->link); free(mode); } free((void *)kywc_output->prop.edid); free((void *)kywc_output->prop.edid_hash); free((void *)kywc_output->uuid); free(output); } static void handle_output_present(struct wl_listener *listener, void *data) { KY_PROFILE_ZONE(zone, __func__); struct output *output = wl_container_of(listener, output, present); output->scene_commit = false; KY_PROFILE_RENDER_COLLECT(output->wlr_output->renderer); if (output->has_pending) { output_manager_add_pending_state(output, &output->pending_state); /* has_pending is reset in handle_output_frame */ if (!output_manager_configure_outputs(NULL)) { output->has_pending = false; } } KY_PROFILE_ZONE_END(zone); } static void handle_output_frame(struct wl_listener *listener, void *data) { if (!output_manager->server->active) { return; } struct output *output = wl_container_of(listener, output, frame); if (output_manager->primary_output == &output->base) { KY_PROFILE_FRAME(); } KY_PROFILE_FRAME_NAME(output->wlr_output->name); KY_PROFILE_ZONE(zone, __func__); /* skip rendering if has_pending, otherwise drm may report busy */ if (output->has_pending) { output->wlr_output->frame_pending = true; output->has_pending = false; KY_PROFILE_ZONE_END(zone); return; } output->scene_commit = ky_scene_output_commit(output->scene_output, NULL); struct timespec now = { 0 }; clock_gettime(CLOCK_MONOTONIC, &now); ky_scene_output_send_frame_done(output->scene_output, &now); KY_PROFILE_ZONE_END(zone); } static void handle_output_commit(struct wl_listener *listener, void *data) { KY_PROFILE_ZONE(zone, __func__); struct output *output = wl_container_of(listener, output, commit); struct wlr_output_event_commit *event = data; bool has_damage = event->state->committed & WLR_OUTPUT_STATE_DAMAGE && pixman_region32_not_empty(&output->scene_output->frame_damage); if (has_damage) { wl_signal_emit_mutable(&output_manager->events.layout_damage, &output->scene_output->frame_damage); } KY_PROFILE_ZONE_END(zone); } static void handle_request_state(struct wl_listener *listener, void *data) { struct output *output = wl_container_of(listener, output, request_state); const struct wlr_output_event_request_state *event = data; const struct wlr_output_state *state = event->state; /* only support mode */ if (!(state->committed & WLR_OUTPUT_STATE_MODE)) { return; } output->pending_state = output->base.state; if (state->mode) { output->pending_state.width = state->mode->width; output->pending_state.height = state->mode->height; output->pending_state.refresh = state->mode->refresh; } else { output->pending_state.width = state->custom_mode.width; output->pending_state.height = state->custom_mode.height; output->pending_state.refresh = state->custom_mode.refresh; } output_manager_add_pending_state(output, &output->pending_state); output_manager_configure_outputs(NULL); } static void handle_new_output(struct wl_listener *listener, void *data) { struct server *server = output_manager->server; struct wlr_output *wlr_output = data; if (wlr_output->non_desktop) { kywc_log(KYWC_WARN, "Not configuring non-desktop output"); return; } if (!wlr_output_init_render(wlr_output, server->allocator, server->renderer)) { kywc_log(KYWC_ERROR, "Unable to init output renderer"); return; } struct output *output = calloc(1, sizeof(*output)); if (!output) { return; } output->wlr_output = wlr_output; wlr_output->data = output; output->present.notify = handle_output_present; wl_signal_add(&wlr_output->events.present, &output->present); output->frame.notify = handle_output_frame; wl_signal_add(&wlr_output->events.frame, &output->frame); output->destroy.notify = handle_output_destroy; wl_signal_add(&wlr_output->events.destroy, &output->destroy); output->commit.notify = handle_output_commit; wl_signal_add(&wlr_output->events.commit, &output->commit); output->request_state.notify = handle_request_state; wl_signal_add(&wlr_output->events.request_state, &output->request_state); struct kywc_output *kywc_output = &output->base; kywc_output->name = wlr_output->name; wl_signal_init(&kywc_output->events.on); wl_signal_init(&kywc_output->events.off); wl_signal_init(&kywc_output->events.scale); wl_signal_init(&kywc_output->events.transform); wl_signal_init(&kywc_output->events.mode); wl_signal_init(&kywc_output->events.position); wl_signal_init(&kywc_output->events.power); wl_signal_init(&kywc_output->events.brightness); wl_signal_init(&kywc_output->events.color_temp); wl_signal_init(&kywc_output->events.color_filter); wl_signal_init(&kywc_output->events.destroy); wl_signal_init(&output->events.configure); wl_signal_init(&output->events.disable); wl_signal_init(&output->events.geometry); wl_signal_init(&output->events.usable_area); wl_signal_init(&output->events.update_usable_area); wl_signal_init(&output->events.update_late_usable_area); wl_list_init(&output->configure_link); output->manager = output_manager; wl_list_insert(&output_manager->outputs, &output->link); output->brightness = 100; output->color_temp = 6500; /* add modes for eDP/LVDS panel */ output_add_custom_modes(output); /* get props */ wl_list_init(&kywc_output->prop.modes); output_get_prop(output, &kywc_output->prop); output_log_prop(KYWC_DEBUG, output, "prop", &kywc_output->prop); if (kywc_output->prop.backend == KYWC_OUTPUT_BACKEND_HEADLESS && strcmp(kywc_output->name, FALLBACK_OUTPUT) == 0) { output_manager->fallback_output = kywc_output; } output_init_quirks(output); output_get_state(output, &kywc_output->state); output_log_state(KYWC_DEBUG, output, "state", &kywc_output->state); output_uuid_generate(kywc_output); kywc_log(KYWC_INFO, "New output %s: %s", kywc_output->name, kywc_output->uuid); assert(!kywc_output->state.enabled); wl_signal_emit_mutable(&output_manager->events.new_output, kywc_output); /* configure the output */ output_manager->impl.get_configures(false); output_manager_configure_outputs(NULL); } static void handle_server_start(struct wl_listener *listener, void *data) { kywc_log(KYWC_INFO, "Configure outputs on server start"); output_manager->impl.get_configures(false); output_manager_configure_outputs(NULL); assert(output_manager->primary_output); output_manager->initial_configured = true; } static void handle_server_suspend(struct wl_listener *listener, void *data) { kywc_log(KYWC_DEBUG, "Handle server D-Bus suspend"); output_manager_power_outputs(false); } static void handle_server_resume(struct wl_listener *listener, void *data) { kywc_log(KYWC_DEBUG, "Handle server D-Bus resume"); output_manager_power_outputs(true); } static void handle_backend_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&output_manager->backend_destroy.link); wl_list_remove(&output_manager->new_output.link); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&output_manager->server_destroy.link); wl_list_remove(&output_manager->server_suspend.link); wl_list_remove(&output_manager->server_resume.link); wl_list_remove(&output_manager->server_start.link); wl_list_remove(&output_manager->configured.link); assert(wl_list_empty(&output_manager->events.new_output.listener_list)); assert(wl_list_empty(&output_manager->events.new_enabled_output.listener_list)); assert(wl_list_empty(&output_manager->events.primary_output.listener_list)); assert(wl_list_empty(&output_manager->events.configured.listener_list)); assert(wl_list_empty(&output_manager->events.layout_damage.listener_list)); assert(wl_list_empty(&output_manager->events.max_scale.listener_list)); free(output_manager); output_manager = NULL; } struct output_manager *output_manager_create(struct server *server) { output_manager = calloc(1, sizeof(*output_manager)); if (!output_manager) { return NULL; } output_manager->max_scale = 1.0; wl_list_init(&output_manager->outputs); wl_list_init(&output_manager->configure_outputs); wl_list_init(&output_manager->configured.link); wl_signal_init(&output_manager->events.new_output); wl_signal_init(&output_manager->events.new_enabled_output); wl_signal_init(&output_manager->events.primary_output); wl_signal_init(&output_manager->events.configured); wl_signal_init(&output_manager->events.layout_damage); wl_signal_init(&output_manager->events.max_scale); output_manager->server = server; output_manager->backend_destroy.notify = handle_backend_destroy; wl_signal_add(&server->backend->events.destroy, &output_manager->backend_destroy); output_manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &output_manager->server_destroy); output_manager->server_suspend.notify = handle_server_suspend; wl_signal_add(&server->events.suspend, &output_manager->server_suspend); output_manager->server_resume.notify = handle_server_resume; wl_signal_add(&server->events.resume, &output_manager->server_resume); output_manager->server_start.notify = handle_server_start; wl_signal_add(&server->events.start, &output_manager->server_start); struct wlr_backend *headless = wlr_headless_backend_create(server->event_loop); assert(headless); wlr_multi_backend_add(server->backend, headless); struct wlr_output *wlr_output = wlr_headless_add_output(headless, 1920, 1080); wlr_output_set_name(wlr_output, "FALLBACK"); output_manager->new_output.notify = handle_new_output; wl_signal_add(&server->backend->events.new_output, &output_manager->new_output); char *env = getenv("KYWC_USE_LAYOUT_MANAGER"); output_manager->has_layout_manager = env && strcmp(env, "1") == 0; output_manager_config_init(output_manager); ukui_output_config_init(output_manager); xdg_output_manager_v1_create(server); ky_output_manager_create(server); kde_output_management_create(server); wlr_output_management_create(server); ukui_output_management_create(server); return output_manager; } kylin-wayland-compositor/src/output/ky_output.c0000664000175000017500000005350015160461067021026 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "kywc-output-v1-protocol.h" #include "output_p.h" struct ky_output_manager { struct wl_global *global; struct wl_event_loop *event_loop; struct wl_event_source *idle_source; struct wl_list resources; struct wl_list outputs; struct wl_listener new_output; struct wl_listener primary_output; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct ky_output { struct ky_output_manager *manager; struct wl_list link; struct wl_list clients; // ky_output_client.link struct kywc_output *kywc_output; struct wl_listener on; struct wl_listener off; struct wl_listener scale; struct wl_listener mode; struct wl_listener position; struct wl_listener transform; struct wl_listener power; struct wl_listener brightness; struct wl_listener color_temp; struct wl_listener destroy; }; // used to manage modes for output per client struct ky_output_client { struct wl_resource *resource; struct wl_list link; struct ky_output *output; struct wl_list mode_resources; }; static struct ky_output *ky_output_from_kywc_output(struct ky_output_manager *manager, struct kywc_output *kywc_output) { struct ky_output *ky_output; wl_list_for_each(ky_output, &manager->outputs, link) { if (ky_output->kywc_output == kywc_output) { return ky_output; } } return NULL; } static void output_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct kywc_output_v1_interface ky_output_impl = { .destroy = output_handle_destroy, }; static void ky_output_resource_destroy(struct wl_resource *resource) { struct ky_output_client *ky_client = wl_resource_get_user_data(resource); if (!ky_client) { return; } struct wl_resource *mode_resource, *tmp; wl_resource_for_each_safe(mode_resource, tmp, &ky_client->mode_resources) { wl_list_remove(wl_resource_get_link(mode_resource)); wl_list_init(wl_resource_get_link(mode_resource)); } wl_list_remove(&ky_client->link); free(ky_client); } static struct ky_output_client * create_output_client_for_resource(struct ky_output *ky_output, struct wl_resource *manager_resource) { struct ky_output_client *ky_client = calloc(1, sizeof(*ky_client)); if (!ky_client) { return NULL; } struct wl_client *client = wl_resource_get_client(manager_resource); struct wl_resource *resource = wl_resource_create(client, &kywc_output_v1_interface, wl_resource_get_version(manager_resource), 0); if (!resource) { wl_client_post_no_memory(client); free(ky_client); return NULL; } ky_client->resource = resource; ky_client->output = ky_output; wl_list_init(&ky_client->mode_resources); wl_list_insert(&ky_output->clients, &ky_client->link); wl_resource_set_implementation(resource, &ky_output_impl, ky_client, ky_output_resource_destroy); kywc_output_manager_v1_send_output(manager_resource, resource, ky_output->kywc_output->uuid); return ky_client; } static void output_mode_handle_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static void output_mode_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct kywc_output_mode_v1_interface output_mode_impl = { .destroy = output_mode_handle_destroy, }; static void send_modes_to_output_client(struct ky_output_client *ky_client) { struct wl_client *client = wl_resource_get_client(ky_client->resource); struct kywc_output *kywc_output = ky_client->output->kywc_output; uint32_t version = wl_resource_get_version(ky_client->resource); struct kywc_output_mode *mode; wl_list_for_each(mode, &kywc_output->prop.modes, link) { struct wl_resource *mode_resource = wl_resource_create(client, &kywc_output_mode_v1_interface, version, 0); if (!mode_resource) { continue; } wl_resource_set_implementation(mode_resource, &output_mode_impl, mode, output_mode_handle_resource_destroy); wl_list_insert(&ky_client->mode_resources, wl_resource_get_link(mode_resource)); kywc_output_v1_send_mode(ky_client->resource, mode_resource); kywc_output_mode_v1_send_size(mode_resource, mode->width, mode->height); kywc_output_mode_v1_send_refresh(mode_resource, mode->refresh); if (mode->preferred) { kywc_output_mode_v1_send_preferred(mode_resource); } } } static void send_current_mode_to_output_client(struct ky_output_client *ky_client) { struct kywc_output *kywc_output = ky_client->output->kywc_output; /* only send if output is enabled */ if (!kywc_output->state.enabled) { return; } struct wl_resource *mode_resource; wl_resource_for_each(mode_resource, &ky_client->mode_resources) { struct kywc_output_mode *mode = wl_resource_get_user_data(mode_resource); if (mode->width != kywc_output->state.width || mode->height != kywc_output->state.height || mode->refresh != kywc_output->state.refresh) { continue; } kywc_output_v1_send_current_mode(ky_client->resource, mode_resource); break; } } static void send_details_to_output_client(struct ky_output_client *client) { if (!client) { return; } struct kywc_output *kywc_output = client->output->kywc_output; kywc_output_v1_send_name(client->resource, kywc_output->name); if (kywc_output->prop.make) { kywc_output_v1_send_make(client->resource, kywc_output->prop.make); } if (kywc_output->prop.model) { kywc_output_v1_send_model(client->resource, kywc_output->prop.model); } if (kywc_output->prop.serial) { kywc_output_v1_send_serial_number(client->resource, kywc_output->prop.serial); } kywc_output_v1_send_description(client->resource, kywc_output->prop.desc); kywc_output_v1_send_physical_size(client->resource, kywc_output->prop.phys_width, kywc_output->prop.phys_height); kywc_output_v1_send_capabilities(client->resource, kywc_output->prop.capabilities); send_modes_to_output_client(client); send_current_mode_to_output_client(client); kywc_output_v1_send_enabled(client->resource, kywc_output->state.enabled); kywc_output_v1_send_position(client->resource, kywc_output->state.lx, kywc_output->state.ly); kywc_output_v1_send_transform(client->resource, kywc_output->state.transform); kywc_output_v1_send_scale(client->resource, wl_fixed_from_double(kywc_output->state.scale)); if (kywc_output->prop.capabilities & KYWC_OUTPUT_CAPABILITY_POWER) { kywc_output_v1_send_power(client->resource, kywc_output->state.power); } if (kywc_output->prop.capabilities & KYWC_OUTPUT_CAPABILITY_BRIGHTNESS) { kywc_output_v1_send_brightness(client->resource, kywc_output->state.brightness); } if (kywc_output->prop.capabilities & KYWC_OUTPUT_CAPABILITY_COLOR_TEMP) { kywc_output_v1_send_color_temp(client->resource, kywc_output->state.color_temp); } } static struct ky_output_client *output_client_find_for_client(struct ky_output *ky_output, struct wl_client *client) { struct ky_output_client *ky_client; wl_list_for_each(ky_client, &ky_output->clients, link) { if (client == wl_resource_get_client(ky_client->resource)) { return ky_client; } } return NULL; } static void manager_handle_stop(struct wl_client *client, struct wl_resource *resource) { kywc_output_manager_v1_send_finished(resource); wl_resource_destroy(resource); } static const struct kywc_output_manager_v1_interface ky_output_manager_impl = { .stop = manager_handle_stop, }; static void manager_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static void ky_output_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct ky_output_manager *manager = data; struct wl_resource *resource = wl_resource_create(client, &kywc_output_manager_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &ky_output_manager_impl, manager, manager_resource_destroy); wl_list_insert(&manager->resources, wl_resource_get_link(resource)); /* send all outputs and details */ struct ky_output *ky_output, *tmp; wl_list_for_each_reverse_safe(ky_output, tmp, &manager->outputs, link) { create_output_client_for_resource(ky_output, resource); } struct ky_output_client *ky_client; wl_list_for_each_reverse_safe(ky_output, tmp, &manager->outputs, link) { ky_client = output_client_find_for_client(ky_output, client); send_details_to_output_client(ky_client); } ky_output = ky_output_from_kywc_output(manager, kywc_output_get_primary()); if (ky_output) { ky_client = output_client_find_for_client(ky_output, client); kywc_output_manager_v1_send_primary(resource, ky_client->resource); } kywc_output_manager_v1_send_done(resource); } static void manager_idle_send_done(void *data) { struct ky_output_manager *manager = data; struct wl_resource *resource; wl_resource_for_each(resource, &manager->resources) { kywc_output_manager_v1_send_done(resource); } manager->idle_source = NULL; } static void manager_update_idle_source(struct ky_output_manager *manager) { if (manager->idle_source || wl_list_empty(&manager->resources)) { return; } manager->idle_source = wl_event_loop_add_idle(manager->event_loop, manager_idle_send_done, manager); } static void manager_create_output_client_for_fallback_output(struct ky_output *ky_output) { wl_list_insert(&ky_output->manager->outputs, &ky_output->link); struct wl_resource *resource; struct ky_output_client *ky_client; struct ky_output_manager *manager = ky_output->manager; wl_resource_for_each(resource, &manager->resources) { ky_client = create_output_client_for_resource(ky_output, resource); send_details_to_output_client(ky_client); /* fallback must be primary when enabled */ kywc_output_manager_v1_send_primary(resource, ky_client->resource); kywc_output_manager_v1_send_done(resource); } } static void manager_destroy_output_client_for_fallback_output(struct ky_output *ky_output) { struct ky_output_client *client, *tmp; wl_list_for_each_safe(client, tmp, &ky_output->clients, link) { struct wl_resource *resource, *tmp; wl_resource_for_each_safe(resource, tmp, &client->mode_resources) { kywc_output_mode_v1_send_finished(resource); wl_list_remove(wl_resource_get_link(resource)); wl_list_init(wl_resource_get_link(resource)); wl_resource_set_user_data(resource, NULL); } kywc_output_v1_send_finished(client->resource); wl_resource_set_user_data(client->resource, NULL); wl_list_remove(&client->link); free(client); } wl_list_remove(&ky_output->link); wl_list_init(&ky_output->link); } static void handle_output_on(struct wl_listener *listener, void *data) { struct ky_output *ky_output = wl_container_of(listener, ky_output, on); if (output_manager_get_fallback() == ky_output->kywc_output) { manager_create_output_client_for_fallback_output(ky_output); return; } struct ky_output_client *client; wl_list_for_each(client, &ky_output->clients, link) { kywc_output_v1_send_enabled(client->resource, true); send_current_mode_to_output_client(client); } manager_update_idle_source(ky_output->manager); } static void handle_output_off(struct wl_listener *listener, void *data) { struct ky_output *ky_output = wl_container_of(listener, ky_output, off); if (ky_output->kywc_output->destroying) { return; } if (output_manager_get_fallback() == ky_output->kywc_output) { manager_destroy_output_client_for_fallback_output(ky_output); return; } struct ky_output_client *client; wl_list_for_each(client, &ky_output->clients, link) { kywc_output_v1_send_enabled(client->resource, false); } manager_update_idle_source(ky_output->manager); } static void handle_output_scale(struct wl_listener *listener, void *data) { struct ky_output *ky_output = wl_container_of(listener, ky_output, scale); struct ky_output_client *client; wl_list_for_each(client, &ky_output->clients, link) { kywc_output_v1_send_scale(client->resource, wl_fixed_from_double(ky_output->kywc_output->state.scale)); } manager_update_idle_source(ky_output->manager); } static void handle_output_mode(struct wl_listener *listener, void *data) { struct ky_output *ky_output = wl_container_of(listener, ky_output, mode); struct ky_output_client *client; wl_list_for_each(client, &ky_output->clients, link) { send_current_mode_to_output_client(client); } manager_update_idle_source(ky_output->manager); } static void handle_output_position(struct wl_listener *listener, void *data) { struct ky_output *ky_output = wl_container_of(listener, ky_output, position); struct ky_output_client *client; wl_list_for_each(client, &ky_output->clients, link) { kywc_output_v1_send_position(client->resource, ky_output->kywc_output->state.lx, ky_output->kywc_output->state.ly); } manager_update_idle_source(ky_output->manager); } static void handle_output_transform(struct wl_listener *listener, void *data) { struct ky_output *ky_output = wl_container_of(listener, ky_output, transform); struct ky_output_client *client; wl_list_for_each(client, &ky_output->clients, link) { kywc_output_v1_send_transform(client->resource, ky_output->kywc_output->state.transform); } manager_update_idle_source(ky_output->manager); } static void handle_output_power(struct wl_listener *listener, void *data) { struct ky_output *ky_output = wl_container_of(listener, ky_output, power); struct ky_output_client *client; wl_list_for_each(client, &ky_output->clients, link) { kywc_output_v1_send_power(client->resource, ky_output->kywc_output->state.power); } manager_update_idle_source(ky_output->manager); } static void handle_output_brightness(struct wl_listener *listener, void *data) { struct ky_output *ky_output = wl_container_of(listener, ky_output, brightness); struct ky_output_client *client; wl_list_for_each(client, &ky_output->clients, link) { kywc_output_v1_send_brightness(client->resource, ky_output->kywc_output->state.brightness); } manager_update_idle_source(ky_output->manager); } static void handle_output_color_temp(struct wl_listener *listener, void *data) { struct ky_output *ky_output = wl_container_of(listener, ky_output, color_temp); struct ky_output_client *client; wl_list_for_each(client, &ky_output->clients, link) { kywc_output_v1_send_color_temp(client->resource, ky_output->kywc_output->state.color_temp); } manager_update_idle_source(ky_output->manager); } static void handle_output_destroy(struct wl_listener *listener, void *data) { struct ky_output *ky_output = wl_container_of(listener, ky_output, destroy); struct ky_output_client *client, *tmp; wl_list_for_each_safe(client, tmp, &ky_output->clients, link) { struct wl_resource *resource, *tmp; wl_resource_for_each_safe(resource, tmp, &client->mode_resources) { kywc_output_mode_v1_send_finished(resource); wl_list_remove(wl_resource_get_link(resource)); wl_list_init(wl_resource_get_link(resource)); wl_resource_set_user_data(resource, NULL); } kywc_output_v1_send_finished(client->resource); wl_resource_set_user_data(client->resource, NULL); wl_list_remove(&client->link); free(client); } wl_list_remove(&ky_output->link); wl_list_remove(&ky_output->on.link); wl_list_remove(&ky_output->off.link); wl_list_remove(&ky_output->mode.link); wl_list_remove(&ky_output->scale.link); wl_list_remove(&ky_output->transform.link); wl_list_remove(&ky_output->position.link); wl_list_remove(&ky_output->power.link); wl_list_remove(&ky_output->brightness.link); wl_list_remove(&ky_output->color_temp.link); wl_list_remove(&ky_output->destroy.link); free(ky_output); } static void handle_new_output(struct wl_listener *listener, void *data) { struct ky_output *ky_output = calloc(1, sizeof(*ky_output)); if (!ky_output) { return; } struct ky_output_manager *manager = wl_container_of(listener, manager, new_output); ky_output->manager = manager; wl_list_init(&ky_output->clients); struct kywc_output *kywc_output = data; ky_output->kywc_output = kywc_output; ky_output->on.notify = handle_output_on; wl_signal_add(&kywc_output->events.on, &ky_output->on); ky_output->off.notify = handle_output_off; wl_signal_add(&kywc_output->events.off, &ky_output->off); ky_output->scale.notify = handle_output_scale; wl_signal_add(&kywc_output->events.scale, &ky_output->scale); ky_output->mode.notify = handle_output_mode; wl_signal_add(&kywc_output->events.mode, &ky_output->mode); ky_output->position.notify = handle_output_position; wl_signal_add(&kywc_output->events.position, &ky_output->position); ky_output->transform.notify = handle_output_transform; wl_signal_add(&kywc_output->events.transform, &ky_output->transform); ky_output->power.notify = handle_output_power; wl_signal_add(&kywc_output->events.power, &ky_output->power); ky_output->brightness.notify = handle_output_brightness; wl_signal_add(&kywc_output->events.brightness, &ky_output->brightness); ky_output->color_temp.notify = handle_output_color_temp; wl_signal_add(&kywc_output->events.color_temp, &ky_output->color_temp); ky_output->destroy.notify = handle_output_destroy; wl_signal_add(&kywc_output->events.destroy, &ky_output->destroy); /* if the new fallback output disabled, return */ if (output_manager_get_fallback() == kywc_output && !kywc_output->state.enabled) { wl_list_init(&ky_output->link); return; } wl_list_insert(&manager->outputs, &ky_output->link); /* send output and details */ struct wl_resource *resource; struct ky_output_client *ky_client; wl_resource_for_each(resource, &manager->resources) { ky_client = create_output_client_for_resource(ky_output, resource); send_details_to_output_client(ky_client); if (kywc_output == kywc_output_get_primary()) { kywc_output_manager_v1_send_primary(resource, ky_client->resource); } kywc_output_manager_v1_send_done(resource); } } static void handle_primary_output(struct wl_listener *listener, void *data) { struct ky_output_manager *manager = wl_container_of(listener, manager, primary_output); struct kywc_output *kywc_output = data; struct ky_output *primary = ky_output_from_kywc_output(manager, kywc_output); if (!primary) { return; } struct wl_resource *resource; struct ky_output_client *client; wl_resource_for_each(resource, &manager->resources) { client = output_client_find_for_client(primary, wl_resource_get_client(resource)); kywc_output_manager_v1_send_primary(resource, client->resource); kywc_output_manager_v1_send_done(resource); } } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct ky_output_manager *manager = wl_container_of(listener, manager, server_destroy); wl_list_remove(&manager->server_destroy.link); free(manager); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct ky_output_manager *manager = wl_container_of(listener, manager, display_destroy); wl_list_remove(&manager->display_destroy.link); wl_list_remove(&manager->new_output.link); wl_list_remove(&manager->primary_output.link); if (manager->idle_source) { wl_event_source_remove(manager->idle_source); } wl_global_destroy(manager->global); } bool ky_output_manager_create(struct server *server) { struct ky_output_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->global = wl_global_create(server->display, &kywc_output_manager_v1_interface, 1, manager, ky_output_manager_bind); if (!manager->global) { kywc_log(KYWC_WARN, "Kywc output manager create failed"); free(manager); return false; } wl_list_init(&manager->resources); wl_list_init(&manager->outputs); manager->event_loop = wl_display_get_event_loop(server->display); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &manager->display_destroy); manager->new_output.notify = handle_new_output; kywc_output_add_new_listener(&manager->new_output); manager->primary_output.notify = handle_primary_output; kywc_output_add_primary_listener(&manager->primary_output); return true; } kylin-wayland-compositor/src/output/config.c0000664000175000017500000004514515160461067020236 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include "config.h" #include "output_p.h" #include "util/dbus.h" static const char *service_path = "/com/kylin/Wlcom/Output"; static const char *service_interface = "com.kylin.Wlcom.Output"; static int list_outputs(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct output_manager *om = userdata; sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(m, &reply)); CK(sd_bus_message_open_container(reply, 'a', "(ss)")); struct output *output; wl_list_for_each(output, &om->outputs, link) { if (output->base.prop.backend != KYWC_OUTPUT_BACKEND_DRM) { continue; } json_object *config = json_object_object_get(om->config->json, output->base.name); const char *cfg = json_object_to_json_string(config); sd_bus_message_append(reply, "(ss)", output->base.name, cfg); } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } static int set_brightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { char *name = NULL; uint32_t value = 0; CK(sd_bus_message_read(m, "su", &name, &value)); struct kywc_output *kywc_output = kywc_output_by_name(name); if (kywc_output) { output_set_brightness(kywc_output, value); } return sd_bus_reply_method_return(m, NULL); } static int set_colortemp(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { char *name = NULL; uint32_t value = 0; CK(sd_bus_message_read(m, "su", &name, &value)); struct kywc_output *kywc_output = kywc_output_by_name(name); if (kywc_output) { output_set_color_temp(kywc_output, value); } return sd_bus_reply_method_return(m, NULL); } static int set_direct_scanout(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { char *name = NULL; int value = 0; CK(sd_bus_message_read(m, "sb", &name, &value)); struct kywc_output *kywc_output = kywc_output_by_name(name); if (kywc_output) { struct output *output = output_from_kywc_output(kywc_output); if (output->scene_output) { output->scene_output->direct_scanout = !!value; } } return sd_bus_reply_method_return(m, NULL); } static int set_color_filter(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { char *name = NULL; uint32_t type; CK(sd_bus_message_read(m, "su", &name, &type)); struct kywc_output *kywc_output = kywc_output_by_name(name); if (kywc_output) { output_set_color_filter(kywc_output, type); } return sd_bus_reply_method_return(m, NULL); } static int set_color_feature(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { char *name = NULL; uint32_t color_feature; CK(sd_bus_message_read(m, "su", &name, &color_feature)); struct kywc_output *kywc_output = kywc_output_by_name(name); if (kywc_output) { output_set_color_feature(kywc_output, color_feature); } return sd_bus_reply_method_return(m, NULL); } static const sd_bus_vtable service_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("ListAllOutputs", "", "a(ss)", list_outputs, 0), SD_BUS_METHOD("SetBrightness", "su", "", set_brightness, 0), SD_BUS_METHOD("SetColortemp", "su", "", set_colortemp, 0), SD_BUS_METHOD("SetDirectScanout", "sb", "", set_direct_scanout, 0), SD_BUS_METHOD("SetColorFilter", "su", "", set_color_filter, 0), SD_BUS_METHOD("SetColorFeature", "su", "", set_color_feature, 0), SD_BUS_VTABLE_END, }; bool output_read_config(struct output *output, struct kywc_output_state *state) { struct output_manager *om = output->manager; if (!om->config || !om->config->json) { return false; } /* get output in layout */ json_object *config = json_object_object_get(om->config->json, output->base.name); if (!config) { return false; } /* finally, get all output config */ json_object *data; if (!json_object_object_get_ex(config, "uuid", &data)) { return false; } const char *uuid = json_object_get_string(data); if (!uuid || strcmp(uuid, output->base.uuid)) { return false; } if (json_object_object_get_ex(config, "enabled", &data)) { state->power = state->enabled = json_object_get_boolean(data); } if (json_object_object_get_ex(config, "width", &data)) { state->width = json_object_get_int(data); } if (json_object_object_get_ex(config, "height", &data)) { state->height = json_object_get_int(data); } if (json_object_object_get_ex(config, "refresh", &data)) { state->refresh = json_object_get_int(data); } if (json_object_object_get_ex(config, "transform", &data)) { state->transform = json_object_get_int(data); } if (json_object_object_get_ex(config, "scale", &data)) { state->scale = json_object_get_double(data); } if (json_object_object_get_ex(config, "lx", &data)) { state->lx = json_object_get_int(data); } if (json_object_object_get_ex(config, "ly", &data)) { state->ly = json_object_get_int(data); } if (json_object_object_get_ex(config, "brightness", &data)) { state->brightness = json_object_get_int(data); } if (json_object_object_get_ex(config, "color_temp", &data)) { state->color_temp = json_object_get_int(data); } return true; } void output_write_config(struct output *output) { struct output_manager *om = output->manager; if (!om->config || !om->config->json || output->base.prop.backend != KYWC_OUTPUT_BACKEND_DRM) { return; } struct kywc_output_state *state = &output->base.state; /* get output in layout, create if no */ json_object *config = json_object_object_get(om->config->json, output->base.name); if (!config) { config = json_object_new_object(); json_object_object_add(om->config->json, output->base.name, config); } json_object_object_add(config, "uuid", json_object_new_string(output->base.uuid)); json_object_object_add(config, "enabled", json_object_new_boolean(state->enabled)); json_object_object_add(config, "width", json_object_new_int(state->width)); json_object_object_add(config, "height", json_object_new_int(state->height)); json_object_object_add(config, "refresh", json_object_new_int(state->refresh)); json_object_object_add(config, "scale", json_object_new_double(state->scale)); json_object_object_add(config, "transform", json_object_new_int(state->transform)); json_object_object_add(config, "lx", json_object_new_int(state->lx)); json_object_object_add(config, "ly", json_object_new_int(state->ly)); json_object_object_add(config, "brightness", json_object_new_int(state->brightness)); json_object_object_add(config, "color_temp", json_object_new_int(state->color_temp)); } struct output_uuid { const char *name, *uuid; }; static void output_get_layout(struct output *output, const char *active_layout, char layout[static UUID_SIZE * 2]) { memcpy(layout, active_layout, UUID_SIZE - 1); layout[UUID_SIZE - 1] = ':'; memcpy(layout + UUID_SIZE, output->base.uuid, UUID_SIZE - 1); layout[UUID_SIZE * 2 - 1] = '\0'; } static const char *output_manager_get_active_layout(struct output_manager *output_manager) { struct output_manager *om = output_manager; if (!om->layout_config || !om->layout_config->json) { return NULL; } /* get outputs_layout from layouts */ json_object *outputs_layout = json_object_object_get(om->layout_config->json, om->outputs_layout); if (!outputs_layout) { return NULL; } json_object *data; /* get active_layout from outputs_layout */ if (json_object_object_get_ex(outputs_layout, "active_layout", &data)) { return json_object_get_string(data); } return NULL; } static void output_manager_set_active_layout(struct output_manager *manager, const char *active_layout) { if (!manager->layout_config || !manager->layout_config->json || !active_layout) { return; } /* get outputs_layout in layouts, create if no */ json_object *outputs_layout = json_object_object_get(manager->layout_config->json, manager->outputs_layout); if (!outputs_layout) { outputs_layout = json_object_new_object(); json_object_object_add(manager->layout_config->json, manager->outputs_layout, outputs_layout); } json_object_object_add(outputs_layout, "active_layout", json_object_new_string(active_layout)); } static bool output_read_layout_config(struct output *output, struct kywc_output_state *state, const char *active_layout) { struct output_manager *om = output->manager; if (!om->layout_config || !om->layout_config->json || !active_layout) { return false; } char layout[UUID_SIZE * 2]; output_get_layout(output, active_layout, layout); json_object *config = json_object_object_get(om->layout_config->json, layout); if (!config) { return false; } json_object *data; if (json_object_object_get_ex(config, "enabled", &data)) { state->enabled = state->power = json_object_get_boolean(data); } if (!state->enabled) { return true; } if (json_object_object_get_ex(config, "primary", &data)) { if (json_object_get_boolean(data)) { output_manager_set_pending_primary(output); } } if (json_object_object_get_ex(config, "width", &data)) { state->width = json_object_get_int(data); } if (json_object_object_get_ex(config, "height", &data)) { state->height = json_object_get_int(data); } if (json_object_object_get_ex(config, "refresh", &data)) { state->refresh = json_object_get_int(data); } if (json_object_object_get_ex(config, "transform", &data)) { state->transform = json_object_get_int(data); } if (json_object_object_get_ex(config, "scale", &data)) { state->scale = json_object_get_double(data); } if (json_object_object_get_ex(config, "lx", &data)) { state->lx = json_object_get_int(data); } if (json_object_object_get_ex(config, "ly", &data)) { state->ly = json_object_get_int(data); } return true; } static void output_write_layout_config(struct output *output, const char *active_layout) { struct output_manager *om = output->manager; if (!om->layout_config || !om->layout_config->json || !active_layout) { return; } char layout[UUID_SIZE * 2]; output_get_layout(output, active_layout, layout); json_object *config = json_object_object_get(om->layout_config->json, layout); if (!config) { config = json_object_new_object(); json_object_object_add(om->layout_config->json, layout, config); } struct kywc_output *kywc_output = &output->base; struct kywc_output_state *state = &kywc_output->state; bool primary = kywc_output_get_primary() == kywc_output; json_object_object_add(config, "enabled", json_object_new_boolean(state->enabled)); if (!state->enabled) { return; } json_object_object_add(config, "primary", json_object_new_boolean(primary)); json_object_object_add(config, "width", json_object_new_int(state->width)); json_object_object_add(config, "height", json_object_new_int(state->height)); json_object_object_add(config, "refresh", json_object_new_int(state->refresh)); json_object_object_add(config, "scale", json_object_new_double(state->scale)); json_object_object_add(config, "transform", json_object_new_int(state->transform)); json_object_object_add(config, "lx", json_object_new_int(state->lx)); json_object_object_add(config, "ly", json_object_new_int(state->ly)); } static int compare_output_uuid(const void *p1, const void *p2) { const char *v1 = ((struct output_uuid *)p1)->name; const char *v2 = ((struct output_uuid *)p2)->name; return strcmp(v1, v2); } static void output_manager_generate_layout(struct output_manager *output_manager, char *layout_uuid, bool is_active_layout) { struct output_uuid *o_uuids = NULL; int actual_cnt = 0; size_t uuid_chunk_size = UUID_SIZE - 1; struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { struct kywc_output *kywc_output = &output->base; if (kywc_output == output_manager_get_fallback()) { continue; } if (is_active_layout && !kywc_output->state.enabled) { continue; } actual_cnt++; } if (!actual_cnt) { return; } o_uuids = malloc(actual_cnt * sizeof(struct output_uuid)); if (!o_uuids) { return; } int index = 0; wl_list_for_each(output, &output_manager->outputs, link) { struct kywc_output *kywc_output = &output->base; if (kywc_output == output_manager_get_fallback()) { continue; } if (is_active_layout && !kywc_output->state.enabled) { continue; } o_uuids[index].name = kywc_output->name; o_uuids[index].uuid = kywc_output->uuid; index++; } if (actual_cnt == 1) { memcpy(layout_uuid, o_uuids[0].uuid, uuid_chunk_size); layout_uuid[uuid_chunk_size] = '\0'; free(o_uuids); return; } qsort(o_uuids, actual_cnt, sizeof(struct output_uuid), compare_output_uuid); uint8_t *uuids = malloc(actual_cnt * uuid_chunk_size); if (!uuids) { free(o_uuids); return; } for (int i = 0; i < actual_cnt; ++i) { memcpy(uuids + i * uuid_chunk_size, o_uuids[i].uuid, uuid_chunk_size); } free(o_uuids); const char *uuid = kywc_identifier_md5_generate_uuid(uuids, actual_cnt * uuid_chunk_size); if (uuid) { memcpy(layout_uuid, uuid, uuid_chunk_size); layout_uuid[uuid_chunk_size] = '\0'; free((void *)uuid); } free(uuids); } static void output_manager_get_layout_configs(bool output_removed) { if (output_manager->server->terminate || !output_manager->server->start) { return; } if (!output_manager_has_actual_outputs()) { struct output *fallback_output = output_from_kywc_output(output_manager_get_fallback()); output_manager_add_pending_state(fallback_output, &fallback_output->pending_state); return; } /* update current outputs layout */ output_manager_generate_layout(output_manager, output_manager->outputs_layout, false); const char *active_layout = output_manager_get_active_layout(output_manager); kywc_log(KYWC_INFO, "Configure outputs layout %s with active layout %s", output_manager->outputs_layout, active_layout ? active_layout : "none"); /* get all outputs configuration */ struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { struct kywc_output *kywc_output = &output->base; if (kywc_output == output_manager_get_fallback()) { continue; } output_fill_preferred_state(output); output_read_layout_config(output, &output->pending_state, active_layout); output_manager_add_pending_state(output, &output->pending_state); } } static void fallback_default_configure(bool output_removed) { if (output_manager->server->terminate || !output_manager->server->start) { return; } if (!output_manager_has_actual_outputs()) { struct output *fallback_output = output_from_kywc_output(output_manager_get_fallback()); output_manager_add_pending_state(fallback_output, &fallback_output->pending_state); return; } if (output_removed) { return; } /* get all outputs configuration */ struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { struct kywc_output *kywc_output = &output->base; if (kywc_output == output_manager_get_fallback()) { continue; } output_fill_preferred_state(output); output_manager_add_pending_state(output, &output->pending_state); } } static void output_manager_save_layouts(struct output_manager *manager) { char active_layout[UUID_SIZE]; output_manager_generate_layout(manager, active_layout, true); output_manager_set_active_layout(manager, active_layout); struct output *output; wl_list_for_each(output, &manager->outputs, link) { struct kywc_output *kywc_output = &output->base; if (kywc_output == output_manager_get_fallback()) { continue; } output_write_layout_config(output, active_layout); } if (kywc_log_get_level() >= KYWC_INFO) { kywc_log(KYWC_INFO, "Save outputs layout %s with active layout %s", manager->outputs_layout, active_layout); wl_list_for_each(output, &manager->outputs, link) { struct kywc_output *kywc_output = &output->base; if (kywc_output == output_manager_get_fallback()) { continue; } output_log_state(KYWC_INFO, output, "layout", &kywc_output->state); } } } static void output_manager_handle_configured(struct wl_listener *listener, void *data) { struct output_manager *output_manager = wl_container_of(listener, output_manager, configured); struct configure_event *event = data; if (!output_manager->has_layout_manager || !output_manager_has_actual_outputs() || event->type != CONFIGURE_TYPE_UPDATE) { return; } output_manager_save_layouts(output_manager); } bool output_manager_config_init(struct output_manager *output_manager) { if (output_manager->has_layout_manager) { output_manager->layout_config = config_manager_add_config("layouts"); if (!output_manager->layout_config) { output_manager->has_layout_manager = false; } else { /* listener output configured signal */ output_manager->configured.notify = output_manager_handle_configured; output_manager_add_configured_listener(&output_manager->configured); output_manager->impl.get_configures = output_manager_get_layout_configs; } } else { output_manager->impl.get_configures = fallback_default_configure; } output_manager->config = config_manager_add_config("outputs"); if (!output_manager->config) { return false; } return dbus_register_object(NULL, service_path, service_interface, service_vtable, output_manager); } kylin-wayland-compositor/src/output/kde_output.c0000664000175000017500000011765415160461067021161 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "dpms-protocol.h" #include "kde-output-device-v2-protocol.h" #include "kde-output-management-v2-protocol.h" #include "kde-primary-output-v1-protocol.h" #include "output_p.h" #include "util/wayland.h" #define OUTPUT_DEVICE_VERSION 8 #define OUTPUT_DEVICE_MODE_VERSION 1 #define OUTPUT_MANAGER_VERSION 9 #define KDE_PRIMARY_OUTPUT_VERSION 2 #define ORG_KDE_KWIN_DPMS_MANAGER_VERSION 1 struct kde_output_management { struct wl_display *display; struct wl_global *global; struct wl_list resources; // clients struct wl_list output_devices; /* for primary output */ struct { struct wl_global *global; struct wl_list resources; struct wl_listener primary_output; struct kywc_output *current_primary; } primary_output; /* for dpms manager */ struct { struct wl_global *global; struct wl_list resources; } dpms_manager; struct wl_listener new_output; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct kde_output_device { struct wl_global *global; struct wl_list link; struct wl_list clients; struct wl_list resources; // for dpms struct wl_event_source *idle_source; struct kywc_output *kywc_output; struct wl_listener on; struct wl_listener off; struct wl_listener scale; struct wl_listener mode; struct wl_listener position; struct wl_listener transform; struct wl_listener power; struct wl_listener destroy; }; struct kde_output_config { struct kde_output_device *device; struct kywc_output_state pending; struct wl_list link; struct wl_listener output_destroy; }; struct kde_output_configs { struct wl_resource *resource; struct kywc_output *pending_primary; struct wl_list configs; bool applied; }; struct kde_output_device_client { struct wl_resource *resource; struct wl_list link; struct kde_output_device *output_device; struct wl_list mode_resources; }; static struct kde_output_management *management = NULL; static void output_configure_handle_output_destroy(struct wl_listener *listener, void *data) { struct kde_output_config *config = wl_container_of(listener, config, output_destroy); wl_list_remove(&config->output_destroy.link); wl_list_remove(&config->link); free(config); } static struct kde_output_config *get_output_device_config(struct kde_output_configs *configs, struct kde_output_device *device) { struct kde_output_config *config; wl_list_for_each(config, &configs->configs, link) { if (config->device == device) { return config; } } config = calloc(1, sizeof(*config)); if (!config) { return NULL; } config->device = device; /* filter changes when apply */ config->pending = device->kywc_output->state; wl_list_insert(&configs->configs, &config->link); config->output_destroy.notify = output_configure_handle_output_destroy; wl_signal_add(&device->kywc_output->events.destroy, &config->output_destroy); return config; } static void output_configure_handle_enable(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, int32_t enable) { struct kde_output_device_client *kod_client = wl_resource_get_user_data(outputdevice); if (!kod_client->output_device) { return; } struct kde_output_configs *configs = wl_resource_get_user_data(resource); struct kde_output_config *config = get_output_device_config(configs, kod_client->output_device); if (config) { config->pending.enabled = config->pending.power = enable; } } static void output_configure_handle_mode(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, struct wl_resource *mode) { struct kde_output_device_client *kod_client = wl_resource_get_user_data(outputdevice); if (!kod_client->output_device) { return; } struct kde_output_configs *configs = wl_resource_get_user_data(resource); struct kde_output_config *config = get_output_device_config(configs, kod_client->output_device); if (config) { struct kywc_output_mode *output_mode = wl_resource_get_user_data(mode); config->pending.width = output_mode->width; config->pending.height = output_mode->height; config->pending.refresh = output_mode->refresh; } } static void output_configure_handle_transform(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, int32_t transform) { struct kde_output_device_client *kod_client = wl_resource_get_user_data(outputdevice); if (!kod_client->output_device) { return; } struct kde_output_configs *configs = wl_resource_get_user_data(resource); struct kde_output_config *config = get_output_device_config(configs, kod_client->output_device); if (config) { config->pending.transform = transform; } } static void output_configure_handle_position(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, int32_t x, int32_t y) { struct kde_output_device_client *kod_client = wl_resource_get_user_data(outputdevice); if (!kod_client->output_device) { return; } struct kde_output_configs *configs = wl_resource_get_user_data(resource); struct kde_output_config *config = get_output_device_config(configs, kod_client->output_device); if (config) { config->pending.lx = x; config->pending.ly = y; } } static void output_configure_handle_scale(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, wl_fixed_t scale) { struct kde_output_device_client *kod_client = wl_resource_get_user_data(outputdevice); if (!kod_client->output_device) { return; } struct kde_output_configs *configs = wl_resource_get_user_data(resource); struct kde_output_config *config = get_output_device_config(configs, kod_client->output_device); if (config) { float output_scale = wl_fixed_to_double(scale); config->pending.scale = output_scale; } } static void output_configure_handle_apply(struct wl_client *client, struct wl_resource *resource) { struct kde_output_configs *configs = wl_resource_get_user_data(resource); if (configs->applied) { wl_resource_post_error(resource, KDE_OUTPUT_CONFIGURATION_V2_ERROR_ALREADY_APPLIED, "an output configuration can be applied only once"); return; } configs->applied = true; struct kywc_output *primary_output = management->primary_output.current_primary; if (wl_list_empty(&management->output_devices) || !primary_output) { kywc_log(KYWC_WARN, "Configuration cannot be applied"); kde_output_configuration_v2_send_failed(resource); return; } struct kde_output_config *config; wl_list_for_each(config, &configs->configs, link) { struct output *output = output_from_kywc_output(config->device->kywc_output); output_manager_add_pending_state(output, &config->pending); } output_manager_set_pending_primary(output_from_kywc_output(configs->pending_primary)); if (!output_manager_configure_outputs(NULL)) { kde_output_configuration_v2_send_failed(resource); return; } kde_output_configuration_v2_send_applied(resource); } static void output_configure_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void output_configure_handle_overscan(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, uint32_t overscan) { struct kde_output_device_client *kod_client = wl_resource_get_user_data(outputdevice); if (!kod_client->output_device) { return; } struct kde_output_configs *configs = wl_resource_get_user_data(resource); struct kde_output_config *config = get_output_device_config(configs, kod_client->output_device); if (config) { kywc_log(KYWC_DEBUG, "Configure output %s overscan to: %d", kod_client->output_device->kywc_output->name, overscan); config->pending.overscan = overscan; } } static void output_configure_set_vrr_policy(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, uint32_t policy) { struct kde_output_device_client *kod_client = wl_resource_get_user_data(outputdevice); if (!kod_client->output_device) { return; } struct kde_output_configs *configs = wl_resource_get_user_data(resource); struct kde_output_config *config = get_output_device_config(configs, kod_client->output_device); if (config) { kywc_log(KYWC_DEBUG, "Configure output %s vrr policy to: %d", kod_client->output_device->kywc_output->name, policy); config->pending.vrr_policy = policy; } } static void output_configure_set_rgb_range(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, uint32_t rgb_range) { struct kde_output_device_client *kod_client = wl_resource_get_user_data(outputdevice); if (!kod_client->output_device) { return; } struct kde_output_configs *configs = wl_resource_get_user_data(resource); struct kde_output_config *config = get_output_device_config(configs, kod_client->output_device); if (config) { kywc_log(KYWC_DEBUG, "Configure output %s rgb_range to: %d", kod_client->output_device->kywc_output->name, rgb_range); config->pending.rgb_range = rgb_range; } } static void output_configure_set_primary_output(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice) { struct kde_output_device_client *kod_client = wl_resource_get_user_data(outputdevice); if (!kod_client->output_device) { return; } kywc_log(KYWC_DEBUG, "Configure primary output to: %s", kod_client->output_device->kywc_output->name); struct kde_output_configs *configs = wl_resource_get_user_data(resource); uint32_t version = wl_resource_get_version(resource); if (version >= KDE_OUTPUT_CONFIGURATION_V2_SET_PRIMARY_OUTPUT_SINCE_VERSION) { configs->pending_primary = kod_client->output_device->kywc_output; } } static void output_configure_set_priority(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, uint32_t priority) { } static void output_configure_set_high_dynamic_range(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, uint32_t enable_hdr) { } static void output_configure_set_sdr_brightness(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, uint32_t sdr_brightness) { } static void output_configure_set_wide_color_gamut(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, uint32_t enable_wcg) { } static void output_configure_set_auto_rotate_policy(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, uint32_t policy) { } static void output_configure_set_icc_profile_path(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, const char *profile_path) { } static void output_configure_set_brightness_overrides( struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, int32_t max_peak_brightness, int32_t max_frame_average_brightness, int32_t min_brightness) { } static void output_configure_set_sdr_gamut_wideness(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, uint32_t gamut_wideness) { } static void output_configure_set_color_profile_source(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, uint32_t color_profile_source) { } static void output_configure_set_brightness(struct wl_client *client, struct wl_resource *resource, struct wl_resource *outputdevice, uint32_t brightness) { struct kde_output_device_client *kod_client = wl_resource_get_user_data(outputdevice); if (!kod_client->output_device) { return; } kywc_log(KYWC_DEBUG, "Configure output: %s brightness to:%d", kod_client->output_device->kywc_output->name, brightness); struct kde_output_configs *configs = wl_resource_get_user_data(resource); struct kde_output_config *config = get_output_device_config(configs, kod_client->output_device); uint32_t version = wl_resource_get_version(resource); if (version >= KDE_OUTPUT_CONFIGURATION_V2_SET_BRIGHTNESS_SINCE_VERSION && config) { config->pending.brightness = brightness; } } static const struct kde_output_configuration_v2_interface kde_output_configure_impl = { .enable = output_configure_handle_enable, .mode = output_configure_handle_mode, .transform = output_configure_handle_transform, .position = output_configure_handle_position, .scale = output_configure_handle_scale, .apply = output_configure_handle_apply, .destroy = output_configure_handle_destroy, .overscan = output_configure_handle_overscan, .set_vrr_policy = output_configure_set_vrr_policy, .set_rgb_range = output_configure_set_rgb_range, .set_primary_output = output_configure_set_primary_output, .set_priority = output_configure_set_priority, .set_high_dynamic_range = output_configure_set_high_dynamic_range, .set_sdr_brightness = output_configure_set_sdr_brightness, .set_wide_color_gamut = output_configure_set_wide_color_gamut, .set_auto_rotate_policy = output_configure_set_auto_rotate_policy, .set_icc_profile_path = output_configure_set_icc_profile_path, .set_brightness_overrides = output_configure_set_brightness_overrides, .set_sdr_gamut_wideness = output_configure_set_sdr_gamut_wideness, .set_color_profile_source = output_configure_set_color_profile_source, .set_brightness = output_configure_set_brightness, }; static void kde_output_configs_handle_resource_destroy(struct wl_resource *resource) { struct kde_output_configs *configs = wl_resource_get_user_data(resource); struct kde_output_config *config, *config_tmp; wl_list_for_each_safe(config, config_tmp, &configs->configs, link) { wl_list_remove(&config->output_destroy.link); wl_list_remove(&config->link); free(config); } free(configs); } static void output_management_handle_create_configure( struct wl_client *client, struct wl_resource *output_management_resource, uint32_t id) { struct kde_output_configs *configs = calloc(1, sizeof(*configs)); if (!configs) { wl_client_post_no_memory(client); return; } uint32_t version = wl_resource_get_version(output_management_resource); struct wl_resource *resource = wl_resource_create(client, &kde_output_configuration_v2_interface, version, id); if (!resource) { wl_client_post_no_memory(client); free(configs); return; } configs->resource = resource; wl_list_init(&configs->configs); configs->pending_primary = management->primary_output.current_primary; wl_resource_set_implementation(resource, &kde_output_configure_impl, configs, kde_output_configs_handle_resource_destroy); } static const struct kde_output_management_v2_interface kde_output_management_impl = { .create_configuration = output_management_handle_create_configure, }; static void kde_output_management_handle_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static void kde_output_management_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &kde_output_management_v2_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &kde_output_management_impl, management, kde_output_management_handle_resource_destroy); wl_list_insert(&management->resources, wl_resource_get_link(resource)); } static void kde_output_management_handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&management->server_destroy.link); free(management); management = NULL; } static void kde_output_management_handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&management->display_destroy.link); wl_list_remove(&management->new_output.link); wl_global_destroy(management->global); if (management->primary_output.global) { wl_list_remove(&management->primary_output.primary_output.link); wl_global_destroy(management->primary_output.global); } if (management->dpms_manager.global) { wl_global_destroy(management->dpms_manager.global); } } static void kde_output_device_handle_mode_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static void kde_output_device_send_modes(struct kde_output_device_client *kod_client) { struct wl_client *client = wl_resource_get_client(kod_client->resource); struct kywc_output *kywc_output = kod_client->output_device->kywc_output; struct kywc_output_mode *mode; wl_list_for_each(mode, &kywc_output->prop.modes, link) { struct wl_resource *mode_resource = wl_resource_create( client, &kde_output_device_mode_v2_interface, OUTPUT_DEVICE_MODE_VERSION, 0); if (!mode_resource) { continue; } wl_list_insert(&kod_client->mode_resources, wl_resource_get_link(mode_resource)); /* for current_mode and mode config */ wl_resource_set_user_data(mode_resource, mode); wl_resource_set_destructor(mode_resource, kde_output_device_handle_mode_resource_destroy); kde_output_device_v2_send_mode(kod_client->resource, mode_resource); kde_output_device_mode_v2_send_size(mode_resource, mode->width, mode->height); kde_output_device_mode_v2_send_refresh(mode_resource, mode->refresh); if (mode->preferred) { kde_output_device_mode_v2_send_preferred(mode_resource); } } } static void kde_output_device_send_current_mode(struct kde_output_device_client *kod_client) { struct kywc_output *kywc_output = kod_client->output_device->kywc_output; /* only send if output is enabled */ if (!kywc_output->state.enabled) { return; } struct wl_resource *mode_resource; wl_resource_for_each(mode_resource, &kod_client->mode_resources) { struct kywc_output_mode *mode = wl_resource_get_user_data(mode_resource); if (mode->width != kywc_output->state.width || mode->height != kywc_output->state.height || mode->refresh != kywc_output->state.refresh) { continue; } kde_output_device_v2_send_current_mode(kod_client->resource, mode_resource); break; } } static void kde_output_device_unbind(struct wl_resource *resource) { struct kde_output_device_client *kod_client = wl_resource_get_user_data(resource); struct wl_resource *mode_resource, *tmp; wl_resource_for_each_safe(mode_resource, tmp, &kod_client->mode_resources) { wl_list_remove(wl_resource_get_link(mode_resource)); wl_list_init(wl_resource_get_link(mode_resource)); } wl_list_remove(&kod_client->link); free(kod_client); } static void kde_output_device_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &kde_output_device_v2_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } struct kde_output_device *output_device = data; if (!output_device) { kywc_log(KYWC_DEBUG, "Failed to bind output device, " "the kde_output_device_v2 has been destroyed"); return; } struct kde_output_device_client *kod_client = calloc(1, sizeof(*kod_client)); if (!kod_client) { wl_client_post_no_memory(client); return; } kod_client->resource = resource; kod_client->output_device = output_device; wl_list_init(&kod_client->mode_resources); /* user_data used in configuration */ wl_resource_set_user_data(resource, kod_client); wl_resource_set_destructor(resource, kde_output_device_unbind); wl_list_insert(&output_device->clients, &kod_client->link); struct kywc_output *kywc_output = output_device->kywc_output; kde_output_device_v2_send_enabled(resource, kywc_output->state.enabled); kde_output_device_v2_send_geometry(resource, kywc_output->state.lx, kywc_output->state.ly, kywc_output->prop.phys_width, kywc_output->prop.phys_height, 0, kywc_output->prop.make, kywc_output->prop.model, kywc_output->state.transform); kde_output_device_send_modes(kod_client); kde_output_device_send_current_mode(kod_client); kde_output_device_v2_send_scale(resource, wl_fixed_from_double(kywc_output->state.scale)); kde_output_device_v2_send_serial_number( resource, kywc_output->prop.serial ? kywc_output->prop.serial : ""); kde_output_device_v2_send_uuid(resource, kywc_output->uuid); kde_output_device_v2_send_edid(resource, kywc_output->prop.edid ? kywc_output->prop.edid : ""); /* TODO: finish these kde_output_device_v2_send_eisa_id(); */ uint32_t capabilities = 0; if (kywc_output->prop.capabilities & KYWC_OUTPUT_CAPABILITY_OVERSCAN) { capabilities |= KDE_OUTPUT_DEVICE_V2_CAPABILITY_OVERSCAN; } if (kywc_output->prop.capabilities & KYWC_OUTPUT_CAPABILITY_VRR) { capabilities |= KDE_OUTPUT_DEVICE_V2_CAPABILITY_VRR; } if (kywc_output->prop.capabilities & KYWC_OUTPUT_CAPABILITY_RGB_RANGE) { capabilities |= KDE_OUTPUT_DEVICE_V2_CAPABILITY_RGB_RANGE; } kde_output_device_v2_send_capabilities(resource, capabilities); if (capabilities & KDE_OUTPUT_DEVICE_V2_CAPABILITY_OVERSCAN) { kde_output_device_v2_send_overscan(resource, kywc_output->state.overscan); } if (capabilities & KDE_OUTPUT_DEVICE_V2_CAPABILITY_VRR) { kde_output_device_v2_send_vrr_policy(resource, kywc_output->state.vrr_policy); } if (capabilities & KDE_OUTPUT_DEVICE_V2_CAPABILITY_RGB_RANGE) { kde_output_device_v2_send_rgb_range(resource, kywc_output->state.rgb_range); } if (version >= KDE_OUTPUT_DEVICE_V2_NAME_SINCE_VERSION) { kde_output_device_v2_send_name(resource, kywc_output->name); } kde_output_device_v2_send_done(resource); } static void kde_output_device_handle_destroy(struct wl_listener *listener, void *data) { struct kde_output_device *output_device = wl_container_of(listener, output_device, destroy); if (output_device->kywc_output != output_manager_get_fallback()) { wl_list_remove(&output_device->on.link); wl_list_remove(&output_device->off.link); wl_list_remove(&output_device->mode.link); wl_list_remove(&output_device->scale.link); wl_list_remove(&output_device->transform.link); wl_list_remove(&output_device->position.link); } wl_list_remove(&output_device->power.link); wl_list_remove(&output_device->destroy.link); wl_list_remove(&output_device->link); struct kde_output_device_client *client, *client_tmp; wl_list_for_each_safe(client, client_tmp, &output_device->clients, link) { wl_list_remove(&client->link); wl_list_init(&client->link); client->output_device = NULL; } struct wl_resource *resource, *tmp; wl_resource_for_each_safe(resource, tmp, &output_device->resources) { wl_list_remove(wl_resource_get_link(resource)); wl_list_init(wl_resource_get_link(resource)); wl_resource_set_user_data(resource, NULL); } if (output_device->idle_source) { wl_event_source_remove(output_device->idle_source); } /* global destroy when output destroy */ if (output_device->global) { wl_global_destroy_safe(output_device->global); } free(output_device); } static void output_idle_send_done(void *data) { struct kde_output_device *output_device = data; struct kde_output_device_client *client; wl_list_for_each(client, &output_device->clients, link) { kde_output_device_v2_send_done(client->resource); } output_device->idle_source = NULL; } static void output_update_idle_source(struct kde_output_device *output_device) { if (output_device->idle_source || wl_list_empty(&output_device->clients)) { return; } struct wl_event_loop *loop = wl_display_get_event_loop(management->display); output_device->idle_source = wl_event_loop_add_idle(loop, output_idle_send_done, output_device); } static void kde_output_device_handle_on(struct wl_listener *listener, void *data) { struct kde_output_device *output_device = wl_container_of(listener, output_device, on); struct kde_output_device_client *client; wl_list_for_each(client, &output_device->clients, link) { kde_output_device_v2_send_enabled(client->resource, true); kde_output_device_send_current_mode(client); } output_update_idle_source(output_device); } static void kde_output_device_handle_off(struct wl_listener *listener, void *data) { struct kde_output_device *output_device = wl_container_of(listener, output_device, off); if (output_device->kywc_output->destroying) { return; } /* destroy the fallback global */ if (output_device->kywc_output == output_manager_get_fallback()) { wl_global_destroy_safe(output_device->global); output_device->global = NULL; return; } struct kde_output_device_client *client; wl_list_for_each(client, &output_device->clients, link) { kde_output_device_v2_send_enabled(client->resource, false); } output_update_idle_source(output_device); } static void kde_output_device_handle_mode(struct wl_listener *listener, void *data) { struct kde_output_device *output_device = wl_container_of(listener, output_device, mode); struct kde_output_device_client *client; wl_list_for_each(client, &output_device->clients, link) { kde_output_device_send_current_mode(client); } output_update_idle_source(output_device); } static void kde_output_device_handle_scale(struct wl_listener *listener, void *data) { struct kde_output_device *output_device = wl_container_of(listener, output_device, scale); struct kde_output_device_client *client; wl_list_for_each(client, &output_device->clients, link) { kde_output_device_v2_send_scale( client->resource, wl_fixed_from_double(output_device->kywc_output->state.scale)); } output_update_idle_source(output_device); } static void kde_output_device_handle_position(struct wl_listener *listener, void *data) { struct kde_output_device *output_device = wl_container_of(listener, output_device, position); struct kywc_output *kywc_output = output_device->kywc_output; struct kde_output_device_client *client; wl_list_for_each(client, &output_device->clients, link) { kde_output_device_v2_send_geometry(client->resource, kywc_output->state.lx, kywc_output->state.ly, kywc_output->prop.phys_width, kywc_output->prop.phys_height, 0, kywc_output->prop.make, kywc_output->prop.model, kywc_output->state.transform); } output_update_idle_source(output_device); } static void kde_output_device_handle_transform(struct wl_listener *listener, void *data) { struct kde_output_device *output_device = wl_container_of(listener, output_device, transform); struct kywc_output *kywc_output = output_device->kywc_output; struct kde_output_device_client *client; wl_list_for_each(client, &output_device->clients, link) { kde_output_device_v2_send_geometry(client->resource, kywc_output->state.lx, kywc_output->state.ly, kywc_output->prop.phys_width, kywc_output->prop.phys_height, 0, kywc_output->prop.make, kywc_output->prop.model, kywc_output->state.transform); } output_update_idle_source(output_device); } static void kde_output_device_handle_power(struct wl_listener *listener, void *data) { struct kde_output_device *output_device = wl_container_of(listener, output_device, power); struct kywc_output *kywc_output = output_device->kywc_output; struct wl_resource *resource; wl_resource_for_each(resource, &output_device->resources) { org_kde_kwin_dpms_send_mode(resource, kywc_output->state.power ? ORG_KDE_KWIN_DPMS_MODE_ON : ORG_KDE_KWIN_DPMS_MODE_OFF); org_kde_kwin_dpms_send_done(resource); } } static void kde_output_management_handle_new_output(struct wl_listener *listener, void *data) { struct kde_output_device *output_device = calloc(1, sizeof(*output_device)); if (!output_device) { return; } struct kywc_output *kywc_output = data; if (output_manager_get_fallback() != kywc_output) { output_device->global = wl_global_create(management->display, &kde_output_device_v2_interface, OUTPUT_DEVICE_VERSION, output_device, kde_output_device_bind); if (!output_device->global) { free(output_device); return; } } wl_list_init(&output_device->clients); wl_list_init(&output_device->resources); wl_list_insert(&management->output_devices, &output_device->link); output_device->kywc_output = kywc_output; output_device->power.notify = kde_output_device_handle_power; wl_signal_add(&kywc_output->events.power, &output_device->power); output_device->destroy.notify = kde_output_device_handle_destroy; wl_signal_add(&kywc_output->events.destroy, &output_device->destroy); if (output_manager_get_fallback() == kywc_output) { return; } output_device->on.notify = kde_output_device_handle_on; wl_signal_add(&kywc_output->events.on, &output_device->on); output_device->off.notify = kde_output_device_handle_off; wl_signal_add(&kywc_output->events.off, &output_device->off); output_device->mode.notify = kde_output_device_handle_mode; wl_signal_add(&kywc_output->events.mode, &output_device->mode); output_device->scale.notify = kde_output_device_handle_scale; wl_signal_add(&kywc_output->events.scale, &output_device->scale); output_device->position.notify = kde_output_device_handle_position; wl_signal_add(&kywc_output->events.position, &output_device->position); output_device->transform.notify = kde_output_device_handle_transform; wl_signal_add(&kywc_output->events.transform, &output_device->transform); } static void kde_primary_output_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct kde_primary_output_v1_interface kde_primary_output_impl = { .destroy = kde_primary_output_destroy, }; static void kde_primary_output_unbind(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static void kde_primary_output_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &kde_primary_output_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &kde_primary_output_impl, NULL, kde_primary_output_unbind); wl_list_insert(&management->primary_output.resources, wl_resource_get_link(resource)); struct kywc_output *primary = management->primary_output.current_primary; if (primary) { kde_primary_output_v1_send_primary_output(resource, primary->name); } } static void kde_output_management_handle_primary_output(struct wl_listener *listener, void *data) { struct kywc_output *kywc_output = data; management->primary_output.current_primary = kywc_output; if (!kywc_output) { return; } struct wl_resource *resource; wl_resource_for_each(resource, &management->primary_output.resources) { kde_primary_output_v1_send_primary_output(resource, kywc_output->name); } } static void kde_dpms_set(struct wl_client *client, struct wl_resource *resource, uint32_t mode) { struct kde_output_device *output_device = wl_resource_get_user_data(resource); if (!output_device) { return; } /* dpms protocol uses wl_output, so output must be enabled */ output_set_power(output_device->kywc_output, mode != ORG_KDE_KWIN_DPMS_MODE_OFF); } static void kde_dpms_release(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct org_kde_kwin_dpms_interface kde_dpms_impl = { .set = kde_dpms_set, .release = kde_dpms_release, }; static void kde_dpms_handle_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static struct kde_output_device *output_device_from_resource(struct wl_resource *resource) { struct output *output = output_from_resource(resource); if (!output) { return NULL; } struct kde_output_device *output_device; wl_list_for_each(output_device, &management->output_devices, link) { if (output_device->kywc_output == &output->base) { return output_device; } } return NULL; } static void kde_dpms_manager_get(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id, struct wl_resource *output_resource) { /* get output from output resource */ struct kde_output_device *output_device = output_device_from_resource(output_resource); if (!output_device) { wl_client_post_implementation_error(client, "invalid output"); return; } uint32_t version = wl_resource_get_version(manager_resource); struct wl_resource *resource = wl_resource_create(client, &org_kde_kwin_dpms_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &kde_dpms_impl, output_device, kde_dpms_handle_resource_destroy); wl_list_insert(&output_device->resources, wl_resource_get_link(resource)); /* send current dpms state */ struct kywc_output *output = output_device->kywc_output; org_kde_kwin_dpms_send_supported(resource, output->prop.capabilities & KYWC_OUTPUT_CAPABILITY_POWER); org_kde_kwin_dpms_send_mode(resource, output->state.power ? ORG_KDE_KWIN_DPMS_MODE_ON : ORG_KDE_KWIN_DPMS_MODE_OFF); org_kde_kwin_dpms_send_done(resource); } static const struct org_kde_kwin_dpms_manager_interface kde_dpms_manager_impl = { .get = kde_dpms_manager_get, }; static void kde_dpms_manager_handle_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static void kde_dpms_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &org_kde_kwin_dpms_manager_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &kde_dpms_manager_impl, management, kde_dpms_manager_handle_resource_destroy); wl_list_insert(&management->dpms_manager.resources, wl_resource_get_link(resource)); } bool kde_output_management_create(struct server *server) { management = calloc(1, sizeof(*management)); if (!management) { return false; } management->display = server->display; wl_list_init(&management->resources); wl_list_init(&management->output_devices); management->global = wl_global_create(server->display, &kde_output_management_v2_interface, OUTPUT_MANAGER_VERSION, management, kde_output_management_bind); if (!management->global) { free(management); return false; } management->display_destroy.notify = kde_output_management_handle_display_destroy; wl_display_add_destroy_listener(server->display, &management->display_destroy); management->server_destroy.notify = kde_output_management_handle_server_destroy; server_add_destroy_listener(server, &management->server_destroy); /* listener new_output signal */ management->new_output.notify = kde_output_management_handle_new_output; kywc_output_add_new_listener(&management->new_output); /* kde_primary_output_v1 support */ management->primary_output.global = wl_global_create(server->display, &kde_primary_output_v1_interface, KDE_PRIMARY_OUTPUT_VERSION, management, kde_primary_output_bind); if (management->primary_output.global) { wl_list_init(&management->primary_output.resources); /* listener primary_output signal */ management->primary_output.primary_output.notify = kde_output_management_handle_primary_output; kywc_output_add_primary_listener(&management->primary_output.primary_output); } else { kywc_log(KYWC_WARN, "Failed to create %s global", kde_primary_output_v1_interface.name); } /* org_kde_kwin_dpms_manager support */ management->dpms_manager.global = wl_global_create(server->display, &org_kde_kwin_dpms_manager_interface, ORG_KDE_KWIN_DPMS_MANAGER_VERSION, management, kde_dpms_manager_bind); if (management->dpms_manager.global) { wl_list_init(&management->dpms_manager.resources); } else { kywc_log(KYWC_WARN, "Failed to create %s global", org_kde_kwin_dpms_manager_interface.name); } return true; } kylin-wayland-compositor/src/output/configure.c0000664000175000017500000006005515160461067020747 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "backend/drm.h" #include "effect/effect.h" #include "output_p.h" #include "render/pass.h" #include "theme.h" #include "util/macros.h" #define COLORTEMP_CLAMP(val) ((val) < 1000 ? 6500 : ((val) > 25000 ? 25000 : (val))) #define BRIGHTNESS_CLAMP(val) ((val) > 100 ? 100 : (val)) static struct kywc_output_mode *output_preferred_mode(struct kywc_output *kywc_output) { struct kywc_output_mode *mode; wl_list_for_each(mode, &kywc_output->prop.modes, link) { if (mode->preferred) { return mode; } } // No preferred mode, choose the first one return wl_container_of(kywc_output->prop.modes.prev, mode, link); } static float get_screen_factor(float phys_size_inch) { if (phys_size_inch < 12.0) { // Tablets (screnn_size < 12 inches) return 20.0; } else if (phys_size_inch < 20.0) { // Laptop internal displays (12 ≤ screen_size < 20 inches) return 24.5; } else { return 28.0; // External monitors (screen_size ≥ 20 inches) } } static float get_scale_level(float scale_factor, int width, int height) { if (scale_factor < 1.2) { return 1.0; } else if (scale_factor < 1.43) { return 1.25; } else if (scale_factor < 1.78) { return (height <= 1200 || width <= 1920) ? 1.25 : 1.5; } else { return 2.0; } } float output_preferred_scale(struct kywc_output *kywc_output, int width, int height) { float phys_width = kywc_output->prop.phys_width; float phys_height = kywc_output->prop.phys_height; if (phys_width == 0 || phys_height == 0) { return width >= 2160 ? 1.25 : (width >= 2000 ? 1.25 : 1.0); } float phys_inch = sqrtf(pow(phys_width, 2) + pow(phys_height, 2)) / 25.4; float factor = get_screen_factor(phys_inch); float ppi = sqrtf(pow(width, 2) + pow(height, 2)) / phys_inch; float scale_factor = ppi * factor / (96.0 * 28.0); double scale = get_scale_level(scale_factor, width, height); kywc_log(KYWC_DEBUG, "Output %s(%d x %d %f inch %f ppi) scale = %f", kywc_output->name, width, height, phys_inch, ppi, scale_factor); return scale; } void output_fill_preferred_state(struct output *output) { struct kywc_output *kywc_output = &output->base; struct kywc_output_state *pending = &output->pending_state; *pending = output->base.state; pending->enabled = pending->power = true; /* use the preferred mode */ struct kywc_output_mode *mode = output_preferred_mode(kywc_output); pending->width = mode->width; pending->height = mode->height; pending->refresh = mode->refresh; /* use the preferred scale */ pending->scale = output_preferred_scale(kywc_output, pending->width, pending->height); /* fallback output position is (0, 0) */ if (output_manager->fallback_output == kywc_output) { pending->lx = pending->ly = 0; } else { /* use auto position from layout */ pending->lx = pending->ly = -1; } pending->transform = WL_OUTPUT_TRANSFORM_NORMAL; pending->brightness = pending->brightness == 0 ? 100 : CLAMP(pending->brightness, 10, 100); pending->color_temp = pending->color_temp == 0 ? 6500 : CLAMP(pending->color_temp, 1000, 25000); } static bool fallback_output_fill_state(bool enabled, struct output *mirror) { struct kywc_output *kywc_output = output_manager->fallback_output; /* no fallback output or still be disabled */ if (!kywc_output || (!kywc_output->state.enabled && !enabled)) { return false; } /* early return if disable the fallback output */ struct output *output = output_from_kywc_output(kywc_output); if (!enabled) { output->pending_state.enabled = false; return true; } if (!mirror) { output_fill_preferred_state(output); return true; } struct kywc_output_state *state = &output->pending_state; *state = mirror->base.state; /* fixup the state */ state->enabled = state->power = true; state->lx = state->ly = 0; /* try to add this mode to fallback output */ struct wlr_output_mode mode = { .width = state->width, .height = state->height, .refresh = state->refresh, }; output_add_mode(output, &mode, true); return true; } static void output_set_primary(struct kywc_output *kywc_output) { assert(kywc_output); if (output_manager->primary_output == kywc_output) { return; } output_manager->primary_output = kywc_output; kywc_log(KYWC_INFO, "Primary output is changed to %s", kywc_output->name); wl_signal_emit_mutable(&output_manager->events.primary_output, kywc_output); } void output_manager_set_pending_primary(struct output *output) { struct kywc_output *kywc_output = &output->base; output_manager->pending_primary = kywc_output; } void output_manager_add_pending_state(struct output *output, struct kywc_output_state *state) { if (state != &output->pending_state) { output->pending_state = *state; } wl_list_remove(&output->configure_link); wl_list_insert(&output_manager->configure_outputs, &output->configure_link); if (!output->base.destroying) { output_log_state(KYWC_INFO, output, "pending", state); } } static void clear_pending_configures(void) { struct output *output, *tmp; wl_list_for_each_safe(output, tmp, &output_manager->configure_outputs, configure_link) { wl_list_remove(&output->configure_link); wl_list_init(&output->configure_link); } assert(wl_list_empty(&output_manager->configure_outputs)); output_manager->pending_primary = NULL; } static void output_manager_emit_configured(enum configure_type type) { struct configure_event event = { .type = type }; wl_signal_emit_mutable(&output_manager->events.configured, &event); } static void output_find_best_mode(struct wlr_output *wlr_output, int32_t width, int32_t height, int32_t refresh, struct wlr_output_mode **best) { struct wlr_output_mode *m; wl_list_for_each(m, &wlr_output->modes, link) { if (width != m->width || height != m->height) { continue; } if (refresh == m->refresh) { *best = m; break; } if (!*best || m->refresh > (*best)->refresh) { *best = m; } } } static void output_state_set_mode(struct output *output, struct wlr_output_state *wlr_state) { struct kywc_output_state *state = &output->pending_state; struct wlr_output *wlr_output = output->wlr_output; struct wlr_output_mode *best = NULL; if (state->width <= 0 || state->height <= 0) { kywc_log(KYWC_INFO, "Set preferred mode as no config found"); best = wlr_output_preferred_mode(wlr_output); } else { output_find_best_mode(wlr_output, state->width, state->height, state->refresh, &best); } if (best) { wlr_output_state_set_mode(wlr_state, best); } else { wlr_output_state_set_custom_mode(wlr_state, state->width, state->height, state->refresh); } if (wlr_output_test_state(wlr_output, wlr_state)) { return; } kywc_log(KYWC_ERROR, "Mode rejected, falling back to another mode"); struct wlr_output_mode *mode; wl_list_for_each(mode, &wlr_output->modes, link) { if (best && best == mode) { continue; } wlr_output_state_set_mode(wlr_state, mode); if (wlr_output_test_state(wlr_output, wlr_state)) { break; } } } static void output_acquire_buffer(struct output *output, struct wlr_output_state *wlr_state) { struct wlr_output *wlr_output = output->wlr_output; struct wlr_buffer *buffer = wlr_swapchain_acquire(wlr_output->swapchain); if (!buffer) { return; } char *env = getenv("KYWC_OUTPUT_FILL_WALLPAPER"); bool filled_with_wallpaper = env && strcmp(env, "1") == 0; int32_t color = 0; struct wlr_buffer *image = NULL; if (filled_with_wallpaper) { image = theme_manager_get_background(&color); if (!image && color < 0) { color = 0; // fallback to empty } else { struct effect *effect = effect_by_name("wallpaper"); if (effect) { effect_set_enabled(effect, true); } } } struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(wlr_output->renderer, buffer, NULL); if (!pass) { wlr_buffer_unlock(buffer); return; } if (image) { struct wlr_texture *tex = wlr_texture_from_buffer(wlr_output->renderer, image); ky_render_pass_add_texture( pass, &(struct ky_render_texture_options){ .base = { .dst_box = (struct wlr_box){ 0, 0, wlr_output->swapchain->width, wlr_output->swapchain->height }, .texture = tex, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, } }); wlr_texture_destroy(tex); } else { ky_render_pass_add_rect( pass, &(struct ky_render_rect_options){ .base = { .color = { ((color >> 16) & 0xff) / 255.0, ((color >> 8) & 0xff) / 255.0, (color & 0xff) / 255.0, 1.0 }, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, } }); } ky_render_pass_submit(pass, output->quirks); wlr_output_state_set_buffer(wlr_state, buffer); wlr_buffer_unlock(buffer); } static bool output_set_state(struct output *output) { struct wlr_output *wlr_output = output->wlr_output; struct kywc_output_state *state = &output->pending_state; if (output->base.destroying) { output_set_layout(output); output_update_state(output); return true; } uint32_t changes = output_get_state_changes(output, state); bool state_changed = (changes & (KYWC_OUTPUT_STATE_ENABLE | KYWC_OUTPUT_STATE_POWER)) || !output->manager->initial_configured; bool mode_changed = changes & KYWC_OUTPUT_STATE_MODE; bool params_changed = changes & (KYWC_OUTPUT_STATE_TRANSFORM | KYWC_OUTPUT_STATE_VRR_POLICY | KYWC_OUTPUT_STATE_SCALE | KYWC_OUTPUT_STATE_RGB_RANGE | KYWC_OUTPUT_STATE_OVERSCAN | KYWC_OUTPUT_STATE_SCALING_MODE); bool enabled = state->enabled && state->power; if (state_changed || mode_changed || params_changed) { struct wlr_output_state wlr_state; wlr_output_state_init(&wlr_state); if (state_changed) { wlr_output_state_set_enabled(&wlr_state, enabled); } if (enabled) { struct wlr_swapchain *old_swapchain = wlr_output->swapchain; if (mode_changed) { output_state_set_mode(output, &wlr_state); } else if (output->wlr_output->enabled && wlr_output_is_drm(wlr_output) && output->wlr_output->width == state->width && output->wlr_output->height == state->height && output->scene_commit) { output->has_pending = true; kywc_log(KYWC_WARN, "Drm output commit need waiting for pageflip"); return true; } wlr_output_configure_primary_swapchain(wlr_output, &wlr_state, &wlr_output->swapchain); if (old_swapchain != wlr_output->swapchain && wlr_output->swapchain) { output_acquire_buffer(output, &wlr_state); } wlr_output_set_color_changed(wlr_output, enabled); wlr_output_state_set_transform(&wlr_state, state->transform); wlr_output_state_set_scale(&wlr_state, state->scale); wlr_output_set_rgb_range(wlr_output, state->rgb_range); wlr_output_set_overscan(wlr_output, state->overscan); wlr_output_set_scaling_mode(wlr_output, state->scaling_mode); } if (output->scene_commit) { output->has_pending = false; } if (!wlr_output_commit_state(wlr_output, &wlr_state)) { kywc_log_ratelimited(KYWC_ERROR, "Failed to commit output: %s", wlr_output->name); wlr_output_state_finish(&wlr_state); return false; } wlr_output_state_finish(&wlr_state); } bool color_feature_changed = changes & (KYWC_OUTPUT_STATE_POWER | KYWC_OUTPUT_STATE_COLOR_FEATURE); bool color_changed = color_feature_changed || changes & (KYWC_OUTPUT_STATE_POWER | KYWC_OUTPUT_STATE_COLOR_TEMP | KYWC_OUTPUT_STATE_BRIGHTNESS | KYWC_OUTPUT_STATE_COLOR_FILTER); if (enabled && color_changed) { wlr_output_set_color_changed(wlr_output, color_changed); bool hardware_color = wlr_output_is_hardware_color_allowed(output->wlr_output, state); if (color_feature_changed || output->hardware_color != hardware_color || !hardware_color) { if (output->scene_output) { ky_scene_output_damage_whole(output->scene_output); } } else { output_schedule_frame(output->wlr_output); } output->hardware_color = hardware_color; } output->color_temp = state->color_temp; output->brightness = state->brightness; output->color_feature = state->color_feature; output->color_filter = state->color_filter; output->vrr_policy = state->vrr_policy; output_set_layout(output); output_update_state(output); return true; } static inline bool output_is_actual_valid(struct kywc_output *output) { return output && !output->destroying && output->state.enabled && output != output_manager->fallback_output; } bool output_manager_configure_outputs(struct output *destroying_output) { if (wl_list_empty(&output_manager->configure_outputs)) { return true; } if (output_manager->server->terminate) { if (destroying_output && destroying_output->base.state.enabled) { output_set_state(destroying_output); } clear_pending_configures(); return false; } /* 1. check configs */ if (!output_manager->pending_primary && output_is_actual_valid(output_manager->primary_output)) { output_manager->pending_primary = output_manager->primary_output; kywc_log(KYWC_DEBUG, "Keep primary output to %s", output_manager->primary_output->name); } struct kywc_output *kywc_output = NULL; struct output *output = NULL, *pending_output = NULL; /* primary output may be disabled, fixup it */ if (output_manager->pending_primary) { output = output_from_kywc_output(output_manager->pending_primary); bool has_configure = !wl_list_empty(&output->configure_link); if ((!has_configure && !output->base.state.enabled) || (has_configure && !output->pending_state.enabled)) { output_manager->pending_primary = NULL; } } /* if all outputs will be disabled in config, find others not in config */ bool have_enabled_output = false, have_zero_coord = false; bool have_actual_output = output_manager_has_actual_outputs(); wl_list_for_each(output, &output_manager->outputs, link) { kywc_output = &output->base; /* skip fallback output if has actual output */ if (have_actual_output && kywc_output == output_manager->fallback_output) { continue; } if (!wl_list_empty(&output->configure_link)) { pending_output = output; have_enabled_output |= output->pending_state.enabled; have_zero_coord |= output->pending_state.enabled && output->pending_state.lx == 0 && output->pending_state.ly == 0; } else { have_enabled_output |= kywc_output->state.enabled; have_zero_coord |= kywc_output->state.enabled && kywc_output->state.lx == 0 && kywc_output->state.ly == 0; } /* don't set primary to fallback output if has actaul enabled output */ if (have_actual_output && have_enabled_output && output_manager->pending_primary == output_manager->fallback_output) { output_manager->pending_primary = NULL; } /* fixup primary output to the (pending)enabled output */ if (have_enabled_output && !output_manager->pending_primary) { kywc_log(KYWC_DEBUG, "Fixup primary output to %s", output->base.name); output_manager->pending_primary = kywc_output; } if (have_enabled_output && have_zero_coord) { break; } } /* have one actual output and coord is not zero */ if (!have_zero_coord && pending_output) { int output_num = wl_list_length(&output_manager->outputs); if (output_num == 1 || (output_num == 2 && output_manager->fallback_output)) { kywc_log(KYWC_DEBUG, "Fixup output %s coord to zero", pending_output->base.name); pending_output->pending_state.lx = pending_output->pending_state.ly = 0; } } bool ret = false; /* don't reject when the only one output plug-out */ if (have_actual_output && !have_enabled_output) { kywc_log(KYWC_WARN, "All outputs will be disabled, reject this configuration"); goto finish; } /* 2. config outputs */ /* call kywc_output_set_state in all pending outputs, enable first and disable outputs later */ wl_list_for_each(output, &output_manager->configure_outputs, configure_link) { if (!output->pending_state.enabled) { continue; } if (!output->base.state.enabled) { continue; } if (!output_set_state(output)) { goto finish; } } wl_list_for_each(output, &output_manager->configure_outputs, configure_link) { if (!output->pending_state.enabled) { continue; } if (output->base.state.enabled) { continue; } if (have_actual_output && output_manager->fallback_output == &output->base) { kywc_log(KYWC_INFO, "Reject enable fallback output if have actual output"); continue; } if (!output_set_state(output)) { goto finish; } } /* should enable fallback output if still no primary output */ if (!output_manager->pending_primary && fallback_output_fill_state(true, destroying_output)) { output_set_state(output_from_kywc_output(output_manager->fallback_output)); output_manager->pending_primary = output_manager->fallback_output; kywc_log(KYWC_DEBUG, "Auto enable fallback output"); } /* switch the primary before output disable */ output_set_primary(output_manager->pending_primary); wl_list_for_each(output, &output_manager->configure_outputs, configure_link) { if (output->pending_state.enabled) { continue; } if (!output->base.state.enabled && output->manager->initial_configured) { continue; } if (!output_set_state(output)) { goto finish; } } ret = true; finish: /* make sure primary output is configured even if above set_state failed */ if (!output_manager->pending_primary || !output_manager->pending_primary->state.enabled) { output_manager->pending_primary = NULL; if (output_is_actual_valid(output_manager->primary_output)) { output_manager->pending_primary = output_manager->primary_output; } else { wl_list_for_each(output, &output_manager->outputs, link) { kywc_output = &output->base; if (output_is_actual_valid(kywc_output)) { output_manager->pending_primary = kywc_output; break; } } } if (!output_manager->pending_primary && fallback_output_fill_state(true, destroying_output)) { output_set_state(output_from_kywc_output(output_manager->fallback_output)); output_manager->pending_primary = output_manager->fallback_output; kywc_log(KYWC_DEBUG, "Auto enable fallback output"); } } output_set_primary(output_manager->pending_primary); /* disable the fallback when an actual enabled output exists */ if (output_manager->primary_output != output_manager->fallback_output && fallback_output_fill_state(false, NULL)) { output_set_state(output_from_kywc_output(output_manager->fallback_output)); kywc_log(KYWC_DEBUG, "Auto disable fallback output"); } /* make sure destroying output is disabled even if above set_state failed */ if (destroying_output && destroying_output->base.state.enabled) { output_set_state(destroying_output); } output_manager_emit_configured(output_manager->initial_configured ? CONFIGURE_TYPE_UPDATE : CONFIGURE_TYPE_INIT); clear_pending_configures(); return ret; } bool output_set_color_feature(struct kywc_output *kywc_output, enum kywc_output_color_feature color_feature) { if (!kywc_output->state.enabled) { return false; } if (kywc_output->state.color_feature == color_feature) { return true; } struct output *output = output_from_kywc_output(kywc_output); output->pending_state = output->base.state; output->pending_state.color_feature = color_feature; output_set_state(output); return true; } bool output_set_color_filter(struct kywc_output *kywc_output, enum kywc_output_color_filter color_filter) { if (!kywc_output->state.enabled) { return false; } if (kywc_output->state.color_filter == color_filter) { return true; } struct output *output = output_from_kywc_output(kywc_output); output->pending_state = output->base.state; output->pending_state.color_filter = color_filter; output_set_state(output); return true; } bool output_set_color_temp(struct kywc_output *kywc_output, uint32_t color_temp) { if (!kywc_output->state.enabled) { return false; } color_temp = COLORTEMP_CLAMP(color_temp); if (kywc_output->state.color_temp == color_temp) { return true; } struct output *output = output_from_kywc_output(kywc_output); output->pending_state = output->base.state; output->pending_state.color_temp = color_temp; output_set_state(output); return true; } bool output_set_brightness(struct kywc_output *kywc_output, uint32_t brightness) { if (!kywc_output->state.enabled) { return false; } brightness = BRIGHTNESS_CLAMP(brightness); if (kywc_output->state.brightness == brightness) { return true; } struct output *output = output_from_kywc_output(kywc_output); output->pending_state = output->base.state; output->pending_state.brightness = brightness; output_set_state(output); return true; } bool output_set_power(struct kywc_output *kywc_output, bool power) { if (!kywc_output->state.enabled) { return false; } if (kywc_output->state.power == power) { return true; } struct output *output = output_from_kywc_output(kywc_output); output->pending_state = output->base.state; output->pending_state.power = power; output_set_state(output); output_manager_emit_configured(CONFIGURE_TYPE_NONE); return true; } void output_manager_power_outputs(bool power) { bool have_powered_output = false; struct output *output; wl_list_for_each(output, &output_manager->outputs, link) { if (!output->base.state.enabled || output->base.state.power == power) { continue; } output->pending_state = output->base.state; output->pending_state.power = power; output_set_state(output); have_powered_output = true; } if (have_powered_output) { output_manager_emit_configured(CONFIGURE_TYPE_NONE); } } kylin-wayland-compositor/src/theme/0000775000175000017500000000000015160461067016356 5ustar fengfengkylin-wayland-compositor/src/theme/theme_p.h0000664000175000017500000000734015160460057020152 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _THEME_P_H_ #define _THEME_P_H_ #include "theme.h" #define FALLBACK_ICON_THEME_NAME "hicolor" struct theme_button_buffer { struct wl_list link; struct wlr_buffer *buffer; float scale; int button_width; }; struct widget_theme { const char *name; enum theme_type type; bool builtin; /* border color */ float active_border_color[4]; float inactive_border_color[4]; /* background color */ float active_bg_color[4]; float inactive_bg_color[4]; /* text color */ float active_text_color[4]; float inactive_text_color[4]; /* shadow color */ struct theme_shadow active_shadow_color; struct theme_shadow inactive_shadow_color; struct theme_shadow modal_active_shadow_color; struct theme_shadow modal_inactive_shadow_color; struct theme_shadow menu_shadow_color; /* default accent color, may override by global */ float accent_color[4]; /* modal mask color */ float modal_mask_color[4]; /* state color */ struct theme_gradient normal_state_color; struct theme_gradient hover_state_color; struct theme_gradient click_state_color; /** * minimize, maximize, restore and close * in different state: active/inactive, hover, click */ const char *button_svg; size_t button_svg_size; }; struct global_theme { /* font config */ char *font_name; int32_t font_size; /* default to -1 */ int32_t accent_color; int32_t window_radius; int32_t menu_radius; int32_t opacity; enum theme_type type; }; struct theme_manager { struct theme theme; struct global_theme global; struct widget_theme *(*load_widget_theme)(const char *name, enum theme_type type); struct { char *picture; uint32_t options; int32_t color; // invalid if < 0 struct wlr_buffer *buffer; } background; struct { struct wl_signal pre_update; struct wl_signal update; struct wl_signal icon_update; struct wl_signal background_update; } events; struct config *config; struct wl_list fallback_icon; struct icon_manager *icon; struct { bool (*set_icon_theme)(struct icon_manager *manager, const char *name); struct icon *(*get_icon)(struct icon_manager *manager, const char *name); const char *(*get_icon_name)(struct icon *icon); struct wlr_buffer *(*get_icon_buffer)(struct icon *icon, int size, float scale); } icon_impl; struct server *server; struct wl_listener server_ready; struct wl_listener server_destroy; }; bool theme_manager_config_init(struct theme_manager *manager); void theme_manager_read_config(struct theme_manager *manager); void theme_manager_write_config(struct theme_manager *manager); const char *theme_manager_read_icon_config(struct theme_manager *manager); void theme_manager_write_icon_config(struct theme_manager *manager, const char *name); #if 1 // HAVE_THEME_ICON struct icon_manager *icon_manager_create(struct theme_manager *manager); #else static __attribute__((unused)) inline struct icon_manager * icon_manager_create(struct theme_manager *manager) { return NULL; } #endif #if HAVE_UKUI_THEME bool ukui_theme_manager_create(struct theme_manager *theme_manager); #else static __attribute__((unused)) inline bool ukui_theme_manager_create(struct theme_manager *theme_manager) { return false; } #endif const char *theme_manager_read_background_config(struct theme_manager *manager, uint32_t *options, int32_t *color); void theme_manager_write_background_config(struct theme_manager *manager); #endif /* _THEME_P_H */ kylin-wayland-compositor/src/theme/meson.build0000664000175000017500000000023615160460057020517 0ustar fengfengwlcom_sources += files( 'config.c', 'icon.c', 'theme.c', ) if have_ukui_theme wlcom_sources += files( 'ukui_theme.c', ) endif subdir('icons') kylin-wayland-compositor/src/theme/icons/0000775000175000017500000000000015160460057017467 5ustar fengfengkylin-wayland-compositor/src/theme/icons/meson.build0000664000175000017500000000046615160460057021637 0ustar fengfengsvgs = [ 'base_light.svg', 'base_dark.svg', ] foreach name : svgs output = name.underscorify() + '_src.h' var = name.underscorify() + '_src' wlcom_sources += custom_target( output, command: [embed, var], input: name, output: output, feed: true, capture: true, ) endforeach kylin-wayland-compositor/src/theme/icons/base_dark.svg0000664000175000017500000004700215160460057022126 0ustar fengfeng kylin-wayland-compositor/src/theme/icons/base_light.svg0000664000175000017500000004651015160460057022317 0ustar fengfeng kylin-wayland-compositor/src/theme/icon.c0000664000175000017500000007735315160461067017471 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "painter.h" #include "server.h" #include "theme_p.h" #include "util/file.h" #include "util/hash_table.h" #include "util/queue.h" #include "util/string.h" #define ICONPATH "~/.icons:~/.local/share/icons:/usr/share/icons:" EXTRA_ICON_PATH #define APPPATH \ "~/.local/share/applications:/usr/local/share/applications:/usr/share/applications:/etc/xdg/" \ "autostart:" EXTRA_APPS_PATH #define PIXMAPPATH "/usr/share/pixmaps:" EXTRA_PIXMAP_PATH // TODO: IN_DELETE support enum icon_type { ICON_TYPE_UNKNOWN = 0, ICON_TYPE_SVG = 1 << 0, ICON_TYPE_PNG = 1 << 1, ICON_TYPE_XPM = 1 << 2, ICON_TYPE_ICO = 1 << 3, }; struct icon_buffer { struct wl_list link; struct wlr_buffer *buffer; int size; float scale; }; struct icon_entry { struct wl_list link; char *path; int size, scale; }; struct icon { struct icon_theme *theme; const char *name; struct wl_signal destroy; struct wl_list buffers; struct wl_list entries; char *svg_path, *svg_data; size_t svg_data_size; char *png_path; // no size hint char *xpm_path; char *ico_path; uint32_t types; uint32_t hash; }; struct icon_theme { struct wl_list link; struct icon_manager *manager; const char *name; // may be NULL size_t name_size; struct wl_array parents; struct hash_table *icons; }; struct desktop { struct wl_list link; const char *name; char *icon_name; char *startup_name; char *app_name; char *exec_name; }; struct icon_pair { const char *name; struct icon *icon; // remove from hash table if icon destroyed struct wl_listener icon_destroy; uint32_t hash; }; struct watch_fd { char *path; char *subpath; int fd; void (*handler)(struct watch_fd *wd, const struct inotify_event *event, void *data); void *data; }; struct icon_manager { struct server *server; struct wl_listener display_destroy; struct wl_listener server_destroy; struct wl_list themes; struct wl_list desktops; struct hash_table *icon_pairs; struct icon_theme *current; struct icon_theme *fallback; // special theme for pixmap icons struct icon_theme *pixmap; // specific path icons struct icon_theme *specific; struct { struct wl_event_source *source; struct hash_table *wds; int event_fd; } watcher; struct queue_fence fence; }; static struct icon_theme *icon_theme_get_or_create(struct icon_manager *manager, const char *name); static void icon_manager_add_watcher_fd( struct icon_manager *manager, const char *path, const char *subpath, void (*handler)(struct watch_fd *wd, const struct inotify_event *event, void *data), void *data); static bool load_icon(const char *fullpath, const char *subpath, const char *name, void *data); static size_t desktop_verify_name(const char *name) { int size = (int)strlen(name) - 8; if (size > 0 && strcmp(name + size, ".desktop") == 0) { return size; } return 0; } static bool icon_manager_add_desktop(struct icon_manager *manager, const char *fullpath, const char *name, size_t size) { struct desktop *desktop; // no need to check for duplicate desktops #if 0 wl_list_for_each(desktop, &manager->desktops, link) { if (strncmp(desktop->name, name, size) == 0) { return false; } } #endif desktop = calloc(1, sizeof(*desktop)); if (!desktop) { return false; } struct file *file = file_open(fullpath, "\n", "="); if (!file) { free(desktop); return false; } desktop->icon_name = file_get_value_copy(file, "Icon"); if (!desktop->icon_name || !*desktop->icon_name) { free(desktop->icon_name); free(desktop); file_close(file); return false; } desktop->name = strndup(name, size); desktop->startup_name = file_get_value_copy(file, "StartupWMClass"); desktop->app_name = file_get_value_copy(file, "Name"); desktop->exec_name = file_get_value_copy(file, "Exec"); if (desktop->exec_name) { string_strip_path(desktop->exec_name); } wl_list_insert(manager->desktops.prev, &desktop->link); file_close(file); return false; } static void handle_desktop(struct watch_fd *wd, const struct inotify_event *event, void *data) { if (event->mask & IN_ISDIR) { return; } size_t size = desktop_verify_name(event->name); if (size == 0) { return; } char *fullpath = string_join_path(wd->path, NULL, event->name); if (fullpath) { icon_manager_add_desktop(data, fullpath, event->name, size); free(fullpath); } } static bool load_desktop(const char *fullpath, const char *subpath, const char *name, void *data) { struct icon_manager *manager = data; if (name == DIR_NOT_EXIST) { kywc_log(KYWC_INFO, "No path %s", fullpath); return false; } if (name) { size_t size = desktop_verify_name(name); if (size > 0) { icon_manager_add_desktop(manager, fullpath, name, size); } return false; } icon_manager_add_watcher_fd(manager, fullpath, NULL, handle_desktop, manager); return false; } static char *icon_theme_is_valid(const char *name) { char *realpath = string_expand_path(ICONPATH); if (!realpath) { return NULL; } size_t len = 0; char **paths = string_split(realpath, ":", &len); if (!paths) { free(realpath); return NULL; } char *fullpath = NULL; for (size_t i = 0; i < len; i++) { fullpath = string_join_path(paths[i], name, "index.theme"); if (!fullpath) { continue; } if (file_exists(fullpath)) { break; } free(fullpath); fullpath = NULL; } string_free_split(paths); free(realpath); return fullpath; } static bool icon_theme_load_parents(struct icon_theme *theme, const char *path) { struct file *file = file_open(path, "\n", "="); if (!file) { return false; } const char *parents = file_get_value(file, "Inherits"); if (!parents) { file_close(file); return false; } size_t len = 0; char **split = string_split(parents, ",", &len); for (size_t i = 0; i < len; i++) { char *parent = split[i]; // skip fallback theme if (strcmp(parent, FALLBACK_ICON_THEME_NAME) == 0) { continue; } struct icon_theme *parent_theme = icon_theme_get_or_create(theme->manager, parent); if (!parent_theme) { continue; } struct icon_theme **ptr = wl_array_add(&theme->parents, sizeof(*ptr)); *ptr = parent_theme; } string_free_split(split); file_close(file); return true; } static enum icon_type icon_get_type(const char *name, char **body) { int index = (int)strlen(name) - 4; if (index <= 0) { return ICON_TYPE_UNKNOWN; } enum icon_type type = ICON_TYPE_UNKNOWN; const char *suffix = name + index; if (strcasecmp(suffix, ".svg") == 0) { type = ICON_TYPE_SVG; } else if (strcasecmp(suffix, ".png") == 0) { type = ICON_TYPE_PNG; } else if (strcasecmp(suffix, ".xpm") == 0) { type = ICON_TYPE_XPM; } else if (strcasecmp(suffix, ".ico") == 0) { type = ICON_TYPE_ICO; } else { return type; } if (body) { *body = *name == '/' ? strdup(name) : strndup(name, index); } return type; } static bool icon_add_image(struct icon *icon, const char *path, enum icon_type type) { if (type == ICON_TYPE_UNKNOWN) { return false; } if (type != ICON_TYPE_PNG) { // skip if already have this type image if (icon->types & type) { return false; } if (type == ICON_TYPE_SVG) { icon->svg_path = strdup(path); } else if (type == ICON_TYPE_XPM) { icon->xpm_path = strdup(path); } else if (type == ICON_TYPE_ICO) { icon->ico_path = strdup(path); } icon->types |= type; return true; } // png image left int size = 0, scale = 1; const char *subpath = NULL; if (icon->theme->name) { subpath = strstr(path, icon->theme->name); if (subpath) { // skip name and '/' subpath += icon->theme->name_size + 1; } }; if (subpath && sscanf(subpath, "%dx%*d@%d", &size, &scale) > 0) { struct icon_entry *entry; wl_list_for_each(entry, &icon->entries, link) { if (entry->size == size && entry->scale == scale) { return false; } } entry = calloc(1, sizeof(*entry)); if (entry) { entry->size = size; entry->scale = scale; entry->path = strdup(path); wl_list_insert(&icon->entries, &entry->link); } } else { if (icon->types & type) { return false; } icon->png_path = strdup(path); } icon->types |= type; return true; } static void icon_destroy(struct icon *icon) { if (!icon) { return; } wl_signal_emit_mutable(&icon->destroy, NULL); assert(wl_list_empty(&icon->destroy.listener_list)); struct icon_buffer *buf, *tmp; wl_list_for_each_safe(buf, tmp, &icon->buffers, link) { wl_list_remove(&buf->link); wlr_buffer_drop(buf->buffer); free(buf); } struct icon_entry *entry, *entry_tmp; wl_list_for_each_safe(entry, entry_tmp, &icon->entries, link) { wl_list_remove(&entry->link); free(entry->path); free(entry); } hash_table_remove_hash(icon->theme->icons, icon->hash, icon->name); free((void *)icon->name); free(icon->xpm_path); free(icon->svg_path); free(icon->png_path); free(icon->svg_data); free(icon->ico_path); free(icon); } static struct icon *icon_theme_add_icon(struct icon_theme *theme, const char *fullpath, enum icon_type type, const char *name) { struct icon *icon = NULL; struct hash_entry *entry = hash_table_search(theme->icons, name); if (entry) { icon = entry->data; } if (!icon) { icon = calloc(1, sizeof(*icon)); if (!icon) { return NULL; } wl_list_init(&icon->buffers); wl_list_init(&icon->entries); wl_signal_init(&icon->destroy); icon->name = name; icon->theme = theme; // store the hash to skip duplicate hash calculation entry = hash_table_insert(theme->icons, icon->name, icon); icon->hash = entry->hash; } else { // name is not used if icon is already created free((void *)name); } icon_add_image(icon, fullpath, type); return icon; } static void handle_icon(struct watch_fd *wd, const struct inotify_event *event, void *data) { struct icon_theme *theme = data; if (event->mask & IN_ISDIR) { if (!theme->name || strcmp(theme->name, FALLBACK_ICON_THEME_NAME) == 0) { file_for_each(wd->path, event->name, load_icon, theme); } return; } char *icon_name = NULL; enum icon_type type = icon_get_type(event->name, &icon_name); if (type == ICON_TYPE_UNKNOWN) { return; } char *fullpath = string_join_path(wd->path, NULL, event->name); if (!fullpath) { free(icon_name); return; } icon_theme_add_icon(theme, fullpath, type, icon_name); free(fullpath); } static void handle_hicolor(struct watch_fd *wd, const struct inotify_event *event, void *data) { if (!(event->mask & IN_ISDIR)) { return; } if (strcmp(event->name, wd->subpath) != 0) { return; } struct icon_theme *theme = data; file_for_each(wd->path, wd->subpath, load_icon, theme); } static bool load_icon(const char *fullpath, const char *subpath, const char *name, void *data) { struct icon_theme *theme = data; struct icon_manager *manager = theme->manager; if (name == DIR_NOT_EXIST) { kywc_log(KYWC_INFO, "No path %s or no %s in it", fullpath, subpath); if (subpath && strcmp(subpath, FALLBACK_ICON_THEME_NAME) == 0) { icon_manager_add_watcher_fd(manager, fullpath, subpath, handle_hicolor, theme); } return false; } if (name) { char *icon_name = NULL; enum icon_type type = icon_get_type(name, &icon_name); if (type != ICON_TYPE_UNKNOWN) { icon_theme_add_icon(theme, fullpath, type, icon_name); } return false; } if (subpath) { // filter subpath int size, scale = 1; int num = sscanf(subpath, "%dx%*d@%d", &size, &scale); #if 0 if (num >= 1 && (size < 24 || size > 128)) { return true; } #endif if (num >= 1 || strncmp(subpath, "scalable", 8) == 0) { char *slash = strchr(subpath, '/'); if (slash && !strstr(slash, "apps") && !strstr(slash, "categories") && !strstr(slash, "status") && !strstr(slash, "devices")) { return true; } } } // monitor pixmap and fallback icon theme if (!theme->name || strcmp(theme->name, FALLBACK_ICON_THEME_NAME) == 0) { icon_manager_add_watcher_fd(manager, fullpath, NULL, handle_icon, theme); } return false; } static struct icon_theme *icon_theme_create_empty(struct icon_manager *manager, const char *name) { struct icon_theme *theme = calloc(1, sizeof(*theme)); if (!theme) { return NULL; } wl_list_init(&theme->link); wl_array_init(&theme->parents); theme->manager = manager; theme->icons = hash_table_create_string(NULL); hash_table_set_max_entries(theme->icons, name ? 2000 : 200); if (name) { theme->name = strdup(name); theme->name_size = strlen(name); wl_list_insert(&manager->themes, &theme->link); } return theme; } static struct icon_theme *icon_manager_get_theme(struct icon_manager *manager, const char *name) { struct icon_theme *theme; wl_list_for_each(theme, &manager->themes, link) { if (strcmp(name, theme->name) == 0) { return theme; } } return NULL; } static struct icon_theme *icon_theme_get_or_create(struct icon_manager *manager, const char *name) { struct icon_theme *theme = icon_manager_get_theme(manager, name); if (theme) { return theme; } // check theme is valid char *theme_path = icon_theme_is_valid(name); if (!theme_path) { return NULL; } theme = icon_theme_create_empty(manager, name); if (!theme) { free(theme_path); return NULL; } icon_theme_load_parents(theme, theme_path); // only load the first theme in ICONPATH theme_path[strlen(theme_path) - strlen("index.theme") - 1] = '\0'; file_for_each(theme_path, NULL, load_icon, theme); free(theme_path); return theme; } static void icon_theme_destroy(struct icon_theme *theme) { if (!theme) { return; } struct hash_entry *entry; hash_table_for_each(entry, theme->icons) { icon_destroy(entry->data); } hash_table_destroy(theme->icons); wl_list_remove(&theme->link); wl_array_release(&theme->parents); free((void *)theme->name); free(theme); } static struct icon *icon_theme_get_icon(struct icon_theme *theme, const char *name) { if (!theme) { return NULL; } struct hash_entry *entry = hash_table_search(theme->icons, name); if (entry && entry->data) { return entry->data; } struct icon_theme **parent; wl_array_for_each(parent, &theme->parents) { struct icon *icon = icon_theme_get_icon(*parent, name); if (icon) { return icon; } } return NULL; } static struct icon *icon_manager_get_icon(struct icon_manager *manager, const char *name) { // try specific path icon if (*name == '/') { if (!file_exists(name)) { return NULL; } char *icon_name = NULL; enum icon_type type = icon_get_type(name, &icon_name); if (type == ICON_TYPE_UNKNOWN) { return NULL; } return icon_theme_add_icon(manager->specific, name, type, icon_name); } struct icon *icon = NULL; if (manager->current) { icon = icon_theme_get_icon(manager->current, name); } if (!icon) { icon = icon_theme_get_icon(manager->fallback, name); } if (!icon) { icon = icon_theme_get_icon(manager->pixmap, name); } return icon; } static const char *get_icon_name_from_desktop(struct icon_manager *manager, const char *name) { // TODO: optimize this struct desktop *desktop; wl_list_for_each(desktop, &manager->desktops, link) { if (strcasecmp(desktop->name, name) == 0 || (desktop->startup_name && strcasecmp(desktop->startup_name, name) == 0) || (desktop->app_name && strcasecmp(desktop->app_name, name) == 0)) { return desktop->icon_name; } } return NULL; } static const char *fuzzy_get_icon_name_from_desktop(struct icon_manager *manager, const char *name) { struct desktop *desktop, *available_desktop = NULL; wl_list_for_each(desktop, &manager->desktops, link) { if (desktop->exec_name && strcasecmp(desktop->exec_name, name) == 0) { // abandon this search if find multiple results if (available_desktop) { available_desktop = NULL; break; } available_desktop = desktop; } } return available_desktop ? available_desktop->icon_name : NULL; } static void pair_handle_icon_destroy(struct wl_listener *listener, void *data) { struct icon_pair *pair = wl_container_of(listener, pair, icon_destroy); wl_list_remove(&pair->icon_destroy.link); struct hash_table *table = pair->icon->theme->manager->icon_pairs; hash_table_remove_hash(table, pair->hash, pair->name); free((void *)pair->name); free(pair); } static void icon_manager_add_icon_pair(struct icon_manager *manager, const char *name, struct icon *icon) { struct icon_pair *pair = calloc(1, sizeof(*pair)); if (!pair) { return; } pair->name = strdup(name); pair->icon_destroy.notify = pair_handle_icon_destroy; wl_list_init(&pair->icon_destroy.link); if (icon) { pair->icon = icon; wl_signal_add(&icon->destroy, &pair->icon_destroy); } else { kywc_log(KYWC_DEBUG, "Cannot find icon for %s", name); } struct hash_entry *entry = hash_table_insert(manager->icon_pairs, pair->name, pair); pair->hash = entry->hash; } static void icon_manager_reset_icon_pairs(struct icon_manager *manager) { struct hash_entry *entry; hash_table_for_each(entry, manager->icon_pairs) { struct icon_pair *pair = entry->data; // don't remove icons in specific theme if (pair->icon && pair->icon->theme == manager->specific) { continue; } hash_table_remove(manager->icon_pairs, entry); wl_list_remove(&pair->icon_destroy.link); free((void *)pair->name); free(pair); } } struct theme_load_data { struct icon_theme *theme; char *theme_path; }; static void load_theme(void *job, void *gdata, int index) { struct theme_load_data *data = job; icon_theme_load_parents(data->theme, data->theme_path); data->theme_path[strlen(data->theme_path) - strlen("index.theme") - 1] = '\0'; file_for_each(data->theme_path, NULL, load_icon, data->theme); free(data->theme_path); free(data); } static bool set_icon_theme(struct icon_manager *manager, const char *name) { struct icon_theme *theme = manager->current; /* current icon_theme is not changed */ if (theme && strcmp(name, theme->name) == 0) { return false; } queue_fence_wait(&manager->fence); /* skip thread-load if server is started */ if (manager->server->start) { theme = icon_theme_get_or_create(manager, name); if (!theme) { return false; } goto out; } /* get theme that already loaded */ theme = icon_manager_get_theme(manager, name); if (theme) { goto out; } // check theme is valid char *theme_path = icon_theme_is_valid(name); if (!theme_path) { return false; } theme = icon_theme_create_empty(manager, name); if (!theme) { free(theme_path); return false; } struct theme_load_data *data = malloc(sizeof(*data)); data->theme = theme; data->theme_path = theme_path; // load all icons in theme if (!queue_add_job(manager->server->queue, data, &manager->fence, load_theme, NULL)) { load_theme(data, manager->server, -1); } out: manager->current = theme; icon_manager_reset_icon_pairs(manager); return true; } static struct icon *get_icon(struct icon_manager *manager, const char *name) { queue_fence_wait(&manager->fence); struct hash_entry *entry = hash_table_search(manager->icon_pairs, name); if (entry && entry->data) { struct icon_pair *pair = entry->data; // may be NULL if no icon found return pair->icon; } struct icon *icon = NULL; // find icon name from desktops const char *icon_name = get_icon_name_from_desktop(manager, name); if (icon_name) { icon = icon_manager_get_icon(manager, icon_name); } // fallback to use orig name if (!icon) { icon = icon_manager_get_icon(manager, name); } // fuzzy search desktops from exec name if (!icon) { icon_name = fuzzy_get_icon_name_from_desktop(manager, name); if (icon_name) { icon = icon_manager_get_icon(manager, icon_name); } } icon_manager_add_icon_pair(manager, name, icon); return icon; } static const char *get_icon_name(struct icon *icon) { return icon->name; } static struct wlr_buffer *get_icon_buffer(struct icon *icon, int size, float scale) { struct icon_buffer *buffer; wl_list_for_each(buffer, &icon->buffers, link) { // maybe ceil(buffer->scale) == ceil(scale) && buffer->width > size * scale if (buffer->size == size && buffer->scale == scale) { return buffer->buffer; } } const char *image_path = NULL, *image_data = NULL; size_t image_data_size = 0; if (icon->types & ICON_TYPE_PNG) { int target_scale = ceil(scale); int target_scale_size = size * target_scale; int scale_size, max_scale_size = 0, min_scale_size = 1024; struct icon_entry *similar_entry = NULL, *max_entry = NULL; struct icon_entry *entry; wl_list_for_each(entry, &icon->entries, link) { if (entry->size == size && entry->scale == target_scale) { image_path = entry->path; break; } scale_size = entry->size * entry->scale; if (scale_size >= target_scale_size && scale_size < min_scale_size) { min_scale_size = scale_size; similar_entry = entry; } if (scale_size > max_scale_size) { max_scale_size = scale_size; max_entry = entry; } } if (!image_path && (similar_entry || max_entry)) { // TODO: drop if the png size is much large than target image_path = similar_entry ? similar_entry->path : max_entry->path; } if (!image_path) { image_path = icon->png_path; } } if (!image_path && icon->types & ICON_TYPE_SVG) { // preload svg data if (!icon->svg_data) { struct file *file = file_open(icon->svg_path, NULL, NULL); if (file) { const char *data = file_get_data(file, &icon->svg_data_size); icon->svg_data = malloc(icon->svg_data_size); if (icon->svg_data) { memcpy(icon->svg_data, data, icon->svg_data_size); } file_close(file); } } image_data = icon->svg_data; image_data_size = icon->svg_data_size; } if (!image_path && !image_data) { if (icon->ico_path) { image_path = icon->ico_path; } else if (icon->xpm_path) { image_path = icon->xpm_path; } else { return NULL; } } buffer = calloc(1, sizeof(*buffer)); if (!buffer) { return NULL; } struct draw_info info = { .width = size, .height = size, .scale = scale, .image = image_path, .svg = { image_data, image_data_size }, }; buffer->buffer = painter_draw_buffer(&info); if (!buffer->buffer) { free(buffer); return NULL; } buffer->size = size; buffer->scale = scale; wl_list_insert(&icon->buffers, &buffer->link); return buffer->buffer; } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct icon_manager *manager = wl_container_of(listener, manager, server_destroy); wl_list_remove(&manager->server_destroy.link); struct desktop *desktop, *desktop_tmp; wl_list_for_each_safe(desktop, desktop_tmp, &manager->desktops, link) { wl_list_remove(&desktop->link); free((void *)desktop->name); free(desktop->icon_name); free(desktop->exec_name); free(desktop->startup_name); free(desktop->app_name); free(desktop); } icon_theme_destroy(manager->pixmap); icon_theme_destroy(manager->specific); struct icon_theme *theme, *theme_tmp; wl_list_for_each_safe(theme, theme_tmp, &manager->themes, link) { icon_theme_destroy(theme); } struct hash_entry *entry; hash_table_for_each(entry, manager->icon_pairs) { struct icon_pair *pair = entry->data; free((void *)pair->name); free(pair); } hash_table_destroy(manager->icon_pairs); queue_fence_finish(&manager->fence); free(manager); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct icon_manager *manager = wl_container_of(listener, manager, display_destroy); wl_list_remove(&manager->display_destroy.link); struct hash_entry *entry; hash_table_for_each(entry, manager->watcher.wds) { struct watch_fd *wd = entry->data; inotify_rm_watch(manager->watcher.event_fd, wd->fd); free(wd->subpath); free(wd->path); free(wd); } hash_table_destroy(manager->watcher.wds); wl_event_source_remove(manager->watcher.source); manager->watcher.source = NULL; close(manager->watcher.event_fd); } static int watcher_process(int fd, uint32_t mask, void *data) { struct icon_manager *manager = data; if (mask & (WL_EVENT_ERROR | WL_EVENT_HANGUP)) { kywc_log(KYWC_ERROR, "Icon file watcher eventfd failed"); handle_display_destroy(&manager->display_destroy, NULL); return 0; } const struct inotify_event *event; struct hash_entry *entry; struct watch_fd *wd; char buf[4096]; ssize_t size; for (;;) { size = read(fd, buf, sizeof(buf)); if (size == -1 && errno != EAGAIN) { break; } if (size <= 0) { break; } for (char *ptr = buf; ptr < buf + size; ptr += sizeof(struct inotify_event) + event->len) { event = (const struct inotify_event *)ptr; if (event->len == 0) { continue; } entry = hash_table_search(manager->watcher.wds, (void *)(uintptr_t)event->wd); if (!entry || !entry->data) { continue; } wd = entry->data; if (wd->handler) { wd->handler(wd, event, wd->data); } } } return 0; } static void icon_manager_add_watcher_fd( struct icon_manager *manager, const char *path, const char *subpath, void (*handler)(struct watch_fd *wd, const struct inotify_event *event, void *data), void *data) { if (!manager->watcher.source) { return; } int fd = inotify_add_watch(manager->watcher.event_fd, path, IN_CREATE | IN_MOVED_TO); if (fd < 0) { return; } // Filter out duplicate calls by file_for_each and handle_icon struct hash_entry *entry = hash_table_search(manager->watcher.wds, (void *)(uintptr_t)fd); if (entry && entry->data) { return; } struct watch_fd *wd = calloc(1, sizeof(*wd)); if (!wd) { inotify_rm_watch(manager->watcher.event_fd, fd); return; } wd->fd = fd; wd->path = strdup(path); if (subpath) { wd->subpath = strdup(subpath); } wd->handler = handler; wd->data = data; hash_table_insert(manager->watcher.wds, (void *)(uintptr_t)fd, wd); } static void icon_manager_init_watcher(struct icon_manager *manager) { manager->watcher.event_fd = inotify_init1(IN_NONBLOCK); if (manager->watcher.event_fd < 0) { return; } manager->watcher.source = wl_event_loop_add_fd(manager->server->event_loop, manager->watcher.event_fd, WL_EVENT_READABLE, watcher_process, manager); if (!manager->watcher.source) { close(manager->watcher.event_fd); return; } manager->watcher.wds = hash_table_create_int(NULL); hash_table_set_max_entries(manager->watcher.wds, 200); manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(manager->server->display, &manager->display_destroy); } static void load_files(void *job, void *gdata, int index) { struct icon_manager *manager = job; kywc_log(KYWC_DEBUG, "Apppath: %s, pixmappath: %s, iconpath: %s", APPPATH, PIXMAPPATH, ICONPATH); // load all desktop files file_for_each(APPPATH, NULL, load_desktop, manager); // load all icons in pixmap dir if (manager->pixmap) { file_for_each(PIXMAPPATH, NULL, load_icon, manager->pixmap); } // load fallback icon theme if (manager->fallback) { file_for_each(ICONPATH, FALLBACK_ICON_THEME_NAME, load_icon, manager->fallback); } } struct icon_manager *icon_manager_create(struct theme_manager *theme_manager) { struct icon_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return NULL; } manager->server = theme_manager->server; manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(manager->server, &manager->server_destroy); icon_manager_init_watcher(manager); wl_list_init(&manager->themes); wl_list_init(&manager->desktops); manager->icon_pairs = hash_table_create_string(NULL); hash_table_set_max_entries(manager->icon_pairs, 200); // create an empty theme for all specific path icons manager->specific = icon_theme_create_empty(manager, NULL); manager->pixmap = icon_theme_create_empty(manager, NULL); manager->fallback = icon_theme_create_empty(manager, FALLBACK_ICON_THEME_NAME); queue_fence_init(&manager->fence); if (!queue_add_job(manager->server->queue, manager, &manager->fence, load_files, NULL)) { load_files(manager, manager->server, -1); } theme_manager->icon_impl.set_icon_theme = set_icon_theme; theme_manager->icon_impl.get_icon = get_icon; theme_manager->icon_impl.get_icon_name = get_icon_name; theme_manager->icon_impl.get_icon_buffer = get_icon_buffer; return manager; } kylin-wayland-compositor/src/theme/config.c0000664000175000017500000002370615160460057017775 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include "config.h" #include "theme_p.h" #include "util/dbus.h" static const char *service_path = "/com/kylin/Wlcom/Theme"; static const char *service_interface = "com.kylin.Wlcom.Theme"; static int print_theme_config(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { struct theme_manager *manager = userdata; const char *config = json_object_to_json_string(manager->config->json); return sd_bus_reply_method_return(msg, "s", config); } static int set_widget_theme(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { char *theme = NULL; uint32_t type = THEME_TYPE_DEFAULT; CK(sd_bus_message_read(msg, "su", &theme, &type)); bool ret = theme_manager_set_widget_theme(theme, type); return sd_bus_reply_method_return(msg, "b", ret); } static int set_icon_theme(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { char *icon_theme_name = NULL; CK(sd_bus_message_read(msg, "s", &icon_theme_name)); bool ret = theme_manager_set_icon_theme(icon_theme_name); return sd_bus_reply_method_return(msg, "b", ret); } static int set_background(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { char *picture = NULL; uint32_t options = 0, color = 0; CK(sd_bus_message_read(msg, "suu", &picture, &options, &color)); bool ret = theme_manager_set_background(picture, options, color); return sd_bus_reply_method_return(msg, "b", ret); } static int set_font(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { char *font_name = NULL; int32_t size; CK(sd_bus_message_read(msg, "si", &font_name, &size)); bool ret = theme_manager_set_font(font_name, size); return sd_bus_reply_method_return(msg, "b", ret); } static int set_accent_color(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { int32_t accent_color; CK(sd_bus_message_read(msg, "i", &accent_color)); bool ret = theme_manager_set_accent_color(accent_color); return sd_bus_reply_method_return(msg, "b", ret); } static int set_corner_radius(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { int32_t window_radius, menu_radius; CK(sd_bus_message_read(msg, "ii", &window_radius, &menu_radius)); bool ret = theme_manager_set_corner_radius(window_radius, menu_radius); return sd_bus_reply_method_return(msg, "b", ret); } static int set_opacity(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { int32_t opacity; CK(sd_bus_message_read(msg, "i", &opacity)); bool ret = theme_manager_set_opacity(opacity); return sd_bus_reply_method_return(msg, "b", ret); } static const sd_bus_vtable service_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("PrintThemeConfig", "", "s", print_theme_config, 0), SD_BUS_METHOD("SetWidgetTheme", "su", "b", set_widget_theme, 0), SD_BUS_METHOD("SetIconTheme", "s", "b", set_icon_theme, 0), SD_BUS_METHOD("SetBackground", "suu", "b", set_background, 0), SD_BUS_METHOD("SetFont", "si", "b", set_font, 0), SD_BUS_METHOD("SetAccentColor", "i", "b", set_accent_color, 0), SD_BUS_METHOD("SetCornerRadius", "ii", "b", set_corner_radius, 0), SD_BUS_METHOD("SetOpacity", "i", "b", set_opacity, 0), SD_BUS_VTABLE_END, }; bool theme_manager_config_init(struct theme_manager *manager) { manager->config = config_manager_add_config("theme"); if (!manager->config) { return false; } return dbus_register_object(NULL, service_path, service_interface, service_vtable, manager); } void theme_manager_read_config(struct theme_manager *manager) { if (!manager->config || !manager->config->json) { return; } json_object *data; /* some global configs */ if (json_object_object_get_ex(manager->config->json, "font_name", &data)) { free(manager->global.font_name); manager->global.font_name = strdup(json_object_get_string(data)); } if (json_object_object_get_ex(manager->config->json, "font_size", &data)) { manager->global.font_size = json_object_get_int(data); } if (json_object_object_get_ex(manager->config->json, "accent_color", &data)) { manager->global.accent_color = json_object_get_int(data); } if (json_object_object_get_ex(manager->config->json, "window_radius", &data)) { manager->global.window_radius = json_object_get_int(data); } if (json_object_object_get_ex(manager->config->json, "menu_radius", &data)) { manager->global.menu_radius = json_object_get_int(data); } if (json_object_object_get_ex(manager->config->json, "opacity", &data)) { manager->global.opacity = json_object_get_int(data); } if (json_object_object_get_ex(manager->config->json, "type", &data)) { manager->global.type = json_object_get_int(data); return; } /* get system default config */ if (manager->config->sys_json && json_object_object_get_ex(manager->config->sys_json, "type", &data)) { manager->global.type = json_object_get_int(data); } } void theme_manager_write_config(struct theme_manager *manager) { if (!manager->config || !manager->config->json) { return; } if (manager->global.type > THEME_TYPE_DEFAULT) { json_object_object_add(manager->config->json, "type", json_object_new_int(manager->global.type)); } else { json_object_object_del(manager->config->json, "type"); } if (manager->global.font_name && strcmp(manager->global.font_name, "sans")) { json_object_object_add(manager->config->json, "font_name", json_object_new_string(manager->global.font_name)); } else { json_object_object_del(manager->config->json, "font_name"); } if (manager->global.font_size != 11) { json_object_object_add(manager->config->json, "font_size", json_object_new_int(manager->global.font_size)); } else { json_object_object_del(manager->config->json, "font_size"); } if (manager->global.accent_color >= 0) { json_object_object_add(manager->config->json, "accent_color", json_object_new_int(manager->global.accent_color)); } else { json_object_object_del(manager->config->json, "accent_color"); } if (manager->global.window_radius != 12) { json_object_object_add(manager->config->json, "window_radius", json_object_new_int(manager->global.window_radius)); } else { json_object_object_del(manager->config->json, "window_radius"); } if (manager->global.menu_radius != 8) { json_object_object_add(manager->config->json, "menu_radius", json_object_new_int(manager->global.menu_radius)); } else { json_object_object_del(manager->config->json, "menu_radius"); } if (manager->global.opacity != 100) { json_object_object_add(manager->config->json, "opacity", json_object_new_int(manager->global.opacity)); } else { json_object_object_del(manager->config->json, "opacity"); } } const char *theme_manager_read_icon_config(struct theme_manager *manager) { if (!manager->config || !manager->config->json) { return NULL; } json_object *data; if (json_object_object_get_ex(manager->config->json, "icon_theme_name", &data)) { return json_object_get_string(data); } /* get system default config */ if (manager->config->sys_json && json_object_object_get_ex(manager->config->sys_json, "icon_theme_name", &data)) { return json_object_get_string(data); } return NULL; } void theme_manager_write_icon_config(struct theme_manager *manager, const char *name) { if (!manager->config || !manager->config->json) { return; } if (strcmp(name, FALLBACK_ICON_THEME_NAME)) { json_object_object_add(manager->config->json, "icon_theme_name", json_object_new_string(name)); } else { json_object_object_del(manager->config->json, "icon_theme_name"); } } const char *theme_manager_read_background_config(struct theme_manager *manager, uint32_t *options, int32_t *color) { if (!manager->config || !manager->config->json) { return NULL; } const char *picture = NULL; json_object *data; if (json_object_object_get_ex(manager->config->json, "background_picture", &data)) { picture = json_object_get_string(data); } if (json_object_object_get_ex(manager->config->json, "background_options", &data)) { *options = json_object_get_int(data); } if (json_object_object_get_ex(manager->config->json, "background_color", &data)) { *color = json_object_get_int(data); } return picture; } void theme_manager_write_background_config(struct theme_manager *manager) { if (!manager->config || !manager->config->json) { return; } if (manager->background.picture) { json_object_object_add(manager->config->json, "background_picture", json_object_new_string(manager->background.picture)); } else { json_object_object_del(manager->config->json, "background_picture"); } if (manager->background.options != 0) { json_object_object_add(manager->config->json, "background_options", json_object_new_int(manager->background.options)); } else { json_object_object_del(manager->config->json, "background_options"); } if (manager->background.color >= 0) { json_object_object_add(manager->config->json, "background_color", json_object_new_int(manager->background.color)); } else { json_object_object_del(manager->config->json, "background_color"); } } kylin-wayland-compositor/src/theme/theme.c0000664000175000017500000006045115160461067017632 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "base_dark_svg_src.h" #include "base_light_svg_src.h" #include "config.h" #include "nls.h" #include "painter.h" #include "render/renderer.h" #include "server.h" #include "theme_p.h" #include "util/color.h" #include "util/file.h" #include "util/macros.h" #include "util/string.h" #define FALLBACK_THEME_NAME "fallback" static struct theme_manager *manager = NULL; static const char *fallback_icon_name = "fallback"; /* fallback light widget theme from ukui-white */ static struct widget_theme widget_light = { .name = FALLBACK_THEME_NAME, .type = THEME_TYPE_LIGHT, .builtin = true, .active_border_color = { 0.0, 0.0, 0.0, 0.15 }, .inactive_border_color = { 0.0, 0.0, 0.0, 0.15 }, .active_bg_color = { 1.0, 1.0, 1.0, 1.0 }, .inactive_bg_color = { 245.0 / 255.0, 245.0 / 255.0, 245.0 / 255.0, 1.0 }, .active_text_color = { 38.0 / 255.0, 38.0 / 255.0, 38.0 / 255.0, 1.0 }, .inactive_text_color = { 38.0 / 255.0, 38.0 / 255.0, 38.0 / 255.0, 0.3 }, .active_shadow_color = { .layers[0] = { { 0.0, 0.0, 0.0, 0.4 }, 0, 0, 40, 0 }, .num_layers = 1 }, .inactive_shadow_color = { .layers[0] = { { 0.0, 0.0, 0.0, 0.25 }, 0, 0, 40, 0 }, .num_layers = 1 }, .modal_active_shadow_color = { .layers[0] = { { 0.0, 0.0, 0.0, 0.25 }, 0, 0, 30, 0 }, .num_layers = 1 }, .modal_inactive_shadow_color = { .layers[0] = { { 0.0, 0.0, 0.0, 0.15 }, 0, 0, 30, 0 }, .num_layers = 1 }, .menu_shadow_color = { .layers[0] = { { 0.0, 0.0, 0.0, 0.2 }, 0, 0, 20, 0 }, .num_layers = 1 }, .accent_color = { 55.0 / 255, 144.0 / 255, 250.0 / 255, 1.0 }, .modal_mask_color = { 0.0, 0.0, 0.0, 0.2 }, .normal_state_color = { .background = { 0.0, 0.0, 0.0, 0.1 } }, .hover_state_color = { .background = { 0.0, 0.0, 0.0, 0.15 } }, .click_state_color = { .background = { 0.0, 0.0, 0.0, 0.2 } }, .button_svg = base_light_svg_src, .button_svg_size = ARRAY_SIZE(base_light_svg_src) - 1, }; /* fallback dark theme from ukui-dark */ static struct widget_theme widget_dark = { .name = FALLBACK_THEME_NAME, .type = THEME_TYPE_DARK, .builtin = true, .active_border_color = { 1.0, 1.0, 1.0, 0.15 }, .inactive_border_color = { 1.0, 1.0, 1.0, 0.15 }, .active_bg_color = { 18.0 / 255.0, 18.0 / 255.0, 18.0 / 255.0, 1.0 }, .inactive_bg_color = { 28.0 / 255.0, 28.0 / 255.0, 28.0 / 255.0, 1.0 }, .active_text_color = { 0xcf / 255.0, 0xcf / 255.0, 0xcf / 255.0, 1.0 }, .inactive_text_color = { 0xcf / 255.0, 0xcf / 255.0, 0xcf / 255.0, 0.3 }, .active_shadow_color = { .layers[0] = { { 0.0, 0.0, 0.0, 0.4 }, 0, 0, 40, 0 }, .num_layers = 1 }, .inactive_shadow_color = { .layers[0] = { { 0.0, 0.0, 0.0, 0.25 }, 0, 0, 40, 0 }, .num_layers = 1 }, .modal_active_shadow_color = { .layers[0] = { { 0.0, 0.0, 0.0, 0.25 }, 0, 0, 30, 0 }, .num_layers = 1 }, .modal_inactive_shadow_color = { .layers[0] = { { 0.0, 0.0, 0.0, 0.15 }, 0, 0, 30, 0 }, .num_layers = 1 }, .menu_shadow_color = { .layers[0] = { { 0.0, 0.0, 0.0, 0.2 }, 0, 0, 20, 0 }, .num_layers = 1 }, .accent_color = { 243.0 / 255, 34.0 / 255, 45.0 / 255, 1.0 }, .modal_mask_color = { 0.0, 0.0, 0.0, 0.2 }, .normal_state_color = { .background = { 1.0, 1.0, 1.0, 0.1 } }, .hover_state_color = { .background = { 1.0, 1.0, 1.0, 0.15 } }, .click_state_color = { .background = { 1.0, 1.0, 1.0, 0.2 } }, .button_svg = base_dark_svg_src, .button_svg_size = ARRAY_SIZE(base_dark_svg_src) - 1, }; static struct theme_button_buffer *draw_theme_button_buffer(struct theme *theme, float scale) { struct theme_button_buffer *buffer = calloc(1, sizeof(*buffer)); if (!buffer) { return NULL; } struct draw_info info = { .width = theme->button_width * 4, .height = theme->button_width * 6, .scale = scale, .svg = { theme->button_svg, theme->button_svg_size }, }; struct wlr_buffer *buf = painter_draw_buffer(&info); if (!buf) { free(buffer); return NULL; } buffer->scale = scale; buffer->button_width = theme->button_width; wl_list_insert(&theme->scaled_buffers, &buffer->link); buffer->buffer = ky_renderer_upload_pixels( manager->server->renderer, manager->server->allocator, buf->width, buf->height, buf); if (buffer->buffer) { wlr_buffer_drop(buf); } else { buffer->buffer = buf; } return buffer; } static void theme_manager_init_configs(struct theme_manager *manager) { struct theme *theme = &manager->theme; theme->layout_is_right_to_left = nls_layout_is_right_to_left(); theme->text_is_right_align = nls_text_is_right_align(); theme->text_justify = theme->layout_is_right_to_left ? JUSTIFY_RIGHT : JUSTIFY_LEFT; theme->button_width = 32; theme->icon_size = 24; theme->title_height = 38; theme->subtitle_height = 38; theme->border_width = 1; theme->normal_radius = 6; /* fallback if theme_manager_read_config failed */ struct global_theme *global = &manager->global; global->type = THEME_TYPE_DEFAULT; global->font_name = strdup("sans"); global->font_size = 11; global->accent_color = -1; global->window_radius = 12; global->menu_radius = 8; global->opacity = 100; manager->background.color = -1; } #define UPDATE_COLOR(name, update) \ if (memcmp(theme->name, widget->name, sizeof(float[4]))) { \ memcpy(theme->name, widget->name, sizeof(float[4])); \ mask |= THEME_UPDATE_MASK_##update; \ } #define UPDATE_GRADIENT(name, update) \ if (memcmp(&theme->name, &widget->name, sizeof(struct theme_gradient))) { \ memcpy(&theme->name, &widget->name, sizeof(struct theme_gradient)); \ mask |= THEME_UPDATE_MASK_##update; \ } #define UPDATE_SHADOW(name, update) \ if (theme->name.num_layers != widget->name.num_layers || \ memcmp(&theme->name.layers, &widget->name.layers, \ widget->name.num_layers * sizeof(struct theme_shadow_layer))) { \ memcpy(&theme->name, &widget->name, sizeof(struct theme_shadow)); \ mask |= THEME_UPDATE_MASK_##update; \ } static uint32_t theme_init(struct widget_theme *widget) { struct theme *theme = &manager->theme; uint32_t mask = THEME_UPDATE_MASK_NONE; /* use name and type from widget */ theme->name = widget->name; theme->builtin = widget->builtin; if (theme->type != widget->type) { theme->type = widget->type; mask |= THEME_UPDATE_MASK_TYPE; } /* copy color from widget */ UPDATE_COLOR(active_border_color, BORDER_COLOR); UPDATE_COLOR(inactive_border_color, BORDER_COLOR); UPDATE_COLOR(active_bg_color, BACKGROUND_COLOR); UPDATE_COLOR(inactive_bg_color, BACKGROUND_COLOR); UPDATE_COLOR(active_text_color, FONT); UPDATE_COLOR(inactive_text_color, FONT); UPDATE_SHADOW(active_shadow_color, SHADOW_COLOR); UPDATE_SHADOW(inactive_shadow_color, SHADOW_COLOR); UPDATE_SHADOW(modal_active_shadow_color, SHADOW_COLOR); UPDATE_SHADOW(modal_inactive_shadow_color, SHADOW_COLOR); UPDATE_SHADOW(menu_shadow_color, SHADOW_COLOR); UPDATE_COLOR(modal_mask_color, MODAL_MASK_COLOR); UPDATE_GRADIENT(normal_state_color, STATE_COLOR); UPDATE_GRADIENT(hover_state_color, STATE_COLOR); UPDATE_GRADIENT(click_state_color, STATE_COLOR); struct global_theme *global = &manager->global; theme->font_name = global->font_name; theme->font_size = global->font_size; if (global->accent_color < 0) { UPDATE_COLOR(accent_color, ACCENT_COLOR); } else { color_uint24_to_float(theme->accent_color, global->accent_color); } theme->window_radius = global->window_radius; theme->menu_radius = global->menu_radius; theme->opacity = global->opacity; struct theme_button_buffer *buffer, *tmp; wl_list_for_each_safe(buffer, tmp, &theme->scaled_buffers, link) { wlr_buffer_drop(buffer->buffer); wl_list_remove(&buffer->link); free(buffer); } theme->button_svg = widget->button_svg; theme->button_svg_size = widget->button_svg_size; return mask; } static void theme_finish(struct theme *theme) { /* destroy all theme buffers */ struct theme_button_buffer *buffer, *tmp; wl_list_for_each_safe(buffer, tmp, &theme->scaled_buffers, link) { wlr_buffer_drop(buffer->buffer); wl_list_remove(&buffer->link); free(buffer); } } static void handle_server_destroy(struct wl_listener *listener, void *data) { assert(wl_list_empty(&manager->events.pre_update.listener_list)); assert(wl_list_empty(&manager->events.update.listener_list)); assert(wl_list_empty(&manager->events.icon_update.listener_list)); assert(wl_list_empty(&manager->events.background_update.listener_list)); wl_list_remove(&manager->server_destroy.link); wl_list_remove(&manager->server_ready.link); theme_finish(&manager->theme); wlr_buffer_drop(manager->background.buffer); free(manager->background.picture); free(manager->global.font_name); free(manager); manager = NULL; } static void handle_server_ready(struct wl_listener *listener, void *data) { assert(wl_list_empty(&manager->events.pre_update.listener_list)); assert(wl_list_empty(&manager->events.update.listener_list)); assert(wl_list_empty(&manager->events.icon_update.listener_list)); assert(wl_list_empty(&manager->events.background_update.listener_list)); /* if widget theme is not set, use fallback widget theme */ if (!manager->theme.name) { /* load theme from config, global theme is synced */ theme_manager_set_widget_theme(NULL, manager->global.type); } /* just read the icon name, may shortcut in set_icon_theme */ const char *icon_theme_name = theme_manager_read_icon_config(manager); theme_manager_set_icon_theme(icon_theme_name); /* background from config */ if (!manager->background.picture && manager->background.color < 0) { uint32_t options = 0; int32_t color = -1; const char *picture = theme_manager_read_background_config(manager, &options, &color); theme_manager_set_background(picture, options, color); } } struct theme_manager *theme_manager_create(struct server *server) { manager = calloc(1, sizeof(*manager)); if (!manager) { return NULL; } wl_list_init(&manager->fallback_icon); wl_signal_init(&manager->events.pre_update); wl_signal_init(&manager->events.update); wl_signal_init(&manager->events.icon_update); wl_signal_init(&manager->events.background_update); manager->server = server; manager->server_ready.notify = handle_server_ready; wl_signal_add(&server->events.ready, &manager->server_ready); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); /* config support */ theme_manager_config_init(manager); manager->icon = icon_manager_create(manager); ukui_theme_manager_create(manager); wl_list_init(&manager->theme.scaled_buffers); theme_manager_init_configs(manager); theme_manager_read_config(manager); return manager; } void theme_manager_add_update_listener(struct wl_listener *listener, bool pre_update) { if (pre_update) { wl_signal_add(&manager->events.pre_update, listener); } else { wl_signal_add(&manager->events.update, listener); } } void theme_manager_add_icon_update_listener(struct wl_listener *listener) { wl_signal_add(&manager->events.icon_update, listener); } void theme_manager_update_theme(uint32_t mask) { struct theme_update_event update_event = { .theme_type = manager->theme.type, .update_mask = mask, }; wl_signal_emit_mutable(&manager->events.pre_update, &update_event); /* update_mask may be changed after pre_update */ if (update_event.update_mask != THEME_UPDATE_MASK_NONE) { wl_signal_emit_mutable(&manager->events.update, &update_event); } } struct theme *theme_manager_get_theme(void) { return &manager->theme; } static struct theme_button_buffer *theme_button_buffer_get_or_create(struct theme *theme, float scale) { /* find scale buffer */ struct theme_button_buffer *buffer; wl_list_for_each(buffer, &theme->scaled_buffers, link) { if (buffer->button_width == theme->button_width && buffer->scale == scale) { return buffer; } } return draw_theme_button_buffer(theme, scale); } struct wlr_buffer *theme_button_buffer_load(struct theme *theme, float scale, enum theme_button_type type, struct wlr_fbox *src, bool activated) { struct theme_button_buffer *buffer = theme_button_buffer_get_or_create(theme, scale); if (!buffer) { return NULL; } if (src) { src->width = theme->button_width * scale; src->height = theme->button_width * scale; src->x = src->width * (type % 4); src->y = src->height * ((int)(type / 4) * 2 + activated); } return buffer->buffer; } bool theme_manager_set_widget_theme(const char *name, enum theme_type type) { // fallback to builtin theme if (!name || !*name || !manager->load_widget_theme) { name = FALLBACK_THEME_NAME; } if (type > THEME_TYPE_DARK) { type = THEME_TYPE_DARK; } struct theme *current = &manager->theme; /* current theme is not changed */ if (current->name && strcmp(current->name, name) == 0 && current->type == type) { return true; } struct widget_theme *widget = NULL; if (strcmp(name, FALLBACK_THEME_NAME) == 0) { widget = type == THEME_TYPE_DARK ? &widget_dark : &widget_light; } else if (manager->load_widget_theme) { widget = manager->load_widget_theme(name, type); } if (!widget) { kywc_log(KYWC_WARN, "Widget theme %s(%d) load failed", name, type); return false; } /* merge widget and global to theme */ uint32_t mask = theme_init(widget); theme_manager_update_theme(mask); manager->global.type = type; theme_manager_write_config(manager); return true; } bool theme_manager_set_font(const char *name, int size) { if (!name || !*name || size <= 0) { return false; } struct global_theme *global = &manager->global; struct theme *theme = &manager->theme; bool changed = false; if (!global->font_name || strcmp(name, global->font_name) != 0) { free(global->font_name); global->font_name = strdup(name); theme->font_name = global->font_name; changed = true; } if (global->font_size != size) { global->font_size = size; theme->font_size = global->font_size; changed = true; } if (!changed) { return true; } theme_manager_update_theme(THEME_UPDATE_MASK_FONT); theme_manager_write_config(manager); return true; } bool theme_manager_set_accent_color(int32_t color) { struct global_theme *global = &manager->global; struct theme *theme = &manager->theme; if (global->accent_color == color) { return true; } global->accent_color = color; color_uint24_to_float(theme->accent_color, global->accent_color); theme_manager_update_theme(THEME_UPDATE_MASK_ACCENT_COLOR); theme_manager_write_config(manager); return true; } bool theme_manager_set_corner_radius(int32_t window_radius, int32_t menu_radius) { if (window_radius < 0) { return false; } struct global_theme *global = &manager->global; struct theme *theme = &manager->theme; if (global->window_radius == window_radius && (menu_radius < 0 || global->menu_radius == menu_radius)) { return true; } global->window_radius = window_radius; theme->window_radius = global->window_radius; if (menu_radius >= 0) { global->menu_radius = menu_radius; theme->menu_radius = global->menu_radius; } theme_manager_update_theme(THEME_UPDATE_MASK_CORNER_RADIUS); theme_manager_write_config(manager); return true; } bool theme_manager_set_opacity(int32_t opacity) { if (opacity < 0 || opacity > 100) { return false; } struct global_theme *global = &manager->global; struct theme *theme = &manager->theme; if (global->opacity == opacity) { return true; } global->opacity = opacity; theme->opacity = global->opacity; theme_manager_update_theme(THEME_UPDATE_MASK_OPACITY); theme_manager_write_config(manager); return true; } bool theme_manager_set_icon_theme(const char *icon_theme_name) { if (!manager->icon_impl.set_icon_theme) { return false; } /* invalid or empty name */ if (!icon_theme_name || !*icon_theme_name) { return false; } /* skip if icon_theme is hicolor */ if (strcmp(icon_theme_name, FALLBACK_ICON_THEME_NAME) == 0) { return false; } bool ok = manager->icon_impl.set_icon_theme(manager->icon, icon_theme_name); if (!ok) { return false; } theme_manager_write_icon_config(manager, icon_theme_name); wl_signal_emit_mutable(&manager->events.icon_update, NULL); return true; } struct icon *theme_icon_from_app_id(const char *app_id) { struct icon *fallback = (struct icon *)manager->fallback_icon.prev; if (!app_id || !*app_id) { return fallback; } if (manager->icon_impl.get_icon) { struct icon *icon = manager->icon_impl.get_icon(manager->icon, app_id); if (icon) { return icon; } } return fallback; } bool theme_icon_is_fallback(struct icon *icon) { return icon == (struct icon *)manager->fallback_icon.prev; } const char *theme_icon_get_name(struct icon *icon) { if (theme_icon_is_fallback(icon)) { return fallback_icon_name; } if (manager->icon_impl.get_icon_name) { return manager->icon_impl.get_icon_name(icon); } return fallback_icon_name; } struct wlr_buffer *theme_icon_get_buffer(struct icon *icon, int size, float scale) { /* hide the fallback icon */ if (theme_icon_is_fallback(icon)) { return NULL; } if (manager->icon_impl.get_icon_buffer) { return manager->icon_impl.get_icon_buffer(icon, size, scale); } return NULL; } void theme_manager_add_background_update_listener(struct wl_listener *listener) { wl_signal_add(&manager->events.background_update, listener); } bool theme_manager_set_background(const char *picture, uint32_t options, int32_t color) { bool changed = false; if (STRING_VALID(picture)) { char *file = string_expand_path(picture); if (!file || !file_exists(file)) { free(file); return false; } if (!manager->background.picture || strcmp(manager->background.picture, file)) { free(manager->background.picture); manager->background.picture = file; wlr_buffer_drop(manager->background.buffer); manager->background.buffer = NULL; changed = true; } else { free(file); changed = manager->background.options != options; } } else { if (color < 0) { return false; } if (manager->background.picture) { free(manager->background.picture); manager->background.picture = NULL; wlr_buffer_drop(manager->background.buffer); manager->background.buffer = NULL; changed = true; } else { changed = manager->background.color != color; } } manager->background.options = options; manager->background.color = color; if (changed) { wl_signal_emit_mutable(&manager->events.background_update, NULL); theme_manager_write_background_config(manager); } return true; } static bool create_background_buffer(void) { struct draw_info info = { .image = manager->background.picture }; struct wlr_buffer *buffer = painter_draw_buffer(&info); if (!buffer) { return false; } int width = buffer->width, height = buffer->height; // downsize if picture is too bigger float ratio = (float)width / height; if (ratio >= 1 && width > 1920) { width = 1920; height = width / ratio; } else if (ratio < 1 && height > 1920) { height = 1920; width = height * ratio; } manager->background.buffer = ky_renderer_upload_pixels( manager->server->renderer, manager->server->allocator, width, height, buffer); if (manager->background.buffer) { wlr_buffer_drop(buffer); } else { manager->background.buffer = buffer; } return true; } struct wlr_buffer *theme_manager_get_background(int32_t *color) { if (!manager->background.picture) { *color = manager->background.color; return NULL; } if (!manager->background.buffer && !create_background_buffer()) { return NULL; } return manager->background.buffer; } bool theme_manager_get_background_box(struct wlr_fbox *dst, struct wlr_fbox *src, int width, int height) { struct wlr_buffer *buffer = manager->background.buffer; if (!buffer) { return false; } bool repeated = false; src->x = src->y = 0; src->width = buffer->width; src->height = buffer->height; switch (manager->background.options) { default: case BACKGROUND_OPTION_STRETCHED: break; case BACKGROUND_OPTION_WALLPAPER: repeated = true; break; case BACKGROUND_OPTION_SCALED: { float dst_ratio = dst->width / dst->height; float src_ratio = src->width / src->height; if (dst_ratio < src_ratio) { float width = src->width; src->width = src->height * dst_ratio; src->x = (width - src->width) / 2; } else { float height = src->height; src->height = src->width / dst_ratio; src->y = (height - src->height) / 2; } break; } case BACKGROUND_OPTION_CENTERED: if (dst->width >= buffer->width) { dst->x += (dst->width - buffer->width) / 2; dst->width = buffer->width; } else { src->x = (buffer->width - dst->width) / 2; src->width = dst->width; } if (dst->height >= buffer->height) { dst->y += (dst->height - buffer->height) / 2; dst->height = buffer->height; } else { src->y = (buffer->height - dst->height) / 2; src->height = dst->height; } break; case BACKGROUND_OPTION_ZOOM: { float dst_ratio = dst->width / dst->height; float src_ratio = src->width / src->height; if (dst_ratio < src_ratio) { float height = dst->height; dst->height = dst->width / src_ratio; dst->y += (height - dst->height) / 2; } else { float width = dst->width; dst->width = dst->height * src_ratio; dst->x += (width - dst->width) / 2; } break; } case BACKGROUND_OPTION_SPANNED: src->x = dst->x / width * src->width; src->y = dst->y / height * src->height; src->width *= dst->width / width; src->height *= dst->height / height; break; } return repeated; } kylin-wayland-compositor/src/theme/ukui_theme.c0000664000175000017500000005724415160460057020673 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include "server.h" #include "theme_p.h" #include "util/color.h" #include "util/file.h" #include "util/hash_table.h" #include "util/macros.h" #define MINIMIZE \ "M19.5 15H10.5C10.2239 15 10 15.2239 10 15.5C10 15.7761 10.2239 16 10.5 16H19.5C19.7761 16 " \ "20 15.7761 20 15.5C20 15.2239 19.7761 15 19.5 15Z" #define MAXMIZE \ "M19 10C19.2652 10 19.5196 10.1054 19.7071 10.2929C19.8946 10.4804 20 10.7348 20 11V19C20 " \ "19.2652 19.8946 19.5196 19.7071 19.7071C19.5196 19.8946 19.2652 20 19 20H11C10.7348 20 " \ "10.4804 19.8946 10.2929 19.7071C10.1054 19.5196 10 19.2652 10 19V11C10 10.7348 10.1054 " \ "10.4804 10.2929 10.2929C10.4804 10.1054 10.7348 10 11 10H19ZM19 9H11C10.4696 9 9.96086 " \ "9.21071 9.58579 9.58579C9.21071 9.96086 9 10.4696 9 11V19C9 19.5304 9.21071 20.0391 9.58579 " \ "20.4142C9.96086 20.7893 10.4696 21 11 21H19C19.5304 21 20.0391 20.7893 20.4142 " \ "20.4142C20.7893 20.0391 21 19.5304 21 19V11C21 10.4696 20.7893 9.96086 20.4142 " \ "9.58579C20.0391 9.21071 19.5304 9 19 9Z" #define RESTORE \ "M20 8H14C13.4696 8 12.9609 8.21071 12.5858 8.58579C12.2107 8.96086 12 9.46957 12 " \ "10V11H11C10.4696 11 9.96086 11.2107 9.58579 11.5858C9.21071 11.9609 9 12.4696 9 13V19C9 " \ "19.5304 9.21071 20.0391 9.58579 20.4142C9.96086 20.7893 10.4696 21 11 21H17C17.5304 21 " \ "18.0391 20.7893 18.4142 20.4142C18.7893 20.0391 19 19.5304 19 19V18H20C20.5304 18 21.0391 " \ "17.7893 21.4142 17.4142C21.7893 17.0391 22 16.5304 22 16V10C22 9.46957 21.7893 8.96086 " \ "21.4142 8.58579C21.0391 8.21071 20.5304 8 20 8ZM18 19C18 19.2652 17.8946 19.5196 17.7071 " \ "19.7071C17.5196 19.8946 17.2652 20 17 20H11C10.7348 20 10.4804 19.8946 10.2929 " \ "19.7071C10.1054 19.5196 10 19.2652 10 19V13C10 12.7348 10.1054 12.4804 10.2929 " \ "12.2929C10.4804 12.1054 10.7348 12 11 12H17C17.2652 12 17.5196 12.1054 17.7071 " \ "12.2929C17.8946 12.4804 18 12.7348 18 13V19ZM21 16C21 16.2652 20.8946 16.5196 20.7071 " \ "16.7071C20.5196 16.8946 20.2652 17 20 17H19V13C19 12.4696 18.7893 11.9609 18.4142 " \ "11.5858C18.0391 11.2107 17.5304 11 17 11H13V10C13 9.73478 13.1054 9.48043 13.2929 " \ "9.29289C13.4804 9.10536 13.7348 9 14 9H20C20.2652 9 20.5196 9.10536 20.7071 9.29289C20.8946 " \ "9.48043 21 9.73478 21 10V16Z" #define CLOSE1 \ "M20.253 19.5459L11.0606 10.3536C10.8653 10.1583 10.5487 10.1583 10.3535 10.3536C10.1582 " \ "10.5488 10.1582 10.8654 10.3535 11.0607L19.5459 20.253C19.7411 20.4483 20.0577 20.4483 " \ "20.253 20.253C20.4482 20.0578 20.4482 19.7412 20.253 19.5459Z" #define CLOSE2 \ "M19.5506 10.3603L10.3582 19.5527C10.1629 19.748 10.1629 20.0645 10.3582 20.2598C10.5535 " \ "20.4551 10.87 20.4551 11.0653 20.2598L20.2577 11.0674C20.4529 10.8722 20.4529 10.5556 " \ "20.2577 10.3603C20.0624 10.165 19.7458 10.165 19.5506 10.3603Z" #define BUTTON_SIZE (32) #define BUTTON_GAP (1) #define THEME_DIR "/usr/share/config/themeconfig/token" enum theme_value_type { theme_value_type_none = 0, theme_value_type_number = 1 << 0, theme_value_type_color = 1 << 1, theme_value_type_gradient = 1 << 2, theme_value_type_shadow = 1 << 3, }; struct gradient { struct color background; struct color start, stop; int angle; }; struct shadow_layer { struct color color; int off_x, off_y; int blur, spread; }; struct shadow { struct shadow_layer layers[THEME_MAX_SHADOW_LAYERS]; int num_layers; }; struct theme_value { enum theme_value_type type; union { struct color color; struct gradient gradient; struct shadow shadow; int64_t number; }; }; struct theme_key_desc { // key name that need to find const char *name; // value type in masks uint32_t type; // value offset in struct ukui_theme size_t offset; }; struct ukui_theme { struct widget_theme theme; struct wl_list link; struct hash_table *ht; struct theme_value default_bg; struct theme_value normal_border; struct theme_value normal_bg; struct theme_value hover_bg; struct theme_value hover_border; struct theme_value click_bg; struct theme_value click_border; struct theme_value close_hover_bg; struct theme_value close_click_bg; struct theme_value white_text; struct theme_value active_text; struct theme_value inactive_text; struct theme_value accent; struct theme_value corner_radius; struct theme_value active_border; struct theme_value inactive_border; struct theme_value active_bg; struct theme_value inactive_bg; struct theme_value modal_mask; struct theme_value active_shadow; struct theme_value inactive_shadow; struct theme_value modal_active_shadow; struct theme_value modal_inactive_shadow; struct theme_value menu_shadow; }; static struct ukui_theme_manager { struct wl_list themes; struct wl_listener server_destroy; } *manager = NULL; static const struct theme_key_desc theme_keys[] = { { "kgray-alpha0", theme_value_type_color, offsetof(struct ukui_theme, default_bg) }, { "kline-component-normal", theme_value_type_color | theme_value_type_gradient, offsetof(struct ukui_theme, normal_border) }, { "kcomponent-alpha-normal", theme_value_type_color | theme_value_type_gradient, offsetof(struct ukui_theme, normal_bg) }, { "kcomponent-alpha-hover", theme_value_type_color | theme_value_type_gradient, offsetof(struct ukui_theme, hover_bg) }, { "kline-component-hover", theme_value_type_color | theme_value_type_gradient, offsetof(struct ukui_theme, hover_border) }, { "kcomponent-alpha-click", theme_value_type_color | theme_value_type_gradient, offsetof(struct ukui_theme, click_bg) }, { "kline-component-click", theme_value_type_color | theme_value_type_gradient, offsetof(struct ukui_theme, click_border) }, { "kerror-hover", theme_value_type_color | theme_value_type_gradient, offsetof(struct ukui_theme, close_hover_bg) }, { "kerror-click", theme_value_type_color | theme_value_type_gradient, offsetof(struct ukui_theme, close_click_bg) }, { "kfont-white", theme_value_type_color, offsetof(struct ukui_theme, white_text) }, { "windowtext-active", theme_value_type_color, offsetof(struct ukui_theme, active_text) }, { "windowtext-inactive", theme_value_type_color, offsetof(struct ukui_theme, inactive_text) }, { "kbrand-normal", theme_value_type_color, offsetof(struct ukui_theme, accent) }, { "kradius-normal", theme_value_type_number, offsetof(struct ukui_theme, corner_radius) }, { "kline-window-active", theme_value_type_color | theme_value_type_gradient, offsetof(struct ukui_theme, active_border) }, { "kline-window-inactive", theme_value_type_color | theme_value_type_gradient, offsetof(struct ukui_theme, inactive_border) }, { "base-active", theme_value_type_color, offsetof(struct ukui_theme, active_bg) }, { "base-inactive", theme_value_type_color, offsetof(struct ukui_theme, inactive_bg) }, { "kmodalmask", theme_value_type_color, offsetof(struct ukui_theme, modal_mask) }, { "kshadow-primary-active", theme_value_type_shadow, offsetof(struct ukui_theme, active_shadow) }, { "kshadow-primary-inactive", theme_value_type_shadow, offsetof(struct ukui_theme, inactive_shadow) }, { "kshadow-secondary-active", theme_value_type_shadow, offsetof(struct ukui_theme, modal_active_shadow) }, { "kshadow-secondary-inactive", theme_value_type_shadow, offsetof(struct ukui_theme, modal_inactive_shadow) }, { "kshadow-menu", theme_value_type_shadow, offsetof(struct ukui_theme, menu_shadow) }, }; static void svg_start(FILE *stream, float x, float y, float width, float height) { fprintf(stream, "", x, y, width, height); } static void svg_stop(FILE *stream) { fprintf(stream, " \n"); } static void svg_begin(FILE *stream, float x, float y, float width, float height) { fprintf(stream, " ", x, y, width, height); } static void svg_end(FILE *stream) { fprintf(stream, " "); } static void svg_add_path(FILE *stream, const char *path, struct theme_value *fill) { fprintf(stream, " ", path, color_to_uint24(&fill->color), fill->color.a); } static void svg_add_rect(FILE *stream, float width, float height, struct theme_value *radius, float border_width, struct theme_value *border_color, struct theme_value *background) { static int url_index = 0; if (background->type == theme_value_type_color) { if (background->color.a == 0) { return; } fprintf(stream, " ", border_width / 2, border_width / 2, width - border_width, height - border_width, (float)radius->number, color_to_uint24(&background->color), color_to_uint24(&border_color->color), border_width, background->color.a); } else if (background->type == theme_value_type_gradient) { fprintf(stream, " ", border_width / 2, border_width / 2, width - border_width, height - border_width, (float)radius->number, color_to_uint24(&background->gradient.background), color_to_uint24(&border_color->color), border_width, background->gradient.background.a); url_index++; fprintf(stream, " ", border_width / 2, border_width / 2, width - border_width, height - border_width, (float)radius->number, url_index, color_to_uint24(&border_color->color), border_width, background->gradient.start.a); double angle = ANGLE(background->gradient.angle); fprintf(stream, " " " ", url_index, 0.5 - sin(angle) * 0.5, 0.5 + cos(angle) * 0.5, 0.5 + sin(angle) * 0.5, 0.5 - cos(angle) * 0.5, color_to_uint24(&background->gradient.start), color_to_uint24(&background->gradient.stop)); } } static void theme_generate_svgs(struct ukui_theme *theme) { char *buffer = NULL; size_t size = 0; FILE *stream = open_memstream(&buffer, &size); if (!stream) { return; } char *paths[5] = { MINIMIZE, MAXMIZE, RESTORE, CLOSE1, CLOSE2 }; float svg_size = BUTTON_SIZE - 2 * BUTTON_GAP; // svg start svg_start(stream, 0, 0, BUTTON_SIZE * 4, BUTTON_SIZE * 6); for (int i = 0; i < 6; i++) { for (int j = 0; j < 4; j++) { svg_begin(stream, j * BUTTON_SIZE + BUTTON_GAP, i * BUTTON_SIZE + BUTTON_GAP, svg_size, svg_size); if (i < 2) { // normal svg_add_rect(stream, svg_size, svg_size, &theme->corner_radius, 0, &theme->normal_border, &theme->default_bg); } else if (i < 4) { // hover svg_add_rect(stream, svg_size, svg_size, &theme->corner_radius, 0, &theme->hover_border, j == 3 ? &theme->close_hover_bg : &theme->hover_bg); } else { // click svg_add_rect(stream, svg_size, svg_size, &theme->corner_radius, 0, &theme->click_border, j == 3 ? &theme->close_click_bg : &theme->click_bg); } if (j == 3) { if (i < 2) { svg_add_path(stream, paths[j], i % 2 ? &theme->active_text : &theme->inactive_text); svg_add_path(stream, paths[j + 1], i % 2 ? &theme->active_text : &theme->inactive_text); } else { svg_add_path(stream, paths[j], &theme->white_text); svg_add_path(stream, paths[j + 1], &theme->white_text); } } else { if (i < 2) { svg_add_path(stream, paths[j], i % 2 ? &theme->active_text : &theme->inactive_text); } else { svg_add_path(stream, paths[j], &theme->active_text); } } svg_end(stream); } } // svg end svg_stop(stream); fclose(stream); #if 0 FILE *file = fopen("theme.svg", "w"); fwrite(buffer, size, 1, file); fclose(file); #endif theme->theme.button_svg = buffer; theme->theme.button_svg_size = size; } #define SET_COLOR(dst, src) \ color_to_float(dst, src.type == theme_value_type_color ? &src.color : &src.gradient.background); #define SET_GRADIENT(dst, src) \ color_to_float(dst.background, &src.gradient.background); \ color_to_float(dst.start, &src.gradient.start); \ color_to_float(dst.stop, &src.gradient.stop); \ dst.angle = src.gradient.angle; #define SET_SHADOW(dst, src) \ dst.num_layers = src.shadow.num_layers; \ for (int i = 0; i < src.shadow.num_layers; i++) { \ color_to_float(dst.layers[i].color, &src.shadow.layers[i].color); \ dst.layers[i].off_x = src.shadow.layers[i].off_x; \ dst.layers[i].off_y = src.shadow.layers[i].off_y; \ dst.layers[i].blur = src.shadow.layers[i].blur; \ dst.layers[i].spread = src.shadow.layers[i].spread; \ } static void theme_apply_values(struct ukui_theme *theme) { // border color SET_COLOR(theme->theme.active_border_color, theme->active_border); SET_COLOR(theme->theme.inactive_border_color, theme->inactive_border); // background color SET_COLOR(theme->theme.active_bg_color, theme->active_bg); SET_COLOR(theme->theme.inactive_bg_color, theme->inactive_bg); // text color SET_COLOR(theme->theme.active_text_color, theme->active_text); SET_COLOR(theme->theme.inactive_text_color, theme->inactive_text); // accent color SET_COLOR(theme->theme.accent_color, theme->accent); // modal mask color SET_COLOR(theme->theme.modal_mask_color, theme->modal_mask); // shadow color SET_SHADOW(theme->theme.active_shadow_color, theme->active_shadow); SET_SHADOW(theme->theme.inactive_shadow_color, theme->inactive_shadow); SET_SHADOW(theme->theme.modal_active_shadow_color, theme->modal_active_shadow); SET_SHADOW(theme->theme.modal_inactive_shadow_color, theme->modal_inactive_shadow); SET_SHADOW(theme->theme.menu_shadow_color, theme->menu_shadow); // state color, may be gradient SET_GRADIENT(theme->theme.normal_state_color, theme->normal_bg); SET_GRADIENT(theme->theme.hover_state_color, theme->hover_bg); SET_GRADIENT(theme->theme.click_state_color, theme->click_bg); } static bool theme_pair_parse(struct file *file, const char *key, const char *value, void *data) { const char *k = strstr(key, "--"); if (!k) { return false; } struct ukui_theme *theme = data; hash_table_insert(theme->ht, k + 2, (void *)value); return false; } static const char *get_alias(const char *str) { char *var = strstr(str, "--"); if (!var) { return NULL; } char *alias = malloc(strlen(str) - 6); // "var(--)" + 1 if (!alias) { return NULL; } char *d = alias, *s = var + 2; // skip -- while (*s != '\0' && *s != ')' && !isspace(*s)) { *d++ = tolower(*s++); } *d = '\0'; return alias; } static const char *theme_color_parse(struct color *color, const char *str) { if (STRING_INVALID(str)) { return str; } char *start = strchr(str, '('); if (!start) { return str; } char *end = strchr(start, ')'); if (!end) { return str; } sscanf(start, "(%hhu,%hhu,%hhu,%f)", &color->r, &color->g, &color->b, &color->a); return end + 1; } static bool theme_shadow_parse(struct shadow *shadow, const char *str) { struct shadow_layer *layer; const char *ptr = str; while (ptr) { if (shadow->num_layers == THEME_MAX_SHADOW_LAYERS) { kywc_log(KYWC_WARN, "Shadow layer > %d", THEME_MAX_SHADOW_LAYERS); return true; } layer = &shadow->layers[shadow->num_layers++]; int num = sscanf(ptr, "%dpx%dpx%dpx%dpx rgba(%hhu,%hhu,%hhu,%f)", &layer->off_x, &layer->off_y, &layer->blur, &layer->spread, &layer->color.r, &layer->color.g, &layer->color.b, &layer->color.a); if (num != 8) { shadow->num_layers = 0; return false; } if ((ptr = strstr(ptr, "),"))) { ptr += 2; } } return true; } static bool ukui_theme_value_parse(struct ukui_theme *theme, const struct theme_key_desc *desc, const char *str) { struct theme_value *value = (struct theme_value *)((char *)theme + desc->offset); if (strncmp(str, "linear-gradient", 15) == 0) { if (!(desc->type & theme_value_type_gradient)) { return false; } value->type = theme_value_type_gradient; const char *start = str + 16; // linear-gradient( char *end = strstr(start, "deg"); if (end) { value->gradient.angle = atoi(start); start = end + 1; } start = theme_color_parse(&value->gradient.start, start); if (end) { start = theme_color_parse(&value->gradient.stop, start); } else { value->gradient.stop = value->gradient.start; } theme_color_parse(&value->gradient.background, start); } else if (strncmp(str, "rgba", 4) == 0) { if (!(desc->type & theme_value_type_color)) { return false; } value->type = theme_value_type_color; theme_color_parse(&value->color, str); } else if (strstr(str, "rgba")) { if (!(desc->type & theme_value_type_shadow)) { return false; } value->type = theme_value_type_shadow; if (!theme_shadow_parse(&value->shadow, str)) { return false; } } else { if (!(desc->type & theme_value_type_number)) { return false; } value->type = theme_value_type_number; value->number = atoi(str); } return true; } static const char *ukui_theme_get_value(struct ukui_theme *theme, const char *key) { struct hash_entry *entry = hash_table_search(theme->ht, key); if (!entry || !entry->data) { return NULL; } const char *value = entry->data; if (strncmp(value, "var", 3) == 0) { const char *alias = get_alias(value); if (!alias) { return NULL; } value = ukui_theme_get_value(theme, alias); free((void *)alias); } return value; } static bool ukui_theme_post_parse(struct ukui_theme *theme) { const struct theme_key_desc *desc; const char *value; for (uint32_t i = 0; i < ARRAY_SIZE(theme_keys); i++) { desc = &theme_keys[i]; value = ukui_theme_get_value(theme, desc->name); if (!value) { kywc_log(KYWC_WARN, "%s is not found", desc->name); return false; } if (!ukui_theme_value_parse(theme, desc, value)) { kywc_log(KYWC_WARN, "%s parse failed", desc->name); return false; } } theme_generate_svgs(theme); theme_apply_values(theme); return true; } static struct ukui_theme *ukui_theme_create(const char *name, enum theme_type type) { struct ukui_theme *theme = calloc(1, sizeof(*theme)); if (!theme) { return NULL; } theme->theme.name = strdup(name); theme->theme.type = type; wl_list_insert(&manager->themes, &theme->link); theme->ht = hash_table_create_string(NULL); hash_table_set_max_entries(theme->ht, 300); return theme; } static void ukui_theme_destroy(struct ukui_theme *theme) { wl_list_remove(&theme->link); free((void *)theme->theme.button_svg); free((void *)theme->theme.name); free(theme); } static struct widget_theme *ukui_theme_load_from_file(const char *name, enum theme_type type) { /* load css file from THEME_DIR */ char path[512]; snprintf(path, 512, "%s/k%s-%s.css", THEME_DIR, name, type == THEME_TYPE_DARK ? "dark" : "light"); kywc_log(KYWC_INFO, "Load widget theme from %s", path); struct file *file = file_open(path, ";", ":"); if (!file) { return NULL; } struct ukui_theme *theme = ukui_theme_create(name, type); if (!theme) { file_close(file); return NULL; } file_parse(file, theme_pair_parse, theme); bool ok = ukui_theme_post_parse(theme); file_close(file); hash_table_destroy(theme->ht); if (!ok) { ukui_theme_destroy(theme); return NULL; } return &theme->theme; } static struct widget_theme *ukui_theme_load(const char *name, enum theme_type type) { struct ukui_theme *theme; wl_list_for_each(theme, &manager->themes, link) { if (strcmp(theme->theme.name, name) == 0 && theme->theme.type == type) { return &theme->theme; } } return ukui_theme_load_from_file(name, type); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->server_destroy.link); struct ukui_theme *theme, *tmp; wl_list_for_each_safe(theme, tmp, &manager->themes, link) { ukui_theme_destroy(theme); } free(manager); manager = NULL; } bool ukui_theme_manager_create(struct theme_manager *theme_manager) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } wl_list_init(&manager->themes); theme_manager->load_widget_theme = ukui_theme_load; manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(theme_manager->server, &manager->server_destroy); return true; } kylin-wayland-compositor/src/render/0000775000175000017500000000000015160461067016533 5ustar fengfengkylin-wayland-compositor/src/render/renderer_p.h0000664000175000017500000000071315160461067021032 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _RENDERER_P_H_ #define _RENDERER_P_H_ #include struct wlr_drm_format; bool wayland_drm_create(struct wl_display *display, struct wlr_renderer *renderer, int master_fd); bool ky_renderer_config_init(struct wlr_renderer *renderer); bool single_pixel_buffer_manager_create(struct wl_display *display); #endif /* _RENDERER_P_H_ */ kylin-wayland-compositor/src/render/meson.build0000664000175000017500000000037515160461067020702 0ustar fengfengwlcom_sources += files( 'config.c', 'pass.c', 'pixel_format.c', 'renderer.c', 'single_pixel.c', 'wayland_drm.c', ) if get_option('tracy') wlcom_sources += files('profile.c', 'opengl/profile.c') endif subdir('allocator') subdir('opengl') kylin-wayland-compositor/src/render/pixel_format.c0000664000175000017500000002253415160460057021374 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "render/pixel_format.h" #include "util/macros.h" /* * The DRM formats are little endian while the GL formats are big endian, * so DRM_FORMAT_ARGB8888 is actually compatible with GL_BGRA_EXT. */ static const struct ky_pixel_format formats[] = { { .drm_format = DRM_FORMAT_ARGB8888, .bytes_per_block = 4, .has_alpha = true, .gl_format = GL_BGRA_EXT, .gl_type = GL_UNSIGNED_BYTE, .pixman_format = PIXMAN_a8r8g8b8, }, { .drm_format = DRM_FORMAT_XRGB8888, .bytes_per_block = 4, .gl_format = GL_BGRA_EXT, .gl_type = GL_UNSIGNED_BYTE, .pixman_format = PIXMAN_x8r8g8b8, }, { .drm_format = DRM_FORMAT_XBGR8888, .bytes_per_block = 4, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_BYTE, .pixman_format = PIXMAN_x8b8g8r8, }, { .drm_format = DRM_FORMAT_ABGR8888, .bytes_per_block = 4, .has_alpha = true, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_BYTE, .pixman_format = PIXMAN_a8b8g8r8, }, { .drm_format = DRM_FORMAT_RGBX8888, .bytes_per_block = 4, .pixman_format = PIXMAN_r8g8b8x8, }, { .drm_format = DRM_FORMAT_RGBA8888, .bytes_per_block = 4, .has_alpha = true, .pixman_format = PIXMAN_r8g8b8a8, }, { .drm_format = DRM_FORMAT_BGRX8888, .bytes_per_block = 4, .pixman_format = PIXMAN_b8g8r8x8, }, { .drm_format = DRM_FORMAT_BGRA8888, .bytes_per_block = 4, .has_alpha = true, .pixman_format = PIXMAN_b8g8r8a8, }, { .drm_format = DRM_FORMAT_R8, .bytes_per_block = 1, }, { .drm_format = DRM_FORMAT_GR88, .bytes_per_block = 2, }, { .drm_format = DRM_FORMAT_RGB888, .bytes_per_block = 3, }, { .drm_format = DRM_FORMAT_BGR888, .bytes_per_block = 3, .gl_format = GL_RGB, .gl_type = GL_UNSIGNED_BYTE, }, /* little endian */ { .drm_format = DRM_FORMAT_RGBX4444, .bytes_per_block = 2, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_SHORT_4_4_4_4, }, { .drm_format = DRM_FORMAT_RGBA4444, .bytes_per_block = 2, .has_alpha = true, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_SHORT_4_4_4_4, }, { .drm_format = DRM_FORMAT_BGRX4444, .bytes_per_block = 2, }, { .drm_format = DRM_FORMAT_BGRA4444, .bytes_per_block = 2, .has_alpha = true, }, { .drm_format = DRM_FORMAT_RGBX5551, .bytes_per_block = 2, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_SHORT_5_5_5_1, }, { .drm_format = DRM_FORMAT_RGBA5551, .bytes_per_block = 2, .has_alpha = true, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_SHORT_5_5_5_1, }, { .drm_format = DRM_FORMAT_BGRX5551, .bytes_per_block = 2, }, { .drm_format = DRM_FORMAT_BGRA5551, .bytes_per_block = 2, .has_alpha = true, }, { .drm_format = DRM_FORMAT_XRGB1555, .bytes_per_block = 2, }, { .drm_format = DRM_FORMAT_ARGB1555, .bytes_per_block = 2, .has_alpha = true, }, { .drm_format = DRM_FORMAT_RGB565, .bytes_per_block = 2, .gl_format = GL_RGB, .gl_type = GL_UNSIGNED_SHORT_5_6_5, .pixman_format = PIXMAN_r5g6b5, }, { .drm_format = DRM_FORMAT_BGR565, .bytes_per_block = 2, .pixman_format = PIXMAN_b5g6r5, }, { .drm_format = DRM_FORMAT_XRGB2101010, .bytes_per_block = 4, .pixman_format = PIXMAN_x2r10g10b10, }, { .drm_format = DRM_FORMAT_ARGB2101010, .bytes_per_block = 4, .has_alpha = true, .pixman_format = PIXMAN_a2r10g10b10, }, { .drm_format = DRM_FORMAT_XBGR2101010, .bytes_per_block = 4, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, .pixman_format = PIXMAN_x2b10g10r10, }, { .drm_format = DRM_FORMAT_ABGR2101010, .bytes_per_block = 4, .has_alpha = true, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, .pixman_format = PIXMAN_a2b10g10r10, }, { .drm_format = DRM_FORMAT_XBGR16161616F, .bytes_per_block = 8, .gl_format = GL_RGBA, .gl_type = GL_HALF_FLOAT_OES, }, { .drm_format = DRM_FORMAT_ABGR16161616F, .bytes_per_block = 8, .has_alpha = true, .gl_format = GL_RGBA, .gl_type = GL_HALF_FLOAT_OES, }, { .drm_format = DRM_FORMAT_XBGR16161616, .bytes_per_block = 8, .gl_internalformat = GL_RGBA16_EXT, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_SHORT, }, { .drm_format = DRM_FORMAT_ABGR16161616, .bytes_per_block = 8, .has_alpha = true, .gl_internalformat = GL_RGBA16_EXT, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_SHORT, }, { .drm_format = DRM_FORMAT_YVYU, .bytes_per_block = 4, .block_width = 2, .block_height = 1, }, { .drm_format = DRM_FORMAT_VYUY, .bytes_per_block = 4, .block_width = 2, .block_height = 1, }, }; void ky_pixel_formats_for_each(ky_pixel_formats_iterator_func_t iterator, void *data) { for (size_t i = 0; i < ARRAY_SIZE(formats); ++i) { if (iterator(&formats[i], data)) { break; } } } const struct ky_pixel_format *ky_pixel_format_from_drm(uint32_t fmt) { for (size_t i = 0; i < ARRAY_SIZE(formats); ++i) { if (formats[i].drm_format == fmt) { return &formats[i]; } } return NULL; } const struct ky_pixel_format *ky_pixel_format_from_gl(GLint gl_format, GLint gl_type, bool alpha) { for (size_t i = 0; i < ARRAY_SIZE(formats); ++i) { if (formats[i].gl_format == gl_format && formats[i].gl_type == gl_type && formats[i].has_alpha == alpha) { return &formats[i]; } } return NULL; } uint32_t ky_pixel_format_pixels_per_block(const struct ky_pixel_format *format) { uint32_t pixels = format->block_width * format->block_height; return pixels > 0 ? pixels : 1; } static int32_t div_round_up(int32_t dividend, int32_t divisor) { int32_t quotient = dividend / divisor; if (dividend % divisor != 0) { quotient++; } return quotient; } int32_t ky_pixel_format_min_stride(const struct ky_pixel_format *format, int32_t width) { int32_t pixels_per_block = (int32_t)ky_pixel_format_pixels_per_block(format); int32_t bytes_per_block = (int32_t)format->bytes_per_block; if (width > INT32_MAX / bytes_per_block) { kywc_log(KYWC_DEBUG, "Invalid width %d (overflow)", width); return 0; } return div_round_up(width * bytes_per_block, pixels_per_block); } bool ky_pixel_format_check_stride(const struct ky_pixel_format *format, int32_t stride, int32_t width) { int32_t bytes_per_block = (int32_t)format->bytes_per_block; if (stride % bytes_per_block != 0) { kywc_log(KYWC_DEBUG, "Invalid stride %d (incompatible with %d bytes-per-block)", stride, bytes_per_block); return false; } int32_t min_stride = ky_pixel_format_min_stride(format, width); if (min_stride <= 0) { return false; } else if (stride < min_stride) { kywc_log(KYWC_DEBUG, "Invalid stride %d (too small for %d bytes-per-block and width %d)", stride, bytes_per_block, width); return false; } return true; } bool ky_opengl_pixel_format_is_supported(const struct ky_opengl_renderer *renderer, const struct ky_pixel_format *format) { if (format->gl_type == 0) { return false; } if (format->gl_type == GL_UNSIGNED_INT_2_10_10_10_REV_EXT && !renderer->exts.EXT_texture_type_2_10_10_10_REV) { return false; } if (format->gl_type == GL_HALF_FLOAT_OES && !renderer->exts.OES_texture_half_float_linear) { return false; } if (format->gl_type == GL_UNSIGNED_SHORT && !renderer->exts.EXT_texture_norm16) { return false; } /* * Note that we don't need to check for GL_EXT_texture_format_BGRA8888 * here, since we've already checked if we have it at renderer creation * time and bailed out if not. We do the check there because Wayland * requires all compositors to support SHM buffers in that format. */ return true; } void ky_opengl_get_shm_formats(const struct ky_opengl_renderer *renderer, struct wlr_drm_format_set *out) { for (size_t i = 0; i < ARRAY_SIZE(formats); i++) { if (!ky_opengl_pixel_format_is_supported(renderer, &formats[i])) { continue; } wlr_drm_format_set_add(out, formats[i].drm_format, DRM_FORMAT_MOD_INVALID); wlr_drm_format_set_add(out, formats[i].drm_format, DRM_FORMAT_MOD_LINEAR); } } kylin-wayland-compositor/src/render/wayland_drm.c0000664000175000017500000002240215160460057021176 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "drm-protocol.h" #include "renderer_p.h" #define WLR_DRM_VERSION 2 struct wayland_drm_buffer { struct wlr_buffer base; struct wl_resource *resource; struct wlr_dmabuf_attributes dmabuf; struct wl_listener release; }; struct wayland_drm { struct wl_global *global; struct wl_listener display_destroy; struct wlr_renderer *renderer; char *node_name; int master_fd; }; static void buffer_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct wl_buffer_interface wl_buffer_impl = { .destroy = buffer_handle_destroy, }; static const struct wlr_buffer_impl buffer_impl; static struct wayland_drm_buffer *drm_buffer_from_buffer(struct wlr_buffer *wlr_buffer) { assert(wlr_buffer->impl == &buffer_impl); struct wayland_drm_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); return buffer; } static void buffer_destroy(struct wlr_buffer *wlr_buffer) { struct wayland_drm_buffer *buffer = drm_buffer_from_buffer(wlr_buffer); if (buffer->resource != NULL) { wl_resource_set_user_data(buffer->resource, NULL); } wlr_dmabuf_attributes_finish(&buffer->dmabuf); wl_list_remove(&buffer->release.link); free(buffer); } static bool buffer_get_dmabuf(struct wlr_buffer *wlr_buffer, struct wlr_dmabuf_attributes *dmabuf) { struct wayland_drm_buffer *buffer = drm_buffer_from_buffer(wlr_buffer); *dmabuf = buffer->dmabuf; return true; } static const struct wlr_buffer_impl buffer_impl = { .destroy = buffer_destroy, .get_dmabuf = buffer_get_dmabuf, }; static bool buffer_resource_is_instance(struct wl_resource *resource) { return wl_resource_instance_of(resource, &wl_buffer_interface, &wl_buffer_impl); } static struct wayland_drm_buffer *drm_buffer_try_from_resource(struct wl_resource *resource) { if (!buffer_resource_is_instance(resource)) { return NULL; } return wl_resource_get_user_data(resource); } static void buffer_handle_resource_destroy(struct wl_resource *resource) { struct wayland_drm_buffer *buffer = drm_buffer_try_from_resource(resource); assert(buffer != NULL); buffer->resource = NULL; wlr_buffer_drop(&buffer->base); } static void buffer_handle_release(struct wl_listener *listener, void *data) { struct wayland_drm_buffer *buffer = wl_container_of(listener, buffer, release); if (buffer->resource != NULL) { wl_buffer_send_release(buffer->resource); } } static void drm_handle_authenticate(struct wl_client *client, struct wl_resource *resource, uint32_t id) { struct wayland_drm *drm = wl_resource_get_user_data(resource); if (drm->master_fd >= 0 && drmAuthMagic(drm->master_fd, id) < 0) { wl_resource_post_error(resource, WL_DRM_ERROR_AUTHENTICATE_FAIL, "authenticate failed"); return; } wl_drm_send_authenticated(resource); } static void drm_handle_create_buffer(struct wl_client *client, struct wl_resource *resource, uint32_t id, uint32_t name, int32_t width, int32_t height, uint32_t stride, uint32_t format) { wl_resource_post_error(resource, WL_DRM_ERROR_INVALID_NAME, "Flink handles are not supported, use DMA-BUF instead"); } static void drm_handle_create_planar_buffer(struct wl_client *client, struct wl_resource *resource, uint32_t id, uint32_t name, int32_t width, int32_t height, uint32_t format, int32_t offset0, int32_t stride0, int32_t offset1, int32_t stride1, int32_t offset2, int32_t stride2) { wl_resource_post_error(resource, WL_DRM_ERROR_INVALID_NAME, "Flink handles are not supported, use DMA-BUF instead"); } static void drm_handle_create_prime_buffer(struct wl_client *client, struct wl_resource *resource, uint32_t id, int fd, int32_t width, int32_t height, uint32_t format, int32_t offset0, int32_t stride0, int32_t offset1, int32_t stride1, int32_t offset2, int32_t stride2) { struct wlr_dmabuf_attributes dmabuf = { .width = width, .height = height, .format = format, .modifier = DRM_FORMAT_MOD_INVALID, .n_planes = 1, .offset[0] = offset0, .stride[0] = stride0, .fd[0] = fd, }; struct wayland_drm_buffer *buffer = calloc(1, sizeof(*buffer)); if (buffer == NULL) { close(fd); wl_resource_post_no_memory(resource); return; } wlr_buffer_init(&buffer->base, &buffer_impl, width, height); buffer->resource = wl_resource_create(client, &wl_buffer_interface, 1, id); if (buffer->resource == NULL) { free(buffer); close(fd); wl_resource_post_no_memory(resource); return; } wl_resource_set_implementation(buffer->resource, &wl_buffer_impl, buffer, buffer_handle_resource_destroy); buffer->dmabuf = dmabuf; buffer->release.notify = buffer_handle_release; wl_signal_add(&buffer->base.events.release, &buffer->release); } static const struct wl_drm_interface drm_impl = { .authenticate = drm_handle_authenticate, .create_buffer = drm_handle_create_buffer, .create_planar_buffer = drm_handle_create_planar_buffer, .create_prime_buffer = drm_handle_create_prime_buffer, }; static void drm_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wayland_drm *drm = data; struct wl_resource *resource = wl_resource_create(client, &wl_drm_interface, version, id); if (resource == NULL) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &drm_impl, drm, NULL); wl_drm_send_device(resource, drm->node_name); wl_drm_send_capabilities(resource, WL_DRM_CAPABILITY_PRIME); const struct wlr_drm_format_set *formats = wlr_renderer_get_texture_formats(drm->renderer, WLR_BUFFER_CAP_DMABUF); for (size_t i = 0; i < formats->len; i++) { const struct wlr_drm_format *fmt = &formats->formats[i]; for (size_t i = 0; i < fmt->len; ++i) { if (fmt->modifiers[i] == DRM_FORMAT_MOD_INVALID) { wl_drm_send_format(resource, fmt->format); break; } } } } static struct wlr_buffer *buffer_from_resource(struct wl_resource *resource) { struct wayland_drm_buffer *buffer = drm_buffer_try_from_resource(resource); assert(buffer != NULL); return &buffer->base; } static const struct wlr_buffer_resource_interface buffer_resource_interface = { .name = "wayland_drm_buffer", .is_instance = buffer_resource_is_instance, .from_resource = buffer_from_resource, }; static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wayland_drm *drm = wl_container_of(listener, drm, display_destroy); wl_list_remove(&drm->display_destroy.link); wl_global_destroy(drm->global); free(drm->node_name); free(drm); } bool wayland_drm_create(struct wl_display *display, struct wlr_renderer *renderer, int master_fd) { int drm_fd = wlr_renderer_get_drm_fd(renderer); if (drm_fd < 0) { kywc_log(KYWC_ERROR, "Failed to get DRM FD from renderer"); return false; } drmDevice *dev = NULL; if (drmGetDevice2(drm_fd, 0, &dev) != 0) { kywc_log_errno(KYWC_ERROR, "Failed with drmGetDevice2"); return false; } char *node_name = NULL; if (dev->available_nodes & (1 << DRM_NODE_RENDER)) { node_name = strdup(dev->nodes[DRM_NODE_RENDER]); } else { assert(dev->available_nodes & (1 << DRM_NODE_PRIMARY)); kywc_log(KYWC_DEBUG, "No DRM render node available, falling back to primary node '%s'", dev->nodes[DRM_NODE_PRIMARY]); node_name = strdup(dev->nodes[DRM_NODE_PRIMARY]); } drmFreeDevice(&dev); if (node_name == NULL) { return false; } struct wayland_drm *drm = calloc(1, sizeof(*drm)); if (drm == NULL) { free(node_name); return false; } drm->node_name = node_name; drm->renderer = renderer; drm->master_fd = master_fd; drm->global = wl_global_create(display, &wl_drm_interface, WLR_DRM_VERSION, drm, drm_bind); if (drm->global == NULL) { goto error; } drm->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(display, &drm->display_destroy); wlr_buffer_register_resource_interface(&buffer_resource_interface); return true; error: free(drm->node_name); free(drm); return false; } kylin-wayland-compositor/src/render/allocator/0000775000175000017500000000000015160461067020513 5ustar fengfengkylin-wayland-compositor/src/render/allocator/meson.build0000664000175000017500000000014115160461067022651 0ustar fengfengwlcom_sources += files( 'allocator.c', 'drm_dumb.c', 'gbm.c', 'shm.c', 'swapchain.c' ) kylin-wayland-compositor/src/render/allocator/drm_dumb.c0000664000175000017500000001510315160461067022450 0ustar fengfeng// SPDX-FileCopyrightText: 2024 The wlroots contributors // SPDX-FileCopyrightText: 2026 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include "allocator_p.h" #include "render/pixel_format.h" struct dumb_buffer { struct wlr_buffer base; struct wl_list link; int drm_fd; // -1 if the allocator has been destroyed struct wlr_dmabuf_attributes dmabuf; uint32_t format; uint32_t handle; uint32_t stride; uint32_t width, height; uint64_t size; void *data; }; struct dumb_alloc { int drm_fd; struct wl_list buffers; }; static const struct wlr_buffer_impl buffer_impl; static struct dumb_buffer *dumb_buffer_from_buffer(struct wlr_buffer *wlr_buffer) { assert(wlr_buffer->impl == &buffer_impl); struct dumb_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); return buffer; } static bool drm_format_has(const struct wlr_drm_format *fmt, uint64_t modifier) { for (size_t i = 0; i < fmt->len; ++i) { if (fmt->modifiers[i] == modifier) { return true; } } return false; } struct wlr_buffer *dumb_buffer_create(struct dumb_alloc *alloc, int width, int height, const struct wlr_drm_format *drm_format) { if (!alloc) { return NULL; } if (drm_format_has(drm_format, DRM_FORMAT_MOD_INVALID) && !drm_format_has(drm_format, DRM_FORMAT_MOD_LINEAR)) { kywc_log(KYWC_ERROR, "DRM dumb allocator only supports INVALID and LINEAR modifiers"); return NULL; } const struct ky_pixel_format *info = ky_pixel_format_from_drm(drm_format->format); if (!info) { kywc_log(KYWC_ERROR, "DRM format 0x%" PRIX32 " not supported", drm_format->format); return NULL; } else if (ky_pixel_format_pixels_per_block(info) != 1) { kywc_log(KYWC_ERROR, "Block formats are not supported"); return NULL; } struct dumb_buffer *buffer = calloc(1, sizeof(*buffer)); if (!buffer) { return NULL; } wlr_buffer_init(&buffer->base, &buffer_impl, width, height); wl_list_insert(&alloc->buffers, &buffer->link); buffer->drm_fd = alloc->drm_fd; uint32_t bpp = 8 * info->bytes_per_block; if (drmModeCreateDumbBuffer(alloc->drm_fd, width, height, bpp, 0, &buffer->handle, &buffer->stride, &buffer->size) != 0) { kywc_log_errno(KYWC_ERROR, "Failed to create DRM dumb buffer"); goto create_destroy; } buffer->width = width; buffer->height = height; buffer->format = drm_format->format; uint64_t offset; if (drmModeMapDumbBuffer(alloc->drm_fd, buffer->handle, &offset) != 0) { kywc_log_errno(KYWC_ERROR, "Failed to map DRM dumb buffer"); goto create_destroy; } buffer->data = mmap(NULL, buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, alloc->drm_fd, offset); if (buffer->data == MAP_FAILED) { kywc_log_errno(KYWC_ERROR, "Failed to mmap DRM dumb buffer"); goto create_destroy; } memset(buffer->data, 0, buffer->size); int prime_fd; if (drmPrimeHandleToFD(alloc->drm_fd, buffer->handle, DRM_CLOEXEC, &prime_fd) != 0) { kywc_log_errno(KYWC_ERROR, "Failed to get PRIME handle from GEM handle"); goto create_destroy; } buffer->dmabuf = (struct wlr_dmabuf_attributes){ .width = buffer->width, .height = buffer->height, .format = drm_format->format, .modifier = DRM_FORMAT_MOD_LINEAR, .n_planes = 1, .offset[0] = 0, .stride[0] = buffer->stride, .fd[0] = prime_fd, }; kywc_log(KYWC_DEBUG, "Allocated %" PRIu32 "x%" PRIu32 " DRM dumb buffer", buffer->width, buffer->height); return &buffer->base; create_destroy: wlr_buffer_drop(&buffer->base); return NULL; } static bool dumb_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, uint32_t flags, void **data, uint32_t *format, size_t *stride) { struct dumb_buffer *buffer = dumb_buffer_from_buffer(wlr_buffer); *data = buffer->data; *stride = buffer->stride; *format = buffer->format; return true; } static void dumb_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { // This space is intentionally left blank } static bool buffer_get_dmabuf(struct wlr_buffer *wlr_buffer, struct wlr_dmabuf_attributes *attribs) { struct dumb_buffer *buffer = dumb_buffer_from_buffer(wlr_buffer); *attribs = buffer->dmabuf; return true; } static void buffer_destroy(struct wlr_buffer *wlr_buffer) { struct dumb_buffer *buffer = dumb_buffer_from_buffer(wlr_buffer); if (buffer->data) { munmap(buffer->data, buffer->size); } wlr_dmabuf_attributes_finish(&buffer->dmabuf); if (buffer->drm_fd >= 0) { if (drmModeDestroyDumbBuffer(buffer->drm_fd, buffer->handle) != 0) { kywc_log_errno(KYWC_ERROR, "Failed to destroy DRM dumb buffer"); } } wl_list_remove(&buffer->link); free(buffer); } static const struct wlr_buffer_impl buffer_impl = { .destroy = buffer_destroy, .get_dmabuf = buffer_get_dmabuf, .begin_data_ptr_access = dumb_buffer_begin_data_ptr_access, .end_data_ptr_access = dumb_buffer_end_data_ptr_access, }; void dumb_alloc_destroy(struct dumb_alloc *alloc) { if (!alloc) { return; } struct dumb_buffer *buf, *buf_tmp; wl_list_for_each_safe(buf, buf_tmp, &alloc->buffers, link) { buf->drm_fd = -1; wl_list_remove(&buf->link); wl_list_init(&buf->link); } close(alloc->drm_fd); free(alloc); } struct dumb_alloc *dumb_alloc_create(int drm_fd) { if (drmGetNodeTypeFromFd(drm_fd) != DRM_NODE_PRIMARY) { kywc_log(KYWC_ERROR, "Cannot use DRM dumb buffers with non-primary DRM FD"); return NULL; } uint64_t has_dumb = 0; if (drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0) { kywc_log(KYWC_ERROR, "Failed to get DRM capabilities"); return NULL; } if (!has_dumb) { kywc_log(KYWC_ERROR, "DRM dumb buffers not supported"); return NULL; } struct dumb_alloc *alloc = calloc(1, sizeof(*alloc)); if (!alloc) { return NULL; } alloc->drm_fd = drm_fd; wl_list_init(&alloc->buffers); kywc_log(KYWC_INFO, "Created DRM dumb allocator"); return alloc; } kylin-wayland-compositor/src/render/allocator/gbm.c0000664000175000017500000003107715160461067021434 0ustar fengfeng// SPDX-FileCopyrightText: 2025 The wlroots contributors // SPDX-FileCopyrightText: 2026 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include #include #include "allocator_p.h" #include "render/allocator.h" #include "render/opengl.h" struct gbm_buffer { struct wlr_buffer base; struct wl_list link; struct gbm_bo *gbm_bo; // NULL if the gbm_device has been destroyed struct wlr_dmabuf_attributes dmabuf; }; struct gbm_alloc { int fd; struct gbm_device *gbm_device; struct wl_list buffers; struct gbm_bo *(*create_with_modifiers2)(struct gbm_device *gbm, uint32_t width, uint32_t height, uint32_t format, const uint64_t *modifiers, const unsigned int count, uint32_t flags); int (*get_fd_for_plane)(struct gbm_bo *bo, int plane); int (*get_format_modifier_plane_count)(struct gbm_device *gbm, uint32_t format, uint64_t modifier); struct wlr_drm_format_set formats; }; static const struct wlr_buffer_impl buffer_impl; static struct gbm_buffer *get_gbm_buffer_from_buffer(struct wlr_buffer *wlr_buffer) { assert(wlr_buffer->impl == &buffer_impl); struct gbm_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); return buffer; } static bool export_gbm_bo(struct gbm_alloc *alloc, struct gbm_bo *bo, struct wlr_dmabuf_attributes *out) { struct wlr_dmabuf_attributes attribs = { 0 }; attribs.n_planes = gbm_bo_get_plane_count(bo); if (attribs.n_planes > WLR_DMABUF_MAX_PLANES) { kywc_log(KYWC_ERROR, "GBM BO contains too many planes (%d)", attribs.n_planes); return false; } attribs.width = gbm_bo_get_width(bo); attribs.height = gbm_bo_get_height(bo); attribs.format = gbm_bo_get_format(bo); attribs.modifier = gbm_bo_get_modifier(bo); int i; int32_t handle = -1; for (i = 0; i < attribs.n_planes; ++i) { if (alloc->get_fd_for_plane != NULL) { attribs.fd[i] = alloc->get_fd_for_plane(bo, i); if (attribs.fd[i] < 0) { kywc_log(KYWC_ERROR, "gbm_bo_get_fd_for_plane failed"); goto error_fd; } } else { // GBM is lacking a function to get a FD for a given plane. Instead, // check all planes have the same handle. We can't use // drmPrimeHandleToFD because that messes up handle ref'counting in // the user-space driver. union gbm_bo_handle plane_handle = gbm_bo_get_handle_for_plane(bo, i); if (plane_handle.s32 < 0) { kywc_log(KYWC_ERROR, "Gbm_bo_get_handle_for_plane failed"); goto error_fd; } if (i == 0) { handle = plane_handle.s32; } else if (plane_handle.s32 != handle) { kywc_log(KYWC_ERROR, "Failed to export GBM BO: " "all planes don't have the same GEM handle"); goto error_fd; } attribs.fd[i] = gbm_bo_get_fd(bo); if (attribs.fd[i] < 0) { kywc_log(KYWC_ERROR, "gbm_bo_get_fd failed"); goto error_fd; } } attribs.offset[i] = gbm_bo_get_offset(bo, i); attribs.stride[i] = gbm_bo_get_stride_for_plane(bo, i); } *out = attribs; return true; error_fd: for (int j = 0; j < i; ++j) { close(attribs.fd[j]); } return false; } static bool drm_format_has(const struct wlr_drm_format *fmt, uint64_t modifier) { for (size_t i = 0; i < fmt->len; ++i) { if (fmt->modifiers[i] == modifier) { return true; } } return false; } static uint32_t gbm_usage_from_flags(uint32_t flags) { uint32_t usage = GBM_BO_USE_SCANOUT; if (flags & ALLOCATOR_BUFFER_RENDERING) { usage |= GBM_BO_USE_RENDERING; } if (flags & ALLOCATOR_BUFFER_NO_SCANOUT) { usage &= ~GBM_BO_USE_SCANOUT; } if (flags & ALLOCATOR_BUFFER_CURSOR) { usage |= GBM_BO_USE_CURSOR; } if (flags & ALLOCATOR_BUFFER_LINEAR) { usage |= GBM_BO_USE_LINEAR; } return usage; } static struct gbm_buffer *create_buffer(struct gbm_alloc *alloc, int width, int height, const struct wlr_drm_format *format, uint32_t flags) { assert(format->len > 0); errno = 0; bool has_modifier = true; uint64_t fallback_modifier = DRM_FORMAT_MOD_INVALID; struct gbm_bo *bo = NULL; struct gbm_device *gbm_device = alloc->gbm_device; uint32_t usage = gbm_usage_from_flags(flags); if (alloc->create_with_modifiers2) { bo = alloc->create_with_modifiers2(gbm_device, width, height, format->format, format->modifiers, format->len, usage); } if (!bo && (usage & GBM_BO_USE_SCANOUT)) { bo = gbm_bo_create_with_modifiers(gbm_device, width, height, format->format, format->modifiers, format->len); } if (!bo) { usage |= GBM_BO_USE_RENDERING; if (format->len == 1 && format->modifiers[0] == DRM_FORMAT_MOD_LINEAR) { usage |= GBM_BO_USE_LINEAR; fallback_modifier = DRM_FORMAT_MOD_LINEAR; } else if (!drm_format_has(format, DRM_FORMAT_MOD_INVALID)) { // If the format doesn't accept an implicit modifier, bail out. kywc_log_errno(KYWC_ERROR, "gbm_bo_create_with_modifiers failed"); return NULL; } errno = 0; bo = gbm_bo_create(gbm_device, width, height, format->format, usage); has_modifier = false; } if (!bo) { kywc_log_errno(KYWC_ERROR, "gbm_bo_create failed"); return NULL; } struct gbm_buffer *buffer = calloc(1, sizeof(*buffer)); if (!buffer) { gbm_bo_destroy(bo); return NULL; } wlr_buffer_init(&buffer->base, &buffer_impl, width, height); buffer->gbm_bo = bo; if (!export_gbm_bo(alloc, bo, &buffer->dmabuf)) { free(buffer); gbm_bo_destroy(bo); return NULL; } // If the buffer has been allocated with an implicit modifier, make sure we // don't populate the modifier field: other parts of the stack may not // understand modifiers, and they can't strip the modifier. if (!has_modifier) { buffer->dmabuf.modifier = fallback_modifier; } wl_list_insert(&alloc->buffers, &buffer->link); char *format_name = drmGetFormatName(buffer->dmabuf.format); char *modifier_name = drmGetFormatModifierName(buffer->dmabuf.modifier); kywc_log(KYWC_DEBUG, "Allocated %dx%d GBM buffer " "with format %s (0x%08" PRIX32 "), modifier %s (0x%016" PRIX64 ")", buffer->base.width, buffer->base.height, format_name ? format_name : "", buffer->dmabuf.format, modifier_name ? modifier_name : "", buffer->dmabuf.modifier); free(format_name); free(modifier_name); return buffer; } static void buffer_destroy(struct wlr_buffer *wlr_buffer) { struct gbm_buffer *buffer = get_gbm_buffer_from_buffer(wlr_buffer); wlr_dmabuf_attributes_finish(&buffer->dmabuf); if (buffer->gbm_bo) { gbm_bo_destroy(buffer->gbm_bo); } wl_list_remove(&buffer->link); free(buffer); } static bool buffer_get_dmabuf(struct wlr_buffer *wlr_buffer, struct wlr_dmabuf_attributes *attribs) { struct gbm_buffer *buffer = get_gbm_buffer_from_buffer(wlr_buffer); *attribs = buffer->dmabuf; return true; } static const struct wlr_buffer_impl buffer_impl = { .destroy = buffer_destroy, .get_dmabuf = buffer_get_dmabuf, }; static void query_single_plane_formats(struct gbm_alloc *alloc, struct wlr_renderer *renderer) { if (!renderer) { return; } if (wlr_renderer_is_opengl(renderer)) { struct ky_opengl_renderer *r = ky_opengl_renderer_from_wlr_renderer(renderer); // modifiers is disabled, no need to query the formats if (!r->egl->has_modifiers) { return; } } if (!renderer->WLR_PRIVATE.impl->get_render_formats) { return; } const struct wlr_drm_format_set *render_formats = renderer->WLR_PRIVATE.impl->get_render_formats(renderer); if (!render_formats) { return; } const struct wlr_drm_format *fmt; for (size_t i = 0; i < render_formats->len; i++) { fmt = &render_formats->formats[i]; for (size_t j = 0; j < fmt->len; ++j) { if (fmt->modifiers[j] == DRM_FORMAT_MOD_INVALID || fmt->modifiers[j] == DRM_FORMAT_MOD_LINEAR) { wlr_drm_format_set_add(&alloc->formats, fmt->format, fmt->modifiers[j]); continue; } if (alloc->get_format_modifier_plane_count && alloc->get_format_modifier_plane_count(alloc->gbm_device, fmt->format, fmt->modifiers[j]) != 1) { continue; } // treat as single plane if no get_format_modifier_plane_count wlr_drm_format_set_add(&alloc->formats, fmt->format, fmt->modifiers[j]); } } } static const struct wlr_drm_format *get_single_plane_formats(struct gbm_alloc *alloc, uint32_t drm_format) { const struct wlr_drm_format *format = wlr_drm_format_set_get(&alloc->formats, drm_format); if (!format) { kywc_log(KYWC_ERROR, "Renderer doesn't support format 0x%" PRIX32, drm_format); return NULL; } return format; } struct wlr_buffer *gbm_buffer_create(struct gbm_alloc *alloc, int width, int height, const struct wlr_drm_format *format, uint32_t flags) { if (!alloc) { return NULL; } const struct wlr_drm_format *alloc_format = NULL; if (flags & ALLOCATOR_BUFFER_SINGLE_PLANE) { alloc_format = get_single_plane_formats(alloc, format->format); } if (!alloc_format) { alloc_format = format; } struct gbm_buffer *buffer = create_buffer(alloc, width, height, alloc_format, flags); if (!buffer) { return NULL; } kywc_log(KYWC_DEBUG, "Allocated %" PRIu32 "x%" PRIu32 " GBM buffer", width, height); return &buffer->base; } void gbm_alloc_destroy(struct gbm_alloc *alloc) { // The gbm_bo objects need to be destroyed before the gbm_device struct gbm_buffer *buf, *buf_tmp; wl_list_for_each_safe(buf, buf_tmp, &alloc->buffers, link) { gbm_bo_destroy(buf->gbm_bo); buf->gbm_bo = NULL; wl_list_remove(&buf->link); wl_list_init(&buf->link); } wlr_drm_format_set_finish(&alloc->formats); gbm_device_destroy(alloc->gbm_device); close(alloc->fd); free(alloc); } struct gbm_alloc *gbm_alloc_create(struct wlr_renderer *renderer, int fd) { uint64_t cap; if (drmGetCap(fd, DRM_CAP_PRIME, &cap) || !(cap & DRM_PRIME_CAP_EXPORT)) { kywc_log(KYWC_ERROR, "PRIME export not supported"); return false; } struct gbm_alloc *alloc = calloc(1, sizeof(*alloc)); if (!alloc) { return NULL; } alloc->gbm_device = gbm_create_device(fd); if (!alloc->gbm_device) { kywc_log(KYWC_ERROR, "gbm_create_device failed"); free(alloc); return NULL; } alloc->fd = fd; wl_list_init(&alloc->buffers); alloc->create_with_modifiers2 = dlsym(RTLD_DEFAULT, "gbm_bo_create_with_modifiers2"); if (!alloc->create_with_modifiers2) { kywc_log(KYWC_WARN, "No gbm_bo_create_with_modifiers2 support!"); } alloc->get_fd_for_plane = dlsym(RTLD_DEFAULT, "gbm_bo_get_fd_for_plane"); if (!alloc->get_fd_for_plane) { kywc_log(KYWC_WARN, "No gbm_bo_get_fd_for_plane support!"); } alloc->get_format_modifier_plane_count = dlsym(RTLD_DEFAULT, "gbm_device_get_format_modifier_plane_count"); if (!alloc->get_format_modifier_plane_count) { kywc_log(KYWC_WARN, "No gbm_device_get_format_modifier_plane_count support!"); } query_single_plane_formats(alloc, renderer); kywc_log(KYWC_INFO, "Created GBM allocator with backend %s", gbm_device_get_backend_name(alloc->gbm_device)); char *drm_name = drmGetDeviceNameFromFd2(fd); kywc_log(KYWC_DEBUG, "Using DRM node %s", drm_name); free(drm_name); return alloc; } kylin-wayland-compositor/src/render/allocator/allocator_p.h0000664000175000017500000000166615160461067023174 0ustar fengfeng// SPDX-FileCopyrightText: 2026 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _ALLOCATOR_P_H_ #define _ALLOCATOR_P_H_ #include #include struct gbm_alloc; struct dumb_alloc; struct wlr_renderer; struct dumb_alloc *dumb_alloc_create(int drm_fd); struct gbm_alloc *gbm_alloc_create(struct wlr_renderer *renderer, int fd); struct wlr_buffer *dumb_buffer_create(struct dumb_alloc *alloc, int width, int height, const struct wlr_drm_format *drm_format); struct wlr_buffer *gbm_buffer_create(struct gbm_alloc *alloc, int width, int height, const struct wlr_drm_format *format, uint32_t flags); struct wlr_buffer *shm_buffer_create(int width, int height, uint32_t fmt); void gbm_alloc_destroy(struct gbm_alloc *alloc); void dumb_alloc_destroy(struct dumb_alloc *alloc); #endif /* _ALLOCATOR_P_H_ */ kylin-wayland-compositor/src/render/allocator/allocator.c0000664000175000017500000001720215160461067022641 0ustar fengfeng// SPDX-FileCopyrightText: 2025 The wlroots contributors // SPDX-FileCopyrightText: 2026 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "allocator_p.h" #include "render/allocator.h" #include "util/quirks.h" struct allocator { struct wlr_allocator base; struct dumb_alloc *dumb; struct gbm_alloc *gbm; struct wl_listener render_destroy; }; /* Re-open the DRM node to avoid GEM handle ref'counting issues. See: * https://gitlab.freedesktop.org/mesa/drm/-/merge_requests/110 */ static int reopen_drm_node(int drm_fd, bool allow_render_node) { if (drmIsMaster(drm_fd)) { // Only recent kernels support empty leases uint32_t lessee_id; int lease_fd = drmModeCreateLease(drm_fd, NULL, 0, O_CLOEXEC, &lessee_id); if (lease_fd >= 0) { return lease_fd; } else if (lease_fd != -EINVAL && lease_fd != -EOPNOTSUPP) { kywc_log_errno(KYWC_ERROR, "DrmModeCreateLease failed"); return -1; } kywc_log(KYWC_DEBUG, "drmModeCreateLease failed, " "falling back to plain open"); } char *name = NULL; if (allow_render_node) { name = drmGetRenderDeviceNameFromFd(drm_fd); } if (name == NULL) { // Either the DRM device has no render node, either the caller wants // a primary node name = drmGetDeviceNameFromFd2(drm_fd); if (name == NULL) { kywc_log(KYWC_ERROR, "DrmGetDeviceNameFromFd2 failed"); return -1; } } int new_fd = open(name, O_RDWR | O_CLOEXEC); if (new_fd < 0) { kywc_log_errno(KYWC_ERROR, "Failed to open DRM node '%s'", name); free(name); return -1; } free(name); // If we're using a DRM primary node and we are DRM master (e.g. because // we're running under the DRM backend), we need to use the legacy DRM // authentication mechanism to have the permission to manipulate DRM dumb // buffers. if (drmIsMaster(drm_fd) && drmGetNodeTypeFromFd(new_fd) == DRM_NODE_PRIMARY) { drm_magic_t magic; if (drmGetMagic(new_fd, &magic) < 0) { kywc_log_errno(KYWC_ERROR, "DrmGetMagic failed"); close(new_fd); return -1; } if (drmAuthMagic(drm_fd, magic) < 0) { kywc_log_errno(KYWC_ERROR, "DrmAuthMagic failed"); close(new_fd); return -1; } } return new_fd; } static const struct wlr_allocator_interface allocator_impl; static struct allocator *allocator_from_wlr_alloc(struct wlr_allocator *wlr_alloc) { assert(wlr_alloc->impl == &allocator_impl); struct allocator *alloc = wl_container_of(wlr_alloc, alloc, base); return alloc; } static void wlr_alloc_destroy(struct wlr_allocator *wlr_allocator) { struct allocator *alloc = allocator_from_wlr_alloc(wlr_allocator); if (alloc->gbm) { gbm_alloc_destroy(alloc->gbm); alloc->gbm = NULL; } if (alloc->dumb) { dumb_alloc_destroy(alloc->dumb); alloc->dumb = NULL; } wl_list_remove(&alloc->render_destroy.link); free(alloc); } static void handle_renderer_destroy(struct wl_listener *listener, void *data) { struct allocator *alloc = wl_container_of(listener, alloc, render_destroy); wl_list_remove(&alloc->render_destroy.link); wl_list_init(&alloc->render_destroy.link); if (alloc->gbm) { gbm_alloc_destroy(alloc->gbm); alloc->gbm = NULL; } } static struct wlr_buffer *wlr_alloc_create_buffer(struct wlr_allocator *wlr_allocator, int width, int height, const struct wlr_drm_format *format) { if (!format) { return NULL; } struct allocator *alloc = allocator_from_wlr_alloc(wlr_allocator); if (wlr_allocator->buffer_caps == WLR_BUFFER_CAP_DMABUF) { return gbm_buffer_create(alloc->gbm, width, height, format, 0); } else if (wlr_allocator->buffer_caps & WLR_BUFFER_CAP_SHM) { return shm_buffer_create(width, height, format->format); } else { return dumb_buffer_create(alloc->dumb, width, height, format); } } struct wlr_buffer *allocator_create_buffer(struct wlr_allocator *wlr_allocator, enum allocator_buffer_type type, int width, int height, const struct wlr_drm_format *format, uint32_t flags) { if (!format) { return NULL; } struct allocator *alloc = allocator_from_wlr_alloc(wlr_allocator); switch (type) { case ALLOCATOR_BUFFER_TYPE_GBM: return gbm_buffer_create(alloc->gbm, width, height, format, flags); case ALLOCATOR_BUFFER_TYPE_DUMB: return dumb_buffer_create(alloc->dumb, width, height, format); case ALLOCATOR_BUFFER_TYPE_SHM: return shm_buffer_create(width, height, format->format); default: return NULL; } } static struct gbm_alloc *gbm_allocator_create(struct wlr_renderer *renderer, int fd) { struct gbm_alloc *alloc = NULL; int gbm_fd = reopen_drm_node(fd, true); if (gbm_fd < 0) { goto error; } if ((alloc = gbm_alloc_create(renderer, gbm_fd)) != NULL) { return alloc; } close(gbm_fd); error: kywc_log(KYWC_WARN, "Failed to create gbm allocator"); return alloc; } static struct dumb_alloc *drm_dumb_allocator_create(int drm_fd) { struct dumb_alloc *alloc = NULL; int dumb_fd = reopen_drm_node(drm_fd, false); if (dumb_fd < 0) { goto error; } if ((alloc = dumb_alloc_create(dumb_fd)) != NULL) { return alloc; } close(dumb_fd); error: kywc_log(KYWC_WARN, "Failed to create drm dumb allocator"); return alloc; } static const struct wlr_allocator_interface allocator_impl = { .destroy = wlr_alloc_destroy, .create_buffer = wlr_alloc_create_buffer, }; struct wlr_allocator *allocator_create(struct wlr_backend *backend, struct wlr_renderer *renderer) { struct allocator *alloc = calloc(1, sizeof(*alloc)); if (!alloc) { return NULL; } uint32_t backend_caps = backend->buffer_caps; uint32_t renderer_caps = renderer->render_buffer_caps; // Note, drm_fd may be negative if unavailable int drm_fd = wlr_backend_get_drm_fd(backend); if (drm_fd < 0) { drm_fd = wlr_renderer_get_drm_fd(renderer); } uint32_t buffer_caps = 0; uint32_t quirks = quirks_by_backend(drm_fd); uint32_t gbm_caps = WLR_BUFFER_CAP_DMABUF; if ((backend_caps & gbm_caps) && (renderer_caps & gbm_caps) && drm_fd >= 0) { alloc->gbm = gbm_allocator_create(renderer, drm_fd); } if (alloc->gbm) { buffer_caps |= gbm_caps; } uint32_t drm_caps = WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_DATA_PTR; if ((backend_caps & drm_caps) && (renderer_caps & drm_caps) && drm_fd >= 0 && drmIsMaster(drm_fd)) { alloc->dumb = drm_dumb_allocator_create(drm_fd); } if (alloc->dumb && (quirks & QUIRKS_MASK_PREFER_DRM_DUMB)) { buffer_caps |= drm_caps; } uint32_t shm_caps = WLR_BUFFER_CAP_SHM | WLR_BUFFER_CAP_DATA_PTR; if ((backend_caps & shm_caps) && (renderer_caps & shm_caps) && !buffer_caps) { buffer_caps |= shm_caps; } wlr_allocator_init(&alloc->base, &allocator_impl, buffer_caps); alloc->render_destroy.notify = handle_renderer_destroy; wl_signal_add(&renderer->events.destroy, &alloc->render_destroy); return &alloc->base; } kylin-wayland-compositor/src/render/allocator/swapchain.c0000664000175000017500000001262315160461067022640 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2026 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include "render/allocator.h" #define SWAPCHAIN_CAP 4 struct swapchain_slot { struct wlr_buffer *buffer; bool acquired; // waiting for release int age; struct wl_listener release; }; struct swapchain { struct wlr_allocator *allocator; int width, height; uint32_t flags; enum allocator_buffer_type type; struct wlr_drm_format format; struct swapchain_slot slots[SWAPCHAIN_CAP]; struct wl_listener allocator_destroy; }; static void swapchain_handle_allocator_destroy(struct wl_listener *listener, void *data) { struct swapchain *swapchain = wl_container_of(listener, swapchain, allocator_destroy); swapchain->allocator = NULL; wl_list_remove(&swapchain->allocator_destroy.link); wl_list_init(&swapchain->allocator_destroy.link); } static bool drm_format_copy(struct wlr_drm_format *dst, const struct wlr_drm_format *src) { assert(src->len <= src->capacity); uint64_t *modifiers = malloc(sizeof(*modifiers) * src->len); if (!modifiers) { return false; } memcpy(modifiers, src->modifiers, sizeof(*modifiers) * src->len); wlr_drm_format_finish(dst); dst->capacity = src->len; dst->len = src->len; dst->format = src->format; dst->modifiers = modifiers; return true; } struct swapchain *allocator_create_swapchain(struct wlr_allocator *alloc, enum allocator_buffer_type type, int width, int height, const struct wlr_drm_format *format, uint32_t flags) { struct swapchain *swapchain = calloc(1, sizeof(*swapchain)); if (!swapchain) { return NULL; } swapchain->allocator = alloc; swapchain->type = type; swapchain->width = width; swapchain->height = height; swapchain->flags = flags; if (!drm_format_copy(&swapchain->format, format)) { free(swapchain); return NULL; } swapchain->allocator_destroy.notify = swapchain_handle_allocator_destroy; wl_signal_add(&alloc->events.destroy, &swapchain->allocator_destroy); return swapchain; } static void slot_reset(struct swapchain_slot *slot) { if (slot->acquired) { wl_list_remove(&slot->release.link); } wlr_buffer_drop(slot->buffer); *slot = (struct swapchain_slot){ 0 }; } void swapchain_destroy(struct swapchain *swapchain) { if (!swapchain) { return; } for (size_t i = 0; i < SWAPCHAIN_CAP; i++) { slot_reset(&swapchain->slots[i]); } wl_list_remove(&swapchain->allocator_destroy.link); wlr_drm_format_finish(&swapchain->format); free(swapchain); } static void slot_handle_release(struct wl_listener *listener, void *data) { struct swapchain_slot *slot = wl_container_of(listener, slot, release); wl_list_remove(&slot->release.link); slot->acquired = false; } static struct wlr_buffer *slot_acquire(struct swapchain *swapchain, struct swapchain_slot *slot, int *age) { assert(!slot->acquired); assert(slot->buffer != NULL); slot->acquired = true; slot->release.notify = slot_handle_release; wl_signal_add(&slot->buffer->events.release, &slot->release); if (age != NULL) { *age = slot->age; } return wlr_buffer_lock(slot->buffer); } struct wlr_buffer *swapchain_acquire(struct swapchain *swapchain, int *age) { struct swapchain_slot *free_slot = NULL; for (size_t i = 0; i < SWAPCHAIN_CAP; i++) { struct swapchain_slot *slot = &swapchain->slots[i]; if (slot->acquired) { continue; } if (slot->buffer != NULL) { return slot_acquire(swapchain, slot, age); } free_slot = slot; } if (!free_slot) { kywc_log(KYWC_ERROR, "No free output buffer slot"); return NULL; } if (!swapchain->allocator) { return NULL; } kywc_log(KYWC_INFO, "Allocating new swapchain buffer"); free_slot->buffer = allocator_create_buffer(swapchain->allocator, swapchain->type, swapchain->width, swapchain->height, &swapchain->format, swapchain->flags); if (!free_slot->buffer) { kywc_log(KYWC_ERROR, "Failed to allocate buffer"); return NULL; } return slot_acquire(swapchain, free_slot, age); } static bool swapchain_has_buffer(struct swapchain *swapchain, struct wlr_buffer *buffer) { for (size_t i = 0; i < SWAPCHAIN_CAP; i++) { struct swapchain_slot *slot = &swapchain->slots[i]; if (slot->buffer == buffer) { return true; } } return false; } void swapchain_set_buffer_submitted(struct swapchain *swapchain, struct wlr_buffer *buffer) { assert(buffer != NULL); if (!swapchain_has_buffer(swapchain, buffer)) { return; } // See the algorithm described in: // https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_buffer_age.txt for (size_t i = 0; i < SWAPCHAIN_CAP; i++) { struct swapchain_slot *slot = &swapchain->slots[i]; if (slot->buffer == buffer) { slot->age = 1; } else if (slot->age > 0) { slot->age++; } } } kylin-wayland-compositor/src/render/allocator/shm.c0000664000175000017500000000762515160461067021460 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2026 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include #include "allocator_p.h" #include "render/pixel_format.h" struct shm_buffer { struct wlr_buffer base; struct wlr_shm_attributes shm; void *data; size_t size; }; static const struct wlr_buffer_impl buffer_impl; static struct shm_buffer *shm_buffer_from_buffer(struct wlr_buffer *wlr_buffer) { assert(wlr_buffer->impl == &buffer_impl); struct shm_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); return buffer; } static void buffer_destroy(struct wlr_buffer *wlr_buffer) { struct shm_buffer *buffer = shm_buffer_from_buffer(wlr_buffer); munmap(buffer->data, buffer->size); close(buffer->shm.fd); free(buffer); } static bool buffer_get_shm(struct wlr_buffer *wlr_buffer, struct wlr_shm_attributes *shm) { struct shm_buffer *buffer = shm_buffer_from_buffer(wlr_buffer); *shm = buffer->shm; return true; } static bool shm_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, uint32_t flags, void **data, uint32_t *format, size_t *stride) { struct shm_buffer *buffer = shm_buffer_from_buffer(wlr_buffer); *data = buffer->data; *format = buffer->shm.format; *stride = buffer->shm.stride; return true; } static void shm_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { // This space is intentionally left blank } static const struct wlr_buffer_impl buffer_impl = { .destroy = buffer_destroy, .get_shm = buffer_get_shm, .begin_data_ptr_access = shm_buffer_begin_data_ptr_access, .end_data_ptr_access = shm_buffer_end_data_ptr_access, }; static int excl_shm_open(char *name) { int retries = 100; do { kywc_identifier_rand_generate(name, 0); --retries; // CLOEXEC is guaranteed to be set by shm_open int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd >= 0) { return fd; } } while (retries > 0 && errno == EEXIST); return -1; } static int allocate_shm_file(size_t size) { char name[] = "/kywc-XXXXXX"; int fd = excl_shm_open(name); if (fd < 0) { return -1; } shm_unlink(name); int ret; do { ret = ftruncate(fd, size); } while (ret < 0 && errno == EINTR); if (ret < 0) { close(fd); return -1; } return fd; } struct wlr_buffer *shm_buffer_create(int width, int height, uint32_t format) { const struct ky_pixel_format *info = ky_pixel_format_from_drm(format); if (!info) { kywc_log(KYWC_ERROR, "Unsupported pixel format 0x%" PRIX32, format); return NULL; } struct shm_buffer *buffer = calloc(1, sizeof(*buffer)); if (!buffer) { return NULL; } wlr_buffer_init(&buffer->base, &buffer_impl, width, height); // TODO: consider using a single file for multiple buffers int stride = ky_pixel_format_min_stride(info, width); // TODO: align? buffer->size = stride * height; buffer->shm.fd = allocate_shm_file(buffer->size); if (buffer->shm.fd < 0) { free(buffer); return NULL; } buffer->shm.format = format; buffer->shm.width = width; buffer->shm.height = height; buffer->shm.stride = stride; buffer->shm.offset = 0; buffer->data = mmap(NULL, buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, buffer->shm.fd, 0); if (buffer->data == MAP_FAILED) { kywc_log_errno(KYWC_ERROR, "Mmap failed"); close(buffer->shm.fd); free(buffer); return NULL; } kywc_log(KYWC_DEBUG, "Allocated %" PRIu32 "x%" PRIu32 " SHM buffer", width, height); return &buffer->base; } kylin-wayland-compositor/src/render/opengl/0000775000175000017500000000000015160461067020017 5ustar fengfengkylin-wayland-compositor/src/render/opengl/meson.build0000664000175000017500000000035215160460057022157 0ustar fengfengepoxy = dependency('epoxy', version: '>=1.5.4') gbm = dependency('gbm', version: '>=17.1.0') wlcom_deps += [epoxy, gbm] wlcom_sources += files( 'buffer.c', 'egl.c', 'pass.c', 'renderer.c', 'texture.c', ) subdir('shaders') kylin-wayland-compositor/src/render/opengl/shaders/0000775000175000017500000000000015160461067021450 5ustar fengfengkylin-wayland-compositor/src/render/opengl/shaders/tex_rgbx.frag0000664000175000017500000000041515160460057024131 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif varying vec2 v_texcoord; uniform sampler2D tex; uniform float alpha; void main() { gl_FragColor = vec4(texture2D(tex, v_texcoord).rgb, 1.0) * alpha; } kylin-wayland-compositor/src/render/opengl/shaders/meson.build0000664000175000017500000000104415160461067023611 0ustar fengfengshaders = [ 'quad.vert', 'quad.frag', 'quad_ex.vert', 'quad_ex.frag', 'tex_common.vert', 'tex_rgba.frag', 'tex_rgbx.frag', 'tex_external.frag', 'tex_ex.vert', 'tex_rgba_ex.frag', 'tex_rgbx_ex.frag', 'tex_external_ex.frag', 'tex_mix.vert', 'tex_mix.frag', ] foreach name : shaders output = name.underscorify() + '_str.h' var = name.underscorify() + '_str' wlcom_sources += custom_target( output, command: [embed, var], input: name, output: output, feed: true, capture: true, ) endforeach kylin-wayland-compositor/src/render/opengl/shaders/quad_ex.vert0000664000175000017500000000051015160460057023772 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform mat3 uvRotation; uniform mat3 uv2ndc; attribute vec2 inUV; varying vec2 vUV; void main() { vec3 uv3 = vec3(inUV, 1.0); vUV = (uvRotation * uv3).xy; gl_Position = vec4(uv2ndc * uv3, 1.0); } kylin-wayland-compositor/src/render/opengl/shaders/tex_rgba_ex.frag0000664000175000017500000000177315160460057024606 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif varying vec2 vUV; varying vec2 vTexcoord; uniform sampler2D tex; uniform float alpha; uniform int forceOpaque; // texture alpha force = 1 uniform float antiAliasing; uniform float aspect; // width / height uniform vec4 roundCornerRadius; float sdRoundedBox(in vec2 p, in vec2 b, in vec4 r) { r.xy = (p.x >0.0) ? r.xy : r.zw; r.x = (p.y > 0.0) ? r.x : r.y; vec2 q = abs(p) - b + r.x; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x; } void main() { vec2 st = vUV; st.x *= aspect; vec2 rectHalfSize = vec2(aspect, 1.0) * 0.5; vec2 rectCenter = st - rectHalfSize; float dist = sdRoundedBox(rectCenter, rectHalfSize, roundCornerRadius); float shape = 1.0 - smoothstep(0.0, antiAliasing, dist); vec4 texColor = texture2D(tex, vTexcoord) * alpha; if (forceOpaque != 0) { texColor.a = 1.0; } gl_FragColor = texColor * shape; } kylin-wayland-compositor/src/render/opengl/shaders/quad_ex.frag0000664000175000017500000000150615160460057023737 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif varying vec2 vUV; uniform vec4 color; uniform float antiAliasing; uniform float aspect; // width / height uniform vec4 roundCornerRadius; float sdRoundedBox(in vec2 p, in vec2 b, in vec4 r) { r.xy = (p.x >0.0) ? r.xy : r.zw; r.x = (p.y > 0.0) ? r.x : r.y; vec2 q = abs(p) - b + r.x; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x; } void main() { vec2 st = vUV; st.x *= aspect; vec2 rectHalfSize = vec2(aspect, 1.0) * 0.5; vec2 rectCenter = st - rectHalfSize; float dist = sdRoundedBox(rectCenter, rectHalfSize, roundCornerRadius); float halfAA = antiAliasing * 0.5; float shape = 1.0 - smoothstep(-halfAA, halfAA, dist); gl_FragColor = color * shape; } kylin-wayland-compositor/src/render/opengl/shaders/quad.frag0000664000175000017500000000026015160460057023237 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform vec4 color; void main() { gl_FragColor = color; } kylin-wayland-compositor/src/render/opengl/shaders/tex_common.vert0000664000175000017500000000053015160460057024516 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform mat3 uv2texcoord; uniform mat3 uv2ndc; attribute vec2 inUV; varying vec2 v_texcoord; void main() { vec3 uv3 = vec3(inUV, 1.0); v_texcoord = (uv2texcoord * uv3).xy; gl_Position = vec4(uv2ndc * uv3, 1.0); } kylin-wayland-compositor/src/render/opengl/shaders/tex_ex.vert0000664000175000017500000000064215160460057023646 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform mat3 uvRotation; uniform mat3 uv2texcoord; uniform mat3 uv2ndc; attribute vec2 inUV; varying vec2 vUV; varying vec2 vTexcoord; void main() { vec3 uv3 = vec3(inUV, 1.0); vUV = (uvRotation * uv3).xy; vTexcoord = (uv2texcoord * uv3).xy; gl_Position = vec4(uv2ndc * uv3, 1.0); } kylin-wayland-compositor/src/render/opengl/shaders/tex_rgbx_ex.frag0000664000175000017500000000201215160460057024620 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif varying vec2 vUV; varying vec2 vTexcoord; uniform sampler2D tex; uniform float alpha; uniform int forceOpaque; // texture alpha force = 1 uniform float antiAliasing; uniform float aspect; // width / height uniform vec4 roundCornerRadius; float sdRoundedBox(in vec2 p, in vec2 b, in vec4 r) { r.xy = (p.x >0.0) ? r.xy : r.zw; r.x = (p.y > 0.0) ? r.x : r.y; vec2 q = abs(p) - b + r.x; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x; } void main() { vec2 st = vUV; st.x *= aspect; vec2 rectHalfSize = vec2(aspect, 1.0) * 0.5; vec2 rectCenter = st - rectHalfSize; float dist = sdRoundedBox(rectCenter, rectHalfSize, roundCornerRadius); float shape = 1.0 - smoothstep(0.0, antiAliasing, dist); vec4 texColor = vec4(texture2D(tex, vTexcoord).rgb, 1.0) * alpha; if (forceOpaque != 0) { texColor.a = 1.0; } gl_FragColor = texColor * shape; } kylin-wayland-compositor/src/render/opengl/shaders/tex_mix.frag0000664000175000017500000000125315160461067023767 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif varying vec2 v_texCoord; varying vec2 v_texCoord2; uniform sampler2D textureA; uniform sampler2D textureB; uniform float blendFactor; uniform float alpha; void main() { vec2 isInDst = step(vec2(0.0, 0.0), v_texCoord2) * step(v_texCoord2, vec2(1.0, 1.0)); float validDst = isInDst.x * isInDst.y; vec4 colorA = texture2D(textureA, v_texCoord); vec4 colorB = texture2D(textureB, v_texCoord2); // mixColor: (A * (1 - f) + B * f) vec4 mixColor = mix(colorA, colorB, blendFactor); gl_FragColor = mix(colorA, mixColor, validDst) * alpha; } kylin-wayland-compositor/src/render/opengl/shaders/tex_mix.vert0000664000175000017500000000067015160461067024032 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform mat3 uv2texcoord; uniform mat3 uv2texcoord2; uniform mat3 uv2ndc; attribute vec2 inUV; varying vec2 v_texCoord; varying vec2 v_texCoord2; void main() { vec3 uv3 = vec3(inUV, 1.0); v_texCoord = (uv2texcoord * uv3).xy; v_texCoord2 = (uv2texcoord2 * uv3).xy; gl_Position = vec4(uv2ndc * uv3, 1.0); } kylin-wayland-compositor/src/render/opengl/shaders/tex_external.frag0000664000175000017500000000045515160460057025015 0ustar fengfeng#extension GL_OES_EGL_image_external : require #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif varying vec2 v_texcoord; uniform samplerExternalOES texture0; uniform float alpha; void main() { gl_FragColor = texture2D(texture0, v_texcoord) * alpha; } kylin-wayland-compositor/src/render/opengl/shaders/quad.vert0000664000175000017500000000037315160460057023305 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform mat3 uv2ndc; attribute vec2 inUV; void main() { vec3 uv3 = vec3(inUV, 1.0); gl_Position = vec4(uv2ndc * uv3, 1.0); } kylin-wayland-compositor/src/render/opengl/shaders/tex_external_ex.frag0000664000175000017500000000204015160460057025501 0ustar fengfeng#extension GL_OES_EGL_image_external : require #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif varying vec2 vUV; varying vec2 vTexcoord; uniform samplerExternalOES tex; uniform float alpha; uniform int forceOpaque; // texture alpha force = 1 uniform float antiAliasing; uniform float aspect; // width / height uniform vec4 roundCornerRadius; float sdRoundedBox(in vec2 p, in vec2 b, in vec4 r) { r.xy = (p.x >0.0) ? r.xy : r.zw; r.x = (p.y > 0.0) ? r.x : r.y; vec2 q = abs(p) - b + r.x; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x; } void main() { vec2 st = vUV; st.x *= aspect; vec2 rectHalfSize = vec2(aspect, 1.0) * 0.5; vec2 rectCenter = st - rectHalfSize; float dist = sdRoundedBox(rectCenter, rectHalfSize, roundCornerRadius); float shape = 1.0 - smoothstep(0.0, antiAliasing, dist); vec4 texColor = texture2D(tex, vTexcoord) * alpha; if (forceOpaque != 0) { texColor.a = 1.0; } gl_FragColor = texColor * shape; } kylin-wayland-compositor/src/render/opengl/shaders/tex_rgba.frag0000664000175000017500000000037615160460057024110 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif varying vec2 v_texcoord; uniform sampler2D tex; uniform float alpha; void main() { gl_FragColor = texture2D(tex, v_texcoord) * alpha; } kylin-wayland-compositor/src/render/opengl/texture.c0000664000175000017500000003532515160460057021671 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include "render/opengl.h" #include "render/pixel_format.h" static const struct wlr_texture_impl texture_impl; bool wlr_texture_is_opengl(struct wlr_texture *wlr_texture) { return wlr_texture->impl == &texture_impl; } struct ky_opengl_texture *ky_opengl_texture_from_wlr_texture(struct wlr_texture *wlr_texture) { assert(wlr_texture->impl == &texture_impl); struct ky_opengl_texture *texture = wl_container_of(wlr_texture, texture, wlr_texture); return texture; } static bool gl_texture_update_from_buffer(struct wlr_texture *wlr_texture, struct wlr_buffer *buffer, const pixman_region32_t *damage) { struct ky_opengl_texture *texture = ky_opengl_texture_from_wlr_texture(wlr_texture); if (texture->drm_format == DRM_FORMAT_INVALID) { return false; } void *data; uint32_t format; size_t stride; if (!wlr_buffer_begin_data_ptr_access(buffer, WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { return false; } if (format != texture->drm_format) { wlr_buffer_end_data_ptr_access(buffer); return false; } const struct ky_pixel_format *fmt = ky_pixel_format_from_drm(texture->drm_format); assert(fmt); if (ky_pixel_format_pixels_per_block(fmt) != 1) { wlr_buffer_end_data_ptr_access(buffer); kywc_log(KYWC_ERROR, "Cannot update texture: block formats are not supported"); return false; } if (!ky_pixel_format_check_stride(fmt, stride, buffer->width)) { wlr_buffer_end_data_ptr_access(buffer); return false; } struct ky_egl_context prev_ctx; ky_egl_make_current(texture->renderer->egl, &prev_ctx); ky_opengl_push_debug(texture->renderer); glBindTexture(GL_TEXTURE_2D, texture->tex); int rects_len = 0; const pixman_box32_t *rects = pixman_region32_rectangles(damage, &rects_len); for (int i = 0; i < rects_len; i++) { pixman_box32_t rect = rects[i]; glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / fmt->bytes_per_block); glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, rect.x1); glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, rect.y1); int width = rect.x2 - rect.x1; int height = rect.y2 - rect.y1; glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x1, rect.y1, width, height, fmt->gl_format, fmt->gl_type, data); } glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0); glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0); glBindTexture(GL_TEXTURE_2D, 0); ky_opengl_pop_debug(texture->renderer); ky_egl_restore_context(&prev_ctx); wlr_buffer_end_data_ptr_access(buffer); return true; } void ky_opengl_texture_destroy(struct ky_opengl_texture *texture) { wl_list_remove(&texture->link); if (texture->buffer) { wlr_buffer_unlock(texture->buffer); } else { struct ky_egl_context prev_ctx; ky_egl_make_current(texture->renderer->egl, &prev_ctx); ky_opengl_push_debug(texture->renderer); glDeleteTextures(1, &texture->tex); glDeleteFramebuffers(1, &texture->fbo); ky_opengl_pop_debug(texture->renderer); ky_egl_restore_context(&prev_ctx); } free(texture); } static void handle_gl_texture_destroy(struct wlr_texture *wlr_texture) { ky_opengl_texture_destroy(ky_opengl_texture_from_wlr_texture(wlr_texture)); } static bool gl_texture_bind(struct ky_opengl_texture *texture) { if (texture->fbo) { glBindFramebuffer(GL_FRAMEBUFFER, texture->fbo); } else if (texture->buffer) { struct ky_opengl_buffer *buffer = ky_opengl_buffer_get(texture->renderer, texture->buffer); if (!buffer) { return false; } if (buffer->external_only) { return false; } GLuint fbo = ky_opengl_buffer_get_fbo(buffer); if (!fbo) { return false; } glBindFramebuffer(GL_FRAMEBUFFER, fbo); } else { glGenFramebuffers(1, &texture->fbo); glBindFramebuffer(GL_FRAMEBUFFER, texture->fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture->target, texture->tex, 0); GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (fb_status != GL_FRAMEBUFFER_COMPLETE) { kywc_log(KYWC_ERROR, "Failed to create FBO"); glDeleteFramebuffers(1, &texture->fbo); texture->fbo = 0; glBindFramebuffer(GL_FRAMEBUFFER, 0); return false; } } return true; } static bool gl_texture_read_pixels(struct wlr_texture *wlr_texture, const struct wlr_texture_read_pixels_options *options) { struct ky_opengl_texture *texture = ky_opengl_texture_from_wlr_texture(wlr_texture); struct wlr_box src; wlr_texture_read_pixels_options_get_src_box(options, wlr_texture, &src); const struct ky_pixel_format *fmt = ky_pixel_format_from_drm(options->format); if (fmt == NULL || !ky_opengl_pixel_format_is_supported(texture->renderer, fmt)) { kywc_log(KYWC_ERROR, "Cannot read pixels: unsupported pixel format 0x%" PRIX32, options->format); return false; } if (fmt->gl_format == GL_BGRA_EXT && !texture->renderer->exts.EXT_read_format_bgra) { kywc_log(KYWC_ERROR, "Cannot read pixels: missing GL_EXT_read_format_bgra extension"); return false; } if (ky_pixel_format_pixels_per_block(fmt) != 1) { kywc_log(KYWC_ERROR, "Cannot read pixels: block formats are not supported"); return false; } struct ky_egl_context prev_ctx; if (!ky_egl_make_current(texture->renderer->egl, &prev_ctx)) { return false; } if (!gl_texture_bind(texture)) { return false; } ky_opengl_push_debug(texture->renderer); // Make sure any pending drawing is finished before we try to read it glFinish(); glGetError(); // Clear the error flag unsigned char *p = wlr_texture_read_pixel_options_get_data(options); glPixelStorei(GL_PACK_ALIGNMENT, 1); uint32_t pack_stride = ky_pixel_format_min_stride(fmt, src.width); if (pack_stride == options->stride && options->dst_x == 0) { // Under these particular conditions, we can read the pixels with only // one glReadPixels call glReadPixels(src.x, src.y, src.width, src.height, fmt->gl_format, fmt->gl_type, p); } else { // Unfortunately GLES2 doesn't support GL_PACK_ROW_LENGTH, so we have to read // the lines out row by row for (int32_t i = 0; i < src.height; ++i) { uint32_t y = src.y + i; glReadPixels(src.x, y, src.width, 1, fmt->gl_format, fmt->gl_type, p + i * options->stride); } } ky_opengl_pop_debug(texture->renderer); return glGetError() == GL_NO_ERROR; } static uint32_t gl_texture_preferred_read_format(struct wlr_texture *wlr_texture) { struct ky_opengl_texture *texture = ky_opengl_texture_from_wlr_texture(wlr_texture); uint32_t fmt = DRM_FORMAT_INVALID; struct ky_egl_context prev_ctx; if (!ky_egl_make_current(texture->renderer->egl, &prev_ctx)) { return fmt; } if (!gl_texture_bind(texture)) { goto out; } ky_opengl_push_debug(texture->renderer); GLint gl_format = -1, gl_type = -1, alpha_size = -1; glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &gl_format); glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &gl_type); glGetIntegerv(GL_ALPHA_BITS, &alpha_size); glBindFramebuffer(GL_FRAMEBUFFER, 0); ky_opengl_pop_debug(texture->renderer); const struct ky_pixel_format *format = ky_pixel_format_from_gl(gl_format, gl_type, alpha_size > 0); if (format != NULL) { return format->drm_format; } if (texture->renderer->exts.EXT_read_format_bgra) { return DRM_FORMAT_XRGB8888; } return DRM_FORMAT_XBGR8888; out: ky_egl_restore_context(&prev_ctx); return fmt; } static const struct wlr_texture_impl texture_impl = { .update_from_buffer = gl_texture_update_from_buffer, .read_pixels = gl_texture_read_pixels, .preferred_read_format = gl_texture_preferred_read_format, .destroy = handle_gl_texture_destroy, }; struct ky_opengl_texture *ky_opengl_texture_create(struct ky_opengl_renderer *renderer, uint32_t width, uint32_t height) { struct ky_opengl_texture *texture = calloc(1, sizeof(*texture)); if (texture == NULL) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); return NULL; } wlr_texture_init(&texture->wlr_texture, &renderer->wlr_renderer, &texture_impl, width, height); texture->renderer = renderer; wl_list_insert(&renderer->textures, &texture->link); return texture; } static struct wlr_texture *gl_texture_from_pixels(struct wlr_renderer *wlr_renderer, uint32_t drm_format, uint32_t stride, uint32_t width, uint32_t height, const void *data) { struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer); const struct ky_pixel_format *fmt = ky_pixel_format_from_drm(drm_format); if (fmt == NULL) { kywc_log(KYWC_ERROR, "Unsupported pixel format 0x%" PRIX32, drm_format); return NULL; } if (ky_pixel_format_pixels_per_block(fmt) != 1) { kywc_log(KYWC_ERROR, "Cannot upload texture: block formats are not supported"); return NULL; } if (!ky_pixel_format_check_stride(fmt, stride, width)) { return NULL; } struct ky_opengl_texture *texture = ky_opengl_texture_create(renderer, width, height); if (texture == NULL) { return NULL; } texture->target = GL_TEXTURE_2D; texture->has_alpha = fmt->has_alpha; texture->drm_format = fmt->drm_format; GLint internal_format = fmt->gl_internalformat; if (!internal_format) { /* on OpenGL ES there is a requirement that internalFormat == format */ internal_format = fmt->gl_format; } /* fix the internal_format in OpenGL */ if (!renderer->egl->is_gles) { if (internal_format == GL_BGRA_EXT || internal_format == GL_ABGR_EXT) { internal_format = GL_RGBA; } else if (internal_format == GL_BGR) { internal_format = GL_RGB; } } struct ky_egl_context prev_ctx; ky_egl_make_current(renderer->egl, &prev_ctx); ky_opengl_push_debug(renderer); glGenTextures(1, &texture->tex); glBindTexture(GL_TEXTURE_2D, texture->tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / fmt->bytes_per_block); glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, fmt->gl_format, fmt->gl_type, data); glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); glBindTexture(GL_TEXTURE_2D, 0); ky_opengl_pop_debug(renderer); ky_egl_restore_context(&prev_ctx); return &texture->wlr_texture; } static struct wlr_texture *gl_texture_from_dmabuf(struct ky_opengl_renderer *renderer, struct wlr_buffer *wlr_buffer, struct wlr_dmabuf_attributes *attribs) { /* for glEGLImageTargetTexture2DOES */ if (!renderer->exts.OES_egl_image) { return NULL; } struct ky_opengl_buffer *buffer = ky_opengl_buffer_get_or_create(renderer, wlr_buffer); if (!buffer) { return NULL; } struct ky_opengl_texture *texture = ky_opengl_texture_create(renderer, attribs->width, attribs->height); if (!texture) { return NULL; } const struct ky_pixel_format *fmt = ky_pixel_format_from_drm(attribs->format); texture->target = buffer->external_only ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; texture->buffer = buffer->buffer; texture->drm_format = DRM_FORMAT_INVALID; // texture can't be written anyways texture->has_alpha = fmt ? fmt->has_alpha : true; struct ky_egl_context prev_ctx; ky_egl_make_current(renderer->egl, &prev_ctx); bool invalid; if (!buffer->tex) { glGenTextures(1, &buffer->tex); invalid = true; } else { // External changes are immediately made visible by the GL implementation invalid = !buffer->external_only; } if (invalid) { glBindTexture(texture->target, buffer->tex); glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glEGLImageTargetTexture2DOES(texture->target, buffer->image); glBindTexture(texture->target, 0); } ky_opengl_pop_debug(renderer); ky_egl_restore_context(&prev_ctx); texture->tex = buffer->tex; wlr_buffer_lock(texture->buffer); return &texture->wlr_texture; } struct wlr_texture *ky_opengl_texture_from_buffer(struct wlr_renderer *wlr_renderer, struct wlr_buffer *buffer) { struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer); void *data; uint32_t format; size_t stride; struct wlr_dmabuf_attributes dmabuf; if (wlr_buffer_get_dmabuf(buffer, &dmabuf)) { return gl_texture_from_dmabuf(renderer, buffer, &dmabuf); } else if (wlr_buffer_is_wayland_buffer(buffer)) { return wlr_texture_from_wayland_buffer(renderer, buffer); } else if (wlr_buffer_begin_data_ptr_access(buffer, WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { struct wlr_texture *tex = gl_texture_from_pixels(wlr_renderer, format, stride, buffer->width, buffer->height, data); wlr_buffer_end_data_ptr_access(buffer); return tex; } else { return NULL; } } void ky_opengl_texture_get_attribs(struct wlr_texture *wlr_texture, struct ky_opengl_texture_attribs *attribs) { struct ky_opengl_texture *texture = ky_opengl_texture_from_wlr_texture(wlr_texture); *attribs = (struct ky_opengl_texture_attribs){ .target = texture->target, .tex = texture->tex, .has_alpha = texture->has_alpha, }; } kylin-wayland-compositor/src/render/opengl/egl.c0000664000175000017500000010717315160460057020741 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include "render/egl.h" #include "util/macros.h" #include "util/quirks.h" #ifndef EGL_DRIVER_NAME_EXT #define EGL_DRIVER_NAME_EXT 0x335E #endif #ifndef EGL_DRM_RENDER_NODE_FILE_EXT #define EGL_DRM_RENDER_NODE_FILE_EXT 0x3377 #endif static bool egl_suppress_debug = false; static enum kywc_log_level egl_log_level_to_kywc(EGLint type) { if (egl_suppress_debug) { return KYWC_INFO; } switch (type) { case EGL_DEBUG_MSG_CRITICAL_KHR: return KYWC_FATAL; case EGL_DEBUG_MSG_ERROR_KHR: return KYWC_ERROR; case EGL_DEBUG_MSG_WARN_KHR: return KYWC_WARN; default: return KYWC_INFO; } } static const char *egl_error_str(EGLint error) { switch (error) { case EGL_SUCCESS: return "EGL_SUCCESS"; case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED"; case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS"; case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC"; case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE"; case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT"; case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG"; case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE"; case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY"; case EGL_BAD_DEVICE_EXT: return "EGL_BAD_DEVICE_EXT"; case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE"; case EGL_BAD_MATCH: return "EGL_BAD_MATCH"; case EGL_BAD_PARAMETER: return "EGL_BAD_PARAMETER"; case EGL_BAD_NATIVE_PIXMAP: return "EGL_BAD_NATIVE_PIXMAP"; case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW"; case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST"; } return "unknown error"; } static void egl_log(EGLenum error, const char *command, EGLint msg_type, EGLLabelKHR thread, EGLLabelKHR obj, const char *msg) { kywc_log(egl_log_level_to_kywc(msg_type), "[EGL] command: %s, error: %s (0x%x), message: %s", command, egl_error_str(error), error, msg); } static void log_modifier(uint64_t modifier, bool external_only) { char *mod_name = drmGetFormatModifierName(modifier); kywc_log(KYWC_DEBUG, " %s (0x%016" PRIX64 "): ✓ texture %s render", mod_name ? mod_name : "", modifier, external_only ? "✗" : "✓"); free(mod_name); } static int get_egl_dmabuf_formats(struct ky_egl *egl, EGLint **formats) { if (!egl->exts.EXT_image_dma_buf_import) { kywc_log(KYWC_DEBUG, "DMA-BUF import extension not present"); return -1; } // when we only have the image_dmabuf_import extension we can't query // which formats are supported. These two are on almost always // supported; it's the intended way to just try to create buffers. // Just a guess but better than not supporting dmabufs at all, // given that the modifiers extension isn't supported everywhere. if (!egl->exts.EXT_image_dma_buf_import_modifiers) { static const EGLint fallback_formats[] = { DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888, }; int num = ARRAY_SIZE(fallback_formats); *formats = calloc(num, sizeof(**formats)); if (!*formats) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); return -1; } memcpy(*formats, fallback_formats, num * sizeof(**formats)); return num; } EGLint num; if (!eglQueryDmaBufFormatsEXT(egl->display, 0, NULL, &num)) { kywc_log(KYWC_ERROR, "Failed to query number of dmabuf formats: %#x", eglGetError()); return -1; } *formats = calloc(num, sizeof(**formats)); if (*formats == NULL) { kywc_log(KYWC_ERROR, "Allocation failed: %s", strerror(errno)); return -1; } if (!eglQueryDmaBufFormatsEXT(egl->display, num, *formats, &num)) { kywc_log(KYWC_ERROR, "Failed to query dmabuf format: %#x", eglGetError()); free(*formats); return -1; } return num; } static int get_egl_dmabuf_modifiers(struct ky_egl *egl, EGLint format, uint64_t **modifiers, EGLBoolean **external_only) { *modifiers = NULL; *external_only = NULL; if (!egl->exts.EXT_image_dma_buf_import) { kywc_log(KYWC_DEBUG, "DMA-BUF extension not present"); return -1; } if (!egl->exts.EXT_image_dma_buf_import_modifiers) { return 0; } EGLint num; if (!eglQueryDmaBufModifiersEXT(egl->display, format, 0, NULL, NULL, &num)) { kywc_log(KYWC_ERROR, "Failed to query dmabuf number of modifiers: %#x", eglGetError()); return -1; } if (num == 0) { return 0; } *modifiers = calloc(num, sizeof(**modifiers)); if (*modifiers == NULL) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); return -1; } *external_only = calloc(num, sizeof(**external_only)); if (*external_only == NULL) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); free(*modifiers); *modifiers = NULL; return -1; } /* GL_TEXTURE_EXTERNAL_OES texture target when GL_OES_EGL_image_external */ if (!eglQueryDmaBufModifiersEXT(egl->display, format, num, *modifiers, *external_only, &num)) { kywc_log(KYWC_ERROR, "Failed to query dmabuf modifiers: %#x", eglGetError()); free(*modifiers); free(*external_only); return -1; } return num; } static void init_dmabuf_formats(struct ky_egl *egl) { char *env = getenv("KYWC_EGL_NO_MODIFIERS"); bool no_modifiers = env && strcmp(env, "1") == 0; no_modifiers = no_modifiers || egl->quirks & QUIRKS_MASK_NO_MODIFIERS; if (no_modifiers) { kywc_log(KYWC_INFO, "KYWC_EGL_NO_MODIFIERS set, disabling modifiers for EGL"); } EGLint *formats; int formats_len = get_egl_dmabuf_formats(egl, &formats); if (formats_len < 0) { return; } kywc_log(KYWC_DEBUG, "Supported DMA-BUF formats:"); bool has_modifiers = false; for (int i = 0; i < formats_len; i++) { EGLint fmt = formats[i]; uint64_t *modifiers = NULL; EGLBoolean *external_only = NULL; int modifiers_len = 0; if (!no_modifiers) { modifiers_len = get_egl_dmabuf_modifiers(egl, fmt, &modifiers, &external_only); } if (modifiers_len < 0) { continue; } has_modifiers = has_modifiers || modifiers_len > 0; bool all_external_only = true; for (int j = 0; j < modifiers_len; j++) { wlr_drm_format_set_add(&egl->dmabuf_texture_formats, fmt, modifiers[j]); if (!external_only[j]) { wlr_drm_format_set_add(&egl->dmabuf_render_formats, fmt, modifiers[j]); all_external_only = false; } } // EGL always supports implicit modifiers. If at least one modifier supports rendering, // assume the implicit modifier supports rendering too. // workaround to fix linux_dmabuf_send_modifiers bool linear_only = modifiers_len == 1 && modifiers[0] == DRM_FORMAT_MOD_LINEAR; if (!linear_only) { wlr_drm_format_set_add(&egl->dmabuf_texture_formats, fmt, DRM_FORMAT_MOD_INVALID); } if (modifiers_len == 0 || !all_external_only) { wlr_drm_format_set_add(&egl->dmabuf_render_formats, fmt, DRM_FORMAT_MOD_INVALID); } if (modifiers_len == 0) { // Assume the linear layout is supported if the driver doesn't // explicitly say otherwise wlr_drm_format_set_add(&egl->dmabuf_texture_formats, fmt, DRM_FORMAT_MOD_LINEAR); wlr_drm_format_set_add(&egl->dmabuf_render_formats, fmt, DRM_FORMAT_MOD_LINEAR); } if (kywc_log_get_level() >= KYWC_DEBUG) { char *fmt_name = drmGetFormatName(fmt); kywc_log(KYWC_DEBUG, " %s (0x%08" PRIX32 ")", fmt_name ? fmt_name : "", fmt); free(fmt_name); if (!linear_only) { log_modifier(DRM_FORMAT_MOD_INVALID, false); } if (modifiers_len == 0) { log_modifier(DRM_FORMAT_MOD_LINEAR, false); } for (int j = 0; j < modifiers_len; j++) { log_modifier(modifiers[j], external_only[j]); } } free(modifiers); free(external_only); } free(formats); egl->has_modifiers = has_modifiers; if (!no_modifiers) { kywc_log(KYWC_INFO, "EGL DMA-BUF format modifiers %s", has_modifiers ? "supported" : "unsupported"); } } static struct ky_egl *egl_create(void) { /* detect client extensions with EGL_NO_DISPLAY */ const char *client_exts_str = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); if (client_exts_str == NULL) { if (eglGetError() == EGL_BAD_DISPLAY) { kywc_log(KYWC_ERROR, "EGL_EXT_client_extensions not supported"); } else { kywc_log(KYWC_ERROR, "Failed to query EGL client extensions: %#x", eglGetError()); } return NULL; } kywc_log(KYWC_INFO, "Supported EGL client extensions: %s", client_exts_str); if (!epoxy_extension_in_string(client_exts_str, "EGL_EXT_platform_base")) { kywc_log(KYWC_ERROR, "EGL_EXT_platform_base not supported"); return NULL; } struct ky_egl *egl = calloc(1, sizeof(*egl)); if (egl == NULL) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); return NULL; } egl->exts.KHR_platform_gbm = epoxy_extension_in_string(client_exts_str, "EGL_KHR_platform_gbm") || epoxy_extension_in_string(client_exts_str, "EGL_MESA_platform_gbm"); egl->exts.EXT_platform_device = epoxy_extension_in_string(client_exts_str, "EGL_EXT_platform_device"); egl->exts.KHR_display_reference = epoxy_extension_in_string(client_exts_str, "EGL_KHR_display_reference"); /* * EGL_EXT_device_base is split in two: * EGL_EXT_device_query and EGL_EXT_device_enumeration */ bool has_device_base = epoxy_extension_in_string(client_exts_str, "EGL_EXT_device_base"); egl->exts.EXT_device_enumeration = has_device_base || epoxy_extension_in_string(client_exts_str, "EGL_EXT_device_enumeration"); egl->exts.EXT_device_query = has_device_base || epoxy_extension_in_string(client_exts_str, "EGL_EXT_device_query"); if (epoxy_extension_in_string(client_exts_str, "EGL_KHR_debug")) { static const EGLAttrib debug_attribs[] = { EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, EGL_NONE, }; eglDebugMessageControlKHR(egl_log, debug_attribs); } return egl; } static bool egl_init_display(struct ky_egl *egl, EGLDisplay display) { egl->display = display; EGLint major, minor; if (eglInitialize(egl->display, &major, &minor) == EGL_FALSE) { kywc_log(KYWC_ERROR, "Failed to initialize EGL: %#x", eglGetError()); return false; } const char *display_exts_str = eglQueryString(egl->display, EGL_EXTENSIONS); if (display_exts_str == NULL) { kywc_log(KYWC_ERROR, "Failed to query EGL display extensions: %#x", eglGetError()); return false; } if (!epoxy_extension_in_string(display_exts_str, "EGL_KHR_no_config_context") && !epoxy_extension_in_string(display_exts_str, "EGL_MESA_configless_context")) { kywc_log(KYWC_ERROR, "EGL_KHR_no_config_context or " "EGL_MESA_configless_context not supported"); return false; } if (!epoxy_extension_in_string(display_exts_str, "EGL_KHR_surfaceless_context")) { kywc_log(KYWC_ERROR, "EGL_KHR_surfaceless_context not supported"); return false; } const char *device_exts_str = NULL, *driver_name = NULL; if (egl->exts.EXT_device_query) { EGLAttrib device_attrib; if (!eglQueryDisplayAttribEXT(egl->display, EGL_DEVICE_EXT, &device_attrib)) { kywc_log(KYWC_ERROR, "Failed with eglQueryDisplayAttribEXT(EGL_DEVICE_EXT): %#x", eglGetError()); return false; } egl->device = (EGLDeviceEXT)device_attrib; device_exts_str = eglQueryDeviceStringEXT(egl->device, EGL_EXTENSIONS); if (device_exts_str == NULL) { kywc_log(KYWC_ERROR, "Failed with eglQueryDeviceStringEXT(EGL_EXTENSIONS): %#x", eglGetError()); return false; } if (epoxy_extension_in_string(device_exts_str, "EGL_MESA_device_software")) { char *env = getenv("KYWC_RENDERER_ALLOW_SOFTWARE"); if (env && strcmp(env, "1") == 0) { kywc_log(KYWC_INFO, "Using software rendering"); egl->is_software = true; } else { kywc_log(KYWC_ERROR, "Software rendering detected, please use " "the KYWC_RENDERER_ALLOW_SOFTWARE environment variable " "to proceed"); return false; } } if (epoxy_extension_in_string(device_exts_str, "EGL_EXT_device_persistent_id")) { driver_name = eglQueryDeviceStringEXT(egl->device, EGL_DRIVER_NAME_EXT); } egl->exts.EXT_device_drm = epoxy_extension_in_string(device_exts_str, "EGL_EXT_device_drm"); egl->exts.EXT_device_drm_render_node = epoxy_extension_in_string(device_exts_str, "EGL_EXT_device_drm_render_node"); } egl->exts.KHR_image_base = epoxy_extension_in_string(display_exts_str, "EGL_KHR_image_base"); egl->exts.EXT_image_dma_buf_import = epoxy_extension_in_string(display_exts_str, "EGL_EXT_image_dma_buf_import"); egl->exts.EXT_image_dma_buf_import_modifiers = epoxy_extension_in_string(display_exts_str, "EGL_EXT_image_dma_buf_import_modifiers"); egl->exts.EXT_create_context_robustness = epoxy_extension_in_string(display_exts_str, "EGL_EXT_create_context_robustness"); egl->exts.IMG_context_priority = epoxy_extension_in_string(display_exts_str, "EGL_IMG_context_priority"); const char *vendor = eglQueryString(egl->display, EGL_VENDOR); int drm_fd = ky_egl_dup_drm_fd(egl); if (drm_fd >= 0) { egl->quirks = quirks_by_renderer(drm_fd, vendor); close(drm_fd); } egl->exts.WL_bind_wayland_display = egl->quirks & QUIRKS_MASK_EGL_WAYLAND && epoxy_extension_in_string(display_exts_str, "EGL_WL_bind_wayland_display"); kywc_log(KYWC_INFO, "Using EGL %d.%d", (int)major, (int)minor); kywc_log(KYWC_INFO, "Supported EGL display extensions: %s", display_exts_str); if (device_exts_str != NULL) { kywc_log(KYWC_INFO, "Supported EGL device extensions: %s", device_exts_str); } kywc_log(KYWC_INFO, "EGL vendor: %s (quirks 0x%x)", vendor, egl->quirks); if (driver_name != NULL) { kywc_log(KYWC_INFO, "EGL driver name: %s", driver_name); } return true; } static void maybe_destroy_context(struct ky_egl *egl) { if (egl->context == EGL_NO_CONTEXT) { return; } ky_egl_unset_current(egl); eglDestroyContext(egl->display, egl->context); egl->context = EGL_NO_CONTEXT; } static bool try_opengl_api(struct ky_egl *egl) { if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { return false; } size_t atti = 0; EGLint attribs[11]; /* not EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR * otherwise we need create VAO and VBO in core profile */ attribs[atti++] = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR; attribs[atti++] = EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR; attribs[atti++] = EGL_CONTEXT_MAJOR_VERSION_KHR; attribs[atti++] = 3; attribs[atti++] = EGL_CONTEXT_MINOR_VERSION_KHR; attribs[atti++] = 2; // Request a high priority context if possible if (egl->exts.IMG_context_priority) { attribs[atti++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG; attribs[atti++] = EGL_CONTEXT_PRIORITY_HIGH_IMG; } if (egl->exts.EXT_create_context_robustness) { // not EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT attribs[atti++] = EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR; attribs[atti++] = EGL_LOSE_CONTEXT_ON_RESET_EXT; } attribs[atti++] = EGL_NONE; assert(atti <= ARRAY_SIZE(attribs)); egl->context = eglCreateContext(egl->display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attribs); if (egl->context == EGL_NO_CONTEXT) { egl->context = eglCreateContext(egl->display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, NULL); } if (egl->context == EGL_NO_CONTEXT) { return false; } if (!ky_egl_make_current(egl, NULL)) { kywc_log(KYWC_ERROR, "Failed to make EGL context current with GL\n"); maybe_destroy_context(egl); return false; } /* needs at least GL 2.1, if the GL version is less than 2.1, * drop the context we created, it's useless. */ int gl_version = epoxy_gl_version(); if (gl_version < 21) { kywc_log(KYWC_ERROR, "GL version is not sufficient (required 21, found %i)", gl_version); maybe_destroy_context(egl); return false; } return true; } static bool try_gles_api(struct ky_egl *egl) { if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { return false; } size_t atti = 0; EGLint attribs[7]; attribs[atti++] = EGL_CONTEXT_CLIENT_VERSION; attribs[atti++] = 2; // opengles 2 // Request a high priority context if possible // Try to reschedule all of our rendering to be completed first. If it // fails, it will fallback to the default priority (MEDIUM). if (egl->exts.IMG_context_priority) { attribs[atti++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG; attribs[atti++] = EGL_CONTEXT_PRIORITY_HIGH_IMG; } if (egl->exts.EXT_create_context_robustness) { attribs[atti++] = EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT; attribs[atti++] = EGL_LOSE_CONTEXT_ON_RESET_EXT; } attribs[atti++] = EGL_NONE; assert(atti <= ARRAY_SIZE(attribs)); egl->context = eglCreateContext(egl->display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attribs); if (egl->context == EGL_NO_CONTEXT) { kywc_log(KYWC_ERROR, "Failed to create EGL context"); return false; } if (!ky_egl_make_current(egl, NULL)) { kywc_log(KYWC_ERROR, "Failed to make EGL context current with GLES2"); maybe_destroy_context(egl); return false; } return true; } static bool egl_init(struct ky_egl *egl, EGLenum platform, void *remote_display) { EGLint display_attribs[3] = { 0 }; size_t display_attribs_len = 0; if (egl->exts.KHR_display_reference) { display_attribs[display_attribs_len++] = EGL_TRACK_REFERENCES_KHR; display_attribs[display_attribs_len++] = EGL_TRUE; } display_attribs[display_attribs_len++] = EGL_NONE; assert(display_attribs_len <= ARRAY_SIZE(display_attribs)); EGLDisplay display = eglGetPlatformDisplayEXT(platform, remote_display, display_attribs); if (display == EGL_NO_DISPLAY) { kywc_log(KYWC_ERROR, "Failed to create EGL display"); return false; } if (!egl_init_display(egl, display)) { if (egl->exts.KHR_display_reference) { eglTerminate(display); } return false; } const char *api = getenv("KYWC_RENDERER"); if (api && strcmp(api, "gl") == 0) { if (!try_opengl_api(egl)) { kywc_log(KYWC_ERROR, "Cannot use GL by env\n"); return false; } } else if (api && strcmp(api, "gles2") == 0) { if (!try_gles_api(egl)) { kywc_log(KYWC_ERROR, "Cannot use GLES2 by env\n"); return false; } } else { if (egl->quirks & QUIRKS_MASK_PREFER_OPENGL) { if (!try_opengl_api(egl) && !try_gles_api(egl)) { kywc_log(KYWC_ERROR, "Cannot use neither GL nor GLES2"); return false; } } else { // try gles2 first if (!try_gles_api(egl) && !try_opengl_api(egl)) { kywc_log(KYWC_ERROR, "Cannot use neither GLES2 nor GL"); return false; } } } egl->is_gles = !epoxy_is_desktop_gl(); if (egl->exts.IMG_context_priority) { EGLint priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG; eglQueryContext(egl->display, egl->context, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &priority); if (priority != EGL_CONTEXT_PRIORITY_HIGH_IMG) { kywc_log(KYWC_INFO, "Failed to obtain a high priority context"); } else { kywc_log(KYWC_DEBUG, "Obtained high priority context"); } } init_dmabuf_formats(egl); return true; } static bool device_has_name(const drmDevice *device, const char *name) { for (size_t i = 0; i < DRM_NODE_MAX; i++) { if (!(device->available_nodes & (1 << i))) { continue; } if (strcmp(device->nodes[i], name) == 0) { return true; } } return false; } static EGLDeviceEXT get_egl_device_from_drm_fd(struct ky_egl *egl, int drm_fd) { if (!egl->exts.EXT_device_enumeration) { kywc_log(KYWC_DEBUG, "EGL_EXT_device_enumeration not supported"); return EGL_NO_DEVICE_EXT; } else if (!egl->exts.EXT_device_query) { kywc_log(KYWC_DEBUG, "EGL_EXT_device_query not supported"); return EGL_NO_DEVICE_EXT; } EGLDeviceEXT egl_device = EGL_NO_DEVICE_EXT; EGLDeviceEXT *devices = NULL; drmDevice *device = NULL; egl_suppress_debug = true; EGLint nb_devices = 0; if (!eglQueryDevicesEXT(0, NULL, &nb_devices)) { kywc_log(KYWC_ERROR, "Failed to query EGL devices: %#x", eglGetError()); goto out; } devices = calloc(nb_devices, sizeof(*devices)); if (!devices) { kywc_log_errno(KYWC_ERROR, "Failed to allocate EGL device list"); goto out; } if (!eglQueryDevicesEXT(nb_devices, devices, &nb_devices)) { kywc_log(KYWC_ERROR, "Failed to query EGL devices: %#x", eglGetError()); goto out; } int ret = drmGetDevice(drm_fd, &device); if (ret < 0) { kywc_log(KYWC_ERROR, "Failed to get DRM device: %s", strerror(-ret)); goto out; } for (int i = 0; i < nb_devices; i++) { const char *egl_device_name = eglQueryDeviceStringEXT(devices[i], EGL_DRM_DEVICE_FILE_EXT); if (egl_device_name == NULL) { continue; } if (device_has_name(device, egl_device_name)) { kywc_log(KYWC_DEBUG, "Using EGL device %s", egl_device_name); egl_device = devices[i]; break; } } out: egl_suppress_debug = false; drmFreeDevice(&device); free(devices); return egl_device; } static bool preferred_drm_fd(struct ky_egl *egl, int drm_fd) { EGLDisplay display = EGL_NO_DISPLAY; bool use_drm_fd = false; struct gbm_device *gbm_device = gbm_create_device(drm_fd); if (!gbm_device) { kywc_log(KYWC_ERROR, "Failed to create GBM device"); goto out; } display = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm_device, NULL); if (display == EGL_NO_DISPLAY) { kywc_log(KYWC_ERROR, "Failed to create EGL display on GBM platform: %#x", eglGetError()); goto out; } if (!eglInitialize(display, NULL, NULL)) { kywc_log(KYWC_ERROR, "Failed to initialize EGL on GBM platform: %#x", eglGetError()); goto out; } const char *vendor = eglQueryString(display, EGL_VENDOR); egl->quirks = quirks_by_renderer(drm_fd, vendor); use_drm_fd = egl->quirks & QUIRKS_MASK_MASTER_FD; out: if (display != EGL_NO_DISPLAY) { eglTerminate(display); } if (gbm_device) { gbm_device_destroy(gbm_device); } return use_drm_fd; } static int open_render_node(struct ky_egl *egl, int drm_fd) { if (preferred_drm_fd(egl, drm_fd)) { kywc_log(KYWC_INFO, "Preferred using drm fd to create GBM"); return fcntl(drm_fd, F_DUPFD_CLOEXEC, 0); } char *render_name = drmGetRenderDeviceNameFromFd(drm_fd); if (render_name == NULL) { // This can happen on split render/display platforms, fallback to primary node render_name = drmGetPrimaryDeviceNameFromFd(drm_fd); if (render_name == NULL) { kywc_log_errno(KYWC_ERROR, "Failed with drmGetPrimaryDeviceNameFromFd"); return -1; } kywc_log(KYWC_DEBUG, "DRM device '%s' has no render node, falling back to primary node", render_name); } int render_fd = open(render_name, O_RDWR | O_CLOEXEC); if (render_fd < 0) { kywc_log_errno(KYWC_ERROR, "Failed to open DRM node '%s'", render_name); } free(render_name); return render_fd; } struct ky_egl *ky_egl_create_with_drm_fd(int drm_fd) { struct ky_egl *egl = egl_create(); if (!egl) { kywc_log(KYWC_ERROR, "Failed to create EGL context"); return NULL; } if (egl->exts.EXT_platform_device) { /* * Search for the EGL device matching the DRM fd using the * EXT_device_enumeration extension. */ EGLDeviceEXT egl_device = get_egl_device_from_drm_fd(egl, drm_fd); if (egl_device != EGL_NO_DEVICE_EXT) { if (egl_init(egl, EGL_PLATFORM_DEVICE_EXT, egl_device)) { kywc_log(KYWC_DEBUG, "Using EGL_PLATFORM_DEVICE_EXT"); return egl; } goto error; } /* Falls back on GBM in case the device was not found */ } else { kywc_log(KYWC_DEBUG, "EXT_platform_device not supported"); } if (egl->exts.KHR_platform_gbm) { int gbm_fd = open_render_node(egl, drm_fd); if (gbm_fd < 0) { kywc_log(KYWC_ERROR, "Failed to open DRM render node"); goto error; } egl->gbm_device = gbm_create_device(gbm_fd); if (!egl->gbm_device) { close(gbm_fd); kywc_log(KYWC_ERROR, "Failed to create GBM device"); goto error; } if (egl_init(egl, EGL_PLATFORM_GBM_KHR, egl->gbm_device)) { kywc_log(KYWC_DEBUG, "Using EGL_PLATFORM_GBM_KHR"); return egl; } gbm_device_destroy(egl->gbm_device); close(gbm_fd); } else { kywc_log(KYWC_DEBUG, "KHR_platform_gbm not supported"); } error: kywc_log(KYWC_ERROR, "Failed to initialize EGL context"); free(egl); eglReleaseThread(); return NULL; } void ky_egl_destroy(struct ky_egl *egl) { if (egl == NULL) { return; } wlr_drm_format_set_finish(&egl->dmabuf_texture_formats); wlr_drm_format_set_finish(&egl->dmabuf_render_formats); eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(egl->display, egl->context); if (egl->exts.KHR_display_reference) { eglTerminate(egl->display); } eglReleaseThread(); if (egl->gbm_device) { int gbm_fd = gbm_device_get_fd(egl->gbm_device); gbm_device_destroy(egl->gbm_device); close(gbm_fd); } free(egl); } bool ky_egl_destroy_image(struct ky_egl *egl, EGLImageKHR image) { if (!egl->exts.KHR_image_base) { return false; } if (!image) { return true; } return eglDestroyImageKHR(egl->display, image); } bool ky_egl_make_current(struct ky_egl *egl, struct ky_egl_context *save_context) { if (save_context) { save_context->display = eglGetCurrentDisplay(); save_context->context = eglGetCurrentContext(); save_context->draw_surface = eglGetCurrentSurface(EGL_DRAW); save_context->read_surface = eglGetCurrentSurface(EGL_READ); } if (!eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl->context)) { kywc_log(KYWC_ERROR, "Failed with eglMakeCurrent: %#x", eglGetError()); return false; } return true; } bool ky_egl_unset_current(struct ky_egl *egl) { if (!eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { kywc_log(KYWC_ERROR, "Failed with eglMakeCurrent: %x", eglGetError()); return false; } return true; } bool ky_egl_restore_context(struct ky_egl_context *context) { // If the saved context is a null-context, we must use the current // display instead of the saved display because eglMakeCurrent() can't // handle EGL_NO_DISPLAY. EGLDisplay display = context->display == EGL_NO_DISPLAY ? eglGetCurrentDisplay() : context->display; // If the current display is also EGL_NO_DISPLAY, we assume that there // is currently no context set and no action needs to be taken to unset // the context. if (display == EGL_NO_DISPLAY) { return true; } return eglMakeCurrent(display, context->draw_surface, context->read_surface, context->context); } EGLImageKHR ky_egl_create_image_from_dmabuf(struct ky_egl *egl, struct wlr_dmabuf_attributes *attributes, bool *external_only) { if (!egl->exts.KHR_image_base || !egl->exts.EXT_image_dma_buf_import) { kywc_log(KYWC_ERROR, "Dmabuf import extension not present"); return NULL; } if (attributes->modifier != DRM_FORMAT_MOD_INVALID && attributes->modifier != DRM_FORMAT_MOD_LINEAR && !egl->has_modifiers) { kywc_log(KYWC_ERROR, "EGL implementation doesn't support modifiers"); return NULL; } unsigned int atti = 0; EGLint attribs[50]; attribs[atti++] = EGL_WIDTH; attribs[atti++] = attributes->width; attribs[atti++] = EGL_HEIGHT; attribs[atti++] = attributes->height; attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; attribs[atti++] = attributes->format; struct { EGLint fd; EGLint offset; EGLint pitch; EGLint mod_lo; EGLint mod_hi; } attr_names[WLR_DMABUF_MAX_PLANES] = { { EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT }, { EGL_DMA_BUF_PLANE1_FD_EXT, EGL_DMA_BUF_PLANE1_OFFSET_EXT, EGL_DMA_BUF_PLANE1_PITCH_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT }, { EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT, EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT }, { EGL_DMA_BUF_PLANE3_FD_EXT, EGL_DMA_BUF_PLANE3_OFFSET_EXT, EGL_DMA_BUF_PLANE3_PITCH_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT } }; for (int i = 0; i < attributes->n_planes; i++) { attribs[atti++] = attr_names[i].fd; attribs[atti++] = attributes->fd[i]; attribs[atti++] = attr_names[i].offset; attribs[atti++] = attributes->offset[i]; attribs[atti++] = attr_names[i].pitch; attribs[atti++] = attributes->stride[i]; if (egl->has_modifiers && attributes->modifier != DRM_FORMAT_MOD_INVALID) { attribs[atti++] = attr_names[i].mod_lo; attribs[atti++] = attributes->modifier & 0xFFFFFFFF; attribs[atti++] = attr_names[i].mod_hi; attribs[atti++] = attributes->modifier >> 32; } } // Our clients don't expect our usage to trash the buffer contents attribs[atti++] = EGL_IMAGE_PRESERVED_KHR; attribs[atti++] = EGL_TRUE; attribs[atti++] = EGL_NONE; assert(atti <= ARRAY_SIZE(attribs)); EGLImageKHR image = eglCreateImageKHR(egl->display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); if (image == EGL_NO_IMAGE_KHR) { kywc_log(KYWC_ERROR, "Failed with eglCreateImageKHR: %#x", eglGetError()); return EGL_NO_IMAGE_KHR; } *external_only = !wlr_drm_format_set_has(&egl->dmabuf_render_formats, attributes->format, attributes->modifier); return image; } static char *get_render_name(const char *name) { uint32_t flags = 0; int devices_len = drmGetDevices2(flags, NULL, 0); if (devices_len < 0) { kywc_log(KYWC_ERROR, "Failed with drmGetDevices2: %s", strerror(-devices_len)); return NULL; } drmDevice **devices = calloc(devices_len, sizeof(drmDevice *)); if (devices == NULL) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); return NULL; } devices_len = drmGetDevices2(flags, devices, devices_len); if (devices_len < 0) { free(devices); kywc_log(KYWC_ERROR, "Failed with drmGetDevices2: %s", strerror(-devices_len)); return NULL; } const drmDevice *match = NULL; for (int i = 0; i < devices_len; i++) { if (device_has_name(devices[i], name)) { match = devices[i]; break; } } char *render_name = NULL; if (match == NULL) { kywc_log(KYWC_ERROR, "Cannot find DRM device %s", name); } else if (!(match->available_nodes & (1 << DRM_NODE_RENDER))) { // Likely a split display/render setup. Pick the primary node and hope // Mesa will open the right render node under-the-hood. kywc_log(KYWC_DEBUG, "DRM device %s has no render node, " "falling back to primary node", name); assert(match->available_nodes & (1 << DRM_NODE_PRIMARY)); render_name = strdup(match->nodes[DRM_NODE_PRIMARY]); } else { render_name = strdup(match->nodes[DRM_NODE_RENDER]); } for (int i = 0; i < devices_len; i++) { drmFreeDevice(&devices[i]); } free(devices); return render_name; } static int dup_egl_device_drm_fd(struct ky_egl *egl) { if (egl->device == EGL_NO_DEVICE_EXT || (!egl->exts.EXT_device_drm && !egl->exts.EXT_device_drm_render_node)) { return -1; } char *render_name = NULL; if (egl->exts.EXT_device_drm_render_node) { const char *name = eglQueryDeviceStringEXT(egl->device, EGL_DRM_RENDER_NODE_FILE_EXT); if (name == NULL) { kywc_log(KYWC_DEBUG, "EGL device has no render node: %#x", eglGetError()); return -1; } render_name = strdup(name); } if (render_name == NULL) { const char *primary_name = eglQueryDeviceStringEXT(egl->device, EGL_DRM_DEVICE_FILE_EXT); if (primary_name == NULL) { kywc_log(KYWC_ERROR, "Failed with eglQueryDeviceStringEXT(EGL_DRM_DEVICE_FILE_EXT): %#x", eglGetError()); return -1; } render_name = get_render_name(primary_name); if (render_name == NULL) { kywc_log(KYWC_ERROR, "Can't find render node name for device %s", primary_name); return -1; } } int render_fd = open(render_name, O_RDWR | O_NONBLOCK | O_CLOEXEC); if (render_fd < 0) { kywc_log_errno(KYWC_ERROR, "Failed to open DRM render node %s", render_name); free(render_name); return -1; } free(render_name); return render_fd; } int ky_egl_dup_drm_fd(struct ky_egl *egl) { int fd = dup_egl_device_drm_fd(egl); if (fd >= 0) { return fd; } // Fallback to GBM's FD if we can't use EGLDevice if (egl->gbm_device == NULL) { return -1; } fd = fcntl(gbm_device_get_fd(egl->gbm_device), F_DUPFD_CLOEXEC, 0); if (fd < 0) { kywc_log_errno(KYWC_ERROR, "Failed to dup GBM FD"); } return fd; } kylin-wayland-compositor/src/render/opengl/profile.c0000664000175000017500000000673215160460057021631 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include "render/opengl.h" #ifdef TRACY_ENABLE #include #define QUERY_COUNT 65536 static struct ky_gl_profile { struct ky_opengl_renderer *gl_renderer; uint8_t context_id; uint32_t queries[QUERY_COUNT]; uint32_t head; uint32_t tail; } profile = { 0 }; static uint32_t next_query_id(void) { uint32_t id = profile.head; profile.head = (profile.head + 1) % QUERY_COUNT; return id; } void ky_profile_gl_create(struct wlr_renderer *renderer) { struct ky_opengl_renderer *gl_renderer = ky_opengl_renderer_from_wlr_renderer(renderer); if (!gl_renderer->exts.EXT_disjoint_timer_query) { return; } ky_egl_make_current(gl_renderer->egl, NULL); profile.gl_renderer = gl_renderer; profile.context_id = 0; profile.head = 0; profile.tail = 0; glGenQueries(QUERY_COUNT, profile.queries); int64_t gpu_time; glGetInteger64v(GL_TIMESTAMP, &gpu_time); const struct ___tracy_gpu_new_context_data context_data = { .gpuTime = gpu_time, .period = 1.f, .context = profile.context_id, .type = 1, }; ___tracy_emit_gpu_new_context(context_data); ky_egl_unset_current(gl_renderer->egl); } void ky_profile_gl_destroy(void) { if (!profile.gl_renderer) { return; } ky_egl_make_current(profile.gl_renderer->egl, NULL); glDeleteQueries(QUERY_COUNT, profile.queries); ky_egl_unset_current(profile.gl_renderer->egl); } void ky_profile_gl_begin(const struct ___tracy_source_location_data *data) { if (!profile.gl_renderer) { return; } uint32_t query_id = next_query_id(); glQueryCounter(profile.queries[query_id], GL_TIMESTAMP); const struct ___tracy_gpu_zone_begin_data begin_data = { .srcloc = (uint64_t)data, .queryId = query_id, .context = profile.context_id, }; ___tracy_emit_gpu_zone_begin(begin_data); } void ky_profile_gl_end(void) { if (!profile.gl_renderer) { return; } uint32_t query_id = next_query_id(); glQueryCounter(profile.queries[query_id], GL_TIMESTAMP); const struct ___tracy_gpu_zone_end_data end_data = { .queryId = query_id, .context = profile.context_id, }; ___tracy_emit_gpu_zone_end(end_data); } void ky_profile_gl_collect(void) { if (!profile.gl_renderer) { return; } ky_egl_make_current(profile.gl_renderer->egl, NULL); if (profile.tail == profile.head) { return; } int64_t gpu_time; glGetInteger64v(GL_TIMESTAMP, &gpu_time); struct ___tracy_gpu_time_sync_data sync_data = { .gpuTime = gpu_time, .context = profile.context_id, }; ___tracy_emit_gpu_time_sync(sync_data); while (profile.tail != profile.head) { uint32_t query = profile.queries[profile.tail]; GLint available; glGetQueryObjectiv(query, GL_QUERY_RESULT_AVAILABLE, &available); if (!available) { return; } uint64_t time; glGetQueryObjectui64v(query, GL_QUERY_RESULT, &time); const struct ___tracy_gpu_time_data time_data = { .gpuTime = time, .queryId = profile.tail, .context = profile.context_id, }; ___tracy_emit_gpu_time(time_data); profile.tail = (profile.tail + 1) % QUERY_COUNT; } ky_egl_unset_current(profile.gl_renderer->egl); } #endif /* TRACY_ENABLE */ kylin-wayland-compositor/src/render/opengl/pass.c0000664000175000017500000005206315160461067021137 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include "render/opengl.h" #include "render/profile.h" #include "util/debug.h" #include "util/matrix.h" #include "util/quirks.h" #define MAX_QUADS 86 // 4kb static const struct wlr_render_pass_impl render_pass_impl; bool wlr_render_pass_is_opengl(struct wlr_render_pass *render_pass) { return render_pass->impl == &render_pass_impl; } struct ky_opengl_render_pass * ky_opengl_render_pass_from_wlr_render_pass(struct wlr_render_pass *wlr_pass) { assert(wlr_pass->impl == &render_pass_impl); struct ky_opengl_render_pass *pass = wl_container_of(wlr_pass, pass, base); return pass; } static bool _render_pass_submit(struct wlr_render_pass *wlr_pass, uint32_t quirks) { KY_PROFILE_ZONE(zone, __func__); struct ky_opengl_render_pass *pass = ky_opengl_render_pass_from_wlr_render_pass(wlr_pass); struct ky_opengl_renderer *renderer = pass->buffer->renderer; struct ky_opengl_render_timer *timer = pass->timer; ky_opengl_push_debug(renderer); if (timer) { // clear disjoint flag GLint64 disjoint; glGetInteger64v(GL_GPU_DISJOINT_EXT, &disjoint); // set up the query glQueryCounterEXT(timer->id, GL_TIMESTAMP_EXT); // get end-of-CPU-work time in GL time domain glGetInteger64v(GL_TIMESTAMP_EXT, &timer->gl_cpu_end); // get end-of-CPU-work time in CPU time domain clock_gettime(CLOCK_MONOTONIC, &timer->cpu_end); } if (quirks & QUIRKS_MASK_EXPLICIT_SYNC) { glFinish(); } else { glFlush(); } glBindFramebuffer(GL_FRAMEBUFFER, 0); ky_opengl_pop_debug(renderer); ky_egl_restore_context(&pass->prev_ctx); wlr_buffer_unlock(pass->buffer->buffer); free(pass); KY_PROFILE_ZONE_END(zone); return true; } static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { return _render_pass_submit(wlr_pass, 0); } static void render(struct wlr_renderer *renderer, const struct kywc_box *box, const pixman_region32_t *clip, GLint attrib) { pixman_region32_t region; pixman_region32_init_rect(®ion, box->x, box->y, box->width, box->height); if (clip) { pixman_region32_intersect(®ion, ®ion, clip); } int rects_len; const pixman_box32_t *rects = pixman_region32_rectangles(®ion, &rects_len); if (rects_len == 0) { pixman_region32_fini(®ion); return; } KY_PROFILE_RENDER_ZONE(renderer, gzone_render, __func__); glEnableVertexAttribArray(attrib); for (int i = 0; i < rects_len;) { int batch = rects_len - i < MAX_QUADS ? rects_len - i : MAX_QUADS; int batch_end = batch + i; size_t vert_index = 0; GLfloat verts[MAX_QUADS * 6 * 2]; for (; i < batch_end; i++) { const pixman_box32_t *rect = &rects[i]; verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; } glVertexAttribPointer(attrib, 2, GL_FLOAT, GL_FALSE, 0, verts); glDrawArrays(GL_TRIANGLES, 0, batch * 6); } glDisableVertexAttribArray(attrib); KY_PROFILE_RENDER_ZONE_END(renderer); pixman_region32_fini(®ion); } static void setup_blending(enum wlr_render_blend_mode mode) { switch (mode) { case WLR_RENDER_BLEND_MODE_PREMULTIPLIED: glEnable(GL_BLEND); break; case WLR_RENDER_BLEND_MODE_NONE: glDisable(GL_BLEND); break; } } static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, const struct wlr_render_texture_options *options) { struct ky_render_texture_options ky_options = { .base = *options, .radius = { 0 }, .repeated = false, .rotation_angle = 0, }; ky_opengl_render_pass_add_texture(wlr_pass, &ky_options); } static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, const struct wlr_render_rect_options *options) { struct ky_render_rect_options ky_options = { .base = *options, .radius = { 0 }, .rotation_angle = 0, }; ky_opengl_render_pass_add_rect(wlr_pass, &ky_options); } static const struct wlr_render_pass_impl render_pass_impl = { .submit = render_pass_submit, .add_texture = render_pass_add_texture, .add_rect = render_pass_add_rect, }; static bool ky_opengl_render_pass_submit(struct wlr_render_pass *wlr_pass, uint32_t quirks) { return _render_pass_submit(wlr_pass, quirks); } static void mix_set_texture(GLuint u_uv2texcoord, const struct ky_opengl_texture *texture, const struct kywc_fbox *src_fbox, struct ky_mat3 *uv_rotation) { struct ky_mat3 uv2texcoord; struct ky_mat3 tex_matrix; ky_mat3_init_scale(&tex_matrix, src_fbox->width, src_fbox->height); ky_mat3_translate(&tex_matrix, src_fbox->x, src_fbox->y); ky_mat3_multiply(&tex_matrix, uv_rotation, &uv2texcoord); glBindTexture(texture->target, texture->tex); glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glUniformMatrix3fv(u_uv2texcoord, 1, GL_FALSE, uv2texcoord.matrix); } static void opengl_render_pass_add_mix_texture(struct ky_opengl_render_pass *pass, struct ky_opengl_renderer *renderer, struct ky_opengl_texture *texture, const struct wlr_box *dst_box, const struct wlr_fbox *src_fbox, float alpha, const struct ky_render_texture_options *options) { const struct ky_render_texture_mix_options *mix_options = options->mix_options; struct ky_opengl_tex_mix_shader *shader = &renderer->shaders.tex_mix; struct wlr_buffer *target_buffer = pass->buffer->buffer; int tex_w = texture->wlr_texture.width; int tex_h = texture->wlr_texture.height; struct kywc_fbox src_kywcfbox = { .x = src_fbox->x / tex_w, .y = src_fbox->y / tex_h, .width = src_fbox->width / tex_w, .height = src_fbox->height / tex_h, }; struct kywc_box dst_kywcbox = { .x = dst_box->x, .y = dst_box->y, .width = dst_box->width, .height = dst_box->height, }; const struct kywc_box *dst_box2 = mix_options->dst_box; float width2 = dst_box2->width, height2 = dst_box2->height; struct kywc_fbox src_kywcfbox2 = { (dst_kywcbox.x - dst_box2->x) / width2, (dst_kywcbox.y - dst_box2->y) / height2, dst_kywcbox.width / width2, dst_kywcbox.height / height2, }; struct ky_mat3 uv2ndc; struct ky_mat3 uv_rotation; ky_mat3_invert_output_transform(&uv_rotation, options->base.transform); ky_mat3_uvofbox_to_ndc(&uv2ndc, target_buffer->width, target_buffer->height, options->rotation_angle, &dst_kywcbox); ky_opengl_push_debug(renderer); KY_PROFILE_RENDER_ZONE(&renderer->wlr_renderer, gzone, __func__); setup_blending(!texture->has_alpha && alpha == 1.0 ? WLR_RENDER_BLEND_MODE_NONE : options->base.blend_mode); glUseProgram(shader->program); glUniform1f(shader->alpha, alpha); glUniform1f(shader->blend_factor, mix_options->blend_factor); glUniformMatrix3fv(shader->uv2ndc, 1, GL_FALSE, uv2ndc.matrix); glActiveTexture(GL_TEXTURE0); glUniform1i(shader->tex_a, 0); mix_set_texture(shader->uv2texcoord, texture, &src_kywcfbox, &uv_rotation); glActiveTexture(GL_TEXTURE1); glUniform1i(shader->tex_b, 1); mix_set_texture(shader->uv2texcoord2, ky_opengl_texture_from_wlr_texture(mix_options->texture), &src_kywcfbox2, &uv_rotation); render(&renderer->wlr_renderer, &dst_kywcbox, options->base.clip, shader->uvpos_attrib); glBindTexture(texture->target, 0); KY_PROFILE_RENDER_ZONE_END(&renderer->wlr_renderer); ky_opengl_pop_debug(renderer); } static struct ky_opengl_texture *create_copy_screen_texture(struct ky_opengl_render_pass *pass, struct wlr_fbox *src_fbox) { struct ky_opengl_renderer *renderer = pass->buffer->renderer; struct ky_opengl_texture *gl_texture = ky_opengl_texture_create(renderer, src_fbox->width, src_fbox->height); if (!gl_texture) { return NULL; } glGenTextures(1, &gl_texture->tex); glBindTexture(GL_TEXTURE_2D, gl_texture->tex); gl_texture->target = GL_TEXTURE_2D; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, src_fbox->width, src_fbox->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, src_fbox->x, src_fbox->y, src_fbox->width, src_fbox->height); return gl_texture; } void ky_opengl_render_pass_add_texture(struct wlr_render_pass *wlr_pass, const struct ky_render_texture_options *options) { struct ky_opengl_render_pass *pass = ky_opengl_render_pass_from_wlr_render_pass(wlr_pass); const struct wlr_render_texture_options *wlr_options = &options->base; struct ky_opengl_renderer *renderer = pass->buffer->renderer; struct wlr_buffer *target_buffer = pass->buffer->buffer; bool use_exshader = ky_render_pass_options_has_radius(&options->radius) || options->enable_force_opaque; struct wlr_box dst_box; struct wlr_fbox src_fbox; wlr_render_texture_options_get_src_box(wlr_options, &src_fbox); wlr_render_texture_options_get_dst_box(wlr_options, &dst_box); float alpha = wlr_render_texture_options_get_alpha(wlr_options); uint32_t tex_w, tex_h; struct ky_opengl_texture *copy_texture = NULL; struct ky_opengl_texture *texture = NULL; struct wlr_texture *wlr_texture = wlr_texture_from_buffer(&renderer->wlr_renderer, target_buffer); bool need_copy = wlr_texture == wlr_options->texture; wlr_texture_destroy(wlr_texture); if (need_copy) { copy_texture = create_copy_screen_texture(pass, &src_fbox); if (!copy_texture) { return; } src_fbox.x = src_fbox.y = 0; texture = copy_texture; } else { texture = ky_opengl_texture_from_wlr_texture(wlr_options->texture); if (options->mix_options) { opengl_render_pass_add_mix_texture(pass, renderer, texture, &dst_box, &src_fbox, alpha, options); return; } } tex_w = texture->wlr_texture.width; tex_h = texture->wlr_texture.height; struct ky_opengl_tex_ex_shader *shader = NULL; switch (texture->target) { case GL_TEXTURE_2D: if (texture->has_alpha) { shader = use_exshader ? &renderer->shaders.tex_rgba_ex : &renderer->shaders.tex_rgba; } else { shader = use_exshader ? &renderer->shaders.tex_rgbx_ex : &renderer->shaders.tex_rgbx; } break; case GL_TEXTURE_EXTERNAL_OES: assert(renderer->exts.OES_egl_image_external); shader = use_exshader ? &renderer->shaders.tex_ext_ex : &renderer->shaders.tex_ext; break; default: abort(); } struct kywc_fbox src_kywcbox = { .x = src_fbox.x / tex_w, .y = src_fbox.y / tex_h, }; int width = dst_box.width; int height = dst_box.height; if (wlr_options->transform & WL_OUTPUT_TRANSFORM_90) { width = dst_box.height; height = dst_box.width; } if (options->repeated) { src_kywcbox.width = width / src_fbox.width; src_kywcbox.height = height / src_fbox.height; } else { src_kywcbox.width = src_fbox.width / tex_w; src_kywcbox.height = src_fbox.height / tex_h; } struct kywc_box dst_kywcbox = { .x = dst_box.x, .y = dst_box.y, .width = dst_box.width, .height = dst_box.height, }; struct ky_mat3 uv_rotation; ky_mat3_invert_output_transform(&uv_rotation, options->base.transform); struct ky_mat3 uv2texcoord; struct ky_mat3 tex_matrix; ky_mat3_init_scale(&tex_matrix, src_kywcbox.width, src_kywcbox.height); ky_mat3_translate(&tex_matrix, src_kywcbox.x, src_kywcbox.y); ky_mat3_multiply(&tex_matrix, &uv_rotation, &uv2texcoord); struct ky_mat3 uv2ndc; ky_mat3_uvofbox_to_ndc(&uv2ndc, target_buffer->width, target_buffer->height, options->rotation_angle, &dst_kywcbox); ky_opengl_push_debug(renderer); KY_PROFILE_RENDER_ZONE(&renderer->wlr_renderer, gzone, __func__); if (use_exshader) { // radius clip always need blend setup_blending(WLR_RENDER_BLEND_MODE_PREMULTIPLIED); } else { setup_blending(!texture->has_alpha && alpha == 1.0 ? WLR_RENDER_BLEND_MODE_NONE : options->base.blend_mode); } glUseProgram(shader->program); glActiveTexture(GL_TEXTURE0); glBindTexture(texture->target, texture->tex); if (options->repeated) { glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, GL_REPEAT); } else { glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } switch (wlr_options->filter_mode) { case WLR_SCALE_FILTER_BILINEAR: glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); break; case WLR_SCALE_FILTER_NEAREST: glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); break; } glUniformMatrix3fv(shader->uv2texcoord, 1, GL_FALSE, uv2texcoord.matrix); glUniformMatrix3fv(shader->uv2ndc, 1, GL_FALSE, uv2ndc.matrix); glUniform1i(shader->tex, 0); glUniform1f(shader->alpha, alpha); if (use_exshader) { glUniformMatrix3fv(shader->uv_rotation, 1, GL_FALSE, uv_rotation.matrix); // avoid texture alpha < 1 bool force_opaque = (!texture->has_alpha && alpha == 1.0) || options->base.blend_mode == WLR_RENDER_BLEND_MODE_NONE; glUniform1i(shader->force_opaque, force_opaque); glUniform1f(shader->aspect, width / (float)height); float one_pixel_distance = 1.0f / height; // shader distance scale glUniform1f(shader->anti_aliasing, one_pixel_distance); glUniform4f(shader->round_corner_radius, options->radius.rb * one_pixel_distance, options->radius.rt * one_pixel_distance, options->radius.lb * one_pixel_distance, options->radius.lt * one_pixel_distance); } render(&renderer->wlr_renderer, &dst_kywcbox, options->base.clip, shader->uv_attrib); glBindTexture(texture->target, 0); KY_PROFILE_RENDER_ZONE_END(&renderer->wlr_renderer); if (copy_texture) { ky_opengl_texture_destroy(copy_texture); } ky_opengl_pop_debug(renderer); } void ky_opengl_render_pass_add_rect(struct wlr_render_pass *wlr_pass, const struct ky_render_rect_options *options) { struct ky_opengl_render_pass *pass = ky_opengl_render_pass_from_wlr_render_pass(wlr_pass); struct ky_opengl_renderer *renderer = pass->buffer->renderer; struct wlr_buffer *target_buffer = pass->buffer->buffer; bool has_radius = ky_render_pass_options_has_radius(&options->radius); struct ky_opengl_rect_ex_shader *shader; if (has_radius) { shader = &renderer->shaders.quad_ex; } else { shader = &renderer->shaders.quad; } const struct wlr_render_color *color = &options->base.color; struct wlr_box box; wlr_render_rect_options_get_box(&options->base, pass->buffer->buffer, &box); ky_opengl_push_debug(renderer); KY_PROFILE_RENDER_ZONE(&renderer->wlr_renderer, gzone, __func__); if (has_radius) { setup_blending(WLR_RENDER_BLEND_MODE_PREMULTIPLIED); } else { setup_blending(color->a == 1.0 ? WLR_RENDER_BLEND_MODE_NONE : options->base.blend_mode); } glUseProgram(shader->program); struct kywc_box dst_box = { .x = box.x, .y = box.y, .width = box.width, .height = box.height, }; int width = dst_box.width; int height = dst_box.height; if (options->transform & WL_OUTPUT_TRANSFORM_90) { width = dst_box.height; height = dst_box.width; } struct ky_mat3 uv2ndc; ky_mat3_uvofbox_to_ndc(&uv2ndc, target_buffer->width, target_buffer->height, options->rotation_angle, &dst_box); glUniformMatrix3fv(shader->uv2ndc, 1, GL_FALSE, uv2ndc.matrix); glUniform4f(shader->color, color->r, color->g, color->b, color->a); if (has_radius) { struct ky_mat3 uv_rotation; ky_mat3_invert_output_transform(&uv_rotation, options->transform); glUniformMatrix3fv(shader->uv_rotation, 1, GL_FALSE, uv_rotation.matrix); glUniform1f(shader->aspect, width / (float)height); float one_pixel_distance = 1.0f / height; // shader distance scale glUniform1f(shader->anti_aliasing, one_pixel_distance); glUniform4f(shader->round_corner_radius, options->radius.rb * one_pixel_distance, options->radius.rt * one_pixel_distance, options->radius.lb * one_pixel_distance, options->radius.lt * one_pixel_distance); } render(&renderer->wlr_renderer, &dst_box, options->base.clip, shader->uv_attrib); KY_PROFILE_RENDER_ZONE_END(&renderer->wlr_renderer); ky_opengl_pop_debug(renderer); } static const struct ky_render_pass_impl ky_render_pass_impl = { .submit = ky_opengl_render_pass_submit, .add_texture = ky_opengl_render_pass_add_texture, .add_rect = ky_opengl_render_pass_add_rect, }; static const char *reset_status_str(GLenum status) { switch (status) { case GL_GUILTY_CONTEXT_RESET_KHR: return "guilty"; case GL_INNOCENT_CONTEXT_RESET_KHR: return "innocent"; case GL_UNKNOWN_CONTEXT_RESET_KHR: return "unknown"; default: return ""; } } struct ky_opengl_render_pass *ky_opengl_begin_buffer_pass(struct ky_opengl_buffer *buffer, struct ky_egl_context *prev_ctx, struct ky_opengl_render_timer *timer) { struct ky_opengl_renderer *renderer = buffer->renderer; struct wlr_buffer *wlr_buffer = buffer->buffer; if (renderer->exts.KHR_robustness) { GLenum status = glGetGraphicsResetStatusKHR(); if (status != GL_NO_ERROR) { kywc_log(KYWC_ERROR, "GPU reset (%s)", reset_status_str(status)); wl_signal_emit_mutable(&renderer->wlr_renderer.events.lost, NULL); return NULL; } } GLint fbo = ky_opengl_buffer_get_fbo(buffer); if (!fbo) { return NULL; } struct ky_opengl_render_pass *pass = calloc(1, sizeof(*pass)); if (pass == NULL) { return NULL; } pass->impl = &ky_render_pass_impl; wlr_render_pass_init(&pass->base, &render_pass_impl); pass->renderer = renderer; wlr_buffer_lock(wlr_buffer); pass->buffer = buffer; pass->timer = timer; pass->prev_ctx = *prev_ctx; ky_opengl_push_debug(renderer); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glViewport(0, 0, wlr_buffer->width, wlr_buffer->height); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_SCISSOR_TEST); ky_opengl_pop_debug(renderer); return pass; } kylin-wayland-compositor/src/render/opengl/buffer.c0000664000175000017500000002237015160460057021436 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include "render/opengl.h" #include "render/pixel_format.h" #include "render/renderer.h" #ifndef EGL_WL_bind_wayland_display #define EGL_WAYLAND_BUFFER_WL 0x31D5 #define EGL_WAYLAND_PLANE_WL 0x31D6 // #define EGL_TEXTURE_Y_U_V_WL 0x31D7 // #define EGL_TEXTURE_Y_UV_WL 0x31D8 // #define EGL_TEXTURE_Y_XUXV_WL 0x31D9 // #define EGL_TEXTURE_EXTERNAL_WL 0x31DA // #define EGL_WAYLAND_Y_INVERTED_WL 0x31DB #endif // EGL_WL_bind_wayland_display typedef GLboolean (*PFNEGLBINDWAYLANDDISPLAYWL)(EGLDisplay dpy, struct wl_display *display); typedef GLboolean (*PFNEGLUNBINDWAYLANDDISPLAYWL)(EGLDisplay dpy, struct wl_display *display); typedef GLboolean (*PFNEGLQUERYWAYLANDBUFFERWL)(EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value); struct ky_wayland_buffer { struct wl_list link; struct wlr_buffer base; struct wl_listener release; struct wl_resource *resource; struct wl_listener resource_destroy; EGLint width, height, format; EGLImageKHR image; GLuint tex; }; struct ky_wayland_buffer_manager { struct wl_list buffers; struct { PFNEGLBINDWAYLANDDISPLAYWL eglBindWaylandDisplayWL; PFNEGLUNBINDWAYLANDDISPLAYWL eglUnbindWaylandDisplayWL; PFNEGLQUERYWAYLANDBUFFERWL eglQueryWaylandBufferWL; } procs; struct ky_opengl_renderer *renderer; struct wl_listener renderer_destroy; struct wl_display *display; struct wl_listener display_destroy; }; static struct ky_wayland_buffer_manager *manager = NULL; static const struct wlr_buffer_impl wayland_buffer_impl; bool wlr_buffer_is_wayland_buffer(struct wlr_buffer *buffer) { return buffer->impl == &wayland_buffer_impl; } static struct ky_wayland_buffer *wayland_buffer_from_buffer(struct wlr_buffer *wlr_buffer) { assert(wlr_buffer->impl == &wayland_buffer_impl); struct ky_wayland_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); return buffer; } static void wayland_buffer_destroy(struct ky_wayland_buffer *buffer) { wl_list_remove(&buffer->resource_destroy.link); wl_list_remove(&buffer->release.link); wl_list_remove(&buffer->link); struct ky_egl_context prev_ctx; ky_egl_make_current(manager->renderer->egl, &prev_ctx); ky_opengl_push_debug(manager->renderer); glDeleteTextures(1, &buffer->tex); ky_opengl_pop_debug(manager->renderer); ky_egl_destroy_image(manager->renderer->egl, buffer->image); ky_egl_restore_context(&prev_ctx); free(buffer); } static void buffer_destroy(struct wlr_buffer *wlr_buffer) { struct ky_wayland_buffer *buffer = wayland_buffer_from_buffer(wlr_buffer); wayland_buffer_destroy(buffer); } // TODO: return format for buffer_is_opaque static const struct wlr_buffer_impl wayland_buffer_impl = { .destroy = buffer_destroy, }; static void wayland_buffer_handle_resource_destroy(struct wl_listener *listener, void *data) { struct ky_wayland_buffer *buffer = wl_container_of(listener, buffer, resource_destroy); buffer->resource = NULL; wl_list_remove(&buffer->resource_destroy.link); wl_list_init(&buffer->resource_destroy.link); wlr_buffer_drop(&buffer->base); } static void wayland_buffer_handle_release(struct wl_listener *listener, void *data) { struct ky_wayland_buffer *buffer = wl_container_of(listener, buffer, release); if (buffer->resource != NULL) { wl_buffer_send_release(buffer->resource); } } static struct ky_wayland_buffer *wayland_buffer_get_or_create(struct wl_resource *resource) { struct ky_wayland_buffer *buffer = NULL; wl_list_for_each(buffer, &manager->buffers, link) { if (buffer->resource == resource) { return buffer; } } buffer = calloc(1, sizeof(*buffer)); if (buffer == NULL) { return NULL; } EGLDisplay display = manager->renderer->egl->display; manager->procs.eglQueryWaylandBufferWL(display, resource, EGL_WIDTH, &buffer->width); manager->procs.eglQueryWaylandBufferWL(display, resource, EGL_HEIGHT, &buffer->height); manager->procs.eglQueryWaylandBufferWL(display, resource, EGL_TEXTURE_FORMAT, &buffer->format); wlr_buffer_init(&buffer->base, &wayland_buffer_impl, buffer->width, buffer->height); buffer->release.notify = wayland_buffer_handle_release; wl_signal_add(&buffer->base.events.release, &buffer->release); buffer->resource = resource; buffer->resource_destroy.notify = wayland_buffer_handle_resource_destroy; wl_resource_add_destroy_listener(resource, &buffer->resource_destroy); wl_list_insert(&manager->buffers, &buffer->link); return buffer; } static struct wlr_buffer *wayland_buffer_from_resource(struct wl_resource *resource) { struct ky_wayland_buffer *buffer = wayland_buffer_get_or_create(resource); return buffer ? &buffer->base : NULL; } static bool wayland_buffer_is_instance(struct wl_resource *resource) { EGLint format; return manager->procs.eglQueryWaylandBufferWL(manager->renderer->egl->display, resource, EGL_TEXTURE_FORMAT, &format); } static const struct wlr_buffer_resource_interface wayland_buffer_interface = { .name = "wayland_buffer", .is_instance = wayland_buffer_is_instance, .from_resource = wayland_buffer_from_resource }; static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->display_destroy.link); manager->procs.eglUnbindWaylandDisplayWL(manager->renderer->egl->display, manager->display); manager->display = NULL; } static void handle_renderer_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->renderer_destroy.link); struct ky_wayland_buffer *buffer, *tmp; wl_list_for_each_safe(buffer, tmp, &manager->buffers, link) { wayland_buffer_destroy(buffer); } free(manager); manager = NULL; } bool ky_wayland_buffer_create(struct wl_display *wl_display, struct wlr_renderer *wlr_renderer) { if (!wlr_renderer_is_opengl(wlr_renderer)) { return false; } struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer); if (!renderer->egl->exts.WL_bind_wayland_display) { return false; } manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->procs.eglBindWaylandDisplayWL = (void *)eglGetProcAddress("eglBindWaylandDisplayWL"); manager->procs.eglUnbindWaylandDisplayWL = (void *)eglGetProcAddress("eglUnbindWaylandDisplayWL"); manager->procs.eglQueryWaylandBufferWL = (void *)eglGetProcAddress("eglQueryWaylandBufferWL"); if (!manager->procs.eglBindWaylandDisplayWL(renderer->egl->display, wl_display)) { free(manager); manager = NULL; return false; } kywc_log(KYWC_INFO, "EGL bind wayland display"); wl_list_init(&manager->buffers); manager->renderer = renderer; manager->renderer_destroy.notify = handle_renderer_destroy; wl_signal_add(&wlr_renderer->events.destroy, &manager->renderer_destroy); manager->display = wl_display; manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(wl_display, &manager->display_destroy); wlr_buffer_register_resource_interface(&wayland_buffer_interface); return true; } struct wlr_texture *wlr_texture_from_wayland_buffer(struct ky_opengl_renderer *renderer, struct wlr_buffer *wlr_buffer) { if (!manager || !renderer->exts.OES_egl_image) { return NULL; } struct ky_wayland_buffer *buffer = wayland_buffer_from_buffer(wlr_buffer); if (!buffer) { return NULL; } struct ky_opengl_texture *texture = ky_opengl_texture_create(renderer, buffer->width, buffer->height); if (!texture) { return NULL; } const struct ky_pixel_format *fmt = ky_pixel_format_from_drm(buffer->format); texture->target = GL_TEXTURE_2D; texture->buffer = &buffer->base; texture->drm_format = DRM_FORMAT_INVALID; // texture can't be written anyways texture->has_alpha = fmt ? fmt->has_alpha : true; struct ky_egl_context prev_ctx; ky_egl_make_current(renderer->egl, &prev_ctx); ky_opengl_push_debug(renderer); if (buffer->image == EGL_NO_IMAGE_KHR) { const EGLint attribs[] = { EGL_WAYLAND_PLANE_WL, 0, EGL_NONE }; buffer->image = eglCreateImageKHR(renderer->egl->display, EGL_NO_CONTEXT, EGL_WAYLAND_BUFFER_WL, (EGLClientBuffer)buffer->resource, attribs); } if (!buffer->tex) { glGenTextures(1, &buffer->tex); } glBindTexture(texture->target, buffer->tex); glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glEGLImageTargetTexture2DOES(texture->target, buffer->image); glBindTexture(texture->target, 0); ky_opengl_pop_debug(renderer); ky_egl_restore_context(&prev_ctx); texture->tex = buffer->tex; wlr_buffer_lock(texture->buffer); return &texture->wlr_texture; } kylin-wayland-compositor/src/render/opengl/renderer.c0000664000175000017500000007036115160461067022000 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include "render/opengl.h" #include "render/pixel_format.h" #include "quad_ex_frag_str.h" #include "quad_ex_vert_str.h" #include "quad_frag_str.h" #include "quad_vert_str.h" #include "tex_common_vert_str.h" #include "tex_ex_vert_str.h" #include "tex_external_ex_frag_str.h" #include "tex_external_frag_str.h" #include "tex_mix_frag_str.h" #include "tex_mix_vert_str.h" #include "tex_rgba_ex_frag_str.h" #include "tex_rgba_frag_str.h" #include "tex_rgbx_ex_frag_str.h" #include "tex_rgbx_frag_str.h" static const float transforms[][9] = { [WL_OUTPUT_TRANSFORM_NORMAL] = { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, [WL_OUTPUT_TRANSFORM_90] = { 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, [WL_OUTPUT_TRANSFORM_180] = { -1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, [WL_OUTPUT_TRANSFORM_270] = { 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, [WL_OUTPUT_TRANSFORM_FLIPPED] = { -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, [WL_OUTPUT_TRANSFORM_FLIPPED_90] = { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, [WL_OUTPUT_TRANSFORM_FLIPPED_180] = { 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, [WL_OUTPUT_TRANSFORM_FLIPPED_270] = { 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, }; void ky_opengl_matrix_projection(float mat[static 9], int width, int height, enum wl_output_transform transform) { memset(mat, 0, sizeof(*mat) * 9); const float *t = transforms[transform]; float x = 2.0f / width; float y = 2.0f / height; // Rotation + reflection mat[0] = x * t[0]; mat[1] = x * t[1]; mat[3] = y * -t[3]; mat[4] = y * -t[4]; // Translation mat[2] = -copysign(1.0f, mat[0] + mat[1]); mat[5] = -copysign(1.0f, mat[3] + mat[4]); // Identity mat[8] = 1.0f; } static const struct wlr_renderer_impl renderer_impl; static const struct wlr_render_timer_impl render_timer_impl; bool wlr_renderer_is_opengl(struct wlr_renderer *wlr_renderer) { return wlr_renderer->WLR_PRIVATE.impl == &renderer_impl; } struct ky_opengl_renderer *ky_opengl_renderer_from_wlr_renderer(struct wlr_renderer *wlr_renderer) { assert(wlr_renderer->WLR_PRIVATE.impl == &renderer_impl); struct ky_opengl_renderer *renderer = wl_container_of(wlr_renderer, renderer, wlr_renderer); return renderer; } static struct ky_opengl_render_timer *gl_get_render_timer(struct wlr_render_timer *wlr_timer) { assert(wlr_timer->impl == &render_timer_impl); struct ky_opengl_render_timer *timer = wl_container_of(wlr_timer, timer, base); return timer; } static void destroy_buffer(struct ky_opengl_buffer *buffer) { wl_list_remove(&buffer->link); wlr_addon_finish(&buffer->addon); struct ky_egl_context prev_ctx; ky_egl_make_current(buffer->renderer->egl, &prev_ctx); ky_opengl_push_debug(buffer->renderer); glDeleteFramebuffers(1, &buffer->fbo); glDeleteRenderbuffers(1, &buffer->rbo); glDeleteTextures(1, &buffer->tex); ky_opengl_pop_debug(buffer->renderer); ky_egl_destroy_image(buffer->renderer->egl, buffer->image); ky_egl_restore_context(&prev_ctx); free(buffer); } static void handle_buffer_destroy(struct wlr_addon *addon) { struct ky_opengl_buffer *buffer = wl_container_of(addon, buffer, addon); destroy_buffer(buffer); } static const struct wlr_addon_interface buffer_addon_impl = { .name = "ky_opengl_buffer", .destroy = handle_buffer_destroy, }; GLuint ky_opengl_buffer_get_fbo(struct ky_opengl_buffer *buffer) { if (buffer->external_only) { kywc_log(KYWC_ERROR, "DMA-BUF format is external-only"); return 0; } if (buffer->fbo) { return buffer->fbo; } ky_opengl_push_debug(buffer->renderer); if (!buffer->rbo) { glGenRenderbuffers(1, &buffer->rbo); glBindRenderbuffer(GL_RENDERBUFFER, buffer->rbo); glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, buffer->image); glBindRenderbuffer(GL_RENDERBUFFER, 0); } glGenFramebuffers(1, &buffer->fbo); glBindFramebuffer(GL_FRAMEBUFFER, buffer->fbo); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, buffer->rbo); GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); glBindFramebuffer(GL_FRAMEBUFFER, 0); if (fb_status != GL_FRAMEBUFFER_COMPLETE) { kywc_log(KYWC_ERROR, "Failed to create FBO"); glDeleteFramebuffers(1, &buffer->fbo); buffer->fbo = 0; } ky_opengl_pop_debug(buffer->renderer); return buffer->fbo; } struct ky_opengl_buffer *ky_opengl_buffer_get(struct ky_opengl_renderer *renderer, struct wlr_buffer *wlr_buffer) { struct wlr_addon *addon = wlr_addon_find(&wlr_buffer->addons, renderer, &buffer_addon_impl); if (addon) { struct ky_opengl_buffer *buffer = wl_container_of(addon, buffer, addon); return buffer; } return NULL; } struct ky_opengl_buffer *ky_opengl_buffer_get_or_create(struct ky_opengl_renderer *renderer, struct wlr_buffer *wlr_buffer) { struct wlr_addon *addon = wlr_addon_find(&wlr_buffer->addons, renderer, &buffer_addon_impl); if (addon) { struct ky_opengl_buffer *buffer = wl_container_of(addon, buffer, addon); return buffer; } struct ky_opengl_buffer *buffer = calloc(1, sizeof(*buffer)); if (buffer == NULL) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); return NULL; } buffer->buffer = wlr_buffer; buffer->renderer = renderer; struct wlr_dmabuf_attributes dmabuf = { 0 }; if (!wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf)) { goto error_buffer; } buffer->image = ky_egl_create_image_from_dmabuf(renderer->egl, &dmabuf, &buffer->external_only); if (buffer->image == EGL_NO_IMAGE_KHR) { goto error_buffer; } wlr_addon_init(&buffer->addon, &wlr_buffer->addons, renderer, &buffer_addon_impl); wl_list_insert(&renderer->buffers, &buffer->link); kywc_log(KYWC_DEBUG, "Created GL FBO for buffer %dx%d", wlr_buffer->width, wlr_buffer->height); return buffer; error_buffer: free(buffer); return NULL; } static const struct wlr_drm_format_set *gl_get_texture_formats(struct wlr_renderer *wlr_renderer, uint32_t buffer_caps) { struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer); if (buffer_caps & WLR_BUFFER_CAP_DMABUF) { return &renderer->egl->dmabuf_texture_formats; } else if (buffer_caps & WLR_BUFFER_CAP_DATA_PTR) { return &renderer->shm_texture_formats; } else { return NULL; } } static const struct wlr_drm_format_set *gl_get_render_formats(struct wlr_renderer *wlr_renderer) { struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer); return &renderer->egl->dmabuf_render_formats; } static int gl_get_drm_fd(struct wlr_renderer *wlr_renderer) { struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer); if (renderer->drm_fd < 0) { renderer->drm_fd = ky_egl_dup_drm_fd(renderer->egl); } return renderer->drm_fd; } struct ky_egl *ky_opengl_renderer_get_egl(struct wlr_renderer *wlr_renderer) { struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer); return renderer->egl; } static void gl_destroy(struct wlr_renderer *wlr_renderer) { struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer); ky_egl_make_current(renderer->egl, NULL); struct ky_opengl_texture *tex, *tex_tmp; wl_list_for_each_safe(tex, tex_tmp, &renderer->textures, link) { ky_opengl_texture_destroy(tex); } struct ky_opengl_buffer *buffer, *buffer_tmp; wl_list_for_each_safe(buffer, buffer_tmp, &renderer->buffers, link) { destroy_buffer(buffer); } ky_opengl_push_debug(renderer); glDeleteProgram(renderer->shaders.quad.program); glDeleteProgram(renderer->shaders.tex_rgba.program); glDeleteProgram(renderer->shaders.tex_rgbx.program); glDeleteProgram(renderer->shaders.tex_ext.program); ky_opengl_pop_debug(renderer); if (renderer->exts.KHR_debug) { glDisable(GL_DEBUG_OUTPUT_KHR); glDebugMessageCallbackKHR(NULL, NULL); } ky_egl_unset_current(renderer->egl); ky_egl_destroy(renderer->egl); wlr_drm_format_set_finish(&renderer->shm_texture_formats); if (renderer->drm_fd >= 0) { close(renderer->drm_fd); } free(renderer); } static struct wlr_render_pass *gl_begin_buffer_pass(struct wlr_renderer *wlr_renderer, struct wlr_buffer *wlr_buffer, const struct wlr_buffer_pass_options *options) { struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer); struct ky_egl_context prev_ctx = { 0 }; if (!ky_egl_make_current(renderer->egl, &prev_ctx)) { return NULL; } struct ky_opengl_render_timer *timer = NULL; if (options->timer) { timer = gl_get_render_timer(options->timer); clock_gettime(CLOCK_MONOTONIC, &timer->cpu_start); } struct ky_opengl_buffer *buffer = ky_opengl_buffer_get_or_create(renderer, wlr_buffer); if (!buffer) { return NULL; } struct ky_opengl_render_pass *pass = ky_opengl_begin_buffer_pass(buffer, &prev_ctx, timer); if (!pass) { return NULL; } return &pass->base; } static struct wlr_render_timer *gl_render_timer_create(struct wlr_renderer *wlr_renderer) { struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer); if (!renderer->exts.EXT_disjoint_timer_query) { kywc_log(KYWC_ERROR, "Can't create timer, EXT_disjoint_timer_query not available"); return NULL; } struct ky_opengl_render_timer *timer = calloc(1, sizeof(*timer)); if (!timer) { return NULL; } timer->base.impl = &render_timer_impl; timer->renderer = renderer; struct ky_egl_context prev_ctx; ky_egl_make_current(renderer->egl, &prev_ctx); glGenQueriesEXT(1, &timer->id); ky_egl_restore_context(&prev_ctx); return &timer->base; } static int64_t timespec_to_nsec(const struct timespec *a) { return (int64_t)a->tv_sec * 1000000000 + a->tv_nsec; } static int gl_get_render_time(struct wlr_render_timer *wlr_timer) { struct ky_opengl_render_timer *timer = gl_get_render_timer(wlr_timer); struct ky_opengl_renderer *renderer = timer->renderer; struct ky_egl_context prev_ctx; ky_egl_make_current(renderer->egl, &prev_ctx); GLint64 disjoint; glGetInteger64v(GL_GPU_DISJOINT_EXT, &disjoint); if (disjoint) { kywc_log(KYWC_ERROR, "A disjoint operation occurred and the render timer is invalid"); ky_egl_restore_context(&prev_ctx); return -1; } GLint available; glGetQueryObjectivEXT(timer->id, GL_QUERY_RESULT_AVAILABLE_EXT, &available); if (!available) { kywc_log(KYWC_ERROR, "Timer was read too early, gpu isn't done!"); ky_egl_restore_context(&prev_ctx); return -1; } GLuint64 gl_render_end; glGetQueryObjectui64vEXT(timer->id, GL_QUERY_RESULT_EXT, &gl_render_end); int64_t cpu_nsec_total = timespec_to_nsec(&timer->cpu_end) - timespec_to_nsec(&timer->cpu_start); ky_egl_restore_context(&prev_ctx); return gl_render_end - timer->gl_cpu_end + cpu_nsec_total; } static void gl_render_timer_destroy(struct wlr_render_timer *wlr_timer) { struct ky_opengl_render_timer *timer = wl_container_of(wlr_timer, timer, base); struct ky_opengl_renderer *renderer = timer->renderer; struct ky_egl_context prev_ctx; ky_egl_make_current(renderer->egl, &prev_ctx); glDeleteQueriesEXT(1, &timer->id); ky_egl_restore_context(&prev_ctx); free(timer); } static const struct wlr_renderer_impl renderer_impl = { .destroy = gl_destroy, .get_texture_formats = gl_get_texture_formats, .get_render_formats = gl_get_render_formats, .get_drm_fd = gl_get_drm_fd, .texture_from_buffer = ky_opengl_texture_from_buffer, .begin_buffer_pass = gl_begin_buffer_pass, .render_timer_create = gl_render_timer_create, }; static const struct wlr_render_timer_impl render_timer_impl = { .get_duration_ns = gl_get_render_time, .destroy = gl_render_timer_destroy, }; // https://www.khronos.org/opengl/wiki/Debug_Output void ky_opengl_push_debug_(struct ky_opengl_renderer *renderer, const char *file, const char *func) { if (!renderer->exts.KHR_debug) { return; } int len = snprintf(NULL, 0, "%s:%s", file, func) + 1; char str[len]; snprintf(str, len, "%s:%s", file, func); glPushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION_KHR, 1, -1, str); } void ky_opengl_pop_debug(struct ky_opengl_renderer *renderer) { if (renderer->exts.KHR_debug) { glPopDebugGroupKHR(); } } static enum kywc_log_level gl_log_level_to_kywc(GLenum type) { switch (type) { case GL_DEBUG_TYPE_ERROR_KHR: return KYWC_ERROR; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR: return KYWC_DEBUG; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR: return KYWC_ERROR; case GL_DEBUG_TYPE_PORTABILITY_KHR: return KYWC_DEBUG; case GL_DEBUG_TYPE_PERFORMANCE_KHR: return KYWC_DEBUG; case GL_DEBUG_TYPE_OTHER_KHR: return KYWC_DEBUG; case GL_DEBUG_TYPE_MARKER_KHR: return KYWC_DEBUG; case GL_DEBUG_TYPE_PUSH_GROUP_KHR: return KYWC_DEBUG; case GL_DEBUG_TYPE_POP_GROUP_KHR: return KYWC_DEBUG; default: return KYWC_DEBUG; } } static void gl_log(GLenum src, GLenum type, GLuint id, GLenum severity, GLsizei len, const GLchar *msg, const void *user) { kywc_log(gl_log_level_to_kywc(type), "[GL] %s", msg); } static GLuint compile_shader(struct ky_opengl_renderer *renderer, GLenum type, const GLchar *src) { ky_opengl_push_debug(renderer); GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &src, NULL); glCompileShader(shader); GLint ok; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); if (ok == GL_FALSE) { kywc_log(KYWC_ERROR, "Failed to compile shader"); glDeleteShader(shader); shader = 0; } ky_opengl_pop_debug(renderer); return shader; } GLuint ky_opengl_create_program(struct ky_opengl_renderer *renderer, const GLchar *vert_src, const GLchar *frag_src) { ky_opengl_push_debug(renderer); GLuint vert = compile_shader(renderer, GL_VERTEX_SHADER, vert_src); if (!vert) { goto error; } GLuint frag = compile_shader(renderer, GL_FRAGMENT_SHADER, frag_src); if (!frag) { glDeleteShader(vert); goto error; } GLuint prog = glCreateProgram(); glAttachShader(prog, vert); glAttachShader(prog, frag); glLinkProgram(prog); glDetachShader(prog, vert); glDetachShader(prog, frag); glDeleteShader(vert); glDeleteShader(frag); GLint ok; glGetProgramiv(prog, GL_LINK_STATUS, &ok); if (ok == GL_FALSE) { kywc_log(KYWC_ERROR, "Failed to link shader: \n%s\n%s", vert_src, frag_src); glDeleteProgram(prog); goto error; } ky_opengl_pop_debug(renderer); return prog; error: ky_opengl_pop_debug(renderer); return 0; } static struct wlr_renderer *ky_opengl_renderer_create(struct ky_egl *egl) { if (!ky_egl_make_current(egl, NULL)) { return NULL; } struct ky_opengl_renderer *renderer = calloc(1, sizeof(*renderer)); if (renderer == NULL) { return NULL; } wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl, WLR_BUFFER_CAP_DMABUF); wl_list_init(&renderer->buffers); wl_list_init(&renderer->textures); renderer->egl = egl; renderer->drm_fd = -1; renderer->is_core_profile = epoxy_gl_version() >= 31 && !epoxy_has_gl_extension("GL_ARB_compatibility"); kywc_log(KYWC_INFO, "Creating OpenGL renderer"); kywc_log(KYWC_INFO, "Using %s", glGetString(GL_VERSION)); kywc_log(KYWC_INFO, "GL vendor: %s", glGetString(GL_VENDOR)); kywc_log(KYWC_INFO, "GL renderer: %s", glGetString(GL_RENDERER)); if (!renderer->egl->exts.EXT_image_dma_buf_import) { kywc_log(KYWC_ERROR, "EGL_EXT_image_dma_buf_import not supported"); free(renderer); return NULL; } if (egl->is_gles && !epoxy_has_gl_extension("GL_EXT_texture_format_BGRA8888")) { kywc_log(KYWC_ERROR, "BGRA8888 format not supported by GLES"); free(renderer); return NULL; } if (egl->is_gles && epoxy_gl_version() < 30 && !epoxy_has_gl_extension("GL_EXT_unpack_subimage")) { kywc_log(KYWC_ERROR, "GL_EXT_unpack_subimage not supported"); free(renderer); return NULL; } renderer->exts.EXT_read_format_bgra = !egl->is_gles || epoxy_has_gl_extension("GL_EXT_read_format_bgra"); renderer->exts.EXT_texture_type_2_10_10_10_REV = epoxy_has_gl_extension("GL_EXT_texture_type_2_10_10_10_REV"); renderer->exts.OES_texture_half_float_linear = epoxy_has_gl_extension("GL_OES_texture_half_float_linear"); renderer->exts.EXT_texture_norm16 = epoxy_has_gl_extension("GL_EXT_texture_norm16"); renderer->exts.OES_egl_image_external = epoxy_has_gl_extension("GL_OES_EGL_image_external"); renderer->exts.OES_egl_image = epoxy_has_gl_extension("GL_OES_EGL_image"); renderer->exts.KHR_robustness = epoxy_has_gl_extension("GL_KHR_robustness"); if (renderer->exts.KHR_robustness) { GLint notif_strategy = 0; glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_KHR, ¬if_strategy); switch (notif_strategy) { case GL_LOSE_CONTEXT_ON_RESET_KHR: kywc_log(KYWC_DEBUG, "GPU reset notifications are enabled"); break; case GL_NO_RESET_NOTIFICATION_KHR: kywc_log(KYWC_DEBUG, "GPU reset notifications are disabled"); break; } } renderer->exts.EXT_disjoint_timer_query = epoxy_has_gl_extension("GL_EXT_disjoint_timer_query"); renderer->exts.KHR_debug = epoxy_has_gl_extension("GL_KHR_debug"); if (renderer->exts.KHR_debug) { glEnable(GL_DEBUG_OUTPUT_KHR); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR); glDebugMessageCallbackKHR(gl_log, NULL); // Silence unwanted message types glDebugMessageControlKHR(GL_DONT_CARE, GL_DEBUG_TYPE_POP_GROUP_KHR, GL_DONT_CARE, 0, NULL, GL_FALSE); glDebugMessageControlKHR(GL_DONT_CARE, GL_DEBUG_TYPE_PUSH_GROUP_KHR, GL_DONT_CARE, 0, NULL, GL_FALSE); } ky_opengl_push_debug(renderer); GLuint prog; renderer->shaders.quad.program = prog = ky_opengl_create_program(renderer, quad_vert_str, quad_frag_str); if (!renderer->shaders.quad.program) { goto error; } renderer->shaders.quad.uv2ndc = glGetUniformLocation(prog, "uv2ndc"); renderer->shaders.quad.color = glGetUniformLocation(prog, "color"); renderer->shaders.quad.uv_attrib = glGetAttribLocation(prog, "inUV"); renderer->shaders.quad.uv_rotation = -1; renderer->shaders.quad.aspect = -1; renderer->shaders.quad.anti_aliasing = -1; renderer->shaders.quad.round_corner_radius = -1; renderer->shaders.tex_rgba.program = prog = ky_opengl_create_program(renderer, tex_common_vert_str, tex_rgba_frag_str); if (!renderer->shaders.tex_rgba.program) { goto error; } renderer->shaders.tex_rgba.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord"); renderer->shaders.tex_rgba.uv2ndc = glGetUniformLocation(prog, "uv2ndc"); renderer->shaders.tex_rgba.tex = glGetUniformLocation(prog, "tex"); renderer->shaders.tex_rgba.alpha = glGetUniformLocation(prog, "alpha"); renderer->shaders.tex_rgba.uv_attrib = glGetAttribLocation(prog, "inUV"); renderer->shaders.tex_rgba.uv_rotation = -1; renderer->shaders.tex_rgba.aspect = -1; renderer->shaders.tex_rgba.anti_aliasing = -1; renderer->shaders.tex_rgba.round_corner_radius = -1; renderer->shaders.tex_rgbx.program = prog = ky_opengl_create_program(renderer, tex_common_vert_str, tex_rgbx_frag_str); if (!renderer->shaders.tex_rgbx.program) { goto error; } renderer->shaders.tex_rgbx.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord"); renderer->shaders.tex_rgbx.uv2ndc = glGetUniformLocation(prog, "uv2ndc"); renderer->shaders.tex_rgbx.tex = glGetUniformLocation(prog, "tex"); renderer->shaders.tex_rgbx.alpha = glGetUniformLocation(prog, "alpha"); renderer->shaders.tex_rgbx.uv_attrib = glGetAttribLocation(prog, "inUV"); renderer->shaders.tex_rgbx.uv_rotation = -1; renderer->shaders.tex_rgbx.aspect = -1; renderer->shaders.tex_rgbx.anti_aliasing = -1; renderer->shaders.tex_rgbx.round_corner_radius = -1; if (renderer->exts.OES_egl_image_external) { renderer->shaders.tex_ext.program = prog = ky_opengl_create_program(renderer, tex_common_vert_str, tex_external_frag_str); if (!renderer->shaders.tex_ext.program) { goto error; } renderer->shaders.tex_ext.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord"); renderer->shaders.tex_ext.uv2ndc = glGetUniformLocation(prog, "uv2ndc"); renderer->shaders.tex_ext.tex = glGetUniformLocation(prog, "tex"); renderer->shaders.tex_ext.alpha = glGetUniformLocation(prog, "alpha"); renderer->shaders.tex_ext.uv_attrib = glGetAttribLocation(prog, "inUV"); renderer->shaders.tex_ext.uv_rotation = -1; renderer->shaders.tex_ext.aspect = -1; renderer->shaders.tex_ext.anti_aliasing = -1; renderer->shaders.tex_ext.round_corner_radius = -1; } // round corner clip shader renderer->shaders.quad_ex.program = prog = ky_opengl_create_program(renderer, quad_ex_vert_str, quad_ex_frag_str); if (!renderer->shaders.quad_ex.program) { goto error; } renderer->shaders.quad_ex.uv_rotation = glGetUniformLocation(prog, "uvRotation"); renderer->shaders.quad_ex.uv2ndc = glGetUniformLocation(prog, "uv2ndc"); renderer->shaders.quad_ex.color = glGetUniformLocation(prog, "color"); renderer->shaders.quad_ex.anti_aliasing = glGetUniformLocation(prog, "antiAliasing"); renderer->shaders.quad_ex.aspect = glGetUniformLocation(prog, "aspect"); renderer->shaders.quad_ex.round_corner_radius = glGetUniformLocation(prog, "roundCornerRadius"); renderer->shaders.quad_ex.uv_attrib = glGetAttribLocation(prog, "inUV"); renderer->shaders.tex_rgba_ex.program = prog = ky_opengl_create_program(renderer, tex_ex_vert_str, tex_rgba_ex_frag_str); if (!renderer->shaders.tex_rgba_ex.program) { goto error; } renderer->shaders.tex_rgba_ex.uv_rotation = glGetUniformLocation(prog, "uvRotation"); renderer->shaders.tex_rgba_ex.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord"); renderer->shaders.tex_rgba_ex.uv2ndc = glGetUniformLocation(prog, "uv2ndc"); renderer->shaders.tex_rgba_ex.tex = glGetUniformLocation(prog, "tex"); renderer->shaders.tex_rgba_ex.alpha = glGetUniformLocation(prog, "alpha"); renderer->shaders.tex_rgba_ex.force_opaque = glGetUniformLocation(prog, "forceOpaque"); renderer->shaders.tex_rgba_ex.anti_aliasing = glGetUniformLocation(prog, "antiAliasing"); renderer->shaders.tex_rgba_ex.aspect = glGetUniformLocation(prog, "aspect"); renderer->shaders.tex_rgba_ex.round_corner_radius = glGetUniformLocation(prog, "roundCornerRadius"); renderer->shaders.tex_rgba_ex.uv_attrib = glGetAttribLocation(prog, "inUV"); renderer->shaders.tex_rgbx_ex.program = prog = ky_opengl_create_program(renderer, tex_ex_vert_str, tex_rgbx_ex_frag_str); if (!renderer->shaders.tex_rgbx_ex.program) { goto error; } renderer->shaders.tex_rgbx_ex.uv_rotation = glGetUniformLocation(prog, "uvRotation"); renderer->shaders.tex_rgbx_ex.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord"); renderer->shaders.tex_rgbx_ex.uv2ndc = glGetUniformLocation(prog, "uv2ndc"); renderer->shaders.tex_rgbx_ex.tex = glGetUniformLocation(prog, "tex"); renderer->shaders.tex_rgbx_ex.alpha = glGetUniformLocation(prog, "alpha"); renderer->shaders.tex_rgbx_ex.force_opaque = glGetUniformLocation(prog, "forceOpaque"); renderer->shaders.tex_rgbx_ex.anti_aliasing = glGetUniformLocation(prog, "antiAliasing"); renderer->shaders.tex_rgbx_ex.aspect = glGetUniformLocation(prog, "aspect"); renderer->shaders.tex_rgbx_ex.round_corner_radius = glGetUniformLocation(prog, "roundCornerRadius"); renderer->shaders.tex_rgbx_ex.uv_attrib = glGetAttribLocation(prog, "inUV"); if (renderer->exts.OES_egl_image_external) { renderer->shaders.tex_ext_ex.program = prog = ky_opengl_create_program(renderer, tex_ex_vert_str, tex_external_ex_frag_str); if (!renderer->shaders.tex_ext.program) { goto error; } renderer->shaders.tex_ext_ex.uv_rotation = glGetUniformLocation(prog, "uvRotation"); renderer->shaders.tex_ext_ex.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord"); renderer->shaders.tex_ext_ex.uv2ndc = glGetUniformLocation(prog, "uv2ndc"); renderer->shaders.tex_ext_ex.tex = glGetUniformLocation(prog, "tex"); renderer->shaders.tex_ext_ex.alpha = glGetUniformLocation(prog, "alpha"); renderer->shaders.tex_ext_ex.force_opaque = glGetUniformLocation(prog, "forceOpaque"); renderer->shaders.tex_ext_ex.anti_aliasing = glGetUniformLocation(prog, "antiAliasing"); renderer->shaders.tex_ext_ex.aspect = glGetUniformLocation(prog, "aspect"); renderer->shaders.tex_ext_ex.round_corner_radius = glGetUniformLocation(prog, "roundCornerRadius"); renderer->shaders.tex_ext_ex.uv_attrib = glGetAttribLocation(prog, "inUV"); } renderer->shaders.tex_mix.program = prog = ky_opengl_create_program(renderer, tex_mix_vert_str, tex_mix_frag_str); if (!renderer->shaders.tex_mix.program) { goto error; } renderer->shaders.tex_mix.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord"); renderer->shaders.tex_mix.uv2texcoord2 = glGetUniformLocation(prog, "uv2texcoord2"); renderer->shaders.tex_mix.uv2ndc = glGetUniformLocation(prog, "uv2ndc"); renderer->shaders.tex_mix.tex_a = glGetUniformLocation(prog, "textureA"); renderer->shaders.tex_mix.tex_b = glGetUniformLocation(prog, "textureB"); renderer->shaders.tex_mix.alpha = glGetUniformLocation(prog, "alpha"); renderer->shaders.tex_mix.uvpos_attrib = glGetAttribLocation(prog, "inUV"); renderer->shaders.tex_mix.blend_factor = glGetUniformLocation(prog, "blendFactor"); ky_opengl_pop_debug(renderer); ky_egl_unset_current(renderer->egl); ky_opengl_get_shm_formats(renderer, &renderer->shm_texture_formats); return &renderer->wlr_renderer; error: glDeleteProgram(renderer->shaders.quad.program); glDeleteProgram(renderer->shaders.tex_rgba.program); glDeleteProgram(renderer->shaders.tex_rgbx.program); glDeleteProgram(renderer->shaders.tex_ext.program); glDeleteProgram(renderer->shaders.quad_ex.program); glDeleteProgram(renderer->shaders.tex_rgba_ex.program); glDeleteProgram(renderer->shaders.tex_rgbx_ex.program); glDeleteProgram(renderer->shaders.tex_ext_ex.program); ky_opengl_pop_debug(renderer); if (renderer->exts.KHR_debug) { glDisable(GL_DEBUG_OUTPUT_KHR); glDebugMessageCallbackKHR(NULL, NULL); } ky_egl_unset_current(renderer->egl); free(renderer); return NULL; } struct wlr_renderer *ky_opengl_renderer_create_with_drm_fd(int drm_fd) { struct ky_egl *egl = ky_egl_create_with_drm_fd(drm_fd); if (!egl) { kywc_log(KYWC_ERROR, "Could not initialize EGL"); return NULL; } struct wlr_renderer *renderer = ky_opengl_renderer_create(egl); if (!renderer) { kywc_log(KYWC_ERROR, "Failed to create OpenGL renderer"); ky_egl_destroy(egl); return NULL; } return renderer; } kylin-wayland-compositor/src/render/profile.c0000664000175000017500000000177515160460057020347 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include "render/profile.h" #ifdef TRACY_ENABLE #include "render/opengl.h" void ky_profile_render_create(struct wlr_renderer *renderer) { if (wlr_renderer_is_opengl(renderer)) { ky_profile_gl_create(renderer); } } void ky_profile_render_destroy(struct wlr_renderer *renderer) { if (wlr_renderer_is_opengl(renderer)) { ky_profile_gl_destroy(); } } void ky_profile_render_begin(struct wlr_renderer *renderer, const struct ___tracy_source_location_data *data) { if (wlr_renderer_is_opengl(renderer)) { ky_profile_gl_begin(data); } } void ky_profile_render_end(struct wlr_renderer *renderer) { if (wlr_renderer_is_opengl(renderer)) { ky_profile_gl_end(); } } void ky_profile_render_collect(struct wlr_renderer *renderer) { if (wlr_renderer_is_opengl(renderer)) { ky_profile_gl_collect(); } } #endif /* TRACY_ENABLE */ kylin-wayland-compositor/src/render/pass.c0000664000175000017500000001112715160460057017645 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "render/opengl.h" #include "render/pass.h" #include "util/macros.h" static bool ky_render_pass_create_rounded_region(pixman_region32_t *region, const struct wlr_box *box, const struct ky_render_round_corner *radius) { int width = box->width, height = box->height; cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_A1, width, height); if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { return false; } cairo_t *cairo = cairo_create(surface); if (radius->lt > 0) { cairo_arc(cairo, radius->lt, radius->lt, radius->lt, ANGLE(-180), ANGLE(-90)); } else { cairo_line_to(cairo, 0, 0); } if (radius->rt > 0) { cairo_arc(cairo, width - radius->rt, radius->rt, radius->rt, ANGLE(-90), ANGLE(0)); } else { cairo_line_to(cairo, width, 0); } if (radius->rb > 0) { cairo_arc(cairo, width - radius->rb, height - radius->rb, radius->rb, ANGLE(0), ANGLE(90)); } else { cairo_line_to(cairo, width, height); } if (radius->lb > 0) { cairo_arc(cairo, radius->lb, height - radius->lb, radius->lb, ANGLE(90), ANGLE(180)); } else { cairo_line_to(cairo, 0, height); } cairo_close_path(cairo); cairo_set_source_rgba(cairo, 0, 0, 0, 1); cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); cairo_fill(cairo); cairo_surface_flush(surface); void *data = cairo_image_surface_get_data(surface); int stride = cairo_image_surface_get_stride(surface); pixman_image_t *image = pixman_image_create_bits_no_clear(PIXMAN_a1, width, height, data, stride); if (!image) { cairo_destroy(cairo); cairo_surface_destroy(surface); return false; } pixman_region32_init_from_image(region, image); pixman_region32_translate(region, box->x, box->y); pixman_image_unref(image); cairo_destroy(cairo); cairo_surface_destroy(surface); return true; } bool ky_render_pass_options_has_radius(const struct ky_render_round_corner *radius) { return radius->lb > 0 || radius->lt > 0 || radius->rb > 0 || radius->rt > 0; } bool ky_render_pass_submit(struct wlr_render_pass *render_pass, uint32_t quirks) { if (wlr_render_pass_is_opengl(render_pass)) { struct ky_opengl_render_pass *opengl_pass = ky_opengl_render_pass_from_wlr_render_pass(render_pass); if (opengl_pass->impl && opengl_pass->impl->submit) { return opengl_pass->impl->submit(render_pass, quirks); } } return wlr_render_pass_submit(render_pass); } void ky_render_pass_add_texture(struct wlr_render_pass *render_pass, const struct ky_render_texture_options *options) { if (wlr_render_pass_is_opengl(render_pass)) { struct ky_opengl_render_pass *opengl_pass = ky_opengl_render_pass_from_wlr_render_pass(render_pass); if (opengl_pass->impl && opengl_pass->impl->add_texture) { opengl_pass->impl->add_texture(render_pass, options); return; } } wlr_render_pass_add_texture(render_pass, &options->base); } void ky_render_pass_add_rect(struct wlr_render_pass *render_pass, const struct ky_render_rect_options *options) { if (wlr_render_pass_is_opengl(render_pass)) { struct ky_opengl_render_pass *opengl_pass = ky_opengl_render_pass_from_wlr_render_pass(render_pass); if (opengl_pass->impl && opengl_pass->impl->add_rect) { opengl_pass->impl->add_rect(render_pass, options); return; } } else if (!options->base.clip && ky_render_pass_options_has_radius(&options->radius)) { pixman_region32_t region; if (ky_render_pass_create_rounded_region(®ion, &options->base.box, &options->radius)) { struct wlr_render_rect_options base = options->base; base.clip = ®ion; wlr_render_pass_add_rect(render_pass, &base); pixman_region32_fini(®ion); return; } } wlr_render_pass_add_rect(render_pass, &options->base); } struct wlr_renderer *ky_render_pass_get_renderer(struct wlr_render_pass *render_pass) { if (wlr_render_pass_is_opengl(render_pass)) { struct ky_opengl_render_pass *opengl_pass = ky_opengl_render_pass_from_wlr_render_pass(render_pass); return &opengl_pass->renderer->wlr_renderer; } return NULL; } kylin-wayland-compositor/src/render/single_pixel.c0000664000175000017500000001427215160461067021367 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include "renderer_p.h" #include "single-pixel-buffer-v1-protocol.h" #define SINGLE_PIXEL_MANAGER_VERSION 1 struct single_pixel_buffer_manager { struct wl_global *global; struct wl_listener display_destroy; }; struct single_pixel_buffer { struct wlr_buffer base; struct wl_resource *resource; struct wl_listener release; // Full-scale for each component is UINT32_MAX uint32_t r, g, b, a; // Packed little-endian DRM_FORMAT_ARGB8888. Used for data_ptr_access uint8_t argb8888[4]; }; static void handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct wl_buffer_interface wl_buffer_impl = { .destroy = handle_destroy, }; static bool buffer_resource_is_instance(struct wl_resource *resource) { return wl_resource_instance_of(resource, &wl_buffer_interface, &wl_buffer_impl); } static struct single_pixel_buffer *single_pixel_buffer_from_resource(struct wl_resource *resource) { assert(buffer_resource_is_instance(resource)); return wl_resource_get_user_data(resource); } static struct wlr_buffer *buffer_from_resource(struct wl_resource *resource) { return &single_pixel_buffer_from_resource(resource)->base; } static const struct wlr_buffer_resource_interface buffer_resource_interface = { .name = "single_pixel_buffer", .is_instance = buffer_resource_is_instance, .from_resource = buffer_from_resource, }; static void buffer_destroy(struct wlr_buffer *wlr_buffer) { struct single_pixel_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); wl_list_remove(&buffer->release.link); if (buffer->resource) { wl_resource_set_user_data(buffer->resource, NULL); } free(buffer); } static bool buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, uint32_t flags, void **data, uint32_t *format, size_t *stride) { struct single_pixel_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); if (flags & ~WLR_BUFFER_DATA_PTR_ACCESS_READ) { return false; // the buffer is read-only } *data = &buffer->argb8888; *format = DRM_FORMAT_ARGB8888; *stride = sizeof(buffer->argb8888); return true; } static void buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { // This space is intentionally left blank } static const struct wlr_buffer_impl buffer_impl = { .destroy = buffer_destroy, .begin_data_ptr_access = buffer_begin_data_ptr_access, .end_data_ptr_access = buffer_end_data_ptr_access, }; static void buffer_handle_resource_destroy(struct wl_resource *resource) { struct single_pixel_buffer *buffer = single_pixel_buffer_from_resource(resource); buffer->resource = NULL; wlr_buffer_drop(&buffer->base); } static void buffer_handle_release(struct wl_listener *listener, void *data) { struct single_pixel_buffer *buffer = wl_container_of(listener, buffer, release); if (buffer->resource) { wl_buffer_send_release(buffer->resource); } } static void handle_create_u32_rgba_buffer(struct wl_client *client, struct wl_resource *resource, uint32_t id, uint32_t r, uint32_t g, uint32_t b, uint32_t a) { struct single_pixel_buffer *buffer = calloc(1, sizeof(*buffer)); if (!buffer) { wl_client_post_no_memory(client); return; } buffer->resource = wl_resource_create(client, &wl_buffer_interface, 1, id); if (!buffer->resource) { wl_client_post_no_memory(client); free(buffer); return; } wlr_buffer_init(&buffer->base, &buffer_impl, 1, 1); wl_resource_set_implementation(buffer->resource, &wl_buffer_impl, buffer, buffer_handle_resource_destroy); buffer->r = r; buffer->g = g; buffer->b = b; buffer->a = a; double f = (double)0xFF / 0xFFFFFFFF; buffer->argb8888[0] = (uint8_t)((double)buffer->b * f); buffer->argb8888[1] = (uint8_t)((double)buffer->g * f); buffer->argb8888[2] = (uint8_t)((double)buffer->r * f); buffer->argb8888[3] = (uint8_t)((double)buffer->a * f); buffer->release.notify = buffer_handle_release; wl_signal_add(&buffer->base.events.release, &buffer->release); } static const struct wp_single_pixel_buffer_manager_v1_interface manager_impl = { .destroy = handle_destroy, .create_u32_rgba_buffer = handle_create_u32_rgba_buffer, }; static void manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &wp_single_pixel_buffer_manager_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &manager_impl, NULL, NULL); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct single_pixel_buffer_manager *manager = wl_container_of(listener, manager, display_destroy); wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); } bool single_pixel_buffer_manager_create(struct wl_display *display) { struct single_pixel_buffer_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->global = wl_global_create(display, &wp_single_pixel_buffer_manager_v1_interface, SINGLE_PIXEL_MANAGER_VERSION, NULL, manager_bind); if (!manager->global) { kywc_log(KYWC_WARN, "Failed to create %s global", wp_single_pixel_buffer_manager_v1_interface.name); free(manager); return false; } manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(display, &manager->display_destroy); wlr_buffer_register_resource_interface(&buffer_resource_interface); return manager; } kylin-wayland-compositor/src/render/renderer.c0000664000175000017500000001346615160461067020517 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include #include "render/allocator.h" #include "render/opengl.h" #include "render/profile.h" #include "render/renderer.h" #include "renderer_p.h" struct wlr_renderer *ky_renderer_autocreate(struct wlr_backend *backend) { struct wlr_renderer *renderer = NULL; const char *api = getenv("KYWC_RENDERER"); if (api && strcmp(api, "pixman") == 0) { renderer = wlr_pixman_renderer_create(); goto done; } /* get drm fd from backend */ int drm_fd = wlr_backend_get_drm_fd(backend); if (drm_fd < 0) { kywc_log(KYWC_ERROR, "Cannot create hardware renderer: no DRM fd available"); } else { if (api && strcmp(api, "vulkan") == 0) { #if WLR_HAS_VULKAN_RENDERER renderer = wlr_vk_renderer_create_with_drm_fd(drm_fd); #else kywc_log(KYWC_ERROR, "Cannot create Vulkan renderer: wlroots not compiled with Vulkan"); #endif } else { renderer = ky_opengl_renderer_create_with_drm_fd(drm_fd); } } if (!renderer) { kywc_log(KYWC_WARN, "Failed to create a hardware renderer"); renderer = wlr_pixman_renderer_create(); } done: if (!renderer) { kywc_log(KYWC_ERROR, "Failed to create a pixman renderer"); kywc_log(KYWC_ERROR, "Could not initialize renderer"); } else { ky_renderer_config_init(renderer); } KY_PROFILE_RENDER_CREATE(renderer); return renderer; } bool ky_renderer_init_wl_display(struct wlr_renderer *renderer, struct wlr_backend *backend, struct wl_display *wl_display, struct wlr_linux_dmabuf_v1 **linux_dmabuf_v1) { wlr_renderer_init_wl_shm(renderer, wl_display); single_pixel_buffer_manager_create(wl_display); if (!wlr_renderer_get_texture_formats(renderer, WLR_BUFFER_CAP_DMABUF)) { kywc_log(KYWC_WARN, "Unable to initialize dmabuf"); return true; } *linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer(wl_display, 4, renderer); if (!ky_wayland_buffer_create(wl_display, renderer)) { /* create wl_drm if not created in driver */ int master_fd = wlr_backend_get_drm_fd(backend); if (master_fd >= 0) { wayland_drm_create(wl_display, renderer, master_fd); } else { kywc_log(KYWC_WARN, "Cannot get renderer DRM FD, disabling wl_drm"); } } return true; } const struct wlr_drm_format *ky_renderer_get_render_format(struct wlr_renderer *renderer, uint32_t fmt) { if (!renderer->WLR_PRIVATE.impl->get_render_formats) { return NULL; } const struct wlr_drm_format_set *render_formats = renderer->WLR_PRIVATE.impl->get_render_formats(renderer); if (!render_formats) { kywc_log(KYWC_ERROR, "Failed to get render formats"); return NULL; } const struct wlr_drm_format *render_format = wlr_drm_format_set_get(render_formats, fmt); if (!render_format) { kywc_log(KYWC_ERROR, "Renderer doesn't support format 0x%" PRIX32, fmt); return NULL; } return render_format; } struct wlr_buffer *ky_renderer_create_buffer(struct wlr_renderer *renderer, struct wlr_allocator *alloc, int width, int height, uint32_t fmt, bool single_plane) { uint32_t flags = ALLOCATOR_BUFFER_NO_SCANOUT; if (single_plane) { flags |= ALLOCATOR_BUFFER_SINGLE_PLANE; } enum allocator_buffer_type type = wlr_renderer_is_pixman(renderer) ? ALLOCATOR_BUFFER_TYPE_SHM : ALLOCATOR_BUFFER_TYPE_GBM; const struct wlr_drm_format *format = ky_renderer_get_render_format(renderer, fmt); return allocator_create_buffer(alloc, type, width, height, format, flags); } bool ky_renderer_is_software(struct wlr_renderer *renderer) { if (wlr_renderer_is_pixman(renderer)) { return true; } /* return true if software opengl */ if (wlr_renderer_is_opengl(renderer)) { struct ky_opengl_renderer *r = ky_opengl_renderer_from_wlr_renderer(renderer); return r->egl->is_software; } return false; } struct wlr_buffer *ky_renderer_upload_pixels(struct wlr_renderer *renderer, struct wlr_allocator *alloc, int width, int height, struct wlr_buffer *pixels) { /* upload is meaningless when pixman */ if (wlr_renderer_is_pixman(renderer)) { return NULL; } void *data = NULL; uint32_t drm_format; size_t stride; if (!wlr_buffer_begin_data_ptr_access(pixels, WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &drm_format, &stride)) { return NULL; } wlr_buffer_end_data_ptr_access(pixels); struct wlr_buffer *buffer = ky_renderer_create_buffer(renderer, alloc, width, height, drm_format, false); if (!buffer) { return NULL; } struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, buffer, NULL); if (!pass) { wlr_buffer_drop(buffer); return NULL; } struct wlr_texture *tex = wlr_texture_from_buffer(renderer, pixels); struct wlr_render_texture_options options = { .dst_box = (struct wlr_box){ 0, 0, width, height }, .texture = tex, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, }; wlr_render_pass_add_texture(pass, &options); wlr_texture_destroy(tex); wlr_render_pass_submit(pass); return buffer; } kylin-wayland-compositor/src/render/config.c0000664000175000017500000000307615160460057020150 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "render/opengl.h" #include "render/renderer.h" #include "renderer_p.h" #include "util/dbus.h" static const char *service_path = "/com/kylin/Wlcom/Renderer"; static const char *service_interface = "com.kylin.Wlcom.Renderer"; static int is_hardware_rendering(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct wlr_renderer *renderer = userdata; return sd_bus_reply_method_return(m, "b", !ky_renderer_is_software(renderer)); } static int get_renderer_name(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct wlr_renderer *renderer = userdata; if (wlr_renderer_is_opengl(renderer)) { struct ky_opengl_renderer *r = ky_opengl_renderer_from_wlr_renderer(renderer); return sd_bus_reply_method_return(m, "s", r->egl->is_gles ? "gles2" : "gl"); #if WLR_HAS_VULKAN_RENDERER } else if (wlr_renderer_is_vk(renderer)) { return sd_bus_reply_method_return(m, "s", "vulkan"); #endif } else { return sd_bus_reply_method_return(m, "s", "pixman"); } } static const sd_bus_vtable service_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("IsHardwareRendering", "", "b", is_hardware_rendering, 0), SD_BUS_METHOD("GetRendererName", "", "s", get_renderer_name, 0), SD_BUS_VTABLE_END, }; bool ky_renderer_config_init(struct wlr_renderer *renderer) { return dbus_register_object(NULL, service_path, service_interface, service_vtable, renderer); } kylin-wayland-compositor/src/main.c0000664000175000017500000002000615160461067016342 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "server.h" #include "util/debug.h" #include "util/limit.h" #include "util/logger.h" #include "util/spawn.h" static struct server server = { .options = { .enable_xwayland = true, .log_to_file = true, .log_in_realtime = true, .log_rate_limit = true, .binding_session = true, .lazy_xwayland = true, }, .session_pid = -1, }; static int exit_value = 0; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "debug", no_argument, NULL, 'd' }, { "version", no_argument, NULL, 'v' }, { "session", required_argument, NULL, 's' }, { "verbose", no_argument, NULL, 'V' }, { 0, 0, 0, 0 }, }; static const char usage[] = "Usage: kylin-wlcom [options] [command]\n" "\n" " -h, --help Show help message and quit.\n" " -d, --debug Enables full logging, including debug information.\n" " -s, --session Run session on startup.\n" " -v, --version Show the version number and quit.\n" " -V, --verbose Enables more verbose logging.\n" "\n"; static bool detect_suid(void) { if (geteuid() != 0 && getegid() != 0) { return false; } if (getuid() == geteuid() && getgid() == getegid()) { return false; } printf("SUID operation is no longer supported, refusing to start.\n"); return true; } static void enable_debug_flag(struct server *server, const char *flag) { if (strcmp(flag, "noxwayland") == 0) { server->options.enable_xwayland = false; } else if (strcmp(flag, "logtostdout") == 0) { server->options.log_to_file = false; } else if (strcmp(flag, "loginmtime") == 0) { server->options.log_in_realtime = false; } else if (strcmp(flag, "lognoratelimit") == 0) { server->options.log_rate_limit = false; } else if (strcmp(flag, "nosessionbinding") == 0) { server->options.binding_session = false; } else if (strcmp(flag, "nolazyxwayland") == 0) { server->options.lazy_xwayland = false; } else { printf("Unknown debug flag: %s\n", flag); } } static void terminate(int exit_code) { if (!server.ready) { exit(exit_code); } else if (!server.terminate) { exit_value = exit_code; wl_display_terminate(server.display); } } static void set_signal(int sig, void *handler) { struct sigaction act; sigemptyset(&act.sa_mask); if (handler != SIG_IGN) { sigaddset(&act.sa_mask, sig); } act.sa_sigaction = handler; act.sa_flags = SA_SIGINFO; sigaction(sig, &act, NULL); } static void sig_handler(int signo, siginfo_t *sip, void *unused) { if (signo == SIGINT && server.terminate) { return; } if (sip->si_code == SI_USER) { kywc_log(KYWC_SILENT, "Received signal %s(%u) sent by process %u, uid %u", strsignal(signo), signo, sip->si_pid, sip->si_uid); } else { switch (signo) { case SIGINT: kywc_log(KYWC_SILENT, "Received signal %s(%u) by Ctrl+C", strsignal(signo), signo); break; case SIGSEGV: case SIGBUS: case SIGILL: case SIGFPE: case SIGABRT: kywc_log(KYWC_SILENT, "Caught %s(%u) at address %p", strsignal(signo), signo, sip->si_addr); debug_backtrace(); /* abort() raises SIGABRT */ set_signal(SIGABRT, SIG_DFL); abort(); } } terminate(EXIT_SUCCESS); } static void child_handler(int signo, siginfo_t *sip, void *unused) { if (sip->si_pid != server.session_pid) { return; } switch (sip->si_code) { case CLD_EXITED: kywc_log(KYWC_ERROR, "Session %d exited with %d", sip->si_pid, sip->si_status); break; case CLD_KILLED: case CLD_DUMPED:; const char *signame = strsignal(sip->si_status); kywc_log(KYWC_ERROR, "Session %d terminated with signal %d (%s)", sip->si_pid, sip->si_status, signame ? signame : "unknown"); break; default: kywc_log(KYWC_ERROR, "Session %d terminated unexpectedly: %d", sip->si_pid, sip->si_code); break; } spawn_wait(server.session_pid); server.session_pid = -1; if (server.options.binding_session) { kywc_log(KYWC_FATAL, "Kylin-wlcom abort..."); terminate(EXIT_SUCCESS); } } static void start_session(void *data) { server.session_pid = spawn_session(server.session_process); if (server.session_pid < 0) { kywc_log(KYWC_ERROR, "Session %s start failed%s", server.session_process, server.options.binding_session ? ", abort" : ""); if (server.options.binding_session) { terminate(EXIT_FAILURE); } } else { kywc_log(KYWC_INFO, "Session %s(%d) started", server.session_process, server.session_pid); } } int main(int argc, char *argv[]) { setlocale(LC_ALL, ""); setlocale(LC_NUMERIC, "C"); #if HAVE_NLS bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); #endif bool enable_debug = false; bool enable_verbose = false; while (1) { int option_index = 0; int c = getopt_long(argc, argv, "hdD:s:vV", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'h': printf("%s", usage); exit(EXIT_SUCCESS); break; case 'd': enable_debug = true; break; case 'D': // extended debug options enable_debug_flag(&server, optarg); break; case 's': server.session_process = optarg; break; case 'v': // version printf("kylin-wlcom version " KYWC_VERSION "\n"); exit(EXIT_SUCCESS); break; case 'V': // verbose enable_verbose = true; break; default: fprintf(stderr, "%s", usage); exit(EXIT_FAILURE); } } /* SUID operation is deprecated, so block it for now */ if (detect_suid()) { exit(EXIT_FAILURE); } enum kywc_log_level level = KYWC_WARN; if (enable_debug) { level = KYWC_DEBUG; } else if (enable_verbose) { level = KYWC_INFO; } logger_init(level, server.options.log_to_file ? "kylin-wlcom.log" : NULL, server.options.log_in_realtime); logger_set_rate_limit(server.options.log_rate_limit ? 5000 : 0, 10); kywc_log(KYWC_SILENT, "kylin-wlcom %s starting...", KYWC_VERSION); kywc_log(KYWC_SILENT, "Current LC_MESSAGES locale: %s", setlocale(LC_MESSAGES, NULL)); /* set Number of open files to max */ limit_set_nofile(); /* ignore SIGPIPE */ set_signal(SIGPIPE, SIG_IGN); /* ctrl+c signal */ set_signal(SIGINT, sig_handler); /* crash signals */ set_signal(SIGSEGV, sig_handler); set_signal(SIGBUS, sig_handler); set_signal(SIGILL, sig_handler); set_signal(SIGFPE, sig_handler); set_signal(SIGABRT, sig_handler); if (!server_init(&server)) { terminate(EXIT_FAILURE); goto shutdown; } if (!server_start(&server)) { terminate(EXIT_FAILURE); goto shutdown; } /* child terminated or stopped */ set_signal(SIGCHLD, child_handler); if (server.session_process) { wl_event_loop_add_idle(server.event_loop, start_session, NULL); } server_run(&server); set_signal(SIGCHLD, SIG_DFL); /* kill the session process */ if (server.session_pid > 0) { if (kill(server.session_pid, SIGTERM) < 0) { kill(server.session_pid, SIGKILL); } } shutdown: server_finish(&server); logger_finish(); return exit_value; } kylin-wayland-compositor/src/server.c0000664000175000017500000002326015160461067016731 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "backend/backend.h" #include "config.h" #include "effect/action.h" #include "effect/effect.h" #include "input/input.h" #include "output.h" #include "plugin.h" #include "render/allocator.h" #include "render/renderer.h" #include "security.h" #include "server.h" #include "theme.h" #include "util/dbus.h" #include "util/sysfs.h" #include "view/view.h" #include "xwayland.h" static int prepare_for_sleep(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { struct server *server = userdata; /* "b" apparently reads into an int, not a bool */ int going_down = 1; int ret = sd_bus_message_read(msg, "b", &going_down); if (ret < 0) { kywc_log(KYWC_WARN, "Failed to parse D-Bus response for Inhibit: %s", strerror(-ret)); return 0; } if (!going_down) { wl_signal_emit_mutable(&server->events.resume, NULL); } else { wl_signal_emit_mutable(&server->events.suspend, NULL); } return 0; } void server_add_destroy_listener(struct server *server, struct wl_listener *listener) { struct wl_signal *signal = &server->events.destroy; wl_list_insert(&signal->listener_list, &listener->link); } static void kywc_log_callback(enum wlr_log_importance verbosity, const char *fmt, va_list args) { /* switch wlr_log_importance to kywc_log_level */ enum kywc_log_level level = KYWC_WARN; switch (verbosity) { case WLR_SILENT: level = KYWC_SILENT; break; case WLR_ERROR: level = KYWC_ERROR; break; case WLR_INFO: level = KYWC_INFO; break; case WLR_DEBUG: level = KYWC_DEBUG; break; default: break; } kywc_vlog(level, fmt, args); } static void kywc_log_callback_dumb(enum wlr_log_importance verbosity, const char *fmt, va_list args) {}; static void server_get_active_vt(struct server *server) { char buf[64] = { 0 }; size_t len = sysfs_read_data("/sys/class/tty/tty0/active", buf, 63); if (len > 0) { sscanf(buf, "tty%u", &server->vtnr); kywc_log(KYWC_INFO, "Current VT is %u", server->vtnr); } else { server->vtnr = 0; } } static void handle_session_active(struct wl_listener *listener, void *data) { struct server *server = wl_container_of(listener, server, session_active); struct wlr_session *session = server->session; kywc_log(KYWC_INFO, "Session becomes %sactive", session->active ? "" : "in"); server->active = session->active; wl_signal_emit_mutable(&server->events.active, NULL); } static bool wlroots_server_init(struct server *server) { /* verbosity is not used when we replaced log_callback */ wlr_log_init(WLR_DEBUG, kywc_log_callback); server->backend = ky_backend_autocreate(server->event_loop, &server->session); if (!server->backend) { kywc_log(KYWC_FATAL, "Unable to create backend"); return false; } if (server->session) { server->active = server->session->active; server->session_active.notify = handle_session_active; wl_signal_add(&server->session->events.active, &server->session_active); server_get_active_vt(server); } else { // mark active if in nested backend server->active = true; } server->renderer = ky_renderer_autocreate(server->backend); if (!server->renderer) { kywc_log(KYWC_FATAL, "Unable to create renderer"); return false; } server->allocator = allocator_create(server->backend, server->renderer); if (!server->allocator) { kywc_log(KYWC_FATAL, "Unable to create allocator"); return false; } // TODO: set renderer to NULL, drop wlr_client_buffer server->compositor = wlr_compositor_create(server->display, 6, server->renderer); wlr_subcompositor_create(server->display); ky_renderer_init_wl_display(server->renderer, server->backend, server->display, &server->linux_dmabuf_v1); server->layout = wlr_output_layout_create(server->display); server->scene = ky_scene_create(); if (server->linux_dmabuf_v1) { ky_scene_set_linux_dmabuf_v1(server->scene, server->linux_dmabuf_v1); } struct wlr_tearing_control_manager_v1 *tearing_control_v1 = wlr_tearing_control_manager_v1_create(server->display, 1); if (tearing_control_v1) { ky_scene_set_tearing_control_v1(server->scene, tearing_control_v1); } wlr_presentation_create(server->display, server->backend, 2); wlr_export_dmabuf_manager_v1_create(server->display); wlr_viewporter_create(server->display); wlr_fractional_scale_manager_v1_create(server->display, 1); alpha_modifier_v1_create(server->display); return true; } static int handle_exit(int signal, void *data) { kywc_log(KYWC_SILENT, "Received signal %s(%u), exit...", strsignal(signal), signal); struct server *server = data; if (!server->ready) { exit(EXIT_SUCCESS); } else if (!server->terminate) { wl_display_terminate(server->display); } return 0; } bool server_init(struct server *server) { server->display = wl_display_create(); server->event_loop = wl_display_get_event_loop(server->display); wl_display_set_default_max_buffer_size(server->display, 1024 * 1024); wl_signal_init(&server->events.ready); wl_signal_init(&server->events.start); wl_signal_init(&server->events.terminate); wl_signal_init(&server->events.destroy); wl_signal_init(&server->events.suspend); wl_signal_init(&server->events.resume); wl_signal_init(&server->events.active); /* use wl event source to sync these signals in multi-thread */ server->sources.sighup = wl_event_loop_add_signal(server->event_loop, SIGHUP, handle_exit, server); server->sources.sigterm = wl_event_loop_add_signal(server->event_loop, SIGTERM, handle_exit, server); dbus_context_create(server); dbus_match_system_signal("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "PrepareForSleep", prepare_for_sleep, server); server->queue = queue_create(64, 4, server); config_manager_create(server); security_manager_create(server); theme_manager_create(server); if (!wlroots_server_init(server)) { return false; } output_manager_create(server); input_manager_create(server); view_manager_create(server); xwayland_server_create(server); action_effect_manager_create(server); effect_manager_create(server); plugin_manager_create(server); server->ready = true; wl_signal_emit_mutable(&server->events.ready, NULL); return true; } bool server_start(struct server *server) { /* Add a Unix socket to the Wayland display. */ const char *socket = wl_display_add_socket_auto(server->display); if (!socket) { kywc_log_errno(KYWC_FATAL, "unable to open wayland socket"); return false; } if (setenv("WAYLAND_DISPLAY", socket, true) < 0) { kywc_log_errno(KYWC_ERROR, "unable to set WAYLAND_DISPLAY"); } else { kywc_log(KYWC_DEBUG, "WAYLAND_DISPLAY = %s", socket); } if (!wlr_backend_start(server->backend)) { kywc_log(KYWC_FATAL, "Unable to start the wlroots backend"); return false; } server->start = true; wl_signal_emit_mutable(&server->events.start, NULL); return true; } void server_run(struct server *server) { kywc_log(KYWC_INFO, "Running wayland compositor on wayland display '%s'", getenv("WAYLAND_DISPLAY")); wl_display_run(server->display); } void server_finish(struct server *server) { server->terminate = true; /* suppress all messages from wlroots */ wlr_log_init(WLR_DEBUG, kywc_log_callback_dumb); wl_event_source_remove(server->sources.sighup); wl_event_source_remove(server->sources.sigterm); wl_signal_emit_mutable(&server->events.terminate, NULL); queue_destroy(server->queue); wl_display_destroy_clients(server->display); /* make sure all xwayland-shells are destroyed */ xwayland_server_destroy(); wlr_backend_destroy(server->backend); wl_display_destroy(server->display); /* call all server_destroy listeners */ wl_signal_emit_mutable(&server->events.destroy, NULL); assert(wl_list_empty(&server->events.ready.listener_list)); assert(wl_list_empty(&server->events.start.listener_list)); assert(wl_list_empty(&server->events.terminate.listener_list)); assert(wl_list_empty(&server->events.destroy.listener_list)); assert(wl_list_empty(&server->events.suspend.listener_list)); assert(wl_list_empty(&server->events.resume.listener_list)); assert(wl_list_empty(&server->events.active.listener_list)); /* scene may be NULL when server_init failed */ if (server->scene) { ky_scene_node_destroy(&server->scene->tree.node); } wlr_allocator_destroy(server->allocator); wlr_renderer_destroy(server->renderer); /* free memory in fontconfig */ pango_cairo_font_map_set_default(NULL); kywc_log(KYWC_SILENT, "Kylin-wlcom finished...\n"); } kylin-wayland-compositor/src/effect/0000775000175000017500000000000015160461067016510 5ustar fengfengkylin-wayland-compositor/src/effect/trail.c0000664000175000017500000003274715160461067020004 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "effect/shape.h" #include "effect_p.h" #include "render/opengl.h" #include "scene/scene.h" #include "util/macros.h" #include "util/matrix.h" #include "util/time.h" #include "util/vec2.h" #include "shape_color_frag.h" #include "shape_color_vert.h" struct trail_point { struct wl_list link; struct ky_vec2 point; uint32_t start_time; }; struct trail_info { struct wl_list link; int32_t id; struct wl_list points; // bbox for compute damage region bool bbox_valid; struct ky_vec2 bbox_min; struct ky_vec2 bbox_max; }; struct trail_effect { struct effect *base; struct effect_manager *manager; struct wl_listener enable; struct wl_listener disable; struct wl_listener destroy; const struct trail_effect_interface *impl; struct trail_effect_options options; void *user_data; struct wl_list trail_infos; pixman_region32_t damage; }; static struct touchtrail_gl_shader { int32_t program; // vs GLint position; GLint logic2ndc; // fs GLint color; } gl_shader = { 0 }; static bool create_opengl_shader(struct ky_opengl_renderer *renderer) { if (gl_shader.program == 0) { GLuint prog = ky_opengl_create_program(renderer, shape_color_vert, shape_color_frag); if (prog == 0) { return false; } gl_shader.program = prog; gl_shader.position = glGetAttribLocation(prog, "position"); gl_shader.logic2ndc = glGetUniformLocation(prog, "logic2ndc"); gl_shader.color = glGetUniformLocation(prog, "color"); } return true; } static void free_trail_infos(struct trail_effect *effect) { struct trail_info *finger, *tmp0; wl_list_for_each_safe(finger, tmp0, &effect->trail_infos, link) { struct trail_point *point, *tmp1; wl_list_for_each_safe(point, tmp1, &finger->points, link) { wl_list_remove(&point->link); free(point); } wl_list_remove(&finger->link); free(finger); } } static void add_damage(struct trail_effect *effect) { if (pixman_region32_not_empty(&effect->damage)) { struct ky_scene *scene = effect->manager->server->scene; ky_scene_add_damage(scene, &effect->damage); } } // reference: // simplify_path simple_threshold = 35.0f; // https://psimpl.sourceforge.net/radial-distance.html // smooth_path smooth_iterations = 2; // https://www.educative.io/answers/what-is-chaikins-algorithm // path.length > 1 // vertices buffer size = path.length * 2 static void triangulate_path(struct wl_list *path, uint32_t path_len, float min_thickness, float max_thickness, struct ky_vec2 *vertices) { size_t index = 0; // first point as tail struct trail_point *first_point = wl_container_of(path->prev, first_point, link); vertices[index++] = first_point->point; // thickness small -> big struct trail_point *point; wl_list_for_each_reverse(point, path, link) { struct trail_point *point_next = wl_container_of(point->link.prev, point_next, link); if (&point_next->link != path) { float thickness = MAX(min_thickness, max_thickness * (index - 1 * 0.5f) / path_len); struct ky_vec2 dir; ky_vec2_sub(&point_next->point, &point->point, &dir); ky_vec2_normalize(&dir); struct ky_vec2 perp_dir; ky_vec2_perpendicular(&dir, &perp_dir); ky_vec2_muls(&perp_dir, thickness * 0.5f); ky_vec2_sub(&point_next->point, &perp_dir, &vertices[index++]); ky_vec2_add(&point_next->point, &perp_dir, &vertices[index++]); } } // head expand triangle struct trail_point *last_point = wl_container_of(point->link.next, last_point, link); struct trail_point *last2_point = wl_container_of(last_point->link.next, last2_point, link); struct ky_vec2 dir; ky_vec2_sub(&last_point->point, &last2_point->point, &dir); ky_vec2_normalize(&dir); ky_vec2_muls(&dir, max_thickness); ky_vec2_add(&last_point->point, &dir, &vertices[index]); } static void compute_finger_boundbox(struct trail_info *finger, struct ky_vec2 *verts, uint32_t verts_len) { struct ky_vec2 *min = &finger->bbox_min; struct ky_vec2 *max = &finger->bbox_max; min->x = FLT_MAX; min->y = FLT_MAX; max->x = FLT_MIN; max->y = FLT_MIN; for (uint32_t i = 0; i < verts_len; i++) { ky_vec2_min(&verts[i], min, min); ky_vec2_max(&verts[i], max, max); } } static void gl_render_finger_effect(struct trail_effect *effect, struct trail_info *finger, struct ky_scene_render_target *target) { int points_len = wl_list_length(&finger->points); if (points_len < 2) { return; } // too many points. use gpu compute final pos, input logic pos uint32_t verts_len = points_len * 2; struct ky_vec2 verts[verts_len]; triangulate_path(&finger->points, points_len, 4.0f, effect->options.thickness, verts); compute_finger_boundbox(finger, verts, verts_len); finger->bbox_valid = true; struct ky_mat3 transform; ky_mat3_identity(&transform); ky_mat3_init_translate(&transform, -target->logical.x, -target->logical.y); struct ky_mat3 to_ndc; ky_mat3_logic_to_ndc(&to_ndc, target->logical.width, target->logical.height, target->transform); struct ky_mat3 logic2ndc; ky_mat3_multiply(&to_ndc, &transform, &logic2ndc); glEnable(GL_BLEND); glUseProgram(gl_shader.program); glEnableVertexAttribArray(gl_shader.position); glVertexAttribPointer(gl_shader.position, 2, GL_FLOAT, GL_FALSE, 0, verts); glUniformMatrix3fv(gl_shader.logic2ndc, 1, GL_FALSE, logic2ndc.matrix); glUniform4fv(gl_shader.color, 1, effect->options.color); glDrawArrays(GL_TRIANGLE_STRIP, 0, verts_len); glUseProgram(0); glDisableVertexAttribArray(gl_shader.position); } static bool frame_render_begin(struct effect_entity *entity, struct ky_scene_render_target *target) { struct trail_effect *effect = entity->user_data; // timer struct trail_info *finger, *tmp0; wl_list_for_each_safe(finger, tmp0, &effect->trail_infos, link) { struct trail_point *point, *tmp1; wl_list_for_each_safe(point, tmp1, &finger->points, link) { uint32_t diff_time = current_time_msec() - point->start_time; if (diff_time > effect->options.life_time) { wl_list_remove(&point->link); free(point); } } if (wl_list_empty(&finger->points)) { wl_list_remove(&finger->link); free(finger); pixman_region32_clear(&effect->damage); } } return true; } static bool frame_render_post(struct effect_entity *entity, struct ky_scene_render_target *target) { struct trail_effect *effect = entity->user_data; // add damage to trigger render event add_damage(effect); return true; } static bool frame_render_end(struct effect_entity *entity, struct ky_scene_render_target *target) { struct trail_effect *effect = entity->user_data; if (gl_shader.program == 0) { struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(effect->manager->server->renderer); create_opengl_shader(renderer); if (gl_shader.program <= 0) { return true; } } bool bbox_valid = false; struct ky_vec2 bbox_min; struct ky_vec2 bbox_max; bbox_min.x = FLT_MAX; bbox_min.y = FLT_MAX; bbox_max.x = FLT_MIN; bbox_max.y = FLT_MIN; struct trail_info *finger = NULL; wl_list_for_each(finger, &effect->trail_infos, link) { gl_render_finger_effect(effect, finger, target); if (finger->bbox_valid) { ky_vec2_min(&finger->bbox_min, &bbox_min, &bbox_min); ky_vec2_max(&finger->bbox_max, &bbox_max, &bbox_max); bbox_valid = true; } } if (bbox_valid) { pixman_region32_fini(&effect->damage); int x = bbox_min.x - 2; int y = bbox_min.y - 2; int width = bbox_max.x - bbox_min.x + 4; int height = bbox_max.y - bbox_min.y + 4; pixman_region32_init_rect(&effect->damage, x, y, width, height); /* add effect damage to target damage, it should be add to frame damage. */ pixman_region32_union(&target->damage, &target->damage, &effect->damage); } return true; } static bool handle_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } return false; } static bool handle_allow_direct_scanout(struct effect *effect, struct ky_scene_render_target *target) { struct trail_effect *trail = effect->user_data; return wl_list_empty(&trail->trail_infos); } static void handle_effect_enable(struct wl_listener *listener, void *data) { struct trail_effect *effect = wl_container_of(listener, effect, enable); if (effect->impl->enable) { effect->impl->enable(effect, effect->user_data); } } static void handle_effect_disable(struct wl_listener *listener, void *data) { struct trail_effect *effect = wl_container_of(listener, effect, disable); if (effect->impl->disable) { effect->impl->disable(effect, effect->user_data); } free_trail_infos(effect); pixman_region32_clear(&effect->damage); } static void handle_effect_destroy(struct wl_listener *listener, void *data) { struct trail_effect *effect = wl_container_of(listener, effect, destroy); wl_list_remove(&effect->destroy.link); wl_list_remove(&effect->enable.link); wl_list_remove(&effect->disable.link); if (effect->impl->destroy) { effect->impl->destroy(effect, effect->user_data); } pixman_region32_fini(&effect->damage); free(effect); } static const struct effect_interface effect_impl = { .frame_render_begin = frame_render_begin, .frame_render_end = frame_render_end, .frame_render_post = frame_render_post, .configure = handle_effect_configure, .allow_direct_scanout = handle_allow_direct_scanout, }; struct trail_effect *trail_effect_create(struct effect_manager *manager, struct trail_effect_options *options, const struct trail_effect_interface *impl, const char *name, int priority, bool enabled, void *user_data) { if (!wlr_renderer_is_opengl(manager->server->renderer)) { return NULL; } struct trail_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return NULL; } effect->base = effect_create(name, priority, enabled, &effect_impl, effect); if (!effect->base) { free(effect); return NULL; } effect->base->category = EFFECT_CATEGORY_SCENE; struct effect_entity *entity = ky_scene_add_effect(manager->server->scene, effect->base); if (!entity) { effect_destroy(effect->base); free(effect); return NULL; } entity->user_data = effect; effect->manager = manager; effect->enable.notify = handle_effect_enable; wl_signal_add(&effect->base->events.enable, &effect->enable); effect->disable.notify = handle_effect_disable; wl_signal_add(&effect->base->events.disable, &effect->disable); effect->destroy.notify = handle_effect_destroy; wl_signal_add(&effect->base->events.destroy, &effect->destroy); effect->impl = impl; effect->options = *options; effect->user_data = user_data; wl_list_init(&effect->trail_infos); pixman_region32_init(&effect->damage); if (effect->base->enabled) { handle_effect_enable(&effect->enable, NULL); } return effect; } void trail_effect_add_trail(struct trail_effect *effect, int32_t id, int32_t start_x, int32_t start_y) { // record multi-trail bool find_id = false; struct trail_info *finger; wl_list_for_each(finger, &effect->trail_infos, link) { if (finger->id == id) { find_id = true; break; } } if (!find_id) { struct trail_info *finger = calloc(1, sizeof(*finger)); wl_list_insert(&effect->trail_infos, &finger->link); finger->id = id; finger->bbox_valid = false; wl_list_init(&finger->points); // insert current point struct trail_point *point = calloc(1, sizeof(*point)); point->point.x = start_x; point->point.y = start_y; point->start_time = current_time_msec(); wl_list_insert(&finger->points, &point->link); add_damage(effect); } } void trail_effect_trail_add_point(struct trail_effect *effect, int32_t id, int32_t x, int32_t y) { bool find_id = false; struct trail_info *finger = NULL; wl_list_for_each(finger, &effect->trail_infos, link) { if (finger->id == id) { find_id = true; break; } } if (find_id) { struct trail_point *point = calloc(1, sizeof(*point)); point->point.x = x; point->point.y = y; point->start_time = current_time_msec(); wl_list_insert(&finger->points, &point->link); } } void trail_effect_remove_all_trails(struct trail_effect *effect) { free_trail_infos(effect); pixman_region32_clear(&effect->damage); } kylin-wayland-compositor/src/effect/meson.build0000664000175000017500000000163615160461067020660 0ustar fengfengwlcom_sources += files( 'action.c', 'animator.c', 'blur.c', 'capture.c', 'circle_progressbar.c', 'color_filter.c', 'config.c', 'effect.c', 'fade.c', 'locate_pointer.c', 'magic_lamp.c', 'mouse_click.c', 'mouse_trail.c', 'move.c', 'output_transform.c', 'showfps.c', 'showkey.c', 'scale.c', 'shake_cursor.c', 'shake_view.c', 'snapshot.c', 'tap_ripple.c', 'touch_click.c', 'touch_long.c', 'touch_trail.c', 'trail.c', 'translation.c', 'transform.c', 'wallpaper.c', 'wlr_screencopy.c', 'zoom.c', ) wlcom_sources += files( 'ky_capture.c', ) if have_ukui_screenshot wlcom_sources += files( 'screenshot.c', ) endif if have_ukui_watermark wlcom_sources += files( 'watermark.c', ) endif if have_kde_slide wlcom_sources += files( 'slide.c', ) endif if have_ukui_effect wlcom_sources += files( 'ukui_effect.c', ) endif subdir('shaders') kylin-wayland-compositor/src/effect/screenshot.c0000664000175000017500000002035515160461067021036 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "effect/capture.h" #include "effect_p.h" #include "util/dbus.h" static const char *registry_bus = "org.ukui.KWin"; static const char *registry_path = "/Screenshot"; static const char *registry_interface = "org.ukui.kwin.Screenshot"; struct screenshot { struct capture *capture; struct wl_listener capture_update; struct wl_listener capture_destroy; sd_bus_message *msg; }; static void screenshot_finish(const char *path, void *data) { sd_bus_message *msg = data; if (path) { sd_bus_reply_method_return(msg, "s", path); kywc_log(KYWC_DEBUG, "Screenshot done, send reply %s", path); } else { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST( "org.ukui.kwin.Screenshot.Error.Cancelled", "Screenshot got cancelled"); sd_bus_reply_method_error(msg, &error); } sd_bus_message_unref(msg); } static void screenshot_destroy(struct screenshot *screenshot) { wl_list_remove(&screenshot->capture_destroy.link); wl_list_remove(&screenshot->capture_update.link); if (screenshot->capture) { capture_destroy(screenshot->capture); } free(screenshot); } static void handle_capture_update(struct wl_listener *listener, void *data) { struct screenshot *screenshot = wl_container_of(listener, screenshot, capture_update); struct capture_update_event *event = data; char path[32]; snprintf(path, 32, "/tmp/%s", "kywc_screenshot_XXXXXX.bmp"); kywc_identifier_rand_generate(path, 4); capture_write_file(event->buffer, event->buffer->width, event->buffer->height, path, screenshot_finish, screenshot->msg); screenshot_destroy(screenshot); } static void handle_capture_destroy(struct wl_listener *listener, void *data) { struct screenshot *screenshot = wl_container_of(listener, screenshot, capture_destroy); /* capture failed, send a error to client */ screenshot_finish(NULL, screenshot->msg); screenshot->capture = NULL; screenshot_destroy(screenshot); } static int screenshot_fullscreen(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { struct screenshot *screenshot = calloc(1, sizeof(*screenshot)); if (!screenshot) { return 0; } screenshot->capture = capture_create_from_fullscreen(CAPTURE_NEED_UNSCALED); if (!screenshot->capture) { free(screenshot); return 0; } screenshot->capture_update.notify = handle_capture_update; screenshot->capture_destroy.notify = handle_capture_destroy; capture_add_update_listener(screenshot->capture, &screenshot->capture_update); capture_add_destroy_listener(screenshot->capture, &screenshot->capture_destroy); screenshot->msg = sd_bus_message_ref(msg); return 1; } static int screenshot2_fullscreen(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { uint32_t cursor = 0; CK(sd_bus_message_read(msg, "b", &cursor)); uint32_t options = CAPTURE_NEED_UNSCALED; options |= cursor ? CAPTURE_NEED_CURSOR : CAPTURE_NEED_NONE; struct screenshot *screenshot = calloc(1, sizeof(*screenshot)); if (!screenshot) { return 0; } screenshot->capture = capture_create_from_fullscreen(options); if (!screenshot->capture) { free(screenshot); return 0; } screenshot->capture_update.notify = handle_capture_update; screenshot->capture_destroy.notify = handle_capture_destroy; capture_add_update_listener(screenshot->capture, &screenshot->capture_update); capture_add_destroy_listener(screenshot->capture, &screenshot->capture_destroy); screenshot->msg = sd_bus_message_ref(msg); return 1; } static int screenshot_full(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { uint32_t unscaled, cursor; CK(sd_bus_message_read(msg, "bb", &unscaled, &cursor)); uint32_t options = unscaled ? CAPTURE_NEED_UNSCALED : CAPTURE_NEED_NONE; options |= cursor ? CAPTURE_NEED_CURSOR : CAPTURE_NEED_NONE; struct screenshot *screenshot = calloc(1, sizeof(*screenshot)); if (!screenshot) { return 0; } screenshot->capture = capture_create_from_fullscreen(options); if (!screenshot->capture) { free(screenshot); return 0; } screenshot->capture_update.notify = handle_capture_update; screenshot->capture_destroy.notify = handle_capture_destroy; capture_add_update_listener(screenshot->capture, &screenshot->capture_update); capture_add_destroy_listener(screenshot->capture, &screenshot->capture_destroy); screenshot->msg = sd_bus_message_ref(msg); return 1; } static int screenshot_output(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *name = NULL; uint32_t unscaled, cursor; CK(sd_bus_message_read(msg, "sbb", &name, &unscaled, &cursor)); struct kywc_output *output = kywc_output_by_name(name); if (!output) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST( "org.ukui.kwin.Screenshot.Error.InvalidOutput", "Invalid output requested"); return sd_bus_reply_method_error(msg, &error); } uint32_t options = unscaled ? CAPTURE_NEED_UNSCALED : CAPTURE_NEED_NONE; options |= cursor ? CAPTURE_NEED_CURSOR : CAPTURE_NEED_NONE; struct screenshot *screenshot = calloc(1, sizeof(*screenshot)); if (!screenshot) { return 0; } screenshot->capture = capture_create_from_output(output_from_kywc_output(output), options); if (!screenshot->capture) { free(screenshot); return 0; } screenshot->capture_update.notify = handle_capture_update; screenshot->capture_destroy.notify = handle_capture_destroy; capture_add_update_listener(screenshot->capture, &screenshot->capture_update); capture_add_destroy_listener(screenshot->capture, &screenshot->capture_destroy); screenshot->msg = sd_bus_message_ref(msg); return 1; } static int screenshot_area(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { int x, y, width, height; uint32_t unscaled, cursor; CK(sd_bus_message_read(msg, "iiiibb", &x, &y, &width, &height, &unscaled, &cursor)); uint32_t options = unscaled ? CAPTURE_NEED_UNSCALED : CAPTURE_NEED_NONE; options |= cursor ? CAPTURE_NEED_CURSOR : CAPTURE_NEED_NONE; struct screenshot *screenshot = calloc(1, sizeof(*screenshot)); if (!screenshot) { return 0; } screenshot->capture = capture_create_from_area(&(struct wlr_box){ x, y, width, height }, options); if (!screenshot->capture) { free(screenshot); return 0; } screenshot->capture_update.notify = handle_capture_update; screenshot->capture_destroy.notify = handle_capture_destroy; capture_add_update_listener(screenshot->capture, &screenshot->capture_update); capture_add_destroy_listener(screenshot->capture, &screenshot->capture_destroy); screenshot->msg = sd_bus_message_ref(msg); return 1; } /** * sd-bus not support method overloaded, https://github.com/systemd/systemd/issues/578 * Add org.kde.KWin screenshotFullscreen with a bool arg for linuxqq, * keep org.ukui.KWin screenshotFullscreen without args for wechat. * kylin-screenshot should use screenshotFull. */ static const sd_bus_vtable screenshot_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("screenshotFullscreen", "", "s", screenshot_fullscreen, 0), SD_BUS_METHOD("screenshotFull", "bb", "s", screenshot_full, 0), SD_BUS_METHOD("screenshotOutput", "sbb", "s", screenshot_output, 0), SD_BUS_METHOD("screenshotArea", "iiiibb", "s", screenshot_area, 0), SD_BUS_VTABLE_END, }; static const sd_bus_vtable screenshot2_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("screenshotFullscreen", "b", "s", screenshot2_fullscreen, 0), SD_BUS_VTABLE_END, }; bool screenshot_effect_create(struct effect_manager *effect_manager) { if (!dbus_register_object(registry_bus, registry_path, registry_interface, screenshot_vtable, NULL)) { return false; } dbus_register_object("org.kde.KWin", registry_path, "org.kde.kwin.Screenshot", screenshot2_vtable, NULL); return true; } kylin-wayland-compositor/src/effect/shaders/0000775000175000017500000000000015160461067020141 5ustar fengfengkylin-wayland-compositor/src/effect/shaders/blur_tex.vert0000664000175000017500000000076015160461067022672 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform mat3 uv2ndc; uniform mat3 uv2texcoord; uniform mat3 uv2shapecoord; attribute vec2 sdfpos; // shape window part attribute vec2 pos; //blur_tex varying vec2 v_uv; varying vec2 v_texcoord; void main() { vec3 pos3 = vec3(pos, 1.0); gl_Position = vec4(uv2ndc * pos3, 1.0); v_texcoord = (uv2texcoord * pos3).xy; v_uv = (uv2shapecoord * vec3(sdfpos, 1.0)).xy; } kylin-wayland-compositor/src/effect/shaders/circle_progressbar.frag0000664000175000017500000000163515160461067024661 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif varying vec2 uv; uniform float antiAliasing; uniform float endAngle; #define PI 3.141592653589793 const float innerRadius = 0.7; const float outerRadius = 1.0; const vec3 fgColor = vec3(0.0, 0.68, 1.0); const vec3 bgColor = vec3(0.6); void main() { vec2 st = uv * 2.0 - 1.0; float dist = length(st); float outer = smoothstep(-antiAliasing, antiAliasing, outerRadius - dist); float inner = smoothstep(-antiAliasing, antiAliasing, dist - innerRadius); float angle = atan(st.y, st.x) + PI; float mask = smoothstep(-antiAliasing, antiAliasing, endAngle - angle); float bandAlpha = outer * inner; vec4 bg = vec4(bgColor, bandAlpha); vec4 fg = vec4(fgColor, bandAlpha * mask); vec3 color = mix(bg.rgb, fg.rgb, fg.a); gl_FragColor = vec4(color * bg.a, bg.a); } kylin-wayland-compositor/src/effect/shaders/magic_lamp.frag0000664000175000017500000000034515160460057023073 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform sampler2D tex; varying vec2 v_texcoord; void main() { gl_FragColor = texture2D(tex, v_texcoord); } kylin-wayland-compositor/src/effect/shaders/meson.build0000664000175000017500000000112615160460057022301 0ustar fengfengshaders = [ 'blur_tex.vert', 'blur_first_down.frag', 'blur_first_down.vert', 'blur_tex_rgba.frag', 'blur.vert', 'blur_up.frag', 'blur_down.frag', 'circle_progressbar.vert', 'circle_progressbar.frag', 'magic_lamp.vert', 'magic_lamp.frag', 'shape_color.vert', 'shape_color.frag', 'tap_ripple.vert', 'tap_ripple.frag', ] foreach name : shaders output = name.underscorify() + '.h' var = name.underscorify() wlcom_sources += custom_target( output, command: [embed, var], input: name, output: output, feed: true, capture: true, ) endforeach kylin-wayland-compositor/src/effect/shaders/blur_first_down.vert0000664000175000017500000000052515160461067024247 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform mat3 uv2tex; attribute vec2 position; varying vec2 uv; void main() { gl_Position = vec4(position, 0.0, 1.0); vec2 tex_uv = (position.xy + vec2(1.0, 1.0)) * 0.5; uv = (uv2tex * vec3(tex_uv, 1.0)).xy; } kylin-wayland-compositor/src/effect/shaders/blur_tex_rgba.frag0000664000175000017500000000306515160460057023623 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif varying vec2 v_uv; varying vec2 v_texcoord; uniform sampler2D tex; uniform float alpha; uniform float antiAliasing; uniform float aspect; // width / height uniform vec4 roundedCornerRadius; uniform float offset; uniform vec2 halfpixel; vec4 get_pixel(vec2 uv) { vec4 sum = texture2D(tex, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset); sum += texture2D(tex, uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0; sum += texture2D(tex, uv + vec2(0.0, halfpixel.y * 2.0) * offset); sum += texture2D(tex, uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0; sum += texture2D(tex, uv + vec2(halfpixel.x * 2.0, 0.0) * offset); sum += texture2D(tex, uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0; sum += texture2D(tex, uv + vec2(0.0, -halfpixel.y * 2.0) * offset); sum += texture2D(tex, uv + vec2(-halfpixel.x, -halfpixel.y) * offset) * 2.0; return sum * 0.0833333333; } float sdRoundedBox(in vec2 p, in vec2 b, in vec4 r) { r.xy = (p.x > 0.0) ? r.xy : r.zw; r.x = (p.y > 0.0) ? r.x : r.y; vec2 q = abs(p) - b + r.x; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x; } void main() { vec2 st = v_uv * 2.0 - 1.0; st.x *= aspect; vec2 fullSize = vec2(aspect, 1.0); float dist = sdRoundedBox(st, fullSize, roundedCornerRadius); float shape = 1.0 - smoothstep(0.0, antiAliasing, dist); vec4 texColor = get_pixel(v_texcoord) * alpha; gl_FragColor = mix(vec4(0.0), texColor, shape); } kylin-wayland-compositor/src/effect/shaders/shape_color.vert0000664000175000017500000000036515160460057023343 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform mat3 logic2ndc; attribute vec2 position; void main() { gl_Position = vec4(logic2ndc * vec3(position, 1.0), 1.0); } kylin-wayland-compositor/src/effect/shaders/blur_up.frag0000664000175000017500000000161015160460057022446 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif varying vec2 uv; uniform float offset; uniform sampler2D texture; uniform vec2 halfpixel; void main() { vec4 sum = texture2D(texture, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset); sum += texture2D(texture, uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0; sum += texture2D(texture, uv + vec2(0.0, halfpixel.y * 2.0) * offset); sum += texture2D(texture, uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0; sum += texture2D(texture, uv + vec2(halfpixel.x * 2.0, 0.0) * offset); sum += texture2D(texture, uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0; sum += texture2D(texture, uv + vec2(0.0, -halfpixel.y * 2.0) * offset); sum += texture2D(texture, uv + vec2(-halfpixel.x, -halfpixel.y) * offset) * 2.0; gl_FragColor = sum * 0.0833333333; } kylin-wayland-compositor/src/effect/shaders/tap_ripple.vert0000664000175000017500000000041015160460057023173 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform mat3 uv2ndc; attribute vec2 inUV; varying vec2 uv; void main() { gl_Position = vec4(uv2ndc * vec3(inUV, 1.0), 1.0); uv = inUV; } kylin-wayland-compositor/src/effect/shaders/blur_first_down.frag0000664000175000017500000000144715160460057024210 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif varying vec2 uv; uniform float offset; uniform sampler2D texture; uniform vec2 halfpixel; uniform vec2 min_uv; uniform vec2 max_uv; void main() { vec4 sum = texture2D(texture, uv) * 4.0; vec2 lb_uv = clamp(uv - halfpixel.xy * offset, min_uv, max_uv); sum += texture2D(texture, lb_uv); vec2 rt_uv = clamp(uv + halfpixel.xy * offset, min_uv, max_uv); sum += texture2D(texture, rt_uv); vec2 rb_uv = clamp(uv + vec2(halfpixel.x, -halfpixel.y) * offset, min_uv, max_uv); sum += texture2D(texture, rb_uv); vec2 lt_uv = clamp(uv - vec2(halfpixel.x, -halfpixel.y) * offset, min_uv, max_uv); sum += texture2D(texture, lt_uv); gl_FragColor = sum * 0.125; } kylin-wayland-compositor/src/effect/shaders/tap_ripple.frag0000664000175000017500000000100515160461067023135 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif varying vec2 uv; uniform float antiAliasing; uniform float radius; uniform float attenuation; uniform vec3 color; void main() { float dist = distance(uv, vec2(0.5, 0.5)); float dist2 = smoothstep(-antiAliasing, antiAliasing, radius - dist); float alpha = radius - dist; alpha = 1.0 - alpha - attenuation; alpha *= dist2; gl_FragColor = vec4(color * dist2 * alpha, alpha); } kylin-wayland-compositor/src/effect/shaders/blur.vert0000664000175000017500000000041215160460057022002 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif attribute vec2 position; varying vec2 uv; void main() { gl_Position = vec4(position, 0.0, 1.0); uv = (position.xy + vec2(1.0, 1.0)) * 0.5; } kylin-wayland-compositor/src/effect/shaders/circle_progressbar.vert0000664000175000017500000000050215160460057024710 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform mat3 uv2ndc; uniform mat3 outputInvert; attribute vec2 inUV; varying vec2 uv; void main() { gl_Position = vec4(uv2ndc * vec3(inUV, 1.0), 1.0); uv = (outputInvert * vec3(inUV, 1.0)).xy; } kylin-wayland-compositor/src/effect/shaders/blur_down.frag0000664000175000017500000000107715160460057023000 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif varying vec2 uv; uniform float offset; uniform sampler2D texture; uniform vec2 halfpixel; void main() { vec4 sum = texture2D(texture, uv) * 4.0; sum += texture2D(texture, uv - halfpixel.xy * offset); sum += texture2D(texture, uv + halfpixel.xy * offset); sum += texture2D(texture, uv + vec2(halfpixel.x, -halfpixel.y) * offset); sum += texture2D(texture, uv - vec2(halfpixel.x, -halfpixel.y) * offset); gl_FragColor = sum * 0.125; } kylin-wayland-compositor/src/effect/shaders/shape_color.frag0000664000175000017500000000026315160460057023277 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform vec4 color; void main() { gl_FragColor = color; } kylin-wayland-compositor/src/effect/shaders/magic_lamp.vert0000664000175000017500000000051715160460057023135 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform mat3 logic2ndc; attribute vec2 in_position; attribute vec2 in_texcoord; varying vec2 v_texcoord; void main() { gl_Position = vec4(logic2ndc * vec3(in_position, 1.0), 1.0); v_texcoord = in_texcoord; } kylin-wayland-compositor/src/effect/slide.c0000664000175000017500000002673215160461067017766 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "effect/action.h" #include "effect/animator.h" #include "effect/slide.h" #include "effect/snapshot.h" #include "effect/transform.h" #include "effect_p.h" #include "output.h" #include "util/time.h" #include "view/view.h" enum slide_style { SLIDE_ON_OUTPUT = 0, SLIDE_ON_NODE, }; enum slide_location { SLIDE_LOCATION_LEFT = 0, SLIDE_LOCATION_TOP = 1, SLIDE_LOCATION_RIGHT = 2, SLIDE_LOCATION_BOTTOM = 3, }; struct slide_entity { struct transform *transform; enum slide_style style; int location; int offset; bool slide_in; struct wl_listener add_effect; struct wl_listener node_destroy; /* No need to monitor view destroy signal */ struct wl_listener destroy; }; struct slide_effect { struct transform_effect *effect; struct action_effect *action_effect; }; static struct slide_effect *slide_effect = NULL; static void slide_get_node_origin_geometry(struct ky_scene_node *node, struct kywc_box *geometry) { struct kywc_box box; ky_scene_node_get_affected_bounding_box(node, KY_SCENE_BOUNDING_WITHOUT_EFFECT_NODE, &box); ky_scene_node_coords(node, &geometry->x, &geometry->y); geometry->x += box.x; geometry->y += box.y; geometry->width = box.width; geometry->height = box.height; } static void slide_calc_start_and_end_geometry(struct slide_entity *slide_entity, const struct kywc_box *node_geometry, bool slide_in, struct kywc_box *start_geometry, struct kywc_box *end_geometry) { struct kywc_box drawer_box = *node_geometry; if (slide_entity->style == SLIDE_ON_OUTPUT) { double center_x = node_geometry->x + node_geometry->width / 2.0; double center_y = node_geometry->y + node_geometry->height / 2.0; struct kywc_output *kywc_output = kywc_output_at_point(center_x, center_y); struct output *output = output_from_kywc_output(kywc_output); drawer_box = output->geometry; } struct kywc_box slide_geometry = *node_geometry; switch (slide_entity->location) { case SLIDE_LOCATION_LEFT: slide_geometry.x = drawer_box.x + slide_entity->offset; slide_geometry.width = 0; break; case SLIDE_LOCATION_TOP: slide_geometry.y = drawer_box.y + slide_entity->offset; slide_geometry.height = 0; break; case SLIDE_LOCATION_BOTTOM: slide_geometry.y = drawer_box.y + drawer_box.height - slide_entity->offset; slide_geometry.height = 0; break; case SLIDE_LOCATION_RIGHT: slide_geometry.x = drawer_box.x + drawer_box.width - slide_entity->offset; slide_geometry.width = 0; break; } if (slide_in) { *start_geometry = slide_geometry; *end_geometry = *node_geometry; } else { *start_geometry = *node_geometry; *end_geometry = slide_geometry; } } static void slide_update_transform_options(struct transform_effect *effect, struct transform *transform, struct ky_scene_node *node, struct transform_options *options, struct animation_data *current, void *data) { struct slide_entity *slide_entity = transform_get_user_data(transform); if (!slide_entity->slide_in) { return; } struct kywc_box node_geometry; slide_get_node_origin_geometry(node, &node_geometry); slide_calc_start_and_end_geometry(slide_entity, &node_geometry, slide_entity->slide_in, &options->start.geometry, &options->end.geometry); } static void slide_get_content_anchors(int location, uint32_t *anchors) { *anchors = TRANSFORM_CONTENT_ANCHOR_ALL; switch (location) { case SLIDE_LOCATION_LEFT: *anchors &= ~TRANSFORM_CONTENT_ANCHOR_LEFT; break; case SLIDE_LOCATION_RIGHT: *anchors &= ~TRANSFORM_CONTENT_ANCHOR_RIGHT; break; case SLIDE_LOCATION_TOP: *anchors &= ~TRANSFORM_CONTENT_ANCHOR_TOP; break; case SLIDE_LOCATION_BOTTOM: *anchors &= ~TRANSFORM_CONTENT_ANCHOR_BOTTOM; break; } } static void slide_effect_destroy(struct transform_effect *effect, void *data) { if (!slide_effect) { return; } if (slide_effect->action_effect) { action_effect_destroy(slide_effect->action_effect); } free(slide_effect); slide_effect = NULL; } static void slide_handle_transform_destroy(struct wl_listener *listener, void *data) { struct slide_entity *slide_entity = wl_container_of(listener, slide_entity, destroy); wl_list_remove(&slide_entity->add_effect.link); wl_list_remove(&slide_entity->node_destroy.link); wl_list_remove(&slide_entity->destroy.link); free(slide_entity); } static void slide_handle_add_effect(struct wl_listener *listener, void *data) { struct slide_entity *slide_entity = wl_container_of(listener, slide_entity, add_effect); struct ky_scene_add_effect_event *event = data; if (!event) { return; } /** * allow_add is false will not add any effects, if the same type of effect is found to be * reused, it will not trigger add_effect signal. if effect type is differnet, it will trigger * signal, so it still supports the interruption of the slide itself. */ event->allow_add = false; } static void slide_handle_node_destroy(struct wl_listener *listener, void *data) { struct slide_entity *slide_entity = wl_container_of(listener, slide_entity, node_destroy); wl_list_remove(&slide_entity->add_effect.link); wl_list_init(&slide_entity->add_effect.link); wl_list_remove(&slide_entity->node_destroy.link); wl_list_init(&slide_entity->node_destroy.link); } static struct slide_entity *create_slide_entity(struct ky_scene_node *node, enum slide_style style, int location, int offset, bool mapped, struct transform_options *options) { struct slide_entity *slide_entity = calloc(1, sizeof(*slide_entity)); if (!slide_entity) { return NULL; } slide_entity->style = style; slide_entity->offset = offset; slide_entity->location = location; slide_entity->slide_in = mapped; struct kywc_box node_geometry; slide_get_node_origin_geometry(node, &node_geometry); slide_calc_start_and_end_geometry(slide_entity, &node_geometry, mapped, &options->start.geometry, &options->end.geometry); struct effect_render_data render_data = { 0 }; struct ky_scene_add_effect_event event = { .render_data = &render_data, }; slide_entity->transform = transform_effect_get_or_create_transform( slide_effect->effect, options, node, slide_entity, &event); if (!slide_entity->transform) { free(slide_entity); return false; } slide_entity->add_effect.notify = slide_handle_add_effect; wl_signal_add(&node->events.add_effect, &slide_entity->add_effect); slide_entity->node_destroy.notify = slide_handle_node_destroy; wl_signal_add(&node->events.destroy, &slide_entity->node_destroy); slide_entity->destroy.notify = slide_handle_transform_destroy; transform_add_destroy_listener(slide_entity->transform, &slide_entity->destroy); return slide_entity; } static void slide_init_action_options(struct action_effect *effect, struct action_effect_options *options) { *options = (struct action_effect_options){ 0 }; options->style = SLIDE_ON_NODE; options->effect_type = ACTION_EFFECT_SLIDE; options->animations.geometry = animation_manager_get(ANIMATION_TYPE_EASE); options->animations.alpha = animation_manager_get(ANIMATION_TYPE_EASE); options->animations.angle = NULL; options->buffer = NULL; options->new_parent = NULL; options->scale = 1.0f; options->alpha = 1.0f; options->duration = 300; options->y_offset = 0; options->location = 0; } static bool add_slide_to_node(struct action_effect *action_effect, struct ky_scene_node *node, enum effect_action action, struct action_effect_options *action_options) { struct transform_options options = { 0 }; options.scale = action_options->scale; options.buffer = action_options->buffer; options.new_parent = action_options->new_parent; options.animations = action_options->animations; options.start.alpha = action == EFFECT_ACTION_MAP ? action_options->alpha : 1.0f; options.end.alpha = action == EFFECT_ACTION_MAP ? 1.0f : action_options->alpha; options.geometry_type = TRANSFORM_GEOMETRY_NODE_BOUNDING; options.duration = action_options->duration; options.start_time = current_time_msec(); slide_get_content_anchors(action_options->location, &options.content_anchors); return create_slide_entity(node, action_options->style, action_options->location, action_options->y_offset, action == EFFECT_ACTION_MAP, &options); } static bool add_slide_to_view(struct action_effect *action_effect, struct view *view, enum effect_action action, struct action_effect_options *action_options) { action_options->buffer = action == EFFECT_ACTION_MAP ? view_try_get_single_buffer(view) : NULL; struct view_layer *old_layer = view_manager_get_layer_by_role(view->base.role); if (old_layer->layer == LAYER_SYSTEM_WINDOW) { struct view_layer *new_layer = view_manager_get_layer(LAYER_NORMAL, false); action_options->new_parent = new_layer->tree; } return add_slide_to_node(action_effect, &view->tree->node, action, action_options); } const struct action_effect_interface slide_action_impl = { .init_options = slide_init_action_options, .add_to_node = add_slide_to_node, .add_to_view = add_slide_to_view, }; struct transform_effect_interface slide_impl = { .update_transform_options = slide_update_transform_options, .destroy = slide_effect_destroy, }; bool slide_effect_create(struct effect_manager *manager) { slide_effect = calloc(1, sizeof(*slide_effect)); if (!slide_effect) { return false; } uint32_t support_actions = EFFECT_ACTION_MAP | EFFECT_ACTION_UNMAP; slide_effect->action_effect = action_effect_create(ACTION_EFFECT_SLIDE, support_actions, slide_effect, &slide_action_impl); slide_effect->effect = transform_effect_create(manager, &slide_impl, support_actions, "slide", 5, slide_effect); if (!slide_effect->effect) { slide_effect_destroy(NULL, NULL); return false; } return true; } bool view_add_slide_effect(struct view *view, bool slide_out) { if (!view->use_slide || !slide_effect || !view->surface) { return false; } struct action_effect_options action_options = { 0 }; slide_init_action_options(NULL, &action_options); action_options.style = SLIDE_ON_OUTPUT; action_options.y_offset = view->slide.offset; action_options.location = view->slide.location; return add_slide_to_view(slide_effect->action_effect, view, slide_out ? EFFECT_ACTION_MAP : EFFECT_ACTION_UNMAP, &action_options); } kylin-wayland-compositor/src/effect/effect_p.h0000664000175000017500000000566115160461067020444 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _EFFECT_P_H_ #define _EFFECT_P_H_ #include "effect/effect.h" #include "server.h" struct effect_manager { struct wl_list effects; struct config *config; struct server *server; struct wl_listener server_ready; struct wl_listener server_destroy; double time_scale; }; bool effect_manager_config_init(struct effect_manager *effect_manager); bool effect_init_config(struct effect *effect); bool showfps_effect_create(struct effect_manager *manager); bool capture_manager_create(struct server *server); bool ky_capture_manager_create(struct server *server); bool wlr_screencopy_manager_create(struct server *server); bool move_effect_create(struct effect_manager *effect_manager); bool blur_effect_create(struct effect_manager *effect_manager); #if HAVE_UKUI_SCREENSHOT bool screenshot_effect_create(struct effect_manager *effect_manager); #else static __attribute__((unused)) inline bool screenshot_effect_create(struct effect_manager *effect_manager) { return false; } #endif #if HAVE_UKUI_WATERMARK bool watermark_effect_create(struct effect_manager *manager); #else static __attribute__((unused)) inline bool watermark_effect_create(struct effect_manager *effect_manager) { return false; } #endif bool scale_effect_create(struct effect_manager *manager); bool mouse_click_effect_create(struct effect_manager *manager); bool mouse_trail_effect_create(struct effect_manager *manager); bool touch_click_effect_create(struct effect_manager *manager); bool touch_long_effect_create(struct effect_manager *manager); bool touch_trail_effect_create(struct effect_manager *manager); bool fade_effect_create(struct effect_manager *manager); #if HAVE_KDE_SLIDE bool slide_effect_create(struct effect_manager *manager); #else static __attribute__((unused)) inline bool slide_effect_create(struct effect_manager *effect_manager) { return false; } #endif bool translation_effect_create(struct effect_manager *manager); bool output_transform_effect_create(struct effect_manager *manager); bool shake_cursor_effect_create(struct effect_manager *manager); bool shake_view_effect_create(struct effect_manager *effect_manager); bool locate_pointer_effect_create(struct effect_manager *manager); bool magic_lamp_effect_create(struct effect_manager *manager); bool zoom_effect_create(struct effect_manager *effect_manager); bool node_transform_effect_create(struct effect_manager *manager); bool showkey_effect_create(struct effect_manager *manager); bool wallpaper_effect_create(struct effect_manager *manager); bool color_filter_effect_create(struct effect_manager *manager); #if HAVE_UKUI_EFFECT bool ukui_effect_create(struct server *server); #else static __attribute__((unused)) inline bool ukui_effect_create(struct server *server) { return false; } #endif bool snapshot_manager_create(struct server *serser); #endif /* _EFFECT_P_H_ */ kylin-wayland-compositor/src/effect/tap_ripple.c0000664000175000017500000002534015160461067021017 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include "effect/shape.h" #include "effect_p.h" #include "render/opengl.h" #include "scene/scene.h" #include "util/matrix.h" #include "util/time.h" #include "tap_ripple_frag.h" #include "tap_ripple_vert.h" struct tap_ripple_point { struct wl_list link; int32_t id, x, y; uint32_t start_time; float radius; float attenuation; }; struct tap_ripple_effect { struct effect *base; struct effect_manager *manager; struct wl_listener enable; struct wl_listener disable; struct wl_listener destroy; const struct tap_ripple_effect_interface *impl; struct tap_ripple_effect_options options; void *user_data; struct wl_list points; }; struct tap_ripple_gl_shader { int32_t program; // vs GLint in_uv; GLint uv2ndc; // fs GLint anti_aliasing; GLint radius; GLint attenuation; GLint color; }; static struct tap_ripple_gl_shader gl_shader = { 0 }; static bool create_opengl_shader(struct ky_opengl_renderer *renderer) { if (gl_shader.program == 0) { GLuint prog = ky_opengl_create_program(renderer, tap_ripple_vert, tap_ripple_frag); if (prog == 0) { return false; } gl_shader.program = prog; gl_shader.in_uv = glGetAttribLocation(prog, "inUV"); gl_shader.uv2ndc = glGetUniformLocation(prog, "uv2ndc"); gl_shader.anti_aliasing = glGetUniformLocation(prog, "antiAliasing"); gl_shader.radius = glGetUniformLocation(prog, "radius"); gl_shader.attenuation = glGetUniformLocation(prog, "attenuation"); gl_shader.color = glGetUniformLocation(prog, "color"); } return true; } static void opengl_render_point(struct ky_scene_render_target *target, struct tap_ripple_point *point, struct tap_ripple_effect_options *options) { static GLfloat verts[8] = { 0.0f, 0.0f, // v0 1.0f, 0.0f, // v1 1.0f, 1.0f, // v2 0.0f, 1.0f, // v3 }; struct wlr_box box = { .x = point->x - target->logical.x, .y = point->y - target->logical.y, .width = options->size, .height = options->size, }; ky_scene_render_box(&box, target); int height = (target->transform & WL_OUTPUT_TRANSFORM_90) ? box.width : box.height; float one_pixel_distance = 1.0f / height; struct ky_mat3 projection; ky_mat3_framebuffer_to_ndc(&projection, target->buffer->width, target->buffer->height); struct ky_mat3 uv2pos; ky_mat3_init_scale_translate(&uv2pos, box.width, box.height, box.x, box.y); struct ky_mat3 uv2ndc; ky_mat3_multiply(&projection, &uv2pos, &uv2ndc); glEnable(GL_BLEND); glUseProgram(gl_shader.program); glEnableVertexAttribArray(gl_shader.in_uv); glVertexAttribPointer(gl_shader.in_uv, 2, GL_FLOAT, GL_FALSE, 0, verts); glUniformMatrix3fv(gl_shader.uv2ndc, 1, GL_FALSE, uv2ndc.matrix); // square not need aspect ratio glUniform1f(gl_shader.anti_aliasing, one_pixel_distance); glUniform1f(gl_shader.radius, point->radius); glUniform1f(gl_shader.attenuation, point->attenuation); glUniform3fv(gl_shader.color, 1, options->color); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glUseProgram(0); glDisableVertexAttribArray(gl_shader.in_uv); } static void free_points(struct tap_ripple_effect *effect) { struct tap_ripple_point *point, *tmp; wl_list_for_each_safe(point, tmp, &effect->points, link) { wl_list_remove(&point->link); free(point); } } static void add_damage(struct tap_ripple_effect *effect) { struct ky_scene *scene = effect->manager->server->scene; struct tap_ripple_point *point; wl_list_for_each(point, &effect->points, link) { pixman_region32_t region; pixman_region32_init_rect(®ion, point->x, point->y, effect->options.size, effect->options.size); ky_scene_add_damage(scene, ®ion); pixman_region32_fini(®ion); } } static bool frame_render_begin(struct effect_entity *entity, struct ky_scene_render_target *target) { struct tap_ripple_effect *effect = entity->user_data; // timer uint32_t duration = effect_manager_scale_time(effect->options.animate_duration); struct tap_ripple_point *point, *tmp; wl_list_for_each_safe(point, tmp, &effect->points, link) { uint32_t diff_time = current_time_msec() - point->start_time; if (diff_time > duration) { wl_list_remove(&point->link); free(point); } else { float t = diff_time / (float)duration; // easing function float factor = 1.f - powf(1.f - t, 4.f); // lerp point->radius = effect->options.start_radius + factor * (effect->options.end_radius - effect->options.start_radius); point->attenuation = effect->options.start_attenuation + factor * (effect->options.end_attenuation - effect->options.start_attenuation); } } return true; } static bool frame_render_post(struct effect_entity *entity, struct ky_scene_render_target *target) { struct tap_ripple_effect *effect = entity->user_data; // add damage to trigger render event add_damage(effect); return true; } static bool frame_render_end(struct effect_entity *entity, struct ky_scene_render_target *target) { struct tap_ripple_effect *effect = entity->user_data; struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(effect->manager->server->renderer); if (!create_opengl_shader(renderer)) { return true; } struct tap_ripple_point *point; wl_list_for_each(point, &effect->points, link) { opengl_render_point(target, point, &effect->options); /* add point bounding box to target expand damage, it should be add to frame damage. */ pixman_region32_union_rect(&target->expand_damage, &target->expand_damage, point->x, point->y, effect->options.size, effect->options.size); } return true; } static bool handle_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } return false; } static bool handle_allow_direct_scanout(struct effect *effect, struct ky_scene_render_target *target) { struct tap_ripple_effect *tap_ripple = effect->user_data; struct tap_ripple_point *point; wl_list_for_each(point, &tap_ripple->points, link) { struct wlr_box damage = { point->x, point->y, tap_ripple->options.size, tap_ripple->options.size }; if (wlr_box_intersection(&damage, &target->logical, &damage)) { return false; } } return true; } static void handle_effect_enable(struct wl_listener *listener, void *data) { struct tap_ripple_effect *effect = wl_container_of(listener, effect, enable); if (effect->impl->enable) { effect->impl->enable(effect, effect->user_data); } } static void handle_effect_disable(struct wl_listener *listener, void *data) { struct tap_ripple_effect *effect = wl_container_of(listener, effect, disable); if (effect->impl->disable) { effect->impl->disable(effect, effect->user_data); } free_points(effect); } static void handle_effect_destroy(struct wl_listener *listener, void *data) { struct tap_ripple_effect *effect = wl_container_of(listener, effect, destroy); if (effect->impl->destroy) { effect->impl->destroy(effect, effect->user_data); } wl_list_remove(&effect->destroy.link); wl_list_remove(&effect->enable.link); wl_list_remove(&effect->disable.link); free(effect); } static const struct effect_interface effect_impl = { .frame_render_begin = frame_render_begin, .frame_render_end = frame_render_end, .frame_render_post = frame_render_post, .configure = handle_effect_configure, .allow_direct_scanout = handle_allow_direct_scanout, }; struct tap_ripple_effect *tap_ripple_effect_create(struct effect_manager *manager, struct tap_ripple_effect_options *options, const struct tap_ripple_effect_interface *impl, const char *name, int priority, bool enabled, void *user_data) { if (!wlr_renderer_is_opengl(manager->server->renderer)) { return NULL; } struct tap_ripple_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return NULL; } effect->base = effect_create(name, priority, enabled, &effect_impl, effect); if (!effect->base) { free(effect); return NULL; } effect->base->category = EFFECT_CATEGORY_SCENE; struct effect_entity *entity = ky_scene_add_effect(manager->server->scene, effect->base); if (!entity) { effect_destroy(effect->base); free(effect); return NULL; } entity->user_data = effect; effect->manager = manager; effect->enable.notify = handle_effect_enable; wl_signal_add(&effect->base->events.enable, &effect->enable); effect->disable.notify = handle_effect_disable; wl_signal_add(&effect->base->events.disable, &effect->disable); effect->destroy.notify = handle_effect_destroy; wl_signal_add(&effect->base->events.destroy, &effect->destroy); effect->impl = impl; effect->options = *options; effect->user_data = user_data; wl_list_init(&effect->points); if (effect->base->enabled) { handle_effect_enable(&effect->enable, NULL); } return effect; } void tap_ripple_effect_add_point(struct tap_ripple_effect *effect, int32_t id, int32_t x, int32_t y) { // record multi-point bool find_id = false; struct tap_ripple_point *point; wl_list_for_each(point, &effect->points, link) { if (point->id == id) { find_id = true; break; } } if (!find_id) { struct tap_ripple_point *point = calloc(1, sizeof(*point)); wl_list_init(&point->link); point->id = id; point->x = x - effect->options.size * 0.5f; point->y = y - effect->options.size * 0.5f; point->start_time = current_time_msec(); wl_list_insert(&effect->points, &point->link); } add_damage(effect); } void tap_ripple_effect_remove_all_points(struct tap_ripple_effect *effect) { free_points(effect); } kylin-wayland-compositor/src/effect/ky_capture.c0000664000175000017500000004706415160461067021035 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include "kywc-capture-v1-protocol.h" #include "effect/capture.h" #include "effect_p.h" #include "input/cursor.h" #include "scene/thumbnail.h" #include "view/workspace.h" enum ky_capture_frame_type { KY_CAPTURE_FRAME_TYPE_OUTPUT = 0, KY_CAPTURE_FRAME_TYPE_WORKSPACE, KY_CAPTURE_FRAME_TYPE_TOPLEVEL, }; struct ky_capture_manager { struct wl_global *global; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct ky_capture_frame { struct wl_resource *resource; struct ky_capture_manager *manager; enum ky_capture_frame_type type; union { struct { struct kywc_output *output; } output; struct { struct workspace *workspace; struct kywc_output *output; } workspace; struct { struct kywc_view *view; } toplevel; }; /* buffer from thumbnail or capture */ struct wlr_buffer *buffer; union { struct capture *capture; struct thumbnail *thumbnail; void *data; }; struct wl_listener buffer_update; struct wl_listener buffer_destroy; struct { struct wl_signal destroy; } events; }; struct ky_capture_cursor { struct wl_resource *resource; struct ky_capture_manager *manager; struct ky_capture_frame *frame; struct wl_listener frame_destroy; struct seat *seat; struct wl_listener cursor_hotspot; struct wl_listener cursor_motion; struct wl_listener capability; struct wl_listener seat_destroy; }; static void ky_capture_frame_destroy(struct ky_capture_frame *frame) { wl_resource_set_user_data(frame->resource, NULL); wl_signal_emit_mutable(&frame->events.destroy, NULL); assert(wl_list_empty(&frame->events.destroy.listener_list)); wl_list_remove(&frame->buffer_update.link); wl_list_remove(&frame->buffer_destroy.link); if (frame->buffer) { wlr_buffer_unlock(frame->buffer); } if (frame->data) { if (frame->type == KY_CAPTURE_FRAME_TYPE_OUTPUT) { capture_destroy(frame->capture); } else { thumbnail_destroy(frame->thumbnail); } } free(frame); } static void frame_handle_release_buffer(struct wl_client *client, struct wl_resource *resource, uint32_t want_buffer) { struct ky_capture_frame *frame = wl_resource_get_user_data(resource); if (!frame) { return; } wlr_buffer_unlock(frame->buffer); frame->buffer = NULL; if (want_buffer) { if (frame->type == KY_CAPTURE_FRAME_TYPE_OUTPUT) { capture_mark_wants_update(frame->capture, true, false); } else { thumbnail_mark_wants_update(frame->thumbnail, true); } } } static void frame_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct kywc_capture_frame_v1_interface frame_impl = { .destroy = frame_handle_destroy, .release_buffer = frame_handle_release_buffer, }; static void frame_handle_resource_destroy(struct wl_resource *resource) { struct ky_capture_frame *frame = wl_resource_get_user_data(resource); if (frame) { ky_capture_frame_destroy(frame); } } static void frame_handle_buffer_destroy(struct wl_listener *listener, void *data) { struct ky_capture_frame *frame = wl_container_of(listener, frame, buffer_destroy); kywc_capture_frame_v1_send_cancelled(frame->resource); frame->data = NULL; ky_capture_frame_destroy(frame); } static void frame_handle_buffer_update(struct wl_listener *listener, void *data) { struct ky_capture_frame *frame = wl_container_of(listener, frame, buffer_update); struct wlr_buffer *buffer = NULL; uint32_t flags = 0; if (frame->type == KY_CAPTURE_FRAME_TYPE_OUTPUT) { struct capture_update_event *event = data; buffer = event->buffer; flags = event->buffer_changed ? 0 : KYWC_CAPTURE_FRAME_V1_FLAGS_REUSED; } else { struct thumbnail_update_event *event = data; buffer = event->buffer; flags = event->buffer_changed ? 0 : KYWC_CAPTURE_FRAME_V1_FLAGS_REUSED; } struct wlr_dmabuf_attributes dmabuf; struct wlr_shm_attributes shm; uint32_t format, n_planes = 1; uint64_t modifier = 0; struct { int fd; uint32_t offset, stride; } planes[WLR_DMABUF_MAX_PLANES] = { 0 }; if (wlr_buffer_get_dmabuf(buffer, &dmabuf)) { flags |= KYWC_CAPTURE_FRAME_V1_FLAGS_DMABUF; format = dmabuf.format; modifier = dmabuf.modifier; n_planes = dmabuf.n_planes; for (uint32_t i = 0; i < n_planes; i++) { planes[i].fd = dmabuf.fd[i]; planes[i].offset = dmabuf.offset[i]; planes[i].stride = dmabuf.stride[i]; } } else if (wlr_buffer_get_shm(buffer, &shm)) { format = shm.format; planes[0].fd = shm.fd; planes[0].offset = shm.offset; planes[0].stride = shm.stride; } else { return; } wlr_buffer_unlock(frame->buffer); frame->buffer = wlr_buffer_lock(buffer); uint32_t mod_high = modifier >> 32; uint32_t mod_low = modifier & 0xFFFFFFFF; kywc_capture_frame_v1_send_buffer(frame->resource, planes[0].fd, format, buffer->width, buffer->height, planes[0].offset, planes[0].stride, mod_high, mod_low, flags); uint32_t version = wl_resource_get_version(frame->resource); if (version >= KYWC_CAPTURE_FRAME_V1_BUFFER_DONE_SINCE_VERSION) { for (uint32_t i = 1; i < n_planes; i++) { kywc_capture_frame_v1_send_buffer_with_plane(frame->resource, i, planes[i].fd, planes[i].offset, planes[i].stride); } kywc_capture_frame_v1_send_buffer_done(frame->resource); } /* enable update if client wants buffer again in release_buffer */ if (frame->type == KY_CAPTURE_FRAME_TYPE_OUTPUT) { capture_mark_wants_update(frame->capture, false, false); } else { thumbnail_mark_wants_update(frame->thumbnail, false); } } static void manager_handle_capture_output(struct wl_client *client, struct wl_resource *resource, uint32_t id, int32_t overlay_cursor, const char *output) { struct ky_capture_frame *frame = calloc(1, sizeof(*frame)); if (!frame) { wl_client_post_no_memory(client); return; } /* create frame resource with id */ uint32_t version = wl_resource_get_version(resource); frame->resource = wl_resource_create(client, &kywc_capture_frame_v1_interface, version, id); if (!frame->resource) { wl_client_post_no_memory(client); free(frame); return; } wl_resource_set_implementation(frame->resource, &frame_impl, frame, frame_handle_resource_destroy); /* check output is valid */ struct kywc_output *kywc_output = kywc_output_by_uuid(output); if (!kywc_output || !kywc_output->state.enabled) { kywc_capture_frame_v1_send_failed(frame->resource); wl_resource_set_user_data(frame->resource, NULL); free(frame); return; } uint32_t options = overlay_cursor ? CAPTURE_NEED_CURSOR : CAPTURE_NEED_NONE; if (version < KYWC_CAPTURE_FRAME_V1_BUFFER_WITH_PLANE_SINCE_VERSION) { options |= CAPTURE_NEED_SINGLE_PLANE; } frame->capture = capture_create_from_output(output_from_kywc_output(kywc_output), options); if (!frame->capture) { kywc_capture_frame_v1_send_failed(frame->resource); wl_resource_set_user_data(frame->resource, NULL); free(frame); return; } struct ky_capture_manager *manager = wl_resource_get_user_data(resource); frame->manager = manager; frame->type = KY_CAPTURE_FRAME_TYPE_OUTPUT; frame->output.output = kywc_output; wl_signal_init(&frame->events.destroy); frame->buffer_update.notify = frame_handle_buffer_update; capture_add_update_listener(frame->capture, &frame->buffer_update); frame->buffer_destroy.notify = frame_handle_buffer_destroy; capture_add_destroy_listener(frame->capture, &frame->buffer_destroy); } static void manager_handle_capture_workspace(struct wl_client *client, struct wl_resource *resource, uint32_t id, const char *workspace, const char *output) { struct ky_capture_frame *frame = calloc(1, sizeof(*frame)); if (!frame) { wl_client_post_no_memory(client); return; } /* create frame resource with id */ uint32_t version = wl_resource_get_version(resource); frame->resource = wl_resource_create(client, &kywc_capture_frame_v1_interface, version, id); if (!frame->resource) { wl_client_post_no_memory(client); free(frame); return; } wl_resource_set_implementation(frame->resource, &frame_impl, frame, frame_handle_resource_destroy); /* check workspace and output */ struct workspace *ws = workspace_by_uuid(workspace); struct kywc_output *kywc_output = kywc_output_by_uuid(output); if (!ws || !kywc_output || !kywc_output->state.enabled) { kywc_capture_frame_v1_send_failed(frame->resource); wl_resource_set_user_data(frame->resource, NULL); free(frame); return; } frame->thumbnail = thumbnail_create_from_workspace( ws, kywc_output, 1.0, version < KYWC_CAPTURE_FRAME_V1_BUFFER_WITH_PLANE_SINCE_VERSION); if (!frame->thumbnail) { kywc_capture_frame_v1_send_failed(frame->resource); wl_resource_set_user_data(frame->resource, NULL); free(frame); return; } struct ky_capture_manager *manager = wl_resource_get_user_data(resource); frame->manager = manager; frame->type = KY_CAPTURE_FRAME_TYPE_WORKSPACE; frame->workspace.workspace = ws; frame->workspace.output = kywc_output; wl_signal_init(&frame->events.destroy); frame->buffer_update.notify = frame_handle_buffer_update; thumbnail_add_update_listener(frame->thumbnail, &frame->buffer_update); frame->buffer_destroy.notify = frame_handle_buffer_destroy; thumbnail_add_destroy_listener(frame->thumbnail, &frame->buffer_destroy); } static void manager_handle_capture_toplevel(struct wl_client *client, struct wl_resource *resource, uint32_t id, const char *toplevel, uint32_t without_decoration) { struct ky_capture_frame *frame = calloc(1, sizeof(*frame)); if (!frame) { wl_client_post_no_memory(client); return; } /* create frame resource with id */ uint32_t version = wl_resource_get_version(resource); frame->resource = wl_resource_create(client, &kywc_capture_frame_v1_interface, version, id); if (!frame->resource) { wl_client_post_no_memory(client); free(frame); return; } wl_resource_set_implementation(frame->resource, &frame_impl, frame, frame_handle_resource_destroy); /* check toplevel */ struct kywc_view *kywc_view = kywc_view_by_uuid(toplevel); if (!kywc_view || !kywc_view->mapped) { kywc_capture_frame_v1_send_failed(frame->resource); wl_resource_set_user_data(frame->resource, NULL); free(frame); return; } struct view *view = view_from_kywc_view(kywc_view); uint32_t options = THUMBNAIL_DISABLE_ROUND_CORNER | THUMBNAIL_ENABLE_SECURITY; options |= without_decoration ? THUMBNAIL_DISABLE_DECOR : THUMBNAIL_DISABLE_SHADOW; if (version < KYWC_CAPTURE_FRAME_V1_BUFFER_WITH_PLANE_SINCE_VERSION) { options |= THUMBNAIL_ENABLE_SINGLE_PLANE; } frame->thumbnail = thumbnail_create_from_view(view, options, 1.0); if (!frame->thumbnail) { kywc_capture_frame_v1_send_failed(frame->resource); wl_resource_set_user_data(frame->resource, NULL); free(frame); return; } struct ky_capture_manager *manager = wl_resource_get_user_data(resource); frame->manager = manager; frame->type = KY_CAPTURE_FRAME_TYPE_TOPLEVEL; frame->toplevel.view = kywc_view; wl_signal_init(&frame->events.destroy); frame->buffer_update.notify = frame_handle_buffer_update; thumbnail_add_update_listener(frame->thumbnail, &frame->buffer_update); frame->buffer_destroy.notify = frame_handle_buffer_destroy; thumbnail_add_destroy_listener(frame->thumbnail, &frame->buffer_destroy); } static void cursor_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct kywc_capture_cursor_v1_interface cursor_impl = { .destroy = cursor_handle_destroy, }; static void ky_capture_cursor_destroy(struct ky_capture_cursor *cursor) { wl_resource_set_user_data(cursor->resource, NULL); wl_list_remove(&cursor->seat_destroy.link); wl_list_remove(&cursor->frame_destroy.link); wl_list_remove(&cursor->cursor_hotspot.link); wl_list_remove(&cursor->cursor_motion.link); wl_list_remove(&cursor->capability.link); free(cursor); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { struct ky_capture_cursor *cursor = wl_container_of(listener, cursor, seat_destroy); ky_capture_cursor_destroy(cursor); } static void handle_frame_destroy(struct wl_listener *listener, void *data) { struct ky_capture_cursor *cursor = wl_container_of(listener, cursor, seat_destroy); ky_capture_cursor_destroy(cursor); } static void handle_cursor_hotspot(struct wl_listener *listener, void *data) { struct ky_capture_cursor *cursor = wl_container_of(listener, cursor, cursor_hotspot); struct wlr_seat_pointer_request_set_cursor_event *event = data; kywc_capture_cursor_v1_send_hotspot(cursor->resource, event->hotspot_x, event->hotspot_y); } static void handle_cursor_motion(struct wl_listener *listener, void *data) { struct ky_capture_cursor *cursor = wl_container_of(listener, cursor, cursor_motion); struct seat_cursor_motion_event *event = data; if (!cursor->frame) { kywc_capture_cursor_v1_send_position(cursor->resource, event->lx, event->ly); } else { /* add actions about * kywc_capture_cursor_v1_send_enter/kywc_capture_cursor_v1_send_leave and so on */ } } static void handle_capability(struct wl_listener *listener, void *data) { struct ky_capture_cursor *cursor = wl_container_of(listener, cursor, capability); if (!(cursor->seat->caps & WL_SEAT_CAPABILITY_POINTER)) { kywc_capture_cursor_v1_send_leave(cursor->resource); } } static void capture_handle_resource_destroy(struct wl_resource *resource) { struct ky_capture_cursor *cursor = wl_resource_get_user_data(resource); if (cursor) { ky_capture_cursor_destroy(cursor); } } static void manager_handle_capture_cursor(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *wl_seat, struct wl_resource *frame) { struct ky_capture_cursor *cursor = calloc(1, sizeof(*cursor)); if (!cursor) { wl_client_post_no_memory(client); return; } /* create frame resource with id */ uint32_t version = wl_resource_get_version(resource); cursor->resource = wl_resource_create(client, &kywc_capture_cursor_v1_interface, version, id); if (!cursor->resource) { wl_client_post_no_memory(client); free(cursor); return; } wl_resource_set_implementation(cursor->resource, &cursor_impl, cursor, capture_handle_resource_destroy); struct seat *seat = seat_from_resource(wl_seat); if (!seat || !(seat->caps & WL_SEAT_CAPABILITY_POINTER)) { kywc_capture_cursor_v1_send_leave(cursor->resource); wl_resource_set_user_data(cursor->resource, NULL); free(cursor); return; } struct ky_capture_manager *manager = wl_resource_get_user_data(resource); cursor->manager = manager; cursor->seat = seat; cursor->seat_destroy.notify = handle_seat_destroy; wl_signal_add(&cursor->seat->events.destroy, &cursor->seat_destroy); cursor->frame_destroy.notify = handle_frame_destroy; wl_list_init(&cursor->frame_destroy.link); if (frame) { cursor->frame = wl_resource_get_user_data(frame); wl_signal_add(&cursor->frame->events.destroy, &cursor->frame_destroy); } else { kywc_capture_cursor_v1_send_enter(cursor->resource); kywc_capture_cursor_v1_send_position(cursor->resource, seat->cursor->lx, seat->cursor->ly); // kywc_capture_cursor_v1_send_hotspot(struct wl_resource *resource_, int32_t x, int32_t y) } cursor->cursor_hotspot.notify = handle_cursor_hotspot; wl_signal_add(&seat->wlr_seat->events.request_set_cursor, &cursor->cursor_hotspot); cursor->cursor_motion.notify = handle_cursor_motion; wl_signal_add(&seat->events.cursor_motion, &cursor->cursor_motion); cursor->capability.notify = handle_capability; wl_signal_add(&seat->events.capability, &cursor->capability); } static void manager_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct kywc_capture_manager_v1_interface ky_capture_manager_impl = { .capture_output = manager_handle_capture_output, .capture_workspace = manager_handle_capture_workspace, .capture_toplevel = manager_handle_capture_toplevel, .capture_cursor = manager_handle_capture_cursor, .destroy = manager_handle_destroy, }; static void ky_capture_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct ky_capture_manager *manager = data; struct wl_resource *resource = wl_resource_create(client, &kywc_capture_manager_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &ky_capture_manager_impl, manager, NULL); } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct ky_capture_manager *manager = wl_container_of(listener, manager, server_destroy); wl_list_remove(&manager->server_destroy.link); free(manager); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct ky_capture_manager *manager = wl_container_of(listener, manager, display_destroy); wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); } bool ky_capture_manager_create(struct server *server) { struct ky_capture_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->global = wl_global_create(server->display, &kywc_capture_manager_v1_interface, 2, manager, ky_capture_manager_bind); if (!manager->global) { kywc_log(KYWC_WARN, "Kywc capture manager create failed"); free(manager); return false; } manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &manager->display_destroy); return true; } kylin-wayland-compositor/src/effect/showkey.c0000664000175000017500000007116415160461067020356 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include "effect_p.h" #include "painter.h" #include "scene/scene.h" #include "theme.h" #include "util/dbus.h" #include "util/macros.h" #include "view/view.h" #include "widget/scaled_buffer.h" #define KEYBOARD_WIDTH (1500) #define KEYBOARD_HEIGHT (500) #define KEY_GAP (20) #define BUTTON_ALPHA (0.85) #define ROW_DISPLAY_TIME (150) #define NO_KEY_WAITING_TIME (1500) #define MIN_BACKGROUND_WIDTH (50) #define MIN_BACKGROUND_HEIGHT (40) #define DEFAULT_FONT_SIZE (18) #define DEFAULT_FONT_FACE "Sans" static float background_color[4] = { 0, 0, 0, 0 }; struct key_unit { const struct keycode_map *key; uint64_t last_press_time; int row_index; int pos_x, pos_y; }; struct showkey_seat { struct wl_list link; struct showkey *showkey; struct seat *seat; struct wl_listener seat_destroy; struct wl_listener keyboard_key; }; struct showkey { struct showkey_effect *effect; struct ky_scene_tree *tree; struct ky_scene_rect *rect; struct kywc_box rect_box; struct key_unit key_unit; struct wl_list seats; struct wl_listener new_seat; struct wl_event_source *timer; }; struct showkey_effect { struct effect *effect; struct wl_listener enable; struct wl_listener disable; struct wl_listener destroy; struct showkey *showkey; struct wl_list scaled_buffers; struct wl_listener theme_update; struct server *server; struct dbus_object *dbus; struct wl_listener display_destroy; }; static struct keycode_map { const char *key; uint32_t code; int index; int width; int height; } keycode_maps[] = { { .key = "Esc", .code = KEY_ESC, .width = 50, .height = 40 }, { .key = "1", .code = KEY_1, .width = 50, .height = 40 }, { .key = "2", .code = KEY_2, .width = 50, .height = 40 }, { .key = "3", .code = KEY_3, .width = 50, .height = 40 }, { .key = "4", .code = KEY_4, .width = 50, .height = 40 }, { .key = "5", .code = KEY_5, .width = 50, .height = 40 }, { .key = "6", .code = KEY_6, .width = 50, .height = 40 }, { .key = "7", .code = KEY_7, .width = 50, .height = 40 }, { .key = "8", .code = KEY_8, .width = 50, .height = 40 }, { .key = "9", .code = KEY_9, .width = 50, .height = 40 }, { .key = "0", .code = KEY_0, .width = 50, .height = 40 }, { .key = "-", .code = KEY_MINUS, .width = 50, .height = 40 }, { .key = "=", .code = KEY_EQUAL, .width = 50, .height = 40 }, { .key = "Back", .code = KEY_BACKSPACE, .width = 50, .height = 40 }, { .key = "Tab", .code = KEY_TAB, .width = 50, .height = 40 }, { .key = "Q", .code = KEY_Q, .width = 50, .height = 40 }, { .key = "W", .code = KEY_W, .width = 50, .height = 40 }, { .key = "E", .code = KEY_E, .width = 50, .height = 40 }, { .key = "R", .code = KEY_R, .width = 50, .height = 40 }, { .key = "T", .code = KEY_T, .width = 50, .height = 40 }, { .key = "Y", .code = KEY_Y, .width = 50, .height = 40 }, { .key = "U", .code = KEY_U, .width = 50, .height = 40 }, { .key = "I", .code = KEY_I, .width = 50, .height = 40 }, { .key = "O", .code = KEY_O, .width = 50, .height = 40 }, { .key = "P", .code = KEY_P, .width = 50, .height = 40 }, { .key = "{", .code = KEY_LEFTBRACE, .width = 50, .height = 40 }, { .key = "}", .code = KEY_RIGHTBRACE, .width = 50, .height = 40 }, { .key = "Enter", .code = KEY_ENTER, .width = 50, .height = 40 }, { .key = "Ctrl", .code = KEY_LEFTCTRL, .width = 50, .height = 40 }, { .key = "A", .code = KEY_A, .width = 50, .height = 40 }, { .key = "S", .code = KEY_S, .width = 50, .height = 40 }, { .key = "D", .code = KEY_D, .width = 50, .height = 40 }, { .key = "F", .code = KEY_F, .width = 50, .height = 40 }, { .key = "G", .code = KEY_G, .width = 50, .height = 40 }, { .key = "H", .code = KEY_H, .width = 50, .height = 40 }, { .key = "J", .code = KEY_J, .width = 50, .height = 40 }, { .key = "K", .code = KEY_K, .width = 50, .height = 40 }, { .key = "L", .code = KEY_L, .width = 50, .height = 40 }, { .key = ";", .code = KEY_SEMICOLON, .width = 50, .height = 40 }, { .key = "'", .code = KEY_APOSTROPHE, .width = 50, .height = 40 }, { .key = "`", .code = KEY_GRAVE, .width = 50, .height = 40 }, { .key = "Shift", .code = KEY_LEFTSHIFT, .width = 50, .height = 40 }, { .key = "\\", .code = KEY_BACKSLASH, .width = 50, .height = 40 }, { .key = "Z", .code = KEY_Z, .width = 50, .height = 40 }, { .key = "X", .code = KEY_X, .width = 50, .height = 40 }, { .key = "C", .code = KEY_C, .width = 50, .height = 40 }, { .key = "V", .code = KEY_V, .width = 50, .height = 40 }, { .key = "B", .code = KEY_B, .width = 50, .height = 40 }, { .key = "N", .code = KEY_N, .width = 50, .height = 40 }, { .key = "M", .code = KEY_M, .width = 50, .height = 40 }, { .key = ",", .code = KEY_COMMA, .width = 50, .height = 40 }, { .key = ".", .code = KEY_DOT, .width = 50, .height = 40 }, { .key = "/", .code = KEY_SLASH, .width = 50, .height = 40 }, { .key = "Shift", .code = KEY_RIGHTSHIFT, .width = 50, .height = 40 }, { .key = "*", .code = KEY_KPASTERISK, .width = 50, .height = 40 }, { .key = "Alt", .code = KEY_LEFTALT, .width = 50, .height = 40 }, { .key = "Space", .code = KEY_SPACE, .width = 50, .height = 40 }, { .key = "Caps", .code = KEY_CAPSLOCK, .width = 50, .height = 40 }, { .key = "F1", .code = KEY_F1, .width = 50, .height = 40 }, { .key = "F2", .code = KEY_F2, .width = 50, .height = 40 }, { .key = "F3", .code = KEY_F3, .width = 50, .height = 40 }, { .key = "F4", .code = KEY_F4, .width = 50, .height = 40 }, { .key = "F5", .code = KEY_F5, .width = 50, .height = 40 }, { .key = "F6", .code = KEY_F6, .width = 50, .height = 40 }, { .key = "F7", .code = KEY_F7, .width = 50, .height = 40 }, { .key = "F8", .code = KEY_F8, .width = 50, .height = 40 }, { .key = "F9", .code = KEY_F9, .width = 50, .height = 40 }, { .key = "F10", .code = KEY_F10, .width = 50, .height = 40 }, { .key = "Num", .code = KEY_NUMLOCK, .width = 50, .height = 40 }, { .key = "SL", .code = KEY_SCROLLLOCK, .width = 50, .height = 40 }, { .key = "7", .code = KEY_KP7, .width = 50, .height = 40 }, { .key = "8", .code = KEY_KP8, .width = 50, .height = 40 }, { .key = "9", .code = KEY_KP9, .width = 50, .height = 40 }, { .key = "-", .code = KEY_KPMINUS, .width = 50, .height = 40 }, { .key = "4", .code = KEY_KP4, .width = 50, .height = 40 }, { .key = "5", .code = KEY_KP5, .width = 50, .height = 40 }, { .key = "6", .code = KEY_KP6, .width = 50, .height = 40 }, { .key = "+", .code = KEY_KPPLUS, .width = 50, .height = 40 }, { .key = "1", .code = KEY_KP1, .width = 50, .height = 40 }, { .key = "2", .code = KEY_KP2, .width = 50, .height = 40 }, { .key = "3", .code = KEY_KP3, .width = 50, .height = 40 }, { .key = "0", .code = KEY_KP0, .width = 50, .height = 40 }, { .key = ".", .code = KEY_KPDOT, .width = 50, .height = 40 }, { .key = "F11", .code = KEY_F11, .width = 50, .height = 40 }, { .key = "F12", .code = KEY_F12, .width = 50, .height = 40 }, { .key = "Enter", .code = KEY_KPENTER, .width = 50, .height = 40 }, { .key = "Ctrl", .code = KEY_RIGHTCTRL, .width = 50, .height = 40 }, { .key = "/", .code = KEY_KPSLASH, .width = 50, .height = 40 }, { .key = "PS", .code = KEY_SYSRQ, .width = 50, .height = 40 }, { .key = "Alt", .code = KEY_RIGHTALT, .width = 50, .height = 40 }, { .key = "HM", .code = KEY_HOME, .width = 50, .height = 40 }, { .key = "↑", .code = KEY_UP, .width = 50, .height = 40 }, { .key = "PU", .code = KEY_PAGEUP, .width = 50, .height = 40 }, { .key = "←", .code = KEY_LEFT, .width = 50, .height = 40 }, { .key = "→", .code = KEY_RIGHT, .width = 50, .height = 40 }, { .key = "End", .code = KEY_END, .width = 50, .height = 40 }, { .key = "↓", .code = KEY_DOWN, .width = 50, .height = 40 }, { .key = "PD", .code = KEY_PAGEDOWN, .width = 50, .height = 40 }, { .key = "Ins", .code = KEY_INSERT, .width = 50, .height = 40 }, { .key = "Del", .code = KEY_DELETE, .width = 50, .height = 40 }, { .key = "PB", .code = KEY_PAUSE, .width = 50, .height = 40 }, { .key = "Win", .code = KEY_LEFTMETA, .width = 50, .height = 40 }, { .key = "Win", .code = KEY_RIGHTMETA, .width = 50, .height = 40 }, { .key = "Menu", .code = KEY_COMPOSE, .width = 50, .height = 40 }, }; struct keyboard_buffer { struct wl_list link; struct wlr_buffer *buffer; struct wlr_fbox src_boxs[ARRAY_SIZE(keycode_maps)]; float scale; }; static int compare_keycode_map(const void *p1, const void *p2) { uint32_t code1 = ((struct keycode_map *)p1)->code; uint32_t code2 = ((struct keycode_map *)p2)->code; return (code1 > code2) ? 1 : ((code1 == code2) ? 0 : -1); } static bool find_keycode_map(uint32_t code, struct keycode_map **result) { struct keycode_map key = { .code = code }; /* binary search for the keycode map */ *result = bsearch(&key, keycode_maps, ARRAY_SIZE(keycode_maps), sizeof(struct keycode_map), compare_keycode_map); return (*result != NULL); } static void showkey_destroy_display_tree(struct showkey *showkey) { if (!showkey->tree) { return; } showkey->key_unit.pos_x = 0; showkey->key_unit.pos_y = 0; showkey->key_unit.last_press_time = 0; showkey->key_unit.row_index = 0; showkey->key_unit.key = NULL; ky_scene_node_destroy(&showkey->tree->node); showkey->tree = NULL; } static void showkey_create_display_tree(struct showkey *showkey) { struct view_layer *layer = view_manager_get_layer(LAYER_ON_SCREEN_DISPLAY, false); showkey->tree = ky_scene_tree_create(layer->tree); ky_scene_node_set_position(&showkey->tree->node, showkey->rect_box.x, showkey->rect_box.y); ky_scene_node_set_input_bypassed(&showkey->tree->node, true); showkey->rect = ky_scene_rect_create(showkey->tree, showkey->rect_box.width, showkey->rect_box.height, background_color); } static bool set_keyboard_timer(struct showkey *showkey, struct seat_keyboard_key_event *event) { if (!event->pressed) { if (wl_event_source_timer_update(showkey->timer, effect_manager_scale_time(NO_KEY_WAITING_TIME)) < 0) { kywc_log(KYWC_DEBUG, "Failed to set key timer"); } return false; } wl_event_source_timer_update(showkey->timer, 0); return true; } static void get_keyboard_buffer_color(double **background, double **text, double **button) { static double button_color[4]; static double text_color[4]; static double light_background[] = { 1.0, 1.0, 1.0, 0 }; static double dark_background[] = { 0.0, 0.0, 0.0, 0 }; struct theme *theme = theme_manager_get_theme(); text_color[0] = theme->active_text_color[0]; text_color[1] = theme->active_text_color[1]; text_color[2] = theme->active_text_color[2]; text_color[3] = theme->active_text_color[3]; *text = text_color; if (theme->type == THEME_TYPE_LIGHT || theme->type == THEME_TYPE_UNDEFINED) { button_color[0] = light_background[0]; button_color[1] = light_background[1]; button_color[2] = light_background[2]; button_color[3] = BUTTON_ALPHA; *background = light_background; } else { button_color[0] = dark_background[0]; button_color[1] = dark_background[1]; button_color[2] = dark_background[2]; button_color[3] = BUTTON_ALPHA; *background = dark_background; } *button = button_color; } static void draw_keyboard_key(cairo_t *cairo_context, int pos_x, int pos_y, int width, int height, const char *label, double *text_color, double *button_color, float scale) { cairo_set_source_rgba(cairo_context, button_color[0], button_color[1], button_color[2], button_color[3]); cairo_rectangle(cairo_context, pos_x, pos_y, width, height); cairo_fill(cairo_context); // draw button labels if (strcmp(label, "") != 0) { cairo_select_font_face(cairo_context, DEFAULT_FONT_FACE, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); double font_size = round(DEFAULT_FONT_SIZE * scale); cairo_set_font_size(cairo_context, font_size); cairo_text_extents_t extents; cairo_text_extents(cairo_context, label, &extents); double text_x = round(pos_x + (width - extents.width) / 2 - extents.x_bearing); double text_y = round(pos_y + (height - extents.height) / 2 - extents.y_bearing); cairo_move_to(cairo_context, text_x, text_y); cairo_set_source_rgba(cairo_context, text_color[0], text_color[1], text_color[2], text_color[3]); cairo_show_text(cairo_context, label); } } static struct keyboard_buffer *draw_keyboard_buffer(struct showkey_effect *effect, float scale) { double *background_color, *text_color, *button_color; get_keyboard_buffer_color(&background_color, &text_color, &button_color); // wlr_buffer struct keyboard_buffer *buffer = calloc(1, sizeof(*buffer)); if (!buffer) { return NULL; } struct wlr_buffer *keyboard_buffer = painter_create_buffer(KEYBOARD_WIDTH, KEYBOARD_HEIGHT, scale); if (!keyboard_buffer) { free(buffer); return NULL; } buffer->scale = scale; buffer->buffer = keyboard_buffer; wl_list_insert(&effect->scaled_buffers, &buffer->link); void *data = NULL; uint32_t drm_format; size_t stride; if (!wlr_buffer_begin_data_ptr_access(keyboard_buffer, WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data, &drm_format, &stride)) { wlr_buffer_drop(keyboard_buffer); free(buffer); return NULL; } memset(data, 0x0, keyboard_buffer->height * stride); int scaled_width = ceil(KEYBOARD_WIDTH * scale); int scaled_height = ceil(KEYBOARD_HEIGHT * scale); // cairo surface cairo_surface_t *surface = cairo_image_surface_create_for_data( data, CAIRO_FORMAT_ARGB32, scaled_width, scaled_height, stride); if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { wlr_buffer_end_data_ptr_access(keyboard_buffer); wlr_buffer_drop(keyboard_buffer); free(buffer); return NULL; } // cairo context cairo_t *cairo_context = cairo_create(surface); if (cairo_status(cairo_context) != CAIRO_STATUS_SUCCESS) { wlr_buffer_end_data_ptr_access(keyboard_buffer); cairo_surface_destroy(surface); wlr_buffer_drop(keyboard_buffer); free(buffer); return NULL; } // set background color cairo_set_source_rgba(cairo_context, background_color[0], background_color[1], background_color[2], background_color[3]); cairo_paint(cairo_context); // button size int key_width = ceil(50 * scale); int key_height = ceil(40 * scale); int padding_x = 10; int padding_y = 10; // draw key int row = 0, col = 0; for (size_t i = 0; i < ARRAY_SIZE(keycode_maps); i++) { struct keycode_map *map = &keycode_maps[i]; int pos_x = ceil(padding_x + col * (key_width + padding_x)); int pos_y = ceil(padding_y + row * (key_height + padding_y)); map->index = i; buffer->src_boxs[i] = (struct wlr_fbox){ pos_x, pos_y, key_width, key_height }; draw_keyboard_key(cairo_context, pos_x, pos_y, key_width, key_height, map->key, text_color, button_color, scale); col++; // line break when exceeding width if (col * (key_width + padding_x) > scaled_width - padding_x) { col = 0; row++; } } cairo_destroy(cairo_context); cairo_surface_destroy(surface); wlr_buffer_end_data_ptr_access(keyboard_buffer); return buffer; } static struct keyboard_buffer *keyboard_buffer_get_or_create(struct showkey_effect *effect, float scale) { /* find scale buffer */ struct keyboard_buffer *buffer; wl_list_for_each(buffer, &effect->scaled_buffers, link) { if (buffer->scale == scale) { return buffer; } } return draw_keyboard_buffer(effect, scale); } static void showkey_update_key_position(struct showkey *showkey, struct seat_keyboard_key_event *event) { uint32_t new_row_time = effect_manager_scale_time(ROW_DISPLAY_TIME); struct key_unit *key_unit = &showkey->key_unit; int key_width = key_unit->key->width; int key_height = key_unit->key->height; bool need_new_row = (key_unit->last_press_time == 0) || (event->time_msec - key_unit->last_press_time > new_row_time) || (key_unit->pos_x + key_width + KEY_GAP > showkey->rect_box.width); if (need_new_row) { key_unit->pos_x = 0; key_unit->row_index++; key_unit->pos_y = showkey->rect_box.height - key_unit->row_index * (key_height + KEY_GAP); if (key_unit->pos_y < 0) { const struct keycode_map *saved_key = showkey->key_unit.key; showkey_destroy_display_tree(showkey); showkey->key_unit.key = saved_key; showkey->key_unit.row_index = 1; showkey->key_unit.pos_y = showkey->rect_box.height - (key_height + KEY_GAP); showkey->key_unit.last_press_time = event->time_msec; return; } } else { key_unit->pos_x += key_width + KEY_GAP; } key_unit->last_press_time = event->time_msec; } static void showkey_destroy_key_buffer(struct ky_scene_buffer *buffer, void *data) { /* buffers are destroyed in tree */ } static void showkey_set_key_buffer(struct showkey *showkey, struct ky_scene_buffer *key_buffer, float scale) { struct showkey_effect *effect = showkey->effect; struct keyboard_buffer *buffer = keyboard_buffer_get_or_create(effect, scale); struct wlr_buffer *wlr_buffer = buffer->buffer; if (key_buffer->buffer != wlr_buffer) { ky_scene_buffer_set_buffer(key_buffer, wlr_buffer); } if (key_buffer->buffer != wlr_buffer) { return; } const struct keycode_map *keycode_map = showkey->key_unit.key; if (!keycode_map) { return; } int index = keycode_map->index; ky_scene_buffer_set_source_box(key_buffer, &buffer->src_boxs[index]); ky_scene_buffer_set_dest_size(key_buffer, keycode_map->width, keycode_map->height); ky_scene_node_set_position(&key_buffer->node, showkey->key_unit.pos_x, showkey->key_unit.pos_y); struct theme *theme = theme_manager_get_theme(); ky_scene_node_set_radius(&key_buffer->node, (int[4]){ theme->menu_radius, theme->menu_radius, theme->menu_radius, theme->menu_radius }); } static void showkey_update_key_buffer(struct ky_scene_buffer *buffer, float scale, void *data) { struct showkey *showkey = data; showkey_set_key_buffer(showkey, buffer, scale); } static void showkey_set_key_display(struct showkey *showkey, struct seat_keyboard_key_event *event, const struct keycode_map *keycode_map) { struct kywc_output *kywc_output = kywc_output_get_primary(); float scale = kywc_output->state.scale; struct ky_scene_buffer *key_buffer = scaled_buffer_create( showkey->tree, scale, showkey_update_key_buffer, showkey_destroy_key_buffer, showkey); showkey_set_key_buffer(showkey, key_buffer, scale); } static void showkey_seat_destroy(struct showkey_seat *showkey_seat) { wl_list_remove(&showkey_seat->seat_destroy.link); wl_list_remove(&showkey_seat->keyboard_key.link); wl_list_remove(&showkey_seat->link); free(showkey_seat); } static void showkey_destroy(struct showkey *showkey) { wl_list_remove(&showkey->new_seat.link); showkey_destroy_display_tree(showkey); struct showkey_seat *showkey_seat, *tmp; wl_list_for_each_safe(showkey_seat, tmp, &showkey->seats, link) { showkey_seat_destroy(showkey_seat); } if (showkey->timer) { wl_event_source_remove(showkey->timer); } free(showkey); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { struct showkey_seat *showkey_seat = wl_container_of(listener, showkey_seat, seat_destroy); showkey_seat_destroy(showkey_seat); } static int handle_showkey_waiting_time(void *data) { struct showkey *showkey = data; showkey_destroy_display_tree(showkey); return true; } static void handle_theme_update(struct wl_listener *listener, void *data) { struct showkey_effect *effect = wl_container_of(listener, effect, theme_update); /* destroy all buffers */ struct keyboard_buffer *buffer, *tmp; wl_list_for_each_safe(buffer, tmp, &effect->scaled_buffers, link) { wlr_buffer_drop(buffer->buffer); wl_list_remove(&buffer->link); free(buffer); } } static void handle_keyboard_key(struct wl_listener *listener, void *data) { struct showkey_seat *showkey_seat = wl_container_of(listener, showkey_seat, keyboard_key); struct seat_keyboard_key_event *event = data; if (event->device && event->device->prop.is_virtual) { return; } struct showkey *showkey = showkey_seat->showkey; if (!set_keyboard_timer(showkey, event)) { return; } struct keycode_map *keycode_map = NULL; if (!find_keycode_map(event->keycode, &keycode_map)) { kywc_log(KYWC_DEBUG, "Not find keycode in keycode_map"); return; } showkey->key_unit.key = keycode_map; showkey_update_key_position(showkey, event); if (!showkey->tree) { showkey_create_display_tree(showkey); } showkey_set_key_display(showkey, event, keycode_map); } static void showkey_seat_create(struct showkey *showkey, struct seat *seat) { struct showkey_seat *showkey_seat = calloc(1, sizeof(*showkey_seat)); if (!showkey_seat) { return; } showkey_seat->seat = seat; showkey_seat->showkey = showkey; wl_list_insert(&showkey->seats, &showkey_seat->link); showkey_seat->seat_destroy.notify = handle_seat_destroy; wl_signal_add(&seat->events.destroy, &showkey_seat->seat_destroy); showkey_seat->keyboard_key.notify = handle_keyboard_key; wl_signal_add(&showkey_seat->seat->events.keyboard_key, &showkey_seat->keyboard_key); } static void handle_new_seat(struct wl_listener *listener, void *data) { struct showkey *showkey = wl_container_of(listener, showkey, new_seat); struct seat *seat = data; showkey_seat_create(showkey, seat); } static bool handle_iterator_seat(struct seat *seat, int index, void *data) { struct showkey *showkey = data; showkey_seat_create(showkey, seat); return false; } static struct showkey *showkey_create(struct showkey_effect *effect) { struct showkey *showkey = calloc(1, sizeof(*showkey)); if (!showkey) { return NULL; } showkey->effect = effect; wl_list_init(&showkey->seats); wl_list_init(&showkey->new_seat.link); input_manager_for_each_seat(handle_iterator_seat, showkey); seat_add_new_listener(&showkey->new_seat); showkey->new_seat.notify = handle_new_seat; struct wl_event_loop *loop = wl_display_get_event_loop(effect->server->display); showkey->timer = wl_event_loop_add_timer(loop, handle_showkey_waiting_time, showkey); return showkey; } static int create_showkey(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct showkey_effect *effect = userdata; uint32_t x, y, width, height; CK(sd_bus_message_read(m, "uuuu", &x, &y, &width, &height)); if (width < MIN_BACKGROUND_WIDTH || height < MIN_BACKGROUND_HEIGHT) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST( "com.kylin.Wlcom.Showkey.Error.Cancelled", "Invalid width or height"); return sd_bus_reply_method_error(m, &error); } if (!effect->showkey) { effect->showkey = showkey_create(effect); if (!effect->showkey) { return sd_bus_reply_method_return(m, "b", false); } } effect->showkey->rect_box = (struct kywc_box){ x, y, width, height }; if (!effect->showkey->tree) { showkey_create_display_tree(effect->showkey); if (wl_event_source_timer_update(effect->showkey->timer, effect_manager_scale_time(NO_KEY_WAITING_TIME)) < 0) { return sd_bus_reply_method_return(m, "b", false); } return sd_bus_reply_method_return(m, "b", true); } ky_scene_node_set_position(&effect->showkey->tree->node, effect->showkey->rect_box.x, effect->showkey->rect_box.y); ky_scene_rect_set_size(effect->showkey->rect, width, height); return sd_bus_reply_method_return(m, "b", true); } static int destroy_showkey(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct showkey_effect *effect = userdata; if (effect->showkey) { showkey_destroy(effect->showkey); effect->showkey = NULL; } return sd_bus_reply_method_return(m, NULL); } static const sd_bus_vtable showkey_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("createShowkey", "uuuu", "b", create_showkey, 0), SD_BUS_METHOD("destroyShowkey", "", "", destroy_showkey, 0), SD_BUS_VTABLE_END, }; static void handle_effect_disable(struct wl_listener *listener, void *data) { struct showkey_effect *effect = wl_container_of(listener, effect, disable); dbus_unregister_object(effect->dbus); wl_list_remove(&effect->theme_update.link); if (effect->showkey) { showkey_destroy(effect->showkey); effect->showkey = NULL; } /* destroy all buffers */ struct keyboard_buffer *buffer, *_tmp; wl_list_for_each_safe(buffer, _tmp, &effect->scaled_buffers, link) { wlr_buffer_drop(buffer->buffer); wl_list_remove(&buffer->link); free(buffer); } } static void handle_effect_enable(struct wl_listener *listener, void *data) { struct showkey_effect *effect = wl_container_of(listener, effect, enable); effect->dbus = dbus_register_object(NULL, "/com/kylin/Wlcom/Showkey", "com.kylin.Wlcom.Showkey", showkey_vtable, effect); effect->theme_update.notify = handle_theme_update; theme_manager_add_update_listener(&effect->theme_update, false); } static void handle_effect_destroy(struct wl_listener *listener, void *data) { struct showkey_effect *effect = wl_container_of(listener, effect, destroy); wl_list_remove(&effect->destroy.link); wl_list_remove(&effect->enable.link); wl_list_remove(&effect->disable.link); free(effect); } static bool handle_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } return false; } static const struct effect_interface showfps_effect_impl = { .configure = handle_effect_configure, }; static void handle_display_destroy(struct wl_listener *listener, void *data) { struct showkey_effect *effect = wl_container_of(listener, effect, display_destroy); wl_list_remove(&effect->display_destroy.link); if (effect->showkey && effect->showkey->timer) { wl_event_source_remove(effect->showkey->timer); effect->showkey->timer = NULL; } } bool showkey_effect_create(struct effect_manager *manager) { struct showkey_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } effect->effect = effect_create("showkey", 106, true, &showfps_effect_impl, effect); if (!effect->effect) { free(effect); return false; } effect->server = manager->server; wl_list_init(&effect->scaled_buffers); effect->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(effect->server->display, &effect->display_destroy); /* sort keycode_maps array in ascending order */ qsort(keycode_maps, ARRAY_SIZE(keycode_maps), sizeof(struct keycode_map), compare_keycode_map); effect->enable.notify = handle_effect_enable; wl_signal_add(&effect->effect->events.enable, &effect->enable); effect->disable.notify = handle_effect_disable; wl_signal_add(&effect->effect->events.disable, &effect->disable); effect->destroy.notify = handle_effect_destroy; wl_signal_add(&effect->effect->events.destroy, &effect->destroy); if (effect->effect->enabled) { handle_effect_enable(&effect->enable, NULL); } return true; } kylin-wayland-compositor/src/effect/touch_click.c0000664000175000017500000001044415160460057021144 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "effect/shape.h" #include "effect_p.h" #include "input/cursor.h" #include "input/seat.h" struct touch_click_effect { struct tap_ripple_effect *base; struct wl_listener new_seat; struct wl_list seat_touchs; }; struct seat_touch { struct wl_list link; struct touch_click_effect *effect; struct seat *seat; struct wl_listener touch_down; struct wl_listener destroy; }; static void handle_touch_down(struct wl_listener *listener, void *data) { struct wlr_touch_down_event *event = data; struct seat_touch *seat_touch = wl_container_of(listener, seat_touch, touch_down); struct touch_click_effect *effect = seat_touch->effect; // new touch if (event->touch_id == 0) { tap_ripple_effect_remove_all_points(effect->base); } int32_t lx = roundf(seat_touch->seat->cursor->lx); int32_t ly = roundf(seat_touch->seat->cursor->ly); tap_ripple_effect_add_point(effect->base, event->touch_id, lx, ly); } static void seat_touch_destroy(struct seat_touch *seat_touch) { wl_list_remove(&seat_touch->link); wl_list_remove(&seat_touch->touch_down.link); wl_list_remove(&seat_touch->destroy.link); free(seat_touch); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { struct seat_touch *seat_touch = wl_container_of(listener, seat_touch, destroy); seat_touch_destroy(seat_touch); } static void seat_touch_create(struct touch_click_effect *effect, struct seat *seat) { struct seat_touch *seat_touch = calloc(1, sizeof(*seat_touch)); if (!seat_touch) { return; } wl_list_insert(&effect->seat_touchs, &seat_touch->link); seat_touch->effect = effect; seat_touch->seat = seat; seat_touch->touch_down.notify = handle_touch_down; wl_signal_add(&seat->cursor->wlr_cursor->events.touch_down, &seat_touch->touch_down); seat_touch->destroy.notify = handle_seat_destroy; wl_signal_add(&seat->events.destroy, &seat_touch->destroy); } static void handle_new_seat(struct wl_listener *listener, void *data) { struct touch_click_effect *effect = wl_container_of(listener, effect, new_seat); struct seat *seat = data; seat_touch_create(effect, seat); } static bool handle_seat(struct seat *seat, int index, void *data) { struct touch_click_effect *effect = data; seat_touch_create(effect, seat); return false; } static void handle_effect_enable(struct tap_ripple_effect *base_effect, void *data) { struct touch_click_effect *effect = data; input_manager_for_each_seat(handle_seat, effect); effect->new_seat.notify = handle_new_seat; seat_add_new_listener(&effect->new_seat); } static void handle_effect_disable(struct tap_ripple_effect *base_effect, void *data) { struct touch_click_effect *effect = data; wl_list_remove(&effect->new_seat.link); wl_list_init(&effect->new_seat.link); struct seat_touch *seat_touch, *tmp0; wl_list_for_each_safe(seat_touch, tmp0, &effect->seat_touchs, link) { seat_touch_destroy(seat_touch); } } static void handle_effect_destroy(struct tap_ripple_effect *base_effect, void *data) { struct touch_click_effect *effect = data; free(effect); } static const struct tap_ripple_effect_interface effect_impl = { .enable = handle_effect_enable, .disable = handle_effect_disable, .destroy = handle_effect_destroy, }; bool touch_click_effect_create(struct effect_manager *manager) { struct touch_click_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } wl_list_init(&effect->new_seat.link); wl_list_init(&effect->seat_touchs); struct tap_ripple_effect_options options = {}; options.size = 80; options.color[0] = 1.0f; options.color[1] = 1.0f; options.color[2] = 1.0f; options.animate_duration = 500; options.start_radius = 0.5f; options.end_radius = 0.0f; options.start_attenuation = 0.4f; options.end_attenuation = 0.8f; effect->base = tap_ripple_effect_create(manager, &options, &effect_impl, "touch_click", 100, true, effect); if (!effect->base) { free(effect); return false; } return true; } kylin-wayland-compositor/src/effect/locate_pointer.c0000664000175000017500000002724615160460057021674 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include "effect_p.h" #include "input/cursor.h" #include "input/seat.h" #include "render/pass.h" #include "theme.h" #include "util/macros.h" #include "util/time.h" #define INTERVAL (750) #define RADIUS (50) #define CIRCLES (4) #define MAX_TIMEOUT (1000) struct seat_pointer { struct wl_list link; struct locate_pointer_effect *effect; struct seat *seat; struct wl_listener keyboard_key; struct wl_listener seat_destroy; struct wl_event_source *timer; double lx, ly; uint32_t animation_start_time; uint32_t duration; bool shown; }; struct locate_pointer_effect { struct effect *effect; struct wl_listener enable; struct wl_listener disable; struct wl_listener destroy; struct wl_list pointers; struct wl_listener new_seat; struct server *server; uint32_t long_press_timeout; }; static int handle_timeout(void *data) { struct seat_pointer *pointer = data; pointer->animation_start_time = current_time_msec(); // pointer position may be different when render, but we only need damage here pixman_region32_t region; pixman_region32_init_rect(®ion, pointer->seat->cursor->lx - RADIUS, pointer->seat->cursor->ly - RADIUS, RADIUS * 2, RADIUS * 2); ky_scene_add_damage(pointer->effect->server->scene, ®ion); pixman_region32_fini(®ion); return 0; } static void handle_keyboard_key(struct wl_listener *listener, void *data) { struct seat_pointer *pointer = wl_container_of(listener, pointer, keyboard_key); struct seat_keyboard_key_event *event = data; if (event->device && event->device->prop.is_virtual) { return; } if (event->keycode != KEY_LEFTCTRL && event->keycode != KEY_RIGHTCTRL) { return; } if (!event->pressed) { wl_event_source_timer_update(pointer->timer, 0); return; } uint32_t timeout = pointer->effect->long_press_timeout; if (timeout > 0 && !pointer->shown) { wl_event_source_timer_update(pointer->timer, timeout); } else if (pointer->shown) { pointer->animation_start_time = event->time_msec; } else { handle_timeout(pointer); } } static void seat_pointer_destroy(struct seat_pointer *pointer) { wl_list_remove(&pointer->seat_destroy.link); wl_list_remove(&pointer->keyboard_key.link); wl_list_remove(&pointer->link); wl_event_source_remove(pointer->timer); free(pointer); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { struct seat_pointer *pointer = wl_container_of(listener, pointer, seat_destroy); seat_pointer_destroy(pointer); } static void seat_pointer_create(struct locate_pointer_effect *effect, struct seat *seat) { struct seat_pointer *pointer = calloc(1, sizeof(*pointer)); if (!pointer) { return; } pointer->effect = effect; wl_list_insert(&effect->pointers, &pointer->link); pointer->duration = effect_manager_scale_time(INTERVAL); pointer->seat = seat; pointer->seat_destroy.notify = handle_seat_destroy; wl_signal_add(&seat->events.destroy, &pointer->seat_destroy); pointer->keyboard_key.notify = handle_keyboard_key; wl_signal_add(&pointer->seat->events.keyboard_key, &pointer->keyboard_key); struct wl_event_loop *loop = wl_display_get_event_loop(seat->wlr_seat->display); pointer->timer = wl_event_loop_add_timer(loop, handle_timeout, pointer); } static bool handle_seat(struct seat *seat, int index, void *data) { struct locate_pointer_effect *effect = data; seat_pointer_create(effect, seat); return false; } static void handle_new_seat(struct wl_listener *listener, void *data) { struct locate_pointer_effect *effect = wl_container_of(listener, effect, new_seat); struct seat *seat = data; seat_pointer_create(effect, seat); } static void handle_effect_enable(struct wl_listener *listener, void *data) { struct locate_pointer_effect *effect = wl_container_of(listener, effect, enable); input_manager_for_each_seat(handle_seat, effect); seat_add_new_listener(&effect->new_seat); } static void handle_effect_disable(struct wl_listener *listener, void *data) { struct locate_pointer_effect *effect = wl_container_of(listener, effect, disable); wl_list_remove(&effect->new_seat.link); wl_list_init(&effect->new_seat.link); struct seat_pointer *pointer, *tmp; wl_list_for_each_safe(pointer, tmp, &effect->pointers, link) { seat_pointer_destroy(pointer); } } static bool handle_frame_render_pre(struct effect_entity *entity, struct ky_scene_render_target *target) { struct locate_pointer_effect *effect = entity->user_data; struct seat_pointer *pointer; uint32_t duration = effect_manager_scale_time(INTERVAL); duration = duration > MAX_TIMEOUT ? MAX_TIMEOUT : duration; wl_list_for_each(pointer, &effect->pointers, link) { pointer->duration = duration; pointer->shown = current_time_msec() - pointer->animation_start_time <= duration; if (pointer->shown) { pointer->lx = pointer->seat->cursor->lx; pointer->ly = pointer->seat->cursor->ly; } } return true; } static bool handle_frame_render_end(struct effect_entity *entity, struct ky_scene_render_target *target) { struct locate_pointer_effect *effect = entity->user_data; struct seat_pointer *pointer; wl_list_for_each(pointer, &effect->pointers, link) { if (!pointer->shown) { continue; } struct wlr_box dst_box = { pointer->lx - RADIUS, pointer->ly - RADIUS, RADIUS * 2, RADIUS * 2 }; struct wlr_box box; if (!wlr_box_intersection(&box, &dst_box, &target->logical)) { continue; } float progress = (current_time_msec() - pointer->animation_start_time) * 1.0f / pointer->duration; float *color = theme_manager_get_theme()->accent_color; float circle_progress, alpha, radius; for (int i = 0; i <= CIRCLES; i++) { if (progress <= 0.) { break; } circle_progress = MIN(1., (progress * 2)); progress -= 0.5 / CIRCLES; if (circle_progress >= 1.) { continue; } alpha = 1 - circle_progress; radius = ceil(RADIUS * circle_progress); dst_box.x = pointer->lx - radius; dst_box.y = pointer->ly - radius; dst_box.height = dst_box.width = radius * 2; /* add dst_box to target expand damage, because it should be add to frame damage */ pixman_region32_union_rect(&target->expand_damage, &target->expand_damage, dst_box.x, dst_box.y, dst_box.height, dst_box.width); dst_box.x -= target->logical.x; dst_box.y -= target->logical.y; ky_scene_render_box(&dst_box, target); radius *= target->scale; struct ky_render_rect_options options = { .base = { .box = dst_box, .color = { color[0] * alpha, color[1] * alpha, color[2] * alpha, alpha }, }, .radius = { radius , radius, radius, radius }, }; ky_render_pass_add_rect(target->render_pass, &options); } } return true; } static bool handle_frame_render_post(struct effect_entity *entity, struct ky_scene_render_target *target) { struct locate_pointer_effect *effect = entity->user_data; struct seat_pointer *pointer; wl_list_for_each(pointer, &effect->pointers, link) { if (!pointer->shown) { continue; } pixman_region32_t region; pixman_region32_init_rect(®ion, pointer->lx - RADIUS, pointer->ly - RADIUS, RADIUS * 2, RADIUS * 2); ky_scene_add_damage(pointer->effect->server->scene, ®ion); pixman_region32_fini(®ion); } return true; } static void handle_effect_destroy(struct wl_listener *listener, void *data) { struct locate_pointer_effect *effect = wl_container_of(listener, effect, destroy); wl_list_remove(&effect->destroy.link); wl_list_remove(&effect->enable.link); wl_list_remove(&effect->disable.link); wl_list_remove(&effect->new_seat.link); free(effect); } static bool handle_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } struct locate_pointer_effect *locate_pointer = effect->user_data; if (strcmp(option->key, "long_press_timeout") == 0) { uint32_t timeout = option->value.num; if (timeout > MAX_TIMEOUT) { kywc_log(KYWC_ERROR, "%s(%s): %d is larger than max timeout %d", effect->name, option->key, timeout, MAX_TIMEOUT); return false; } if (timeout == locate_pointer->long_press_timeout) { return false; } locate_pointer->long_press_timeout = timeout; return true; } return false; } static bool handle_allow_direct_scanout(struct effect *effect, struct ky_scene_render_target *target) { struct locate_pointer_effect *locate_pointer = effect->user_data; struct seat_pointer *pointer; wl_list_for_each(pointer, &locate_pointer->pointers, link) { if (!pointer->shown) { continue; } struct wlr_box damage = { pointer->lx - RADIUS, pointer->ly - RADIUS, RADIUS * 2, RADIUS * 2 }; if (wlr_box_intersection(&damage, &target->logical, &damage)) { return false; } } return true; } static const struct effect_interface locate_pointer_effect_impl = { .frame_render_pre = handle_frame_render_pre, .frame_render_end = handle_frame_render_end, .frame_render_post = handle_frame_render_post, .configure = handle_effect_configure, .allow_direct_scanout = handle_allow_direct_scanout, }; bool locate_pointer_effect_create(struct effect_manager *manager) { struct locate_pointer_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } effect->effect = effect_create("locate_pointer", 111, false, &locate_pointer_effect_impl, effect); if (!effect->effect) { free(effect); return false; } effect->effect->category = EFFECT_CATEGORY_UTILS; struct effect_entity *entity = ky_scene_add_effect(manager->server->scene, effect->effect); if (!entity) { effect_destroy(effect->effect); free(effect); return false; } entity->user_data = effect; effect->server = manager->server; wl_list_init(&effect->pointers); effect->long_press_timeout = effect_get_option_int(effect->effect, "long_press_timeout", 0); effect->enable.notify = handle_effect_enable; wl_signal_add(&effect->effect->events.enable, &effect->enable); effect->disable.notify = handle_effect_disable; wl_signal_add(&effect->effect->events.disable, &effect->disable); effect->destroy.notify = handle_effect_destroy; wl_signal_add(&effect->effect->events.destroy, &effect->destroy); effect->new_seat.notify = handle_new_seat; wl_list_init(&effect->new_seat.link); if (effect->effect->enabled) { handle_effect_enable(&effect->enable, NULL); } return true; } kylin-wayland-compositor/src/effect/showfps.c0000664000175000017500000001701215160461067020346 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include "effect_p.h" #include "output.h" #include "painter.h" #include "scene/scene.h" #include "view/view.h" #include "widget/widget.h" #define MAX_FRAMES (1024) #define FRAME_MASK (MAX_FRAMES - 1) struct frame_output { struct wl_list link; struct showfps_effect *effect; struct widget *widget; struct ky_scene_node *node; struct ky_scene_output *output; struct wl_listener present; // wlr_output struct wl_listener frame; struct wl_listener destroy; uint32_t frames[MAX_FRAMES]; int head, tail, count, fps; }; struct showfps_effect { struct wl_list outputs; struct wl_listener new_enabled_output; struct effect *effect; struct wl_listener enable; struct wl_listener disable; struct wl_listener destroy; struct effect_manager *manager; /* always schedule frame by default */ bool always_repaint; }; static void frame_output_destroy(struct frame_output *output) { wl_list_remove(&output->link); wl_list_remove(&output->destroy.link); wl_list_remove(&output->frame.link); wl_list_remove(&output->present.link); widget_destroy(output->widget); free(output); } static void output_handle_destroy(struct wl_listener *listener, void *data) { struct frame_output *output = wl_container_of(listener, output, destroy); frame_output_destroy(output); } static void output_handle_frame(struct wl_listener *listener, void *data) { struct frame_output *output = wl_container_of(listener, output, frame); char text[4]; snprintf(text, 4, "%3d", output->fps); widget_set_text(output->widget, text, TEXT_ALIGN_LEFT, TEXT_ATTR_NONE); widget_update(output->widget, !output->effect->always_repaint); ky_scene_node_set_position(output->node, output->output->geometry.x + 5, output->output->geometry.y + 5); } static void output_handle_present(struct wl_listener *listener, void *data) { struct frame_output *output = wl_container_of(listener, output, present); struct wlr_output_event_present *event = data; uint32_t now = (uint32_t)((uint64_t)event->when.tv_sec * 1000ULL + (uint64_t)event->when.tv_nsec / 1000000ULL); while (output->count > 0) { if (now - output->frames[output->tail] >= 1000) { output->tail = (output->tail + 1) & FRAME_MASK; output->count--; } else { break; } } output->frames[output->head] = now; output->head = (output->head + 1) & FRAME_MASK; output->count++; if (output->count > MAX_FRAMES) { output->tail = (output->tail + 1) & FRAME_MASK; output->count = MAX_FRAMES; } output->fps = output->count; } static void frame_output_create(struct showfps_effect *effect, struct ky_scene_output *scene_output) { struct frame_output *output = calloc(1, sizeof(*output)); if (!output) { return; } output->effect = effect; wl_list_insert(&effect->outputs, &output->link); output->output = scene_output; output->present.notify = output_handle_present; wl_signal_add(&scene_output->output->events.present, &output->present); output->frame.notify = output_handle_frame; wl_signal_add(&scene_output->events.frame, &output->frame); output->destroy.notify = output_handle_destroy; wl_signal_add(&scene_output->events.destroy, &output->destroy); struct view_layer *layer = view_manager_get_layer(LAYER_ON_SCREEN_DISPLAY, false); output->widget = widget_create(layer->tree); widget_set_font(output->widget, NULL, 20); widget_set_max_size(output->widget, 500, 200); widget_set_auto_resize(output->widget, AUTO_RESIZE_ONLY); widget_set_front_color(output->widget, (float[4]){ 1, 0, 0, 1 }); widget_set_enabled(output->widget, true); output->node = ky_scene_node_from_widget(output->widget); ky_scene_node_set_input_bypassed(output->node, true); output_schedule_frame(output->output->output); } static void handle_new_enabled_output(struct wl_listener *listener, void *data) { struct showfps_effect *effect = wl_container_of(listener, effect, new_enabled_output); struct kywc_output *output = data; frame_output_create(effect, output_from_kywc_output(output)->scene_output); } static void handle_effect_enable(struct wl_listener *listener, void *data) { struct showfps_effect *effect = wl_container_of(listener, effect, enable); assert(wl_list_empty(&effect->outputs)); struct ky_scene *scene = effect->manager->server->scene; struct ky_scene_output *output; wl_list_for_each(output, &scene->outputs, link) { frame_output_create(effect, output); } effect->new_enabled_output.notify = handle_new_enabled_output; output_manager_add_new_enabled_listener(&effect->new_enabled_output); } static void handle_effect_disable(struct wl_listener *listener, void *data) { struct showfps_effect *effect = wl_container_of(listener, effect, disable); wl_list_remove(&effect->new_enabled_output.link); wl_list_init(&effect->new_enabled_output.link); struct frame_output *output, *tmp; wl_list_for_each_safe(output, tmp, &effect->outputs, link) { frame_output_destroy(output); } } static void handle_effect_destroy(struct wl_listener *listener, void *data) { struct showfps_effect *effect = wl_container_of(listener, effect, destroy); assert(wl_list_empty(&effect->outputs)); wl_list_remove(&effect->destroy.link); wl_list_remove(&effect->enable.link); wl_list_remove(&effect->disable.link); free(effect); } static bool handle_effect_configure(struct effect *effect, const struct effect_option *option) { if (strcmp(option->key, "always_repaint")) { return false; } struct showfps_effect *showfps = effect->user_data; if (showfps->always_repaint == option->value.boolean) { return false; } showfps->always_repaint = option->value.boolean; /* schedule all outputs if always_repaint changes to true */ if (showfps->always_repaint) { struct frame_output *output; wl_list_for_each(output, &showfps->outputs, link) { output_schedule_frame(output->output->output); } } return true; } static const struct effect_interface showfps_effect_impl = { .configure = handle_effect_configure, }; bool showfps_effect_create(struct effect_manager *manager) { struct showfps_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } effect->effect = effect_create("showfps", 0, false, &showfps_effect_impl, effect); if (!effect->effect) { free(effect); return false; } effect->manager = manager; effect->effect->category = EFFECT_CATEGORY_UTILS; effect->always_repaint = effect_get_option_boolean(effect->effect, "always_repaint", true); wl_list_init(&effect->outputs); wl_list_init(&effect->new_enabled_output.link); effect->enable.notify = handle_effect_enable; wl_signal_add(&effect->effect->events.enable, &effect->enable); effect->disable.notify = handle_effect_disable; wl_signal_add(&effect->effect->events.disable, &effect->disable); effect->destroy.notify = handle_effect_destroy; wl_signal_add(&effect->effect->events.destroy, &effect->destroy); if (effect->effect->enabled) { handle_effect_enable(&effect->enable, NULL); } return true; } kylin-wayland-compositor/src/effect/touch_trail.c0000664000175000017500000001146715160461067021202 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "effect/shape.h" #include "effect_p.h" #include "input/cursor.h" #include "input/seat.h" struct seat_touch { struct wl_list link; struct touch_trail_effect *effect; struct seat *seat; struct wl_listener touch_down; struct wl_listener touch_motion; struct wl_listener destroy; }; struct touch_trail_effect { struct trail_effect *base; struct wl_listener new_seat; struct wl_list seat_touchs; }; static void handle_touch_down(struct wl_listener *listener, void *data) { struct wlr_touch_down_event *event = data; struct seat_touch *seat_touch = wl_container_of(listener, seat_touch, touch_down); struct touch_trail_effect *effect = seat_touch->effect; // new touch if (event->touch_id == 0) { trail_effect_remove_all_trails(effect->base); } int32_t lx = roundf(seat_touch->seat->cursor->lx); int32_t ly = roundf(seat_touch->seat->cursor->ly); trail_effect_add_trail(effect->base, event->touch_id, lx, ly); } static void handle_touch_motion(struct wl_listener *listener, void *data) { struct wlr_touch_motion_event *event = data; struct seat_touch *seat_touch = wl_container_of(listener, seat_touch, touch_motion); int32_t lx = roundf(seat_touch->seat->cursor->lx); int32_t ly = roundf(seat_touch->seat->cursor->ly); trail_effect_trail_add_point(seat_touch->effect->base, event->touch_id, lx, ly); } static void seat_touch_destroy(struct seat_touch *seat_touch) { wl_list_remove(&seat_touch->link); wl_list_remove(&seat_touch->touch_down.link); wl_list_remove(&seat_touch->touch_motion.link); wl_list_remove(&seat_touch->destroy.link); free(seat_touch); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { struct seat_touch *seat_touch = wl_container_of(listener, seat_touch, destroy); seat_touch_destroy(seat_touch); } static void seat_touch_create(struct touch_trail_effect *effect, struct seat *seat) { struct seat_touch *seat_touch = calloc(1, sizeof(*seat_touch)); if (!seat_touch) { return; } wl_list_insert(&effect->seat_touchs, &seat_touch->link); seat_touch->seat = seat; seat_touch->effect = effect; seat_touch->touch_down.notify = handle_touch_down; wl_signal_add(&seat->cursor->wlr_cursor->events.touch_down, &seat_touch->touch_down); seat_touch->touch_motion.notify = handle_touch_motion; wl_signal_add(&seat->cursor->wlr_cursor->events.touch_motion, &seat_touch->touch_motion); seat_touch->destroy.notify = handle_seat_destroy; wl_signal_add(&seat->events.destroy, &seat_touch->destroy); } static void handle_new_seat(struct wl_listener *listener, void *data) { struct touch_trail_effect *effect = wl_container_of(listener, effect, new_seat); struct seat *seat = data; seat_touch_create(effect, seat); } static bool handle_seat(struct seat *seat, int index, void *data) { struct touch_trail_effect *effect = data; seat_touch_create(effect, seat); return false; } static void handle_effect_enable(struct trail_effect *base_effect, void *user_data) { struct touch_trail_effect *effect = user_data; input_manager_for_each_seat(handle_seat, effect); effect->new_seat.notify = handle_new_seat; seat_add_new_listener(&effect->new_seat); } static void handle_effect_disable(struct trail_effect *base_effect, void *user_data) { struct touch_trail_effect *effect = user_data; wl_list_remove(&effect->new_seat.link); wl_list_init(&effect->new_seat.link); struct seat_touch *seat_touch, *tmp0; wl_list_for_each_safe(seat_touch, tmp0, &effect->seat_touchs, link) { seat_touch_destroy(seat_touch); } } static void handle_effect_destroy(struct trail_effect *base_effect, void *user_data) { struct touch_trail_effect *effect = user_data; free(effect); } static const struct trail_effect_interface effect_impl = { .enable = handle_effect_enable, .disable = handle_effect_disable, .destroy = handle_effect_destroy, }; bool touch_trail_effect_create(struct effect_manager *manager) { struct touch_trail_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } wl_list_init(&effect->new_seat.link); wl_list_init(&effect->seat_touchs); struct trail_effect_options options = {}; options.color[0] = 0.4f; options.color[1] = 0.4f; options.color[2] = 0.4f; options.color[3] = 0.4f; options.thickness = 8.0f; options.life_time = 200; effect->base = trail_effect_create(manager, &options, &effect_impl, "touch_trail", 100, true, effect); if (!effect->base) { free(effect); return false; } return true; } kylin-wayland-compositor/src/effect/output_transform.c0000664000175000017500000003716415160461067022322 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include "effect/output_transform.h" #include "effect_p.h" #include "output.h" #include "render/opengl.h" #include "scene/thumbnail.h" #include "util/macros.h" #include "util/time.h" // output - output_entry - effect_entity. life cycle: output_entry = effect_entity struct output_entry { struct output_transform_effect *effect; struct effect_entity *effect_entity; struct ky_scene_output *output; struct wl_listener destroy; enum wl_output_transform last_transform; // animate uint32_t start_time; float start_angle, end_angle, angle; float start_width, end_width, width; float start_height, end_height, height; float fade_alpha; // output screenshot struct thumbnail *old_thumbnail; struct wlr_texture *old_thumbnail_texture; struct wl_listener old_thumbnail_update; struct wl_listener old_thumbnail_destroy; struct thumbnail *new_thumbnail; struct wlr_texture *new_thumbnail_texture; struct wl_listener new_thumbnail_update; struct wl_listener new_thumbnail_destroy; enum wl_output_transform new_thumbnail_transform; }; struct output_transform_effect { struct effect *effect; struct wl_listener destroy; struct effect_manager *manager; uint32_t animate_duration; }; static int output_transform_to_angle(enum wl_output_transform transform) { if (transform == WL_OUTPUT_TRANSFORM_90) { return 90; } else if (transform == WL_OUTPUT_TRANSFORM_180) { return 180; } else if (transform == WL_OUTPUT_TRANSFORM_270) { return 270; } else if (transform == WL_OUTPUT_TRANSFORM_FLIPPED) { return 360; } else if (transform == WL_OUTPUT_TRANSFORM_FLIPPED_90) { return 90; } else if (transform == WL_OUTPUT_TRANSFORM_FLIPPED_180) { return 180; } else if (transform == WL_OUTPUT_TRANSFORM_FLIPPED_270) { return 270; } else { return 0; } } static enum wl_output_transform diff_angle_to_new_thumbnail_transform(int angle) { if (angle == 90) { return WL_OUTPUT_TRANSFORM_90; } else if (angle == 180) { return WL_OUTPUT_TRANSFORM_180; } else if (angle == -90) { return WL_OUTPUT_TRANSFORM_270; } else if (angle == -180) { return WL_OUTPUT_TRANSFORM_180; } else { return WL_OUTPUT_TRANSFORM_NORMAL; } } static void output_entry_start_animate(struct output_entry *output) { output->start_time = current_time_msec(); output->start_angle = (float)output_transform_to_angle(output->last_transform); output->end_angle = (float)output_transform_to_angle(output->output->output->transform); output->end_angle = output->start_angle - output->end_angle; // reduce rotate angle if (output->end_angle > 180.0f) { output->end_angle -= 360.0f; } else if (output->end_angle < -180.0f) { output->end_angle += 360.0f; } // keep angle not equal zero if (fabsf(output->end_angle) < 0.01f) { output->end_angle = 360.0f; } output->start_angle = 0; output->angle = output->start_angle; int diff_angle = (int)output->end_angle; // vertical rotation need resize animation if (diff_angle == 90 || diff_angle == -90) { output->start_width = output->output->output->width; output->start_height = output->output->output->height; output->end_width = output->start_height; output->end_height = output->start_width; } output->new_thumbnail_transform = diff_angle_to_new_thumbnail_transform(diff_angle); output->fade_alpha = 0.0f; } static void entity_destroy(struct effect_entity *entity) { struct output_entry *output = entity->user_data; if (!output) { return; } if (output->old_thumbnail_texture) { wlr_texture_destroy(output->old_thumbnail_texture); } if (output->new_thumbnail_texture) { wlr_texture_destroy(output->new_thumbnail_texture); } if (output->old_thumbnail) { wl_list_remove(&output->old_thumbnail_update.link); wl_list_remove(&output->old_thumbnail_destroy.link); thumbnail_destroy(output->old_thumbnail); } if (output->new_thumbnail) { wl_list_remove(&output->new_thumbnail_update.link); wl_list_remove(&output->new_thumbnail_destroy.link); thumbnail_destroy(output->new_thumbnail); } wl_list_remove(&output->destroy.link); free(output); } static void handle_output_destroy(struct wl_listener *listener, void *data) { struct output_entry *output = wl_container_of(listener, output, destroy); if (output->effect_entity) { effect_entity_destroy(output->effect_entity); } } static struct output_entry *output_entry_create(struct output_transform_effect *effect, struct ky_scene_output *scene_output, enum wl_output_transform last_transform) { struct output_entry *output = calloc(1, sizeof(*output)); if (!output) { return NULL; } output->effect = effect; output->last_transform = last_transform; output->output = scene_output; wl_list_init(&output->old_thumbnail_update.link); wl_list_init(&output->old_thumbnail_destroy.link); wl_list_init(&output->new_thumbnail_update.link); wl_list_init(&output->new_thumbnail_destroy.link); output->destroy.notify = handle_output_destroy; wl_signal_add(&scene_output->events.destroy, &output->destroy); return output; } static void handle_old_thumbnail_update(struct wl_listener *listener, void *data) { struct thumbnail_update_event *event = data; if (!event->buffer_changed) { return; } struct output_entry *output = wl_container_of(listener, output, old_thumbnail_update); if (output->old_thumbnail_texture) { wlr_texture_destroy(output->old_thumbnail_texture); } struct wlr_renderer *renderer = output->effect->manager->server->renderer; output->old_thumbnail_texture = wlr_texture_from_buffer(renderer, event->buffer); } static void handle_old_thumbnail_destroy(struct wl_listener *listener, void *data) { struct output_entry *output = wl_container_of(listener, output, old_thumbnail_destroy); wl_list_remove(&output->old_thumbnail_destroy.link); wl_list_remove(&output->old_thumbnail_update.link); output->old_thumbnail = NULL; } static void handle_new_thumbnail_update(struct wl_listener *listener, void *data) { struct thumbnail_update_event *event = data; if (!event->buffer_changed) { return; } struct output_entry *output = wl_container_of(listener, output, new_thumbnail_update); if (output->new_thumbnail_texture) { wlr_texture_destroy(output->new_thumbnail_texture); } struct wlr_renderer *renderer = output->effect->manager->server->renderer; output->new_thumbnail_texture = wlr_texture_from_buffer(renderer, event->buffer); } static void handle_new_thumbnail_destroy(struct wl_listener *listener, void *data) { struct output_entry *output = wl_container_of(listener, output, new_thumbnail_destroy); wl_list_remove(&output->new_thumbnail_destroy.link); wl_list_remove(&output->new_thumbnail_update.link); output->new_thumbnail = NULL; } static struct output_transform_effect *transform_effect = NULL; // direct call from output.c not from ky_output transform event bool output_add_transform_effect(struct kywc_output *kywc_output, struct kywc_output_state *old_state, struct kywc_output_state *new_state) { if (!transform_effect || !old_state->enabled || !new_state->enabled) { return false; } struct ky_scene_output *scene_output = output_from_kywc_output(kywc_output)->scene_output; if (!scene_output) { return false; } // prevent duplicate add struct ky_scene *scene = transform_effect->manager->server->scene; struct effect_entity *entity = ky_scene_find_effect_entity(scene, transform_effect->effect); if (entity) { struct output_entry *output = entity->user_data; if (output->output == scene_output) { return false; } } struct thumbnail *output_old_thumbnail = thumbnail_create_from_output(scene_output, old_state, 1.0f); if (!output_old_thumbnail) { return false; } struct thumbnail *output_new_thumbnail = thumbnail_create_from_output(scene_output, new_state, 1.0f); if (!output_new_thumbnail) { goto failed; } entity = ky_scene_add_effect(scene, transform_effect->effect); if (!entity) { goto failed; } struct output_entry *output = output_entry_create(transform_effect, scene_output, old_state->transform); if (!output) { effect_entity_destroy(entity); goto failed; } output->effect_entity = entity; entity->user_data = output; // screenshot before transform output->old_thumbnail = output_old_thumbnail; output->old_thumbnail_update.notify = handle_old_thumbnail_update; thumbnail_add_update_listener(output->old_thumbnail, &output->old_thumbnail_update); output->old_thumbnail_destroy.notify = handle_old_thumbnail_destroy; thumbnail_add_destroy_listener(output->old_thumbnail, &output->old_thumbnail_destroy); // call once update and remove thumbnail_update(output->old_thumbnail); thumbnail_destroy(output->old_thumbnail); // screenshot after transform output->new_thumbnail = output_new_thumbnail; output->new_thumbnail_update.notify = handle_new_thumbnail_update; thumbnail_add_update_listener(output->new_thumbnail, &output->new_thumbnail_update); output->new_thumbnail_destroy.notify = handle_new_thumbnail_destroy; thumbnail_add_destroy_listener(output->new_thumbnail, &output->new_thumbnail_destroy); // unlike old thumbnail, new thumbnail need wait multiple update. so remove later // create animate output_entry_start_animate(output); // trigger render event ky_scene_output_damage_whole(scene_output); return true; failed: if (output_new_thumbnail) { thumbnail_destroy(output_new_thumbnail); } thumbnail_destroy(output_old_thumbnail); return false; } static bool frame_render_pre(struct effect_entity *entity, struct ky_scene_render_target *target) { struct output_entry *output = entity->user_data; if (output->output != target->output) { return true; } struct output_transform_effect *effect = output->effect; // timer uint32_t diff_time = current_time_msec() - output->start_time; uint32_t duration = effect_manager_scale_time(effect->animate_duration); if (diff_time > duration) { effect_entity_destroy(output->effect_entity); } else { float t = diff_time / (float)duration; // easing function float factor = t * t; // lerp output->angle = output->start_angle + factor * (output->end_angle - output->start_angle); output->width = output->start_width + factor * (output->end_width - output->start_width); output->height = output->start_height + factor * (output->end_height - output->start_height); output->fade_alpha = CLAMP((factor - 0.5f) * 2.0f, 0.0f, 1.0f); } return true; } static bool frame_render_post(struct effect_entity *entity, struct ky_scene_render_target *target) { struct output_entry *output = entity->user_data; if (output->output != target->output) { return true; } // add damage to trigger render event ky_scene_output_damage_whole(output->output); return true; } static bool frame_render_end(struct effect_entity *entity, struct ky_scene_render_target *target) { struct output_entry *output = entity->user_data; if (output->output != target->output) { return true; } if (!output->old_thumbnail_texture || !output->new_thumbnail_texture) { return true; } struct wlr_box dst_box = { .x = 0, .y = 0, .width = output->output->output->width, .height = output->output->output->height, }; // background color wlr_render_pass_add_rect(target->render_pass, &(struct wlr_render_rect_options){ .box = dst_box, .color = { .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 }, }); // size transformed if (output->start_width != output->end_width) { dst_box.x = (dst_box.width - output->width) / 2; dst_box.y = (dst_box.height - output->height) / 2; dst_box.width = output->width; dst_box.height = output->height; } ky_opengl_render_pass_add_texture(target->render_pass, &(struct ky_render_texture_options){ .base = { .texture = output->old_thumbnail_texture, .alpha = &(float){ 1.0f }, .dst_box = dst_box }, .rotation_angle = output->angle, }); ky_opengl_render_pass_add_texture(target->render_pass, &(struct ky_render_texture_options){ .base = { .texture = output->new_thumbnail_texture, .transform = output->new_thumbnail_transform, .alpha = &output->fade_alpha, .dst_box = dst_box }, .rotation_angle = output->angle, }); return true; } static void handle_effect_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&transform_effect->destroy.link); free(transform_effect); transform_effect = NULL; } static bool handle_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } return false; } static bool handle_allow_direct_scanout(struct effect *effect, struct ky_scene_render_target *target) { struct ky_scene *scene = transform_effect->manager->server->scene; return !ky_scene_find_effect_entity(scene, transform_effect->effect); } static const struct effect_interface effect_impl = { .entity_destroy = entity_destroy, .frame_render_pre = frame_render_pre, .frame_render_end = frame_render_end, .frame_render_post = frame_render_post, .configure = handle_effect_configure, .allow_direct_scanout = handle_allow_direct_scanout, }; bool output_transform_effect_create(struct effect_manager *manager) { if (!wlr_renderer_is_opengl(manager->server->renderer)) { return false; } transform_effect = calloc(1, sizeof(*transform_effect)); if (!transform_effect) { return false; } transform_effect->effect = effect_create("output_transform", 105, true, &effect_impl, NULL); if (!transform_effect->effect) { free(transform_effect); transform_effect = NULL; return false; } transform_effect->effect->category = EFFECT_CATEGORY_SCENE; transform_effect->destroy.notify = handle_effect_destroy; wl_signal_add(&transform_effect->effect->events.destroy, &transform_effect->destroy); transform_effect->manager = manager; transform_effect->animate_duration = 500; return true; } kylin-wayland-compositor/src/effect/move.c0000664000175000017500000001526615160460057017632 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "effect/move.h" #include "effect_p.h" #include "scene/box.h" #include "scene/thumbnail.h" enum move_effect_type { MOVE_EFFECT_TYPE_BORDER = 0, // no window content shown MOVE_EFFECT_TYPE_OPACITY, }; struct move_proxy { struct wl_list link; struct view *view; struct ky_scene_node *node; /* MOVE_EFFECT_BORDER */ struct ky_scene_box *box; /* MOVE_EFFECT_OPACITY */ struct ky_scene_buffer *buffer; struct thumbnail *thumbnail; struct wl_listener thumbnail_update; struct wl_listener thumbnail_destroy; struct { struct wl_signal destroy; } events; int x, y, width, height; }; static struct move_effect { struct wl_list proxies; struct effect *effect; struct wl_listener enable; struct wl_listener disable; struct wl_listener destroy; enum move_effect_type type; } *effect = NULL; static void proxy_handle_thumbnail_update(struct wl_listener *listener, void *data) { struct move_proxy *proxy = wl_container_of(listener, proxy, thumbnail_update); struct thumbnail_update_event *event = data; ky_scene_buffer_set_dest_size(proxy->buffer, event->buffer->width, event->buffer->height); ky_scene_buffer_set_buffer(proxy->buffer, event->buffer); } static void proxy_handle_thumbnail_destroy(struct wl_listener *listener, void *data) { struct move_proxy *proxy = wl_container_of(listener, proxy, thumbnail_destroy); proxy->thumbnail = NULL; move_proxy_destroy(proxy); } static void proxy_create_node(struct move_proxy *proxy) { struct ky_scene_tree *tree = proxy->view->tree->node.parent; if (effect->type == MOVE_EFFECT_TYPE_BORDER) { float color[4] = { 0.5, 0.5, 0.5, 1.0 }; proxy->box = ky_scene_box_create(tree, proxy->width, proxy->height, color, 1); proxy->node = ky_scene_node_from_box(proxy->box); } else if (effect->type == MOVE_EFFECT_TYPE_OPACITY) { proxy->buffer = ky_scene_buffer_create(tree, NULL); proxy->node = &proxy->buffer->node; ky_scene_buffer_set_opacity(proxy->buffer, 0.5); proxy->thumbnail = thumbnail_create_from_view(proxy->view, THUMBNAIL_DISABLE_SHADOW, 1.0); proxy->thumbnail_update.notify = proxy_handle_thumbnail_update; thumbnail_add_update_listener(proxy->thumbnail, &proxy->thumbnail_update); proxy->thumbnail_destroy.notify = proxy_handle_thumbnail_destroy; thumbnail_add_destroy_listener(proxy->thumbnail, &proxy->thumbnail_destroy); /* thumbnail still update by ky_scene_node_force_damage_event */ ky_scene_node_set_enabled(&proxy->view->tree->node, false); } } void move_proxy_move(struct move_proxy *proxy, int x, int y) { proxy->x = x; proxy->y = y; if (!proxy->node) { proxy_create_node(proxy); } ky_scene_node_set_position(proxy->node, x - proxy->view->base.margin.off_x, y - proxy->view->base.margin.off_y); } void move_proxy_resize(struct move_proxy *proxy, int width, int height) { proxy->width = width; proxy->height = height; if (proxy->box) { ky_scene_box_set_size(proxy->box, width, height); } } void move_proxy_destroy(struct move_proxy *proxy) { wl_signal_emit_mutable(&proxy->events.destroy, NULL); assert(wl_list_empty(&proxy->events.destroy.listener_list)); wl_list_remove(&proxy->link); wl_list_remove(&proxy->thumbnail_update.link); wl_list_remove(&proxy->thumbnail_destroy.link); if (proxy->node) { view_do_move(proxy->view, proxy->x, proxy->y); ky_scene_node_destroy(proxy->node); } if (proxy->thumbnail) { thumbnail_destroy(proxy->thumbnail); ky_scene_node_set_enabled(&proxy->view->tree->node, true); } free(proxy); } struct move_proxy *move_proxy_create(struct view *view, int width, int height) { if (!effect || !effect->effect->enabled) { return NULL; } struct move_proxy *proxy = calloc(1, sizeof(*proxy)); if (!proxy) { return NULL; } proxy->view = view; proxy->width = width; proxy->height = height; wl_signal_init(&proxy->events.destroy); wl_list_init(&proxy->thumbnail_update.link); wl_list_init(&proxy->thumbnail_destroy.link); wl_list_insert(&effect->proxies, &proxy->link); return proxy; } void move_proxy_add_destroy_listener(struct move_proxy *proxy, struct wl_listener *listener) { wl_signal_add(&proxy->events.destroy, listener); } static void handle_effect_enable(struct wl_listener *listener, void *data) { // do nothing as we don't create proxy when effect is disabled } static void handle_effect_disable(struct wl_listener *listener, void *data) { struct move_proxy *proxy, *tmp; wl_list_for_each_safe(proxy, tmp, &effect->proxies, link) { move_proxy_destroy(proxy); } } static void handle_effect_destroy(struct wl_listener *listener, void *data) { assert(wl_list_empty(&effect->proxies)); wl_list_remove(&effect->destroy.link); wl_list_remove(&effect->enable.link); wl_list_remove(&effect->disable.link); free(effect); effect = NULL; } static bool handle_effect_configure(struct effect *eff, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } if (strcmp(option->key, "type")) { return false; } int type = option->value.num; if (type != MOVE_EFFECT_TYPE_BORDER && type != MOVE_EFFECT_TYPE_OPACITY) { return false; } effect->type = type; return true; } static const struct effect_interface move_effect_impl = { .configure = handle_effect_configure, }; bool move_effect_create(struct effect_manager *effect_manager) { effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } effect->effect = effect_create("move", 0, false, &move_effect_impl, NULL); if (!effect->effect) { free(effect); effect = NULL; return false; } effect->effect->support_actions = EFFECT_ACTION_MOVE; effect->effect->category = EFFECT_CATEGORY_ACTION; wl_list_init(&effect->proxies); effect->type = effect_get_option_int(effect->effect, "type", MOVE_EFFECT_TYPE_BORDER); effect->enable.notify = handle_effect_enable; wl_signal_add(&effect->effect->events.enable, &effect->enable); effect->disable.notify = handle_effect_disable; wl_signal_add(&effect->effect->events.disable, &effect->disable); effect->destroy.notify = handle_effect_destroy; wl_signal_add(&effect->effect->events.destroy, &effect->destroy); return true; } kylin-wayland-compositor/src/effect/zoom.c0000664000175000017500000007505715160461067017656 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "effect_p.h" #include "input/cursor.h" #include "input/seat.h" #include "output.h" #include "render/opengl.h" #include "render/pass.h" #include "util/dbus.h" #include "util/macros.h" #include "util/spawn.h" static const char *zoom_dbus_interface = "org.ukui.Magnifier"; static const char *zoom_path = "/Magnifier"; static const char *command = "kylin-magnifier"; #define MAX_SCALE (16) #define WINDOW_WIDTH (320) #define WINDOW_HEIGHT (180) #define WINDOW_BORDER (2) enum zoom_type { ZOOM_OUTPUT = 0, ZOOM_WINDOW, }; struct zoom_output { struct wl_list link; struct output *output; struct wl_listener disable; }; struct zoom_output_info { double offset_x, offset_y; pixman_region32_t viewport_region; }; struct seat_cursor { struct wl_list link; struct zoom_effect *effect; struct seat *seat; struct wl_listener seat_destroy; struct wl_listener cursor_motion; struct kywc_box geo; double lx, ly; bool moved; }; struct zoom_effect { struct effect *effect; struct wl_listener enable; struct wl_listener disable; struct wl_listener destroy; struct wl_list outputs; struct zoom_output_info output_info; struct wl_listener new_enabled_output; struct wl_list seat_cursors; struct wl_listener new_seat; enum zoom_type type; int scale; bool enabled; bool renderer_is_opengl; struct wl_client *client; struct server *server; struct ky_scene *scene; struct dbus_object *dbus; }; static struct zoom_effect *zoom = NULL; enum zoom_action { ZOOM_IN, ZOOM_OUT, ZOOM_STOP, }; static struct shortcut { char *keybind; char *desc; enum zoom_action action; } shortcuts[] = { { "Win+Equal:no", "zoom in", ZOOM_IN }, { "Win+Minus:no", "zoom out", ZOOM_OUT }, { "Win+Escape:no", "stop zoom", ZOOM_STOP }, }; static void handle_cursor_motion(struct wl_listener *listener, void *data) { struct seat_cursor *cursor = wl_container_of(listener, cursor, cursor_motion); struct seat_cursor_motion_event *event = data; cursor->lx = event->lx; cursor->ly = event->ly; cursor->moved = true; } static bool handle_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } if (strcmp(option->key, "type") == 0) { int type = option->value.num; if (type != ZOOM_OUTPUT && type != ZOOM_WINDOW) { kywc_log(KYWC_WARN, "Invalid type"); return false; } zoom->type = type; return true; } if (strcmp(option->key, "scale") == 0) { if (option->value.num < 1 || option->value.num > MAX_SCALE) { kywc_log(KYWC_WARN, "Invalid scale"); return false; } zoom->scale = option->value.num; return true; } return false; } static void seat_cursor_destroy(struct seat_cursor *cursor) { wl_list_remove(&cursor->seat_destroy.link); wl_list_remove(&cursor->cursor_motion.link); wl_list_remove(&cursor->link); free(cursor); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { struct seat_cursor *cursor = wl_container_of(listener, cursor, seat_destroy); seat_cursor_destroy(cursor); } static void seat_cursor_create(struct zoom_effect *effect, struct seat *seat) { struct seat_cursor *seat_cursor = calloc(1, sizeof(*seat_cursor)); if (!seat_cursor) { return; } seat_cursor->effect = effect; seat_cursor->lx = seat->cursor->lx; seat_cursor->ly = seat->cursor->ly; wl_list_insert(&effect->seat_cursors, &seat_cursor->link); seat_cursor->seat = seat; seat_cursor->seat_destroy.notify = handle_seat_destroy; wl_signal_add(&seat->events.destroy, &seat_cursor->seat_destroy); seat_cursor->cursor_motion.notify = handle_cursor_motion; wl_signal_add(&seat->events.cursor_motion, &seat_cursor->cursor_motion); } static void handle_new_seat(struct wl_listener *listener, void *data) { struct zoom_effect *effect = wl_container_of(listener, effect, new_seat); struct seat *seat = data; seat_cursor_create(effect, seat); } static bool handle_seat(struct seat *seat, int index, void *data) { struct zoom_effect *effect = data; seat_cursor_create(effect, seat); return false; } static void zoom_output_cursor_outside_viewport_region(struct zoom_effect *effect, struct ky_scene_render_target *target) { struct seat_cursor *seat_cursor; struct cursor *cursor; wl_list_for_each(seat_cursor, &effect->seat_cursors, link) { cursor = seat_cursor->seat->cursor; if (!pixman_region32_contains_point(&effect->output_info.viewport_region, cursor->lx, cursor->ly, NULL)) { double lx = target->logical.x + target->logical.width * 0.5; double ly = target->logical.y + target->logical.height * 0.5; cursor_move(cursor, NULL, lx, ly, false, false); } } } static void zoom_output_calc_closest_point(struct zoom_effect *effect, double lx, double ly, double *dest_x, double *dest_y) { double distance_x, distance_y, distance; double min_x = lx, min_y = ly, min_distance = DBL_MAX; int rects_len; pixman_box32_t *rects = pixman_region32_rectangles(&effect->output_info.viewport_region, &rects_len); for (int i = 0; i < rects_len; i++) { struct wlr_box box; pixman_box32_t *rect = &rects[i]; box.x = rect->x1; box.y = rect->y1; box.width = rect->x2 - rect->x1; box.height = rect->y2 - rect->y1; wlr_box_closest_point(&box, lx, ly, &distance_x, &distance_y); // calculate squared distance suitable for comparison distance = (lx - distance_x) * (lx - distance_x) + (ly - distance_y) * (ly - distance_y); if (!isfinite(distance)) { distance = DBL_MAX; } if (distance < min_distance) { min_x = distance_x; min_y = distance_y; min_distance = distance; } } *dest_x = min_x; *dest_y = min_y; } static void zoom_output_update_viewport_region(struct zoom_effect *effect) { pixman_region32_clear(&effect->output_info.viewport_region); int trans_width, trans_height = 0; struct ky_scene_output *scene_output; wl_list_for_each(scene_output, &effect->scene->outputs, link) { wlr_output_transformed_resolution(scene_output->output, &trans_width, &trans_height); float scale = effect->scale * scene_output->output->scale; struct kywc_box logic_box = { .x = (int)(scene_output->geometry.x / effect->scale) + effect->output_info.offset_x, .y = (int)(scene_output->geometry.y / effect->scale) + effect->output_info.offset_y, .width = trans_width / scale, .height = trans_height / scale, }; pixman_region32_union_rect(&effect->output_info.viewport_region, &effect->output_info.viewport_region, logic_box.x, logic_box.y, logic_box.width, logic_box.height); } } static void zoom_constrain_viewport_offset(struct zoom_effect *effect) { int layout_width, layout_height; int layout_x = 0, layout_y = 0; int dx = 0, dy = 0; output_layout_get_size(&layout_width, &layout_height); pixman_box32_t *extents = pixman_region32_extents(&effect->output_info.viewport_region); int vport_x = extents->x1; int vport_y = extents->y1; int vport_w = extents->x2 - extents->x1; int vport_h = extents->y2 - extents->y1; // left right if (vport_x < layout_x) { dx = layout_x - vport_x; } else if (vport_x + vport_w > layout_x + layout_width) { dx = (layout_x + layout_width) - (vport_x + vport_w); } // top bottom if (vport_y < layout_y) { dy = layout_y - vport_y; } else if (vport_y + vport_h > layout_y + layout_height) { dy = (layout_y + layout_height) - (vport_y + vport_h); } if (dx != 0 || dy != 0) { effect->output_info.offset_x += dx; effect->output_info.offset_y += dy; zoom_output_update_viewport_region(effect); } } static void zoom_output_calc_viewport_region(struct zoom_effect *effect) { zoom_output_update_viewport_region(effect); zoom_constrain_viewport_offset(effect); } static void zoom_scale_changed(struct zoom_effect *effect, struct ky_scene_render_target *target, int new_scale, int old_scale, double cursor_x, double cursor_y) { if (new_scale == 1) { effect->output_info.offset_x = 0; effect->output_info.offset_y = 0; pixman_region32_clear(&effect->output_info.viewport_region); return; } double old_viewport_x = (int)(target->output->geometry.x / old_scale) + effect->output_info.offset_x; double old_viewport_y = (int)(target->output->geometry.y / old_scale) + effect->output_info.offset_y; double rel_cx = cursor_x - old_viewport_x; double rel_cy = cursor_y - old_viewport_y; double new_viewport_x = cursor_x - rel_cx * ((double)old_scale / new_scale); double new_viewport_y = cursor_y - rel_cy * ((double)old_scale / new_scale); effect->output_info.offset_x = new_viewport_x - (int)(target->output->geometry.x / new_scale); effect->output_info.offset_y = new_viewport_y - (int)(target->output->geometry.y / new_scale); zoom_output_calc_viewport_region(effect); } static bool handle_output_frame_render_pre(struct effect_entity *entity, struct ky_scene_render_target *target) { struct zoom_effect *effect = entity->user_data; if (wl_list_empty(&effect->seat_cursors)) { return true; } struct seat_cursor *cursor = wl_container_of(effect->seat_cursors.next, cursor, link); static int last_scale = 1; if (effect->scale != last_scale) { zoom_scale_changed(effect, target, effect->scale, last_scale, cursor->lx, cursor->ly); ky_scene_damage_whole(zoom->scene); last_scale = effect->scale; } /* check if the mouse is in the area */ bool need_check = !pixman_region32_not_empty(&effect->output_info.viewport_region); if (cursor->moved || need_check) { if (!pixman_region32_contains_point(&effect->output_info.viewport_region, cursor->lx, cursor->ly, NULL)) { double closest_x = 0, closest_y = 0; zoom_output_calc_closest_point(effect, cursor->lx, cursor->ly, &closest_x, &closest_y); int distance_x = cursor->lx - closest_x; int distance_y = cursor->ly - closest_y; effect->output_info.offset_x += distance_x; effect->output_info.offset_y += distance_y; zoom_output_calc_viewport_region(effect); } ky_scene_damage_whole(zoom->scene); cursor->moved = false; } /* calc target logical */ float output_scale = target->output->output->scale; target->scale = effect->scale * output_scale; target->logical.width = target->logical.width / effect->scale; target->logical.height = target->logical.height / effect->scale; target->logical.x = (int)(target->output->geometry.x / effect->scale) + effect->output_info.offset_x; target->logical.y = (int)(target->output->geometry.y / effect->scale) + effect->output_info.offset_y; ky_scene_output_set_viewport_source_box(target->output, &target->logical); /* force rendering cursor */ target->options |= KY_SCENE_RENDER_ENABLE_CURSORS; if (need_check) { zoom_output_cursor_outside_viewport_region(effect, target); } return true; } static void zoom_window_update_geo(struct seat_cursor *cursor, struct ky_scene_render_target *target) { int width = (target->transform & WL_OUTPUT_TRANSFORM_90) ? WINDOW_HEIGHT : WINDOW_WIDTH; int height = (target->transform & WL_OUTPUT_TRANSFORM_90) ? WINDOW_WIDTH : WINDOW_HEIGHT; int logical_width = (int)ceil(width / (float)target->scale); int logical_height = (int)ceil(height / (float)target->scale); cursor->geo.x = ceil(cursor->lx - logical_width * 0.5); cursor->geo.y = ceil(cursor->ly - logical_height * 0.5); cursor->geo.width = logical_width; cursor->geo.height = logical_height; } static void zoom_window_add_damage(struct kywc_box *geo, struct ky_scene *scene) { pixman_region32_t region; pixman_region32_init_rect(®ion, geo->x, geo->y, geo->width, geo->height); ky_scene_add_damage(scene, ®ion); pixman_region32_fini(®ion); } static bool handle_window_frame_render_pre(struct effect_entity *entity, struct ky_scene_render_target *target) { struct zoom_effect *effect = entity->user_data; struct ky_scene *scene = effect->server->scene; struct seat_cursor *cursor; wl_list_for_each(cursor, &effect->seat_cursors, link) { struct kywc_box old_geo = cursor->geo; zoom_window_update_geo(cursor, target); if (!kywc_box_equal(&old_geo, &cursor->geo)) { zoom_window_add_damage(&old_geo, scene); zoom_window_add_damage(&cursor->geo, scene); return true; } if (pixman_region32_not_empty(&scene->collected_damage) || pixman_region32_not_empty(&scene->pushed_damage)) { struct wlr_box box = { cursor->geo.x, cursor->geo.y, cursor->geo.width, cursor->geo.height }; if (!wlr_box_intersection(&box, &target->logical, &box)) { continue; } zoom_window_add_damage(&cursor->geo, scene); } } return true; } static bool handle_frame_render_pre(struct effect_entity *entity, struct ky_scene_render_target *target) { struct zoom_effect *effect = entity->user_data; if (!effect->enabled) { return true; } if (effect->type == ZOOM_OUTPUT) { return handle_output_frame_render_pre(entity, target); } else if (effect->type == ZOOM_WINDOW) { return handle_window_frame_render_pre(entity, target); } return true; } static bool handle_frame_render_end(struct effect_entity *entity, struct ky_scene_render_target *target) { struct zoom_effect *effect = entity->user_data; if (!effect->enabled || effect->type == ZOOM_OUTPUT || effect->scale == 1) { return true; } struct seat_cursor *cursor; wl_list_for_each(cursor, &effect->seat_cursors, link) { struct wlr_box dst_box = { .x = cursor->geo.x - target->logical.x, .y = cursor->geo.y - target->logical.y, .width = cursor->geo.width, .height = cursor->geo.height, }; ky_scene_render_box(&dst_box, target); int src_width = dst_box.width / effect->scale; int src_height = dst_box.height / effect->scale; struct wlr_box buffer_src_box = { .x = dst_box.x + (dst_box.width - src_width) / 2.0f, .y = dst_box.y + (dst_box.height - src_height) / 2.0f, .width = src_width, .height = src_height, }; struct wlr_box src_box; struct wlr_box target_box = { 0, 0, target->buffer->width, target->buffer->height }; if (!wlr_box_intersection(&src_box, &buffer_src_box, &target_box)) { continue; } pixman_region32_t render_region; pixman_region32_init(&render_region); pixman_region32_copy(&render_region, &target->damage); pixman_region32_translate(&render_region, -target->logical.x, -target->logical.y); ky_scene_render_region(&render_region, target); pixman_region32_intersect_rect(&render_region, &render_region, dst_box.x, dst_box.y, dst_box.width, dst_box.height); if (!pixman_region32_not_empty(&render_region)) { pixman_region32_fini(&render_region); continue; } struct wlr_texture *wlr_texture = wlr_texture_from_buffer(effect->server->renderer, target->buffer); if (!wlr_texture) { pixman_region32_fini(&render_region); continue; } struct ky_render_texture_options options = { .base = { .texture = wlr_texture, .src_box = { src_box.x, src_box.y, src_box.width, src_box.height }, .dst_box = dst_box, .transform = WL_OUTPUT_TRANSFORM_NORMAL, }, .repeated = false, }; ky_render_pass_add_texture(target->render_pass, &options); wlr_texture_destroy(wlr_texture); pixman_region32_fini(&render_region); pixman_region32_t region; pixman_region32_t clip_region; pixman_region32_init(&clip_region); pixman_region32_init_rect(®ion, dst_box.x, dst_box.y, dst_box.width, dst_box.height); struct wlr_box inner_box = { .x = dst_box.x + WINDOW_BORDER, .y = dst_box.y + WINDOW_BORDER, .width = dst_box.width - 2 * WINDOW_BORDER, .height = dst_box.height - 2 * WINDOW_BORDER, }; pixman_region32_t inner_region; pixman_region32_init_rect(&inner_region, inner_box.x, inner_box.y, inner_box.width, inner_box.height); pixman_region32_subtract(&clip_region, ®ion, &inner_region); struct ky_render_rect_options border = { .base = { .box = dst_box, .clip = &clip_region, .color = { 1, 1, 1, 1.0 }, }, }; ky_render_pass_add_rect(target->render_pass, &border); pixman_region32_fini(&clip_region); pixman_region32_fini(®ion); pixman_region32_fini(&inner_region); } return true; } static void zoom_output_destroy(struct zoom_output *output) { wlr_output_lock_software_cursors(output->output->wlr_output, false); wl_list_remove(&output->link); wl_list_remove(&output->disable.link); free(output); } static void output_handle_disable(struct wl_listener *listener, void *data) { struct zoom_output *output = wl_container_of(listener, output, disable); zoom_output_destroy(output); } static void zoom_output_create(struct zoom_effect *effect, struct kywc_output *kywc_output) { struct zoom_output *output = calloc(1, sizeof(*output)); if (!output) { return; } output->output = output_from_kywc_output(kywc_output); output->disable.notify = output_handle_disable; wl_signal_add(&output->output->events.disable, &output->disable); wl_list_insert(&effect->outputs, &output->link); wlr_output_lock_software_cursors(output->output->wlr_output, true); } static bool output_create_request(struct kywc_output *kywc_output, int index, void *data) { zoom_output_create(zoom, kywc_output); return false; } static void handle_new_enabled_output(struct wl_listener *listener, void *data) { struct kywc_output *kywc_output = data; zoom_output_create(zoom, kywc_output); } static void zoom_init_state(struct zoom_effect *zoom) { input_manager_for_each_seat(handle_seat, zoom); seat_add_new_listener(&zoom->new_seat); if (zoom->type == ZOOM_OUTPUT) { pixman_region32_init(&zoom->output_info.viewport_region); output_manager_for_each_output(output_create_request, true, zoom); zoom->new_enabled_output.notify = handle_new_enabled_output; output_manager_add_new_enabled_listener(&zoom->new_enabled_output); } } static void zoom_reset_state(struct zoom_effect *effect, uint32_t old_type) { wl_list_remove(&effect->new_seat.link); wl_list_init(&effect->new_seat.link); struct seat_cursor *cursor, *cursor_tmp; wl_list_for_each_safe(cursor, cursor_tmp, &effect->seat_cursors, link) { seat_cursor_destroy(cursor); } if (old_type == ZOOM_OUTPUT) { pixman_region32_fini(&effect->output_info.viewport_region); wl_list_remove(&effect->new_enabled_output.link); wl_list_init(&effect->new_enabled_output.link); struct zoom_output *output, *output_tmp; wl_list_for_each_safe(output, output_tmp, &effect->outputs, link) { zoom_output_destroy(output); } struct ky_scene_output *scene_output; wl_list_for_each(scene_output, &effect->scene->outputs, link) { ky_scene_output_set_viewport_source_box(scene_output, NULL); } } } static void zoom_client_destory(void) { if (!zoom->client) { return; } wl_client_destroy(zoom->client); zoom->client = NULL; } static int start_zoom_effect(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { uint32_t type, scale; CK(sd_bus_message_read(m, "uu", &type, &scale)); if (type != ZOOM_OUTPUT && type != ZOOM_WINDOW) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST( "org.ukui.KWin.Zoom.Error.Cancelled", "Invalid zoom type, 0 = output, 1 = window"); return sd_bus_reply_method_error(m, &error); } if (scale < 1 || scale > 16) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Scale must be between 1 to 16 "); return sd_bus_reply_method_error(m, &error); } if (zoom->enabled) { return sd_bus_reply_method_return(m, "b", false); } if (!zoom->renderer_is_opengl && type == ZOOM_WINDOW) { return sd_bus_reply_method_return(m, "b", false); } zoom->enabled = true; zoom->type = type; zoom->scale = scale; zoom_init_state(zoom); zoom_client_destory(); zoom->client = spawn_client(zoom->server->display, command); ky_scene_damage_whole(zoom->scene); dbus_emit_signal(zoom_path, zoom_dbus_interface, "zoomStart", ""); return sd_bus_reply_method_return(m, "b", true); } static int set_zoom_type(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { uint32_t new_type; CK(sd_bus_message_read(m, "u", &new_type)); if (new_type != ZOOM_OUTPUT && new_type != ZOOM_WINDOW) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST( SD_BUS_ERROR_INVALID_ARGS, "Invalid zoom type, 0 = output, 1 = window"); return sd_bus_reply_method_error(m, &error); } if (zoom->type == new_type) { return sd_bus_reply_method_return(m, "b", false); } if (!zoom->renderer_is_opengl && new_type == ZOOM_WINDOW) { return sd_bus_reply_method_return(m, "b", false); } uint32_t old_type = zoom->type; zoom->type = new_type; effect_set_option_int(zoom->effect, "type", new_type); if (zoom->enabled) { zoom_reset_state(zoom, old_type); zoom_init_state(zoom); ky_scene_damage_whole(zoom->scene); } return sd_bus_reply_method_return(m, "b", true); } static int stop_zoom_effect(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { if (!zoom->enabled) { return sd_bus_reply_method_return(m, NULL); } zoom->enabled = false; zoom_reset_state(zoom, zoom->type); ky_scene_damage_whole(zoom->scene); zoom_client_destory(); dbus_emit_signal(zoom_path, zoom_dbus_interface, "zoomStop", ""); return sd_bus_reply_method_return(m, NULL); } static int get_zoom_enabled(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { return sd_bus_reply_method_return(m, "b", zoom->enabled); } static int get_zoom_scale(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { return sd_bus_reply_method_return(m, "i", zoom->scale); } static int get_zoom_type(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { return sd_bus_reply_method_return(m, "i", zoom->type); } static int set_zoom_out(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { if (!zoom->enabled || zoom->scale <= 1) { return sd_bus_reply_method_return(m, "b", false); } zoom->scale--; ky_scene_damage_whole(zoom->scene); dbus_emit_signal(zoom_path, zoom_dbus_interface, "scaleChanged", "u", zoom->scale); return sd_bus_reply_method_return(m, "b", true); } static int set_zoom_in(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { if (!zoom->enabled || zoom->scale >= MAX_SCALE) { return sd_bus_reply_method_return(m, "b", false); } zoom->scale++; ky_scene_damage_whole(zoom->scene); dbus_emit_signal(zoom_path, zoom_dbus_interface, "scaleChanged", "u", zoom->scale); return sd_bus_reply_method_return(m, "b", true); } static const sd_bus_vtable zoom_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("StartZoom", "uu", "b", start_zoom_effect, 0), SD_BUS_METHOD("SetMagnifierMode", "u", "b", set_zoom_type, 0), SD_BUS_METHOD("ZoomIn", "", "b", set_zoom_in, 0), SD_BUS_METHOD("ZoomOut", "", "b", set_zoom_out, 0), SD_BUS_METHOD("IsZooming", "", "b", get_zoom_enabled, 0), SD_BUS_METHOD("GetScale", "", "i", get_zoom_scale, 0), SD_BUS_METHOD("GetType", "", "i", get_zoom_type, 0), SD_BUS_METHOD("StopZoom", "", "", stop_zoom_effect, 0), SD_BUS_VTABLE_END, }; static void zoom_shortcut_in(struct key_binding *binding, void *data) { if (!zoom->enabled) { if (zoom->type == ZOOM_OUTPUT || (zoom->type == ZOOM_WINDOW && zoom->renderer_is_opengl)) { zoom->enabled = true; zoom_client_destory(); zoom->client = spawn_client(zoom->server->display, command); zoom_init_state(zoom); ky_scene_damage_whole(zoom->scene); dbus_emit_signal(zoom_path, zoom_dbus_interface, "zoomStart", ""); } return; } if (zoom->scale < MAX_SCALE) { zoom->scale++; ky_scene_damage_whole(zoom->scene); dbus_emit_signal(zoom_path, zoom_dbus_interface, "scaleChanged", "u", zoom->scale); } } static void zoom_shortcut_out(struct key_binding *binding, void *data) { if (!zoom->enabled || zoom->scale <= 1) { return; } zoom->scale--; ky_scene_damage_whole(zoom->scene); dbus_emit_signal(zoom_path, zoom_dbus_interface, "scaleChanged", "u", zoom->scale); } static void zoom_shortcut_exit(struct key_binding *binding, void *data) { if (!zoom->enabled) { return; } zoom->enabled = false; zoom_reset_state(zoom, zoom->type); ky_scene_damage_whole(zoom->scene); zoom_client_destory(); dbus_emit_signal(zoom_path, zoom_dbus_interface, "zoomStop", ""); } static void shortcut_action(struct key_binding *binding, void *data) { struct shortcut *shortcut = data; switch (shortcut->action) { case ZOOM_IN: zoom_shortcut_in(binding, data); break; case ZOOM_OUT: zoom_shortcut_out(binding, data); break; case ZOOM_STOP: zoom_shortcut_exit(binding, data); break; } } static void zoom_register_shortcuts(void) { for (size_t i = 0; i < ARRAY_SIZE(shortcuts); i++) { struct shortcut *shortcut = &shortcuts[i]; struct key_binding *binding = kywc_key_binding_create(shortcut->keybind, shortcut->desc); if (!binding) { continue; } if (!kywc_key_binding_register(binding, KEY_BINDING_TYPE_ZOOM, shortcut_action, shortcut)) { kywc_key_binding_destroy(binding); continue; } } } static void handle_effect_enable(struct wl_listener *listener, void *data) { zoom->dbus = dbus_register_object(NULL, zoom_path, zoom_dbus_interface, zoom_vtable, zoom); } static void handle_effect_disable(struct wl_listener *listener, void *data) { dbus_unregister_object(zoom->dbus); if (!zoom->enabled) { return; } zoom->enabled = false; zoom_reset_state(zoom, zoom->type); ky_scene_damage_whole(zoom->scene); zoom_client_destory(); } static void handle_effect_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&zoom->destroy.link); wl_list_remove(&zoom->enable.link); wl_list_remove(&zoom->disable.link); wl_list_remove(&zoom->new_seat.link); free(zoom); zoom = NULL; } static bool handle_allow_direct_scanout(struct effect *effect, struct ky_scene_render_target *target) { if (!zoom->enabled) { return true; } if (zoom->type == ZOOM_OUTPUT) { return !ky_scene_find_effect_entity(zoom->scene, zoom->effect); } struct seat_cursor *cursor; wl_list_for_each(cursor, &zoom->seat_cursors, link) { struct wlr_box box; struct wlr_box geo = { cursor->geo.x, cursor->geo.y, cursor->geo.width, cursor->geo.height }; if (wlr_box_intersection(&box, &target->logical, &geo)) { return false; } } return true; } static const struct effect_interface zoom_impl = { .frame_render_pre = handle_frame_render_pre, .frame_render_end = handle_frame_render_end, .configure = handle_effect_configure, .allow_direct_scanout = handle_allow_direct_scanout, }; bool zoom_effect_create(struct effect_manager *effect_manager) { zoom = calloc(1, sizeof(*zoom)); if (!zoom) { return false; } zoom->effect = effect_create("zoom", 122, false, &zoom_impl, zoom); if (!zoom->effect) { free(zoom); zoom = NULL; return false; } struct effect_entity *entity = ky_scene_add_effect(effect_manager->server->scene, zoom->effect); if (!entity) { effect_destroy(zoom->effect); free(zoom); zoom = NULL; return false; } entity->user_data = zoom; zoom->server = effect_manager->server; zoom->scene = effect_manager->server->scene; zoom->renderer_is_opengl = wlr_renderer_is_opengl(effect_manager->server->renderer); zoom->enabled = false; zoom->scale = effect_get_option_int(zoom->effect, "scale", 2); zoom->type = effect_get_option_int(zoom->effect, "type", ZOOM_WINDOW); wl_list_init(&zoom->seat_cursors); wl_list_init(&zoom->outputs); zoom->enable.notify = handle_effect_enable; wl_signal_add(&zoom->effect->events.enable, &zoom->enable); zoom->disable.notify = handle_effect_disable; wl_signal_add(&zoom->effect->events.disable, &zoom->disable); zoom->destroy.notify = handle_effect_destroy; wl_signal_add(&zoom->effect->events.destroy, &zoom->destroy); zoom->new_seat.notify = handle_new_seat; wl_list_init(&zoom->new_seat.link); if (zoom->effect->enabled) { handle_effect_enable(&zoom->enable, zoom); } zoom_register_shortcuts(); return true; } kylin-wayland-compositor/src/effect/ukui_effect.c0000664000175000017500000006014315160461067021151 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "effect/action.h" #include "effect/animator.h" #include "effect_p.h" #include "server.h" #include "ukui-effect-v1-protocol.h" #include "view/view.h" #define UKUI_FLOAT_PRECISION 100.0f enum ukui_effect_stage { UKUI_EFFECT_STAGE_UNKNOW = 0, UKUI_EFFECT_STAGE_MAP = 1 << 0, UKUI_EFFECT_STAGE_UNMAP = 1 << 1, UKUI_EFFECT_STAGE_ALL = (1 << 2) - 1, }; enum effect_options_mask { EFFECT_OPTION_NONE = 0, EFFECT_OPTION_XY = 1 << 0, EFFECT_OPTION_TYPE = 1 << 1, EFFECT_OPTION_SIZE = 1 << 2, EFFECT_OPTION_ALPHA = 1 << 3, EFFECT_OPTION_OFFSET = 1 << 4, EFFECT_OPTION_DURATION = 1 << 5, EFFECT_OPTION_ANIMATION = 1 << 6, EFFECT_OPTION_ALL = (1 << 7) - 1, }; struct ukui_effect { struct wl_global *global; struct wl_list resources; struct wl_list builtin_animations; struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct effect_animation { struct wl_list link; struct ukui_effect *effect; struct animation *base_animation; struct wl_resource *resource; struct wl_list clients; // effect_animation_client.link }; struct effect_animation_client { struct wl_resource *resource; struct wl_list link; }; struct ukui_effect_surface { struct wlr_surface *surface; struct wl_list effect_entities; struct wlr_addon addon; }; struct ukui_effect_entities { uint32_t stages; uint32_t options_mask; enum action_effect_type type; struct action_effect_options options; struct wl_list link; struct wl_resource *resource; }; static struct effect_animation_client * find_effect_animation_client(struct effect_animation *animation, struct wl_client *wl_client) { struct effect_animation_client *client; wl_list_for_each(client, &animation->clients, link) { if (wl_client == wl_resource_get_client(client->resource)) { return client; } } return NULL; } static void effect_animation_resource_destroy(struct wl_resource *resource) { struct effect_animation *animation = wl_resource_get_user_data(resource); if (!animation) { return; } if (wl_list_empty(&animation->clients)) { kywc_log(KYWC_DEBUG, "custom animation's clients is empty, destroy animation: %p", animation->base_animation); animation_manager_destroy_animation(animation->base_animation); free(animation); return; } /* builtin_animations */ struct effect_animation_client *effect_client = find_effect_animation_client(animation, wl_resource_get_client(resource)); if (!effect_client) { return; } wl_list_remove(&effect_client->link); free(effect_client); } static void animation_curve_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct animation_curve_v1_interface animation_curve_impl = { .destroy = animation_curve_handle_destroy, }; static struct effect_animation_client * create_effect_animation_client_for_resource(struct effect_animation *animation, struct wl_resource *effect_client_resource) { struct effect_animation_client *animation_client = calloc(1, sizeof(*animation_client)); if (!animation_client) { return NULL; } struct wl_client *client = wl_resource_get_client(effect_client_resource); struct wl_resource *animation_client_resource = wl_resource_create( client, &animation_curve_v1_interface, wl_resource_get_version(effect_client_resource), 0); if (!animation_client_resource) { wl_client_post_no_memory(client); free(animation_client); return NULL; } animation_client->resource = animation_client_resource; wl_list_insert(&animation->clients, &animation_client->link); wl_resource_set_implementation(animation_client_resource, &animation_curve_impl, animation, effect_animation_resource_destroy); ukui_effect_v1_send_builtin_animation_curves( effect_client_resource, animation_client->resource, animation->base_animation->p1.x * 100, animation->base_animation->p1.y * 100, animation->base_animation->p2.x * 100, animation->base_animation->p2.y * 100); return animation_client; } static bool check_value(uint32_t x1, uint32_t x2, uint32_t y1, uint32_t y2) { return x1 > 100 || x2 > 100 || y1 > 100 || y2 > 100; } static struct effect_animation *create_effect_animation(struct ukui_effect *effect, struct wl_resource *resource, struct animation *base_animation) { struct effect_animation *animation = calloc(1, sizeof(*animation)); if (!animation) { return NULL; } animation->base_animation = base_animation; animation->resource = resource; animation->effect = effect; wl_list_init(&animation->clients); return animation; } static void handle_create_animation(struct wl_client *client, struct wl_resource *effect_resource, uint32_t id, uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2) { if (check_value(x1, x2, y1, y2)) { wl_resource_post_error(effect_resource, UKUI_EFFECT_V1_ERROR_OUT_OF_RANGE, "value is out of range"); return; } int version = wl_resource_get_version(effect_resource); struct wl_resource *resource = wl_resource_create(client, &animation_curve_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &animation_curve_impl, NULL, effect_animation_resource_destroy); struct animation *base_animation = animation_manager_create_animation( (struct point){ x1 / 100.0f, y1 / 100.0f }, (struct point){ x2 / 100.0f, y2 / 100.0f }); if (!base_animation) { return; } struct ukui_effect *effect = wl_resource_get_user_data(effect_resource); struct effect_animation *animation = create_effect_animation(effect, resource, base_animation); if (!animation) { animation_manager_destroy_animation(base_animation); return; } wl_resource_set_user_data(resource, animation); } static struct ukui_effect_entities *find_ukui_effect(struct ukui_effect_surface *surface, uint32_t stages) { struct ukui_effect_entities *effect; wl_list_for_each(effect, &surface->effect_entities, link) { if (effect->stages & stages) { return effect; } } return NULL; } static void ukui_effect_entities_destroy(struct ukui_effect_entities *effect) { wl_list_remove(&effect->link); free(effect); } static struct ukui_effect_entities *create_ukui_effect_entities(struct ukui_effect_surface *surface, struct wl_resource *resource, uint32_t stages, enum action_effect_type type) { struct ukui_effect_entities *effect_entities = calloc(1, sizeof(*effect_entities)); if (!effect_entities) { return NULL; } effect_entities->type = type; effect_entities->stages = stages; effect_entities->resource = resource; wl_list_insert(&surface->effect_entities, &effect_entities->link); return effect_entities; } static void ukui_effect_surface_destroy(struct wlr_addon *addon) { struct ukui_effect_surface *effect_surface = wl_container_of(addon, effect_surface, addon); wlr_addon_finish(&effect_surface->addon); struct ukui_effect_entities *effect_entities, *tmp; wl_list_for_each_safe(effect_entities, tmp, &effect_surface->effect_entities, link) { wl_list_remove(&effect_entities->link); wl_list_init(&effect_entities->link); } free(effect_surface); } static const struct wlr_addon_interface ukui_effect_surface_impl = { .name = "ukui_effect_surface", .destroy = ukui_effect_surface_destroy, }; static struct ukui_effect_surface *create_ukui_effect_surface(struct wlr_surface *wlr_surface) { struct ukui_effect_surface *effect_surface = calloc(1, sizeof(*effect_surface)); if (!effect_surface) { return NULL; } effect_surface->surface = wlr_surface; wl_list_init(&effect_surface->effect_entities); wlr_addon_init(&effect_surface->addon, &wlr_surface->addons, wlr_surface, &ukui_effect_surface_impl); return effect_surface; } static void fade_effect_resource_destroy(struct wl_resource *resource) { struct ukui_effect_entities *effect = wl_resource_get_user_data(resource); if (effect) { ukui_effect_entities_destroy(effect); } } static void fade_effect_handle_set_animation(struct wl_client *client, struct wl_resource *resource, struct wl_resource *geometry, struct wl_resource *opacity) { struct ukui_effect_entities *surface_effect = wl_resource_get_user_data(resource); if (!surface_effect) { return; } surface_effect->options_mask |= EFFECT_OPTION_ANIMATION; struct effect_animation *geometry_animation = geometry ? wl_resource_get_user_data(geometry) : NULL; struct effect_animation *opacity_animation = opacity ? wl_resource_get_user_data(opacity) : NULL; surface_effect->options.animations.geometry = geometry_animation ? geometry_animation->base_animation : NULL; surface_effect->options.animations.alpha = opacity_animation ? opacity_animation->base_animation : NULL; } static void fade_effect_handle_set_duration(struct wl_client *client, struct wl_resource *resource, uint32_t duration) { struct ukui_effect_entities *surface_effect = wl_resource_get_user_data(resource); if (!surface_effect) { return; } surface_effect->options_mask |= EFFECT_OPTION_DURATION; surface_effect->options.duration = duration; } static void fade_effect_handle_set_opacity(struct wl_client *client, struct wl_resource *resource, uint32_t opacity) { struct ukui_effect_entities *surface_effect = wl_resource_get_user_data(resource); if (!surface_effect) { return; } surface_effect->options_mask |= EFFECT_OPTION_ALPHA; surface_effect->options.alpha = opacity / UKUI_FLOAT_PRECISION; } static void fade_effect_handle_set_scale(struct wl_client *client, struct wl_resource *resource, uint32_t scale_type, uint32_t width_scale, uint32_t height_scale) { struct ukui_effect_entities *surface_effect = wl_resource_get_user_data(resource); if (!surface_effect) { return; } surface_effect->options_mask |= EFFECT_OPTION_TYPE; surface_effect->options_mask |= EFFECT_OPTION_SIZE; surface_effect->options.style = scale_type; surface_effect->options.width_scale = width_scale / UKUI_FLOAT_PRECISION; surface_effect->options.height_scale = height_scale / UKUI_FLOAT_PRECISION; } static void fade_effect_handle_set_offset(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y) { struct ukui_effect_entities *surface_effect = wl_resource_get_user_data(resource); if (!surface_effect) { return; } surface_effect->options_mask |= EFFECT_OPTION_OFFSET; surface_effect->options.x_offset = x; surface_effect->options.y_offset = y; } static void fade_effect_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct fade_effect_v1_interface fade_effect_impl = { .set_animation = fade_effect_handle_set_animation, .set_duration = fade_effect_handle_set_duration, .set_opacity = fade_effect_handle_set_opacity, .set_scale = fade_effect_handle_set_scale, .set_offset = fade_effect_handle_set_offset, .destroy = fade_effect_handle_destroy, }; static void handle_create_fade_effect(struct wl_client *client, struct wl_resource *effect_resource, uint32_t id, struct wl_resource *surface, uint32_t stage) { struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface); struct ukui_effect_surface *effect_surface = NULL; struct wlr_addon *ukui_addon = wlr_addon_find(&wlr_surface->addons, wlr_surface, &ukui_effect_surface_impl); if (ukui_addon) { effect_surface = wl_container_of(ukui_addon, effect_surface, addon); } if (!effect_surface) { effect_surface = create_ukui_effect_surface(wlr_surface); } else if (find_ukui_effect(effect_surface, stage)) { wl_resource_post_error(effect_resource, UKUI_EFFECT_V1_ERROR_EFFECT_EXIST, "effect has existed on the same stage"); return; } if (!effect_surface) { wl_client_post_no_memory(client); return; } int version = wl_resource_get_version(effect_resource); struct wl_resource *effect_entities_resource = wl_resource_create(client, &fade_effect_v1_interface, version, id); if (!effect_entities_resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(effect_entities_resource, &fade_effect_impl, NULL, fade_effect_resource_destroy); struct ukui_effect_entities *effect_entities = create_ukui_effect_entities( effect_surface, effect_entities_resource, stage, ACTION_EFFECT_FADE); if (!effect_entities) { return; } wl_resource_set_user_data(effect_entities_resource, effect_entities); } static void slide_effect_resource_destroy(struct wl_resource *resource) { struct ukui_effect_entities *effect_entities = wl_resource_get_user_data(resource); if (effect_entities) { ukui_effect_entities_destroy(effect_entities); } } static void slide_effect_handle_set_duration(struct wl_client *client, struct wl_resource *resource, uint32_t duration) { struct ukui_effect_entities *effect_entities = wl_resource_get_user_data(resource); if (!effect_entities) { return; } effect_entities->options_mask |= EFFECT_OPTION_DURATION; effect_entities->options.duration = duration; } static void slide_effect_handle_set_pos(struct wl_client *client, struct wl_resource *resource, uint32_t location, int32_t offset) { struct ukui_effect_entities *effect_entities = wl_resource_get_user_data(resource); if (!effect_entities) { return; } effect_entities->options_mask |= EFFECT_OPTION_XY; effect_entities->options.location = location; effect_entities->options.y_offset = offset; } static void slide_effect_handle_set_opacity(struct wl_client *client, struct wl_resource *resource, uint32_t opacity) { struct ukui_effect_entities *effect_entities = wl_resource_get_user_data(resource); if (!effect_entities) { return; } effect_entities->options_mask |= EFFECT_OPTION_ALPHA; effect_entities->options.alpha = opacity / UKUI_FLOAT_PRECISION; } static void slide_effect_handle_set_type(struct wl_client *client, struct wl_resource *resource, uint32_t type) { struct ukui_effect_entities *effect_entities = wl_resource_get_user_data(resource); if (!effect_entities) { return; } effect_entities->options_mask |= EFFECT_OPTION_TYPE; effect_entities->options.style = type; } static void slide_effect_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct slide_effect_v1_interface slide_effect_impl = { .set_duration = slide_effect_handle_set_duration, .set_opacity = slide_effect_handle_set_opacity, .set_position = slide_effect_handle_set_pos, .set_type = slide_effect_handle_set_type, .destroy = slide_effect_handle_destroy, }; static void handle_create_slide_effect(struct wl_client *client, struct wl_resource *effect_resource, uint32_t id, struct wl_resource *surface, uint32_t stage) { struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface); struct ukui_effect_surface *effect_surface = NULL; struct wlr_addon *ukui_addon = wlr_addon_find(&wlr_surface->addons, wlr_surface, &ukui_effect_surface_impl); if (ukui_addon) { effect_surface = wl_container_of(ukui_addon, effect_surface, addon); } if (!effect_surface) { effect_surface = create_ukui_effect_surface(wlr_surface); } else if (find_ukui_effect(effect_surface, stage)) { wl_resource_post_error(effect_resource, UKUI_EFFECT_V1_ERROR_EFFECT_EXIST, "effect has existed on the same stage"); return; } if (!effect_surface) { wl_client_post_no_memory(client); return; } int version = wl_resource_get_version(effect_resource); struct wl_resource *entities_resource = wl_resource_create(client, &slide_effect_v1_interface, version, id); if (!entities_resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(entities_resource, &slide_effect_impl, NULL, slide_effect_resource_destroy); struct ukui_effect_entities *effect_entities = create_ukui_effect_entities(effect_surface, entities_resource, stage, ACTION_EFFECT_SLIDE); if (!effect_entities) { return; } wl_resource_set_user_data(entities_resource, effect_entities); } static void handle_ukui_effect_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static const struct ukui_effect_v1_interface ukui_effect_impl = { .create_animation_curve = handle_create_animation, .create_fade_effect = handle_create_fade_effect, .create_slide_effect = handle_create_slide_effect, .destroy = handle_ukui_effect_destroy, }; static void ukui_effect_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static void ukui_effect_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct ukui_effect *effect = data; struct wl_resource *effect_client_resource = wl_resource_create(client, &ukui_effect_v1_interface, version, id); if (!effect_client_resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(effect_client_resource, &ukui_effect_impl, effect, ukui_effect_resource_destroy); wl_list_insert(&effect->resources, wl_resource_get_link(effect_client_resource)); /* send all builtin_animations */ struct effect_animation *animation; wl_list_for_each(animation, &effect->builtin_animations, link) { create_effect_animation_client_for_resource(animation, effect_client_resource); } ukui_effect_v1_send_done(effect_client_resource); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct ukui_effect *effect = wl_container_of(listener, effect, display_destroy); wl_list_remove(&effect->display_destroy.link); wl_global_destroy(effect->global); } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct ukui_effect *effect = wl_container_of(listener, effect, server_destroy); wl_list_remove(&effect->server_destroy.link); struct effect_animation *animation, *temp; wl_list_for_each_safe(animation, temp, &effect->builtin_animations, link) { wl_list_remove(&animation->link); free(animation); } free(effect); } static bool animation_manager_builtin_animation_callback(struct animation *base_animation, void *data) { struct ukui_effect *effect = data; struct effect_animation *animation = create_effect_animation(effect, NULL, base_animation); if (!animation) { return false; } wl_list_insert(&effect->builtin_animations, &animation->link); return false; } static void apply_options(struct action_effect_options *ukui_options, uint32_t options_mask, struct action_effect_options *options) { if (options_mask & EFFECT_OPTION_XY) { options->y_offset = ukui_options->y_offset; options->location = ukui_options->location; } if (options_mask & EFFECT_OPTION_TYPE) { options->style = ukui_options->style; } if (options_mask & EFFECT_OPTION_SIZE) { options->width_scale = ukui_options->width_scale; options->height_scale = ukui_options->height_scale; } if (options_mask & EFFECT_OPTION_ANIMATION) { options->animations = ukui_options->animations; } if (options_mask & EFFECT_OPTION_DURATION) { options->duration = ukui_options->duration; } if (options_mask & EFFECT_OPTION_ALPHA) { options->alpha = ukui_options->alpha; } if (options_mask & EFFECT_OPTION_OFFSET) { options->x_offset = ukui_options->x_offset; options->y_offset = ukui_options->y_offset; } } static enum ukui_effect_stage action_to_ukui_stage(enum effect_action action) { switch (action) { case EFFECT_ACTION_MAP: return UKUI_EFFECT_STAGE_MAP; case EFFECT_ACTION_UNMAP: return UKUI_EFFECT_STAGE_UNMAP; default: return UKUI_EFFECT_STAGE_UNKNOW; } } static void action_effect_options_process(enum action_effect_options_step step, enum effect_action action, struct action_effect_options *options, void *user_data) { struct wlr_surface *surface = options->surface; if (!surface) { return; } struct wlr_addon *ukui_addon = wlr_addon_find(&surface->addons, surface, &ukui_effect_surface_impl); if (!ukui_addon) { return; } struct ukui_effect_surface *effect_surface = wl_container_of(ukui_addon, effect_surface, addon); struct ukui_effect_entities *effect = find_ukui_effect(effect_surface, action_to_ukui_stage(action)); if (!effect) { return; } struct action_effect *action_effect = action_effect_manager_get_effect(effect->type); if (!action_effect) { kywc_log(KYWC_WARN, "effect type: %d not found.", effect->type); return; } if (!action_effect_init_options(action_effect, options)) { options->effect_type = effect->type; kywc_log(KYWC_WARN, "effect type: %d havn't default options.", effect->type); } apply_options(&effect->options, effect->options_mask, options); } bool ukui_effect_create(struct server *server) { struct ukui_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } effect->global = wl_global_create(server->display, &ukui_effect_v1_interface, 1, effect, ukui_effect_bind); if (!effect->global) { kywc_log(KYWC_WARN, "surface effect manager create failed"); free(effect); return false; } wl_list_init(&effect->resources); wl_list_init(&effect->builtin_animations); effect->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &effect->server_destroy); effect->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &effect->display_destroy); animation_manager_for_each_builtin_animation(animation_manager_builtin_animation_callback, effect); view_manager_set_effect_options_impl(action_effect_options_process); return true; } kylin-wayland-compositor/src/effect/effect.c0000664000175000017500000011742615160461067020123 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include "effect/animator.h" #include "effect_p.h" #include "scene/thumbnail.h" #include "util/macros.h" enum effect_type { EFFECT_TYPE_NONE = 0, EFFECT_TYPE_NODE = 1 << 0, EFFECT_TYPE_VIEW = 1 << 1, EFFECT_TYPE_SCENE = 1 << 2, }; static struct effect_manager *manager = NULL; static const struct wlr_addon_interface effect_addon_impl; static void wlr_box_to_kywc_box(struct wlr_box *box, struct kywc_box *kywc_box) { kywc_box->x = box->x; kywc_box->y = box->y; kywc_box->width = box->width; kywc_box->height = box->height; } static uint32_t get_effect_types(const struct effect_interface *impl) { uint32_t types = 0; if (!impl) { return 0; } if (impl->frame_render || impl->frame_render_pre || impl->frame_render_begin || impl->frame_render_end || impl->frame_render_post) { types |= EFFECT_TYPE_SCENE; } if (impl->node_render || impl->node_push_damage || impl->entity_bounding_box || impl->entity_clip_region || impl->entity_opaque_region) { types |= EFFECT_TYPE_NODE; } return types; } /* Do not trigger damage event that node with effect entity */ void effect_entity_push_damage(struct effect_entity *entity, uint32_t damage_type) { struct kywc_box box = { 0 }; if (entity->effect->impl->entity_bounding_box) { entity->effect->impl->entity_bounding_box(entity, &box); } pixman_region32_t damage_region; pixman_region32_init_rect(&damage_region, box.x, box.y, box.width, box.height); if (!pixman_region32_not_empty(&damage_region)) { pixman_region32_fini(&damage_region); return; } struct effect_chain *chain = entity->slot.chain; struct node_effect_chain *node_chain = wl_container_of(chain, node_chain, base); node_chain->damage_type |= damage_type; pixman_region32_translate(&damage_region, node_chain->node->x, node_chain->node->y); struct ky_scene_node *parent_node = &node_chain->node->parent->node; parent_node->impl.push_damage(parent_node, node_chain->node, node_chain->damage_type, &damage_region); pixman_region32_fini(&damage_region); } static void calc_bounding(int *min_x1, int *min_y1, int *max_x2, int *max_y2, struct kywc_box *box) { *min_x1 = MIN(*min_x1, box->x); *min_y1 = MIN(*min_y1, box->y); *max_x2 = MAX(*max_x2, box->x + box->width); *max_y2 = MAX(*max_y2, box->y + box->height); } void effect_entity_get_bounding_box(struct effect_entity *entity, enum ky_scene_bounding_type type, struct kywc_box *box) { assert(entity->effect->types & EFFECT_TYPE_NODE); assert(!(type & KY_SCENE_BOUNDING_WITHOUT_EFFECT_NODE)); struct effect_chain *chain = entity->slot.chain; struct node_effect_chain *node_chain = wl_container_of(chain, node_chain, base); struct ky_scene_node *node = node_chain->node; bool stash_enabled = node->enabled; struct wlr_box _box; if (type == KY_SCENE_BOUNDING_WITHOUT_EFFECT) { node->enabled = true; node_chain->impl.get_bounding_box(node, &_box); node->enabled = stash_enabled; wlr_box_to_kywc_box(&_box, box); return; } int min_x1 = INT_MAX, min_y1 = INT_MAX; int max_x2 = INT_MIN, max_y2 = INT_MIN; bool ret = false; struct kywc_box child_box; struct effect_entity *other_entity; struct effect_slot *slot; /* high priority to low priority */ wl_list_for_each_reverse(slot, &node_chain->base.slots, link) { other_entity = wl_container_of(slot, other_entity, slot); /** * the larger the value, the higher the priority. * only getting bounding box for higher priority effects. */ if ((other_entity->effect->priority >= entity->effect->priority && other_entity != entity) || !other_entity->effect->impl->entity_bounding_box) { continue; } ret = other_entity->effect->impl->entity_bounding_box(other_entity, &child_box); calc_bounding(&min_x1, &min_y1, &max_x2, &max_y2, &child_box); if (!ret) { break; } } if (!ret) { node_chain->impl.get_bounding_box(node, &_box); wlr_box_to_kywc_box(&_box, box); calc_bounding(&min_x1, &min_y1, &max_x2, &max_y2, box); } box->x = min_x1 == INT_MAX ? 0 : min_x1; box->y = min_y1 == INT_MAX ? 0 : min_y1; box->width = max_x2 == INT_MIN ? 0 : max_x2 - min_x1; box->height = max_y2 == INT_MIN ? 0 : max_y2 - min_y1; } static bool entities_enabled(struct node_effect_chain *chain) { struct ky_scene_node *node = chain->node; if (!node) { return false; } struct effect_slot *slot; struct effect_entity *entity; bool enable = false, is_root = node->parent == NULL; wl_list_for_each(slot, &chain->base.slots, link) { entity = is_root ? wl_container_of(slot, entity, frame_slot) : wl_container_of(slot, entity, slot); enable |= entity->enabled; if (enable) { break; } } return enable; } static void entities_clip_region(struct node_effect_chain *chain, pixman_region32_t *region) { if (wl_list_empty(&chain->base.slots)) { return; } pixman_region32_clear(region); pixman_region32_t clip_region; pixman_region32_init(&clip_region); struct effect_entity *entity; struct effect_slot *slot; wl_list_for_each_reverse(slot, &chain->base.slots, link) { entity = wl_container_of(slot, entity, slot); if (!entity->effect->impl->entity_clip_region) { continue; } bool ret = entity->effect->impl->entity_clip_region(entity, &clip_region); pixman_region32_union(region, region, &clip_region); if (!ret) { break; } } pixman_region32_fini(&clip_region); } static void entities_opaque_region(struct node_effect_chain *chain, pixman_region32_t *region) { if (wl_list_empty(&chain->base.slots)) { return; } pixman_region32_clear(region); pixman_region32_t opaque_region; pixman_region32_init(&opaque_region); struct effect_entity *entity; struct effect_slot *slot; wl_list_for_each_reverse(slot, &chain->base.slots, link) { entity = wl_container_of(slot, entity, slot); if (!entity->effect->impl->entity_opaque_region) { continue; } bool ret = entity->effect->impl->entity_opaque_region(entity, &opaque_region); pixman_region32_union(region, region, &opaque_region); if (!ret) { break; } } pixman_region32_fini(&opaque_region); } /* if return false, box value isn't valid */ static bool entities_bounding_box(struct node_effect_chain *chain, struct kywc_box *box) { if (wl_list_empty(&chain->base.slots)) { *box = (struct kywc_box){ 0 }; return false; } struct effect_slot *slot = wl_container_of(chain->base.slots.next, slot, link); struct effect_entity *first_entity = wl_container_of(slot, first_entity, slot); effect_entity_get_bounding_box(first_entity, KY_SCENE_BOUNDING_WITH_EFFECT, box); return true; } static struct node_effect_chain *node_effect_chain_from_node(struct ky_scene_node *node) { struct wlr_addon *addon = wlr_addon_find(&node->addons, node, &effect_addon_impl); struct node_effect_chain *chain = wl_container_of(addon, chain, addon); return chain; } static struct ky_scene_node *node_chain_accept_input(struct ky_scene_node *node, int lx, int ly, double px, double py, double *rx, double *ry) { struct node_effect_chain *chain = node_effect_chain_from_node(node); return chain->impl.accept_input(node, lx, ly, px, py, rx, ry); } static void node_chain_update_outputs(struct ky_scene_node *node, int lx, int ly, struct wl_list *outputs, struct ky_scene_output *ignore, struct ky_scene_output *force) { struct node_effect_chain *chain = node_effect_chain_from_node(node); chain->impl.update_outputs(node, lx, ly, outputs, ignore, force); } static void node_chain_subeffect_collect_damage(struct ky_scene_node *node, int lx, int ly, bool parent_enabled, uint32_t damage_type, pixman_region32_t *damage, pixman_region32_t *invisible, pixman_region32_t *affected) { if (node->type != KY_SCENE_NODE_TREE) { return; } struct ky_scene_node *child; struct ky_scene_tree *tree_node = ky_scene_tree_from_node(node); wl_list_for_each(child, &tree_node->children, link) { if (child->has_effect) { struct node_effect_chain *child_chain = node_effect_chain_from_node(child); bool node_enabled = entities_enabled(child_chain) && parent_enabled; child->impl.collect_damage(child, lx + child->x, ly + child->y, node_enabled, damage_type | node->damage_type, damage, invisible, affected); continue; } node_chain_subeffect_collect_damage( child, lx + child->x, ly + child->y, parent_enabled && node->enabled, damage_type | node->damage_type, damage, invisible, affected); } } static void node_chain_collect_invisible(struct node_effect_chain *chain, pixman_region32_t *clip_region, int lx, int ly, pixman_region32_t *invisible) { pixman_region32_t opaque_region; pixman_region32_init(&opaque_region); entities_opaque_region(chain, &opaque_region); if (!pixman_region32_not_empty(&opaque_region)) { pixman_region32_fini(&opaque_region); return; } if (pixman_region32_not_empty(clip_region)) { pixman_region32_intersect(&opaque_region, &opaque_region, clip_region); } pixman_region32_translate(&opaque_region, lx, ly); pixman_region32_union(invisible, invisible, &opaque_region); pixman_region32_fini(&opaque_region); } static void node_chain_collect_damage(struct ky_scene_node *node, int lx, int ly, bool parent_enabled, uint32_t damage_type, pixman_region32_t *damage, pixman_region32_t *invisible, pixman_region32_t *affected) { bool is_root = node->parent == NULL; struct node_effect_chain *chain = node_effect_chain_from_node(node); if (wl_list_empty(&chain->base.slots) || is_root) { pixman_region32_union(damage, damage, &chain->visible_region); pixman_region32_clear(&chain->visible_region); chain->impl.collect_damage(node, lx, ly, parent_enabled, damage_type, damage, invisible, affected); return; } bool chain_enabled = entities_enabled(chain); if (!chain->last_enabled && !chain_enabled) { return; } /** * if node state is changed, some entities maybe not in the affected region. * so it can't be skipped by affected region. */ damage_type |= chain->damage_type; bool no_damage = chain_enabled == chain->last_enabled && damage_type == KY_SCENE_DAMAGE_NONE; if (!no_damage) { /** * if chain has KY_SCENE_DAMAGE_HARMFUL damage or chain changes to disabled, * last visible region is added to damgae. */ if (chain->last_enabled && (!chain_enabled || damage_type & KY_SCENE_DAMAGE_HARMFUL)) { pixman_region32_union(damage, damage, &chain->visible_region); } } pixman_region32_clear(&chain->visible_region); pixman_region32_t clip_region; pixman_region32_init(&clip_region); entities_clip_region(chain, &clip_region); if (!chain_enabled) { chain->last_enabled = chain_enabled; chain->damage_type = KY_SCENE_DAMAGE_NONE; node->damage_type = KY_SCENE_DAMAGE_NONE; pixman_region32_fini(&clip_region); return; } /* recalc visible region */ struct kywc_box box; if (!entities_bounding_box(chain, &box)) { pixman_region32_fini(&clip_region); return; } pixman_region32_union_rect(&chain->visible_region, &chain->visible_region, box.x, box.y, box.width, box.height); bool has_clip_region = pixman_region32_not_empty(&clip_region); if (has_clip_region) { pixman_region32_intersect(&chain->visible_region, &chain->visible_region, &clip_region); } pixman_region32_translate(&chain->visible_region, lx, ly); pixman_region32_subtract(&chain->visible_region, &chain->visible_region, invisible); if (!no_damage) { pixman_region32_union(damage, damage, &chain->visible_region); if (node->type != KY_SCENE_NODE_TREE) { pixman_region32_copy(&node->visible_region, &chain->visible_region); } } node_chain_collect_invisible(chain, &clip_region, lx, ly, invisible); pixman_region32_fini(&clip_region); chain->last_enabled = chain_enabled; chain->damage_type = KY_SCENE_DAMAGE_NONE; /** * although the node already has effect entities, * it's damage_type can still be modified by ky_scene_node_push_damage. */ node->damage_type = KY_SCENE_DAMAGE_NONE; node_chain_subeffect_collect_damage(node, lx + node->x, ly + node->y, parent_enabled, damage_type, damage, invisible, affected); } static void node_chain_get_opaque_region(struct ky_scene_node *node, pixman_region32_t *opaque_region) { bool is_root = node->parent == NULL; struct node_effect_chain *chain = node_effect_chain_from_node(node); if (wl_list_empty(&chain->base.slots) || is_root) { chain->impl.get_opaque_region(node, opaque_region); return; } pixman_region32_clear(opaque_region); bool chain_enabled = entities_enabled(chain); bool enable = chain_enabled | node->enabled; if (!enable) { return; } entities_opaque_region(chain, opaque_region); } static void node_chain_push_damage(struct ky_scene_node *node, struct ky_scene_node *damage_node, uint32_t damage_type, pixman_region32_t *damage) { bool is_root = node->parent == NULL; struct node_effect_chain *chain = node_effect_chain_from_node(node); if (wl_list_empty(&chain->base.slots) || is_root) { chain->impl.push_damage(node, damage_node, damage_type, damage); return; } bool chain_enabled = entities_enabled(chain); bool enable = chain_enabled | node->enabled; if (!enable && !node->force_damage_event) { return; } damage_type |= chain->damage_type | node->damage_type; /* emit damage when node content damaged or children damaged */ if ((node == damage_node && damage_type & KY_SCENE_DAMAGE_HARMLESS) || node != damage_node) { wl_signal_emit_mutable(&node->events.damage, damage_node); } struct effect_entity *entity; struct effect_slot *slot, *tmp; uint32_t tmp_damage_type = damage_type; wl_list_for_each_reverse_safe(slot, tmp, &chain->base.slots, link) { entity = wl_container_of(slot, entity, slot); if (entity->effect->impl->node_push_damage && !entity->effect->impl->node_push_damage(entity, damage_node, &tmp_damage_type, damage)) { break; } } chain->damage_type = tmp_damage_type | damage_type; pixman_region32_translate(damage, node->x, node->y); node->parent->node.impl.push_damage(&node->parent->node, damage_node, chain->damage_type, damage); } static void node_chain_get_bounding_box(struct ky_scene_node *node, struct wlr_box *box) { /* always use origin function */ struct node_effect_chain *chain = node_effect_chain_from_node(node); chain->impl.get_bounding_box(node, box); } static void node_chain_get_affected_bounding_box(struct ky_scene_node *node, uint32_t type, struct kywc_box *box) { struct node_effect_chain *chain = node_effect_chain_from_node(node); /* has effect and type is KY_SCENE_BOUNDING_WITHOUT_EFFECT_NODE return empty box */ if (type == KY_SCENE_BOUNDING_WITHOUT_EFFECT_NODE && node->has_effect) { *box = (struct kywc_box){ 0 }; return; } /* have effect return effect box */ if (type == KY_SCENE_BOUNDING_WITH_EFFECT && entities_bounding_box(chain, box)) { return; } /* no effect return origin box */ struct wlr_box bbox; chain->impl.get_bounding_box(node, &bbox); wlr_box_to_kywc_box(&bbox, box); } static void node_chain_render_subeffect(struct ky_scene_node *node, int lx, int ly, struct ky_scene_render_target *target) { if (node->type != KY_SCENE_NODE_TREE) { return; } struct ky_scene_node *child; struct ky_scene_tree *tree_node = ky_scene_tree_from_node(node); wl_list_for_each(child, &tree_node->children, link) { if (child->has_effect) { child->impl.render(child, lx + child->x, ly + child->y, target); continue; } if (target->options & KY_SCENE_RENDER_DISABLE_SUBEFFECT_NODE) { continue; } node_chain_render_subeffect(child, lx + child->x, ly + child->y, target); } } static void node_chain_render(struct ky_scene_node *node, int lx, int ly, struct ky_scene_render_target *target) { bool skip_node_effect = (node->parent == NULL) || (target->options & KY_SCENE_RENDER_DISABLE_EFFECT); struct node_effect_chain *chain = node_effect_chain_from_node(node); if (wl_list_empty(&chain->base.slots) || skip_node_effect) { chain->impl.render(node, lx, ly, target); return; } struct effect_entity *entity; struct effect_slot *slot, *temp_slot; wl_list_for_each_reverse_safe(slot, temp_slot, &chain->base.slots, link) { entity = wl_container_of(slot, entity, slot); if (entity->effect->impl->node_render && !entity->effect->impl->node_render(entity, lx, ly, target)) { node_chain_render_subeffect(node, lx, ly, target); return; } } chain->impl.render(node, lx, ly, target); } static void node_chain_destroy(struct ky_scene_node *node) { struct node_effect_chain *chain = node_effect_chain_from_node(node); chain->impl.destroy(node); } static const struct ky_scene_node_interface node_effect_impl = { .accept_input = node_chain_accept_input, .update_outputs = node_chain_update_outputs, .collect_damage = node_chain_collect_damage, .get_opaque_region = node_chain_get_opaque_region, .render = node_chain_render, .get_bounding_box = node_chain_get_bounding_box, .get_affected_bounding_box = node_chain_get_affected_bounding_box, .push_damage = node_chain_push_damage, .destroy = node_chain_destroy, }; static void effect_entity_remove(struct effect_entity *entity) { wl_list_remove(&entity->slot.link); wl_list_init(&entity->slot.link); wl_list_remove(&entity->frame_slot.link); wl_list_init(&entity->frame_slot.link); struct effect_chain *chain = entity->slot.chain; if (chain && wl_list_empty(&chain->slots)) { struct node_effect_chain *node_chain = wl_container_of(chain, node_chain, base); node_chain->node->has_effect = false; } } void effect_entity_destroy(struct effect_entity *entity) { /* don't trigger node damage event */ effect_entity_push_damage(entity, KY_SCENE_DAMAGE_BOTH); effect_entity_remove(entity); wl_list_remove(&entity->slot.chain_destroy.link); wl_list_remove(&entity->frame_slot.chain_destroy.link); wl_list_remove(&entity->effect_link); wl_list_remove(&entity->effect_enable.link); wl_list_remove(&entity->effect_disable.link); wl_list_remove(&entity->effect_destroy.link); if (entity->slot.chain) { struct node_effect_chain *chain = wl_container_of(entity->slot.chain, chain, base); ky_scene_node_push_damage(chain->node, KY_SCENE_DAMAGE_BOTH, NULL); } /* if effects just use on scene root node, damage added in entity destroy function */ if (entity->effect->impl->entity_destroy) { entity->effect->impl->entity_destroy(entity); } free(entity); } static void node_effect_chain_addon_destroy(struct wlr_addon *addon) { struct node_effect_chain *chain = wl_container_of(addon, chain, addon); wl_signal_emit(&chain->base.events.destroy, NULL); assert(wl_list_empty(&chain->base.events.destroy.listener_list)); wlr_addon_finish(&chain->addon); ky_scene_add_damage(ky_scene_from_node(chain->node), &chain->visible_region); pixman_region32_fini(&chain->visible_region); chain->node->impl = chain->impl; free(chain); } static const struct wlr_addon_interface effect_addon_impl = { .name = "node_effect_chain", .destroy = node_effect_chain_addon_destroy, }; static void node_effect_chain_set_visible_region(struct node_effect_chain *chain, struct ky_scene_node *node) { if (node->type == KY_SCENE_NODE_TREE) { struct ky_scene_tree *tree = ky_scene_tree_from_node(node); struct ky_scene_node *child_node; wl_list_for_each(child_node, &tree->children, link) { node_effect_chain_set_visible_region(chain, child_node); } } if (pixman_region32_not_empty(&node->visible_region)) { pixman_region32_union(&chain->visible_region, &chain->visible_region, &node->visible_region); } } typedef bool (*effect_entity_iterator_func_t)(struct effect_entity *entity, void *user_data); static void node_effect_chain_for_each_entity(struct node_effect_chain *chain, effect_entity_iterator_func_t iterator, void *data) { bool is_root = chain->node->role.type == KY_SCENE_ROLE_ROOT; struct effect_slot *slot, *tmp; wl_list_for_each_reverse_safe(slot, tmp, &chain->base.slots, link) { struct effect_entity *entity = is_root ? wl_container_of(slot, entity, frame_slot) : wl_container_of(slot, entity, slot); if (iterator && iterator(entity, data)) { break; } } } static struct node_effect_chain *node_effect_chain_create(struct ky_scene_node *node) { struct node_effect_chain *chain = calloc(1, sizeof(*chain)); if (!chain) { return NULL; } chain->last_enabled = false; chain->damage_type = node->damage_type; pixman_region32_init(&chain->visible_region); wl_signal_init(&chain->base.events.destroy); wl_list_init(&chain->base.slots); chain->node = node; chain->impl = node->impl; node->impl = node_effect_impl; wlr_addon_init(&chain->addon, &node->addons, node, &effect_addon_impl); /* node isn't root */ if (node->parent) { node_effect_chain_set_visible_region(chain, node); } return chain; } static void entity_handle_chain_destroy(struct wl_listener *listener, void *data) { struct effect_entity *entity = wl_container_of(listener, entity, slot.chain_destroy); effect_entity_destroy(entity); } static void frame_entity_handle_chain_destroy(struct wl_listener *listener, void *data) { struct effect_entity *entity = wl_container_of(listener, entity, frame_slot.chain_destroy); effect_entity_destroy(entity); } static void entity_handle_effect_destroy(struct wl_listener *listener, void *data) { struct effect_entity *entity = wl_container_of(listener, entity, effect_destroy); effect_entity_destroy(entity); } static void entity_handle_effect_disable(struct wl_listener *listener, void *data) { struct effect_entity *entity = wl_container_of(listener, entity, effect_disable); effect_entity_remove(entity); } static void entity_insert_to_chain(struct effect_entity *entity, bool frame) { struct effect_chain *chain = frame ? entity->frame_slot.chain : entity->slot.chain; if (!chain) { return; } struct wl_list *list = &chain->slots; struct effect_entity *_entity; struct effect_slot *slot; wl_list_for_each(slot, &chain->slots, link) { _entity = frame ? wl_container_of(slot, _entity, frame_slot) : wl_container_of(slot, _entity, slot); if (_entity->effect->priority > entity->effect->priority) { break; } list = &slot->link; } if (frame) { wl_list_insert(list, &entity->frame_slot.link); } else { struct node_effect_chain *node_chain = wl_container_of(chain, node_chain, base); if (wl_list_empty(&chain->slots) && !pixman_region32_not_empty(&node_chain->visible_region)) { node_effect_chain_set_visible_region(node_chain, node_chain->node); } wl_list_insert(list, &entity->slot.link); node_chain->node->has_effect = true; } } static void entity_handle_effect_enable(struct wl_listener *listener, void *data) { struct effect_entity *entity = wl_container_of(listener, entity, effect_enable); if (entity->slot.chain) { entity_insert_to_chain(entity, false); } if (entity->frame_slot.chain) { entity_insert_to_chain(entity, true); } } void ky_scene_node_get_affected_bounding_box(struct ky_scene_node *node, enum ky_scene_bounding_type type, struct kywc_box *box) { if (!node->has_effect || type != KY_SCENE_BOUNDING_WITHOUT_EFFECT_NODE) { node->impl.get_affected_bounding_box(node, type, box); return; } /* have effect and bounding type is KY_SCENE_NODE_BOUNDING_EXCEPT_EFFECT_NODE */ struct node_effect_chain *chain = node_effect_chain_from_node(node); chain->impl.get_affected_bounding_box(node, type, box); } struct ky_scene_node *ky_scene_node_find_upper_effect_node(struct ky_scene_node *node, struct ky_scene_node *final_node) { struct ky_scene_node *parent = &node->parent->node; if (!parent || node == final_node || parent == final_node) { return NULL; } if (parent->has_effect) { return node; } return ky_scene_node_find_upper_effect_node(parent, final_node); } struct effect_entity *ky_scene_node_find_effect_entity(struct ky_scene_node *node, struct effect *effect) { bool is_root = node->parent == NULL; struct wlr_addon *addon = wlr_addon_find(&node->addons, node, &effect_addon_impl); if (!addon) { return NULL; } struct effect_entity *entity; struct effect_slot *slot; struct node_effect_chain *chain = wl_container_of(addon, chain, addon); wl_list_for_each(slot, &chain->base.slots, link) { entity = is_root ? wl_container_of(slot, entity, frame_slot) : wl_container_of(slot, entity, slot); if (entity->effect == effect) { return entity; } } return NULL; } static bool node_effect_chain_destroy_entity(struct effect_entity *entity, void *user_data) { effect_entity_destroy(entity); return false; } static void node_upper_effect_check(struct ky_scene_node *node, struct ky_scene_add_effect_event *event) { if (!node->parent) { return; } if (!node->has_effect) { node_upper_effect_check(&node->parent->node, event); return; } if (event) { event->is_subeffect = true; } wl_signal_emit_mutable(&node->events.add_effect, event); } struct effect_entity *ky_scene_node_add_effect(struct ky_scene_node *node, struct effect *effect, struct ky_scene_add_effect_event *event_data) { bool is_root = node->parent == NULL; if (is_root && !(effect->types & EFFECT_TYPE_SCENE)) { return NULL; } else if (!is_root && !(effect->types & EFFECT_TYPE_NODE)) { return NULL; } struct wlr_addon *addon = wlr_addon_find(&node->addons, node, &effect_addon_impl); struct node_effect_chain *chain = addon ? wl_container_of(addon, chain, addon) : node_effect_chain_create(node); if (!chain) { return NULL; } if (event_data) { event_data->effect = effect; event_data->is_subeffect = false; event_data->allow_add = true; } wl_signal_emit_mutable(&node->events.add_effect, event_data); if (!node->has_effect) { node_upper_effect_check(node, event_data); } if (event_data && !event_data->allow_add) { return NULL; } /** * add effects through general nodes. usually, occur when both the * scene effects interface and the node effects interface are implemented. */ if ((effect->types & EFFECT_TYPE_SCENE) && !is_root) { struct ky_scene *scene = ky_scene_from_node(node); struct effect_entity *entity = ky_scene_add_effect(scene, effect); if (!entity) { return NULL; } if (effect->types & EFFECT_TYPE_NODE) { entity->slot.chain = &chain->base; wl_list_init(&entity->slot.link); entity->slot.chain_destroy.notify = entity_handle_chain_destroy; wl_signal_add(&chain->base.events.destroy, &entity->slot.chain_destroy); /* remove all exsit effect entities */ node_effect_chain_for_each_entity(chain, node_effect_chain_destroy_entity, NULL); if (effect->enabled) { entity_insert_to_chain(entity, false); } } return entity; } struct effect_entity *entity = calloc(1, sizeof(*entity)); if (!entity) { return NULL; } entity->enabled = true; if (!is_root) { entity->slot.chain = &chain->base; wl_list_init(&entity->slot.link); entity->slot.chain_destroy.notify = entity_handle_chain_destroy; wl_signal_add(&chain->base.events.destroy, &entity->slot.chain_destroy); wl_list_init(&entity->frame_slot.link); wl_list_init(&entity->frame_slot.chain_destroy.link); /* remove all exsit effect entities */ node_effect_chain_for_each_entity(chain, node_effect_chain_destroy_entity, NULL); } else { wl_list_init(&entity->slot.link); wl_list_init(&entity->slot.chain_destroy.link); entity->frame_slot.chain = &chain->base; wl_list_init(&entity->frame_slot.link); entity->frame_slot.chain_destroy.notify = frame_entity_handle_chain_destroy; wl_signal_add(&chain->base.events.destroy, &entity->frame_slot.chain_destroy); } entity->effect = effect; wl_list_insert(&effect->entities, &entity->effect_link); entity->effect_enable.notify = entity_handle_effect_enable; wl_signal_add(&effect->events.enable, &entity->effect_enable); entity->effect_disable.notify = entity_handle_effect_disable; wl_signal_add(&effect->events.disable, &entity->effect_disable); entity->effect_destroy.notify = entity_handle_effect_destroy; wl_signal_add(&effect->events.destroy, &entity->effect_destroy); if (effect->enabled) { entity_insert_to_chain(entity, is_root); } if (effect->impl->entity_create) { effect->impl->entity_create(entity); } return entity; } void effect_set_enabled(struct effect *effect, bool enabled) { if (effect->enabled == enabled) { return; } effect->enabled = enabled; if (enabled) { wl_signal_emit_mutable(&effect->events.enable, NULL); } else { wl_signal_emit_mutable(&effect->events.disable, NULL); } } void effect_destroy(struct effect *effect) { effect->destroying = true; if (effect->enabled) { wl_signal_emit_mutable(&effect->events.disable, NULL); } wl_signal_emit_mutable(&effect->events.destroy, NULL); assert(wl_list_empty(&effect->events.enable.listener_list)); assert(wl_list_empty(&effect->events.disable.listener_list)); assert(wl_list_empty(&effect->events.destroy.listener_list)); assert(wl_list_empty(&effect->entities)); wl_list_remove(&effect->link); free((void *)effect->name); free(effect); } struct effect *effect_create(const char *name, int priority, bool enabled, const struct effect_interface *impl, void *data) { struct effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return NULL; } effect->name = strdup(name); effect->priority = priority; effect->impl = impl; effect->enabled = enabled; effect->types = get_effect_types(impl); effect->user_data = data; wl_list_init(&effect->entities); wl_signal_init(&effect->events.enable); wl_signal_init(&effect->events.disable); wl_signal_init(&effect->events.destroy); wl_list_insert(&manager->effects, &effect->link); effect->manager = manager; effect->enabled = effect_init_config(effect); return effect; } struct effect *effect_by_name(const char *name) { struct effect *effect; wl_list_for_each(effect, &manager->effects, link) { if (strcmp(effect->name, name) == 0) { return effect; } } return NULL; } static void handle_server_ready(struct wl_listener *listener, void *data) { showkey_effect_create(manager); wallpaper_effect_create(manager); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->server_destroy.link); wl_list_remove(&manager->server_ready.link); struct effect *effect, *tmp; wl_list_for_each_safe(effect, tmp, &manager->effects, link) { effect_destroy(effect); } free(manager); manager = NULL; } bool effect_manager_create(struct server *server) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->time_scale = 1.0; wl_list_init(&manager->effects); animation_manager_create(server); thumbnail_manager_create(server); snapshot_manager_create(server); capture_manager_create(server); ky_capture_manager_create(server); wlr_screencopy_manager_create(server); /* must after animation manager create */ ukui_effect_create(server); manager->server = server; manager->server_ready.notify = handle_server_ready; wl_signal_add(&manager->server->events.ready, &manager->server_ready); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); effect_manager_config_init(manager); /* builtin effects */ showfps_effect_create(manager); blur_effect_create(manager); screenshot_effect_create(manager); watermark_effect_create(manager); move_effect_create(manager); scale_effect_create(manager); mouse_click_effect_create(manager); mouse_trail_effect_create(manager); touch_click_effect_create(manager); touch_long_effect_create(manager); touch_trail_effect_create(manager); fade_effect_create(manager); slide_effect_create(manager); translation_effect_create(manager); output_transform_effect_create(manager); color_filter_effect_create(manager); shake_cursor_effect_create(manager); shake_view_effect_create(manager); locate_pointer_effect_create(manager); magic_lamp_effect_create(manager); zoom_effect_create(manager); node_transform_effect_create(manager); return true; } uint32_t effect_manager_scale_time(uint32_t effect_duration) { if (!manager) { return effect_duration; } manager->time_scale = MAX(0.1f, manager->time_scale); return floor(manager->time_scale * effect_duration); } bool effect_manager_allow_direct_scanout(struct ky_scene_render_target *target) { struct effect *effect; wl_list_for_each(effect, &manager->effects, link) { if (effect->enabled && effect->impl->allow_direct_scanout && !effect->impl->allow_direct_scanout(effect, target)) { return false; } } return true; } struct effect_entity *ky_scene_find_effect_entity(struct ky_scene *scene, struct effect *effect) { return ky_scene_node_find_effect_entity(&scene->tree.node, effect); } struct effect_entity *ky_scene_add_effect(struct ky_scene *scene, struct effect *effect) { return ky_scene_node_add_effect(&scene->tree.node, effect, NULL); } enum interface_name { RENDER_PRE, RENDER_BEGIN, RENDER, RENDER_END, RENDER_POST, }; #define scene_effect_run(entity, target, interface_name) \ if (entity->effect->impl->frame_##interface_name && \ !entity->effect->impl->frame_##interface_name(entity, target)) { \ return; \ } static void scene_output_run_effect(struct ky_scene_output *scene_output, enum interface_name name, struct ky_scene_render_target *target) { struct ky_scene *scene = scene_output->scene; struct ky_scene_node *node = &scene->tree.node; struct wlr_addon *addon = wlr_addon_find(&node->addons, node, &effect_addon_impl); if (!addon) { return; } struct node_effect_chain *chain = wl_container_of(addon, chain, addon); struct effect_entity *entity; struct effect_slot *slot, *temp_slot; wl_list_for_each_reverse_safe(slot, temp_slot, &chain->base.slots, link) { entity = wl_container_of(slot, entity, frame_slot); switch (name) { case RENDER_PRE: if (entity->effect->impl->frame_render_pre && !entity->effect->impl->frame_render_pre(entity, target)) { return; } break; case RENDER_BEGIN: scene_effect_run(entity, target, render_begin); break; case RENDER: break; case RENDER_END: scene_effect_run(entity, target, render_end); break; case RENDER_POST: scene_effect_run(entity, target, render_post); break; default: break; } } } void ky_scene_output_render_pre(struct ky_scene_render_target *target) { scene_output_run_effect(target->output, RENDER_PRE, target); } void ky_scene_output_render_begin(struct ky_scene_render_target *target) { scene_output_run_effect(target->output, RENDER_BEGIN, target); } bool ky_scene_output_render(struct ky_scene_render_target *target) { struct ky_scene *scene = target->output->scene; struct ky_scene_node *node = &scene->tree.node; struct wlr_addon *addon = wlr_addon_find(&node->addons, node, &effect_addon_impl); if (!addon) { return false; } struct node_effect_chain *chain = wl_container_of(addon, chain, addon); bool has_rendered = false; struct effect_entity *entity; struct effect_slot *slot, *temp_slot; wl_list_for_each_reverse_safe(slot, temp_slot, &chain->base.slots, link) { entity = wl_container_of(slot, entity, frame_slot); if (entity->effect->impl->frame_render) { has_rendered = true; entity->effect->impl->frame_render(entity, target); return has_rendered; } } return has_rendered; } void ky_scene_output_render_end(struct ky_scene_render_target *target) { scene_output_run_effect(target->output, RENDER_END, target); } void ky_scene_output_render_post(struct ky_scene_render_target *target) { scene_output_run_effect(target->output, RENDER_POST, target); } kylin-wayland-compositor/src/effect/transform.c0000664000175000017500000006536615160461067020707 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "effect/animator.h" #include "effect/node_transform.h" #include "effect/snapshot.h" #include "effect/transform.h" #include "effect_p.h" #include "render/opengl.h" #include "render/renderer.h" #include "scene/scene.h" #include "scene/thumbnail.h" #include "util/time.h" #include "xwayland.h" struct transform { struct effect_entity *entity; struct transform_effect *effect; struct animator *animator; /* current geometry is dst_box */ struct animation_data current; struct transform_options pending_options; struct transform_options options; struct ky_scene_tree *old_parent; struct wl_listener old_parent_destroy; /* reuse transforms */ int references; struct ky_scene_buffer *buffer; /** * thumbnail textrue comes from three aspects. One, node is tree, it actively generates * thumbnails. Two, node is rect, the map stage, directly rendered without using thumbnails, but * the destroy stage, need to regenerate thumbnails. Thirdly, node is buffer, the map stage, * converted from the buffer to the texture through zero-copy-buffer without generating * thumbnails. the destroy stage, the buffer is obtained through thumbnail buffer */ struct snapshot *snapshot; struct ky_scene_node *node; struct wl_listener node_destroy; struct wl_listener add_effect; struct { struct wl_signal destroy; } events; void *user_data; }; struct transform_effect { struct effect *effect; const struct transform_effect_interface *impl; struct effect_manager *manager; struct wlr_renderer *renderer; bool is_opengl_renderer; struct wl_listener enable; struct wl_listener disable; struct wl_listener destroy; void *user_data; }; static struct transform_effect *node_transform_effect = NULL; static const struct effect_interface transform_effect_impl; static bool transform_create_animator(struct transform *transform); static struct transform *transform_create(struct transform_options *options, struct transform_effect *effect, struct ky_scene_node *node, void *data); static bool transform_effect_entity_bounding_box(struct effect_entity *entity, struct kywc_box *box) { struct effect_chain *chain = entity->slot.chain; struct node_effect_chain *node_chain = wl_container_of(chain, node_chain, base); struct transform *transform = entity->user_data; if (!transform) { node_chain->impl.get_affected_bounding_box(node_chain->node, KY_SCENE_BOUNDING_WITHOUT_EFFECT, box); return false; } *box = transform->current.geometry; if (box->width <= 0 || box->height <= 0) { effect_entity_get_bounding_box(entity, KY_SCENE_BOUNDING_WITHOUT_EFFECT, box); return false; } int lx, ly; ky_scene_node_coords(node_chain->node, &lx, &ly); box->x -= lx; box->y -= ly; return false; } static bool transform_effect_frame_render_pre(struct effect_entity *entity, struct ky_scene_render_target *target) { struct transform *transform = entity->user_data; uint32_t time = current_time_msec(); if (time > transform->pending_options.start_time + transform->pending_options.duration) { effect_entity_destroy(entity); return true; } struct transform_effect *effect = transform->effect; struct transform_options *options = &transform->options; /* update transform options */ if (effect->impl && effect->impl->update_transform_options) { effect->impl->update_transform_options(effect, transform, transform->node, &transform->pending_options, &transform->current, effect->user_data); } /* compare geometry, alpha, angle, time */ if (options->start_time != transform->pending_options.start_time) { animator_set_time(transform->animator, transform->pending_options.start_time + transform->pending_options.duration); } if ((!kywc_box_equal(&options->start.geometry, &transform->pending_options.start.geometry)) || (options->start.alpha != transform->pending_options.start.alpha) || (options->start.angle != transform->pending_options.start.angle)) { if (!transform_create_animator(transform)) { effect_entity_destroy(entity); return true; } } else { if (options->end.alpha != transform->pending_options.end.alpha) { animator_set_alpha(transform->animator, transform->pending_options.end.alpha); } if (options->end.angle != transform->pending_options.end.angle) { animator_set_angle(transform->animator, transform->pending_options.end.angle); } if (!kywc_box_equal(&options->end.geometry, &transform->pending_options.end.geometry)) { animator_set_position(transform->animator, transform->pending_options.end.geometry.x, transform->pending_options.end.geometry.y); animator_set_size(transform->animator, transform->pending_options.end.geometry.width, transform->pending_options.end.geometry.height); } } transform->options = transform->pending_options; const struct animation_data *animation_data = animator_value(transform->animator, time); transform->current = *animation_data; if (transform->snapshot) { snapshot_set_opacity(transform->snapshot, transform->current.alpha); snapshot_set_dst_box(transform->snapshot, &transform->current.geometry); snapshot_get_render_box(transform->snapshot, &transform->current.geometry); } effect_entity_push_damage(entity, KY_SCENE_DAMAGE_BOTH); return true; } static void transform_effect_handle_enable(struct wl_listener *listener, void *data) { struct transform_effect *effect = wl_container_of(listener, effect, enable); if (effect->impl && effect->impl->enable) { effect->impl->enable(effect, true, effect->user_data); } } static void transform_effect_handle_disable(struct wl_listener *listener, void *data) { struct transform_effect *effect = wl_container_of(listener, effect, disable); if (effect->impl && effect->impl->enable) { effect->impl->enable(effect, false, effect->user_data); } } static void transform_effect_handle_destroy(struct wl_listener *listener, void *data) { struct transform_effect *effect = wl_container_of(listener, effect, destroy); wl_list_remove(&effect->enable.link); wl_list_remove(&effect->disable.link); wl_list_remove(&effect->destroy.link); if (effect->impl && effect->impl->destroy) { effect->impl->destroy(effect, effect->user_data); } struct effect_entity *entity, *tmp; wl_list_for_each_safe(entity, tmp, &effect->effect->entities, effect_link) { effect_entity_destroy(entity); } free(effect); } struct transform_effect *transform_effect_create(struct effect_manager *manager, struct transform_effect_interface *impl, uint32_t support_actions, const char *name, int priority, void *data) { struct transform_effect *transform_effect = calloc(1, sizeof(*transform_effect)); if (!transform_effect) { return NULL; } transform_effect->impl = impl; transform_effect->user_data = data; transform_effect->manager = manager; transform_effect->renderer = manager->server->renderer; bool enabled = !ky_renderer_is_software(manager->server->renderer); transform_effect->is_opengl_renderer = wlr_renderer_is_opengl(transform_effect->renderer); struct effect *effect = effect_create(name, priority, enabled, &transform_effect_impl, transform_effect); if (!effect) { free(transform_effect); return NULL; } effect->support_actions = support_actions; effect->category = EFFECT_CATEGORY_ACTION; transform_effect->effect = effect; transform_effect->enable.notify = transform_effect_handle_enable; wl_signal_add(&transform_effect->effect->events.enable, &transform_effect->enable); transform_effect->disable.notify = transform_effect_handle_disable; wl_signal_add(&transform_effect->effect->events.disable, &transform_effect->disable); transform_effect->destroy.notify = transform_effect_handle_destroy; wl_signal_add(&transform_effect->effect->events.destroy, &transform_effect->destroy); return transform_effect; } void transform_destroy(struct transform *transform) { if (!transform->entity) { return; } effect_entity_destroy(transform->entity); } void transform_set_user_data(struct transform *transform, void *data) { transform->user_data = data; } void *transform_get_user_data(struct transform *transform) { return transform->user_data; } struct transform_options *transform_get_options(struct transform *transform) { return &transform->pending_options; } struct animation_data *transform_get_current(struct transform *transform) { return &transform->current; } void transform_block_source_update(struct transform *transform, bool block) { if (!transform->snapshot) { return; } snapshot_mark_wants_update(transform->snapshot, !block); } static bool transform_effect_node_push_damage(struct effect_entity *entity, struct ky_scene_node *damage_node, uint32_t *damage_type, pixman_region32_t *damage) { struct kywc_box box; transform_effect_entity_bounding_box(entity, &box); pixman_region32_union_rect(damage, damage, box.x, box.y, box.width, box.height); return false; } static bool transform_effect_frame_render_post(struct effect_entity *entity, struct ky_scene_render_target *target) { effect_entity_push_damage(entity, KY_SCENE_DAMAGE_BOTH); return true; } static bool handle_transform_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } return false; } static void transform_do_destroy(struct transform *transform) { transform->references--; if (transform->references != 0) { return; } wl_signal_emit_mutable(&transform->events.destroy, transform); assert(wl_list_empty(&transform->events.destroy.listener_list)); if (transform->old_parent) { wl_list_remove(&transform->old_parent_destroy.link); ky_scene_node_reparent(transform->node, transform->old_parent); } if (transform->buffer) { ky_scene_node_destroy(&transform->buffer->node); } if (transform->snapshot) { snapshot_destroy(transform->snapshot); } if (transform->animator) { animator_destroy(transform->animator); } wl_list_remove(&transform->node_destroy.link); wl_list_remove(&transform->add_effect.link); free(transform); } static void transform_effect_entity_destroy(struct effect_entity *entity) { struct transform *transform = entity->user_data; if (!transform) { return; } transform_do_destroy(transform); } static bool transform_effect_node_render(struct effect_entity *entity, int lx, int ly, struct ky_scene_render_target *target) { struct transform *transform = entity->user_data; if (!target->output) { return false; } if (transform->node->type == KY_SCENE_NODE_RECT) { struct ky_scene_node *rect_node = transform->node; struct ky_scene_rect *rect = ky_scene_rect_from_node(rect_node); float alpha = transform->current.alpha; float color[4] = { rect->color[0] * alpha, rect->color[1] * alpha, rect->color[2] * alpha, rect->color[3] * alpha }; ky_scene_rect_render(rect_node, transform->current.geometry, color, false, target); return false; } if (transform->snapshot) { snapshot_render(transform->snapshot, target); } return false; } static bool handle_transform_effect_allow_direct_scanout(struct effect *effect, struct ky_scene_render_target *target) { struct effect_entity *entity; wl_list_for_each(entity, &effect->entities, effect_link) { struct transform *transform = entity->user_data; if (!transform) { continue; } struct wlr_box geo = { transform->current.geometry.x, transform->current.geometry.y, transform->current.geometry.width, transform->current.geometry.height }; if (wlr_box_intersection(&geo, &target->logical, &geo)) { return false; } } return true; } static const struct effect_interface transform_effect_impl = { .entity_destroy = transform_effect_entity_destroy, .entity_bounding_box = transform_effect_entity_bounding_box, .node_push_damage = transform_effect_node_push_damage, .node_render = transform_effect_node_render, .frame_render_pre = transform_effect_frame_render_pre, .frame_render_post = transform_effect_frame_render_post, .configure = handle_transform_effect_configure, .allow_direct_scanout = handle_transform_effect_allow_direct_scanout, }; static void transform_handle_buffer_node_destroy(struct wl_listener *listener, void *data) { struct transform *transform = wl_container_of(listener, transform, node_destroy); /** * node destroy before effect entity destroy. * so when transform destroy, node_destroy already remove. */ wl_list_remove(&transform->node_destroy.link); wl_list_init(&transform->node_destroy.link); transform->buffer = NULL; } static void transform_handle_old_parent_destroy(struct wl_listener *listener, void *data) { struct transform *transform = wl_container_of(listener, transform, old_parent_destroy); wl_list_remove(&transform->old_parent_destroy.link); wl_list_init(&transform->old_parent_destroy.link); transform->old_parent = NULL; /* old_parent destroy, transform->node should be destroy */ ky_scene_node_destroy(transform->node); } static bool transform_create_animator(struct transform *transform) { struct transform_options *options = &transform->pending_options; struct animator *animator = animator_create(&options->start, &options->animations, options->start_time, options->start_time + options->duration); if (!animator) { return false; } if (transform->animator) { animator_destroy(transform->animator); } transform->animator = animator; animator_set_position(transform->animator, options->end.geometry.x, options->end.geometry.y); animator_set_size(transform->animator, options->end.geometry.width, options->end.geometry.height); animator_set_alpha(transform->animator, options->end.alpha); animator_set_angle(transform->animator, options->end.angle); return true; } static struct transform *transform_create(struct transform_options *options, struct transform_effect *effect, struct ky_scene_node *node, void *data) { struct transform *transform = calloc(1, sizeof(*transform)); if (!transform) { return NULL; } transform->buffer = NULL; transform->effect = effect; transform->node = node; transform->pending_options = *options; transform->options = *options; transform->user_data = data; transform->references = 1; wl_signal_init(&transform->events.destroy); wl_list_init(&transform->node_destroy.link); wl_list_init(&transform->add_effect.link); wl_list_init(&transform->old_parent_destroy.link); if (!transform_create_animator(transform)) { transform_do_destroy(transform); return NULL; } if (transform->options.new_parent) { transform->old_parent = node->parent; ky_scene_node_reparent(transform->node, transform->options.new_parent); transform->old_parent_destroy.notify = transform_handle_old_parent_destroy; wl_signal_add(&transform->old_parent->node.events.destroy, &transform->old_parent_destroy); } /* node is rect, don't generate thumbnails */ if (transform->node->type == KY_SCENE_NODE_RECT) { return transform; } float scale = options->scale <= 0 ? 1.0f : options->scale; uint32_t snapshot_options = effect->is_opengl_renderer ? 0 : SNAPSHOT_RENDER_DISABLE_BLUR; if (options->view) { if (options->geometry_type == TRANSFORM_GEOMETRY_VIEW_BOX) { snapshot_options |= SNAPSHOT_RENDER_ENABLE_VIEW_BOX; } transform->snapshot = snapshot_create_from_view(options->view, scale, snapshot_options); } else { assert(options->geometry_type == TRANSFORM_GEOMETRY_NODE_BOUNDING); transform->snapshot = snapshot_create_from_node( options->buffer ? &options->buffer->node : node, scale, snapshot_options); } if (!transform->snapshot) { transform_do_destroy(transform); return NULL; } uint32_t snapshot_anchors = (options->content_anchors & SNAPSHOT_ANCHOR_TOP) | (options->content_anchors & SNAPSHOT_ANCHOR_LEFT) | (options->content_anchors & SNAPSHOT_ANCHOR_RIGHT) | (options->content_anchors & SNAPSHOT_ANCHOR_BOTTOM); snapshot_set_anchors(transform->snapshot, snapshot_anchors); return transform; } static void transform_handle_add_effect(struct wl_listener *listener, void *data) { struct transform *transform = wl_container_of(listener, transform, add_effect); struct ky_scene_add_effect_event *event = data; if (!event) { return; } if (event->is_subeffect) { event->allow_add = true; return; } struct effect_render_data *render_data = event->render_data; if (render_data) { render_data->is_valid = true; render_data->exist_entity = transform->entity; render_data->geometry = transform->current.geometry; render_data->alpha = transform->current.alpha; render_data->angle = transform->current.angle; } if (event->effect == transform->effect->effect) { event->allow_add = false; return; } event->allow_add = true; } static void transform_handle_node_destroy(struct wl_listener *listener, void *data) { struct transform *transform = wl_container_of(listener, transform, node_destroy); /** * node destroy before effect entity destroy. * so when transform data destroy, node_destroy already remove. */ wl_list_remove(&transform->node_destroy.link); wl_list_init(&transform->node_destroy.link); wl_list_remove(&transform->add_effect.link); wl_list_init(&transform->add_effect.link); /** * if the node is destroyed, it does not need to be placed back under the old parent node. * the newly created buffer node will not be placed under the old parent node. */ wl_list_remove(&transform->old_parent_destroy.link); wl_list_init(&transform->old_parent_destroy.link); transform->old_parent = NULL; if (transform->node->type == KY_SCENE_NODE_RECT) { int snapshot_options = (transform->effect->is_opengl_renderer ? 0 : SNAPSHOT_RENDER_DISABLE_BLUR); transform->snapshot = snapshot_create_from_node( transform->node, transform->options.scale <= 0 ? 1.0f : transform->options.scale, snapshot_options); } if (!transform->snapshot) { return; } snapshot_detach(transform->snapshot); /** * when node destroy, if visible is empty, there is no need to render, not add effect. * when node was just created, it was never rendered, visible is also empty, but it needs to * render and add effect. */ struct effect_entity *_entity = transform->entity; struct node_effect_chain *node_chain = wl_container_of(_entity->slot.chain, node_chain, base); if (!pixman_region32_not_empty(&node_chain->visible_region)) { return; } struct view_layer *layer = view_manager_get_layer_by_node(transform->node, true); if (!layer) { kywc_log(KYWC_ERROR, "Node is not in layer"); return; } int node_x = transform->node->x, node_y = transform->node->y; struct ky_scene_node *sibing = transform->node; struct ky_scene_tree *parent = sibing->parent; while (parent != layer->tree) { sibing = &parent->node; node_x += sibing->x; node_y += sibing->y; parent = parent->node.parent; } struct ky_scene_buffer *buffer = snapshot_create_scene_buffer(parent, transform->snapshot); if (!buffer) { return; } transform->buffer = buffer; ky_scene_node_set_position(&buffer->node, node_x, node_y); transform->options.below_view ? ky_scene_node_place_below(&buffer->node, sibing) : ky_scene_node_place_above(&buffer->node, sibing); ky_scene_node_set_input_bypassed(&buffer->node, true); struct effect_entity *entity = ky_scene_node_add_effect(&buffer->node, transform->effect->effect, NULL); if (!entity) { ky_scene_node_destroy(&buffer->node); transform->buffer = NULL; return; } /** * in general, transform is used to modify node. When destroy signal is triggered, the proxy * node method is used. transform is used to modify buffer node */ transform->node = &buffer->node; transform->entity = entity; entity->user_data = transform; transform->references++; transform->node_destroy.notify = transform_handle_buffer_node_destroy; wl_signal_add(&buffer->node.events.destroy, &transform->node_destroy); } void transform_add_destroy_listener(struct transform *transform, struct wl_listener *listener) { wl_signal_add(&transform->events.destroy, listener); } struct effect_entity *node_add_custom_transform_effect(struct ky_scene_node *node, struct transform_effect *effect, struct transform_options *options, struct ky_scene_add_effect_event *event) { if (!effect->effect->enabled) { return NULL; } struct effect_entity *entity = ky_scene_node_find_effect_entity(node, effect->effect); /* current effect interrupt previous effect, reuse same entity and transform */ if (entity) { struct transform *transform = entity->user_data; /* prevent timeout remove effect entity before update function */ transform->pending_options.start_time = current_time_msec(); transform->pending_options.end = options->end; return entity; } entity = ky_scene_node_add_effect(node, effect->effect, event); if (!entity) { return NULL; } if (event && event->render_data) { struct effect_render_data *render_data = event->render_data; if (render_data->is_valid) { options->start.geometry = render_data->geometry; options->start.alpha = render_data->alpha; options->start.angle = render_data->angle; } /** * TODO: start geometry is dst_box, not the view_box. * some effect should convert it to view_box. */ if ((!kywc_box_equal(&options->start.geometry, &options->end.geometry) && options->animations.geometry == NULL)) { options->animations.alpha = animation_manager_get(ANIMATION_TYPE_EASE); } if ((options->start.alpha != options->end.alpha) && options->animations.alpha == NULL) { options->animations.alpha = animation_manager_get(ANIMATION_TYPE_EASE); } if ((options->start.angle != options->end.angle) && options->animations.angle == NULL) { options->animations.angle = animation_manager_get(ANIMATION_TYPE_EASE); } } return entity; } struct transform *transform_effect_create_transform(struct transform_effect *effect, struct effect_entity *entity, struct transform_options *options, struct ky_scene_node *node, void *data) { options->duration = effect_manager_scale_time(options->duration); struct transform *transform = transform_create(options, effect, node, data); if (!transform) { return NULL; } transform->add_effect.notify = transform_handle_add_effect; wl_signal_add(&node->events.add_effect, &transform->add_effect); transform->node_destroy.notify = transform_handle_node_destroy; wl_signal_add(&node->events.destroy, &transform->node_destroy); transform->entity = entity; entity->user_data = transform; return transform; } struct transform *transform_effect_get_or_create_transform(struct transform_effect *effect, struct transform_options *options, struct ky_scene_node *node, void *data, struct ky_scene_add_effect_event *event) { struct effect_entity *entity = node_add_custom_transform_effect(node, effect, options, event); if (!entity) { return NULL; } if (entity->user_data) { transform_set_user_data(entity->user_data, data); return entity->user_data; } struct transform *transform = transform_effect_create_transform(effect, entity, options, node, data); if (!transform) { effect_entity_destroy(entity); return NULL; } return transform; } bool node_transform_effect_create(struct effect_manager *manager) { uint32_t support_actions = EFFECT_ACTION_TRANSFORM; node_transform_effect = transform_effect_create(manager, NULL, support_actions, "transform_effect", 98, NULL); if (!node_transform_effect) { return false; } return true; } bool node_add_transform_effect(struct ky_scene_node *node, struct transform_options *options) { if (!node_transform_effect) { return false; } return transform_effect_get_or_create_transform(node_transform_effect, options, node, NULL, NULL); } kylin-wayland-compositor/src/effect/snapshot.c0000664000175000017500000006411715160461067020524 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include "effect/snapshot.h" #include "effect_p.h" #include "scene/surface.h" #include "scene/thumbnail.h" #include "server.h" #include "util/macros.h" #include "view/view.h" #include "xwayland.h" struct snapshot; struct snapshot_impl { void (*snapshot_detach)(struct snapshot *snapshot); void (*snapshot_destroy)(struct snapshot *snapshot); float (*snapshot_get_scale)(const struct snapshot *snapshot); bool (*snapshot_ensure_buffer_bindable)(struct snapshot *snapshot); bool (*snapshot_mark_wants_update)(struct snapshot *snapshot, bool wants); void (*snapshot_render)(struct snapshot *snapshot, struct ky_scene_render_target *target); }; struct snapshot { struct snapshot_impl *impl; struct thumbnail *thumbnail; struct wlr_texture *texture; struct wlr_buffer *buffer; struct snapshot *mix_snapshot; struct kywc_fbox src_box; /* logic coord */ struct kywc_box dst_box; float opacity, blend_factor; uint32_t anchors, options; float scale; bool detached; bool wants_update; bool was_damaged; struct { struct wl_signal destroy; } events; struct wl_listener update; struct wl_listener destroy; struct wl_list binders; }; enum snapshot_source_type { SNAPSHOT_SOURCE_TYPE_VIEW, SNAPSHOT_SOURCE_TYPE_NODE }; struct snapshot_source { enum snapshot_source_type type; union { struct view *view; struct ky_scene_node *node; }; }; struct node_snapshot { struct snapshot base; struct ky_scene_node *node; struct snapshot_source source; int blur_offset_x, blur_offset_y; bool need_blur; bool is_zerocopy; int blur_radius[4]; float surface_scale; struct blur_info blur_info; bool without_subeffect_node; struct wl_listener node_damage; struct wl_listener node_destroy; struct wl_listener buffer_destroy; }; struct snapshot_binder { struct wl_list link; struct snapshot *snapshot; struct ky_scene_buffer *scene_buffer; struct wl_listener scene_buffer_destroy; }; static struct snapshot_manager { struct server *server; struct wl_listener destroy; } *manager = NULL; static void snapshot_binder_destroy(struct snapshot_binder *snapshot_binder) { wl_list_remove(&snapshot_binder->scene_buffer_destroy.link); wl_list_remove(&snapshot_binder->link); free(snapshot_binder); } static void snapshot_binder_handle_scene_buffer_destroy(struct wl_listener *listener, void *data) { struct snapshot_binder *binder = wl_container_of(listener, binder, scene_buffer_destroy); snapshot_binder_destroy(binder); } static struct snapshot_binder *snapshot_binder_create(struct snapshot *snapshot, struct ky_scene_buffer *buffer) { struct snapshot_binder *binder = calloc(1, sizeof(*binder)); if (!binder) { return NULL; } binder->snapshot = snapshot; binder->scene_buffer = buffer; wl_list_insert(&snapshot->binders, &binder->link); binder->scene_buffer_destroy.notify = snapshot_binder_handle_scene_buffer_destroy; wl_signal_add(&buffer->node.events.destroy, &binder->scene_buffer_destroy); return binder; } static void node_snapshot_update_blur_info(struct node_snapshot *node_snapshot) { if (!node_snapshot->need_blur) { return; } ky_scene_node_get_blur_info(node_snapshot->node, &node_snapshot->blur_info); ky_scene_node_get_radius(node_snapshot->node, KY_SCENE_ROUND_CORNERS_FOR_OUTERMOST, node_snapshot->blur_radius); } static void node_snapshot_get_blur_offset(struct snapshot *snapshot) { struct node_snapshot *node_snapshot = wl_container_of(snapshot, node_snapshot, base); if (!snapshot->thumbnail || !node_snapshot->base.wants_update || !node_snapshot->need_blur) { return; } thumbnail_get_node_offset(snapshot->thumbnail, node_snapshot->node, &node_snapshot->blur_offset_x, &node_snapshot->blur_offset_y); } /* don't destroy the thumbnail in snapshot,just assigning thumbnail as a null pointer */ static void snapshot_remove_thumbnail(struct snapshot *snapshot) { wl_list_remove(&snapshot->destroy.link); wl_list_remove(&snapshot->update.link); wl_list_init(&snapshot->destroy.link); wl_list_init(&snapshot->update.link); snapshot->wants_update = false; snapshot->thumbnail = NULL; } static void snapshot_push_binder_damage(struct snapshot *snapshot) { struct snapshot_binder *binder; wl_list_for_each(binder, &snapshot->binders, link) { ky_scene_node_push_damage(&binder->scene_buffer->node, KY_SCENE_DAMAGE_HARMLESS, NULL); } } static void snapshot_update_binder_buffer(struct snapshot *snapshot) { if (wl_list_empty(&snapshot->binders)) { return; } if (snapshot->impl->snapshot_ensure_buffer_bindable && !snapshot->impl->snapshot_ensure_buffer_bindable(snapshot)) { return; } struct snapshot_binder *binder; wl_list_for_each(binder, &snapshot->binders, link) { ky_scene_buffer_set_buffer(binder->scene_buffer, snapshot->buffer); } } static void node_snapshot_handle_thumbnail_update(struct wl_listener *listener, void *data) { struct snapshot *snapshot = wl_container_of(listener, snapshot, update); struct thumbnail_update_event *event = data; node_snapshot_get_blur_offset(snapshot); if (!event->buffer_changed) { snapshot_push_binder_damage(snapshot); return; } snapshot->buffer = event->buffer; wlr_texture_destroy(snapshot->texture); snapshot_update_binder_buffer(snapshot); snapshot->texture = wlr_texture_from_buffer(manager->server->renderer, event->buffer); } static void node_snapshot_handle_thumbnail_destroy(struct wl_listener *listener, void *data) { struct snapshot *snapshot = wl_container_of(listener, snapshot, destroy); snapshot_remove_thumbnail(snapshot); } static bool snapshot_init_thumbnail(struct snapshot *snapshot, struct thumbnail *thumbnail) { snapshot->detached = false; snapshot->wants_update = true; snapshot->thumbnail = thumbnail; snapshot->update.notify = node_snapshot_handle_thumbnail_update; thumbnail_add_update_listener(thumbnail, &snapshot->update); snapshot->destroy.notify = node_snapshot_handle_thumbnail_destroy; thumbnail_add_destroy_listener(thumbnail, &snapshot->destroy); thumbnail_update(thumbnail); /* if the update fails, a destroy message will be sent */ if (!snapshot->thumbnail) { return false; } /* just in case */ if (!snapshot->buffer) { snapshot_remove_thumbnail(snapshot); return false; } return true; } static void node_snapshot_set_zerocopy_buffer(struct node_snapshot *node_snapshot, struct ky_scene_buffer *scene_buffer) { struct snapshot *snapshot = &node_snapshot->base; if (snapshot->buffer == scene_buffer->buffer) { snapshot_push_binder_damage(snapshot); return; } if (snapshot->buffer) { wl_list_remove(&node_snapshot->buffer_destroy.link); wl_list_init(&node_snapshot->buffer_destroy.link); } struct ky_scene_surface *scene_surface = ky_scene_surface_try_from_buffer(scene_buffer); node_snapshot->surface_scale = scene_surface ? ky_scene_surface_get_scale(scene_surface->surface) : 1.0f; snapshot->buffer = scene_buffer->buffer; snapshot_update_binder_buffer(snapshot); if (snapshot->buffer) { wl_signal_add(&snapshot->buffer->events.destroy, &node_snapshot->buffer_destroy); } } /* the dst_box must be logic coord */ static void snapshot_get_render_src_box(const struct snapshot *snapshot, const struct wlr_texture *texture, const struct kywc_box *dst_box, struct kywc_fbox *src_box) { *src_box = snapshot->src_box; if (kywc_fbox_empty(src_box)) { src_box->height = texture->height; src_box->width = texture->width; } if (!snapshot->anchors) { return; } /** * scale factor use for logic size -> thumbnail buffer size, * render logic size content to dst box. */ float scale = snapshot->impl->snapshot_get_scale ? snapshot->impl->snapshot_get_scale(snapshot) : snapshot->scale; if (snapshot->anchors & SNAPSHOT_ANCHOR_TOP) { if (!(snapshot->anchors & SNAPSHOT_ANCHOR_BOTTOM)) { src_box->height = MIN(src_box->height, dst_box->height * scale); } } else { if ((snapshot->anchors & SNAPSHOT_ANCHOR_BOTTOM)) { int y2 = src_box->y + src_box->height; int new_y1 = y2 - dst_box->height * scale; src_box->y = MAX(src_box->y, new_y1); src_box->height = y2 - src_box->y; } } if (snapshot->anchors & SNAPSHOT_ANCHOR_LEFT) { if (!(snapshot->anchors & SNAPSHOT_ANCHOR_RIGHT)) { src_box->width = MIN(src_box->width, dst_box->width * scale); } } else { if ((snapshot->anchors & SNAPSHOT_ANCHOR_RIGHT)) { int x2 = src_box->x + src_box->width; int new_x1 = x2 - dst_box->width * scale; src_box->x = MAX(src_box->x, new_x1); src_box->width = x2 - src_box->x; } } } static void snapshot_get_render_dst_box(const struct snapshot *snapshot, int tex_width, int tex_heigt, struct kywc_box *dst_box) { *dst_box = snapshot->dst_box; if (kywc_box_empty(dst_box)) { /* allow dst box is emtpy */ return; } if (!(snapshot->options & SNAPSHOT_RENDER_ENABLE_VIEW_BOX) || !snapshot->thumbnail) { return; } int top = 0, left = 0, right = 0, bottom = 0; if (thumbnail_get_padding(snapshot->thumbnail, &top, &left, &right, &bottom)) { int width = tex_width - right - left; int height = tex_heigt - top - bottom; float width_scale = width == 0 ? 0 : 1.0 * dst_box->width / width; float height_scale = height == 0 ? 0 : 1.0 * dst_box->height / height; int scaled_left = width_scale * left; int scaled_right = width_scale * right; int scaled_top = ceil(height_scale * top); int scaled_bottom = ceil(height_scale * bottom); dst_box->x -= scaled_left; dst_box->y -= scaled_top; dst_box->width += scaled_right + scaled_left; dst_box->height += scaled_bottom + scaled_top; } } static void node_snapshot_render(struct snapshot *snapshot, struct ky_scene_render_target *target) { struct node_snapshot *node_snapshot = wl_container_of(snapshot, node_snapshot, base); if (snapshot->was_damaged && snapshot->wants_update) { snapshot->was_damaged = false; node_snapshot_update_blur_info(node_snapshot); if (node_snapshot->is_zerocopy) { node_snapshot_set_zerocopy_buffer(node_snapshot, ky_scene_buffer_from_node(node_snapshot->node)); } } struct wlr_texture *texture = snapshot->texture; if (node_snapshot->is_zerocopy && snapshot->wants_update) { texture = ky_scene_buffer_get_texture(ky_scene_buffer_from_node(node_snapshot->node), target->output->output->renderer); } if (!texture) { return; } struct kywc_box geometry = { 0 }; snapshot_get_render_dst_box(snapshot, texture->width, texture->height, &geometry); if (kywc_box_empty(&geometry)) { return; } struct kywc_fbox src_box = { 0 }; snapshot_get_render_src_box(snapshot, texture, &geometry, &src_box); /* if data need blur is false, blur region is empty */ bool need_blur = pixman_region32_not_empty(&node_snapshot->blur_info.region); const struct ky_scene_render_texture_options opts = { .texture = texture, .geometry_box = &geometry, .alpha = &snapshot->opacity, .src = &src_box, .blur = { .scale = &snapshot->scale, .alpha = &snapshot->opacity, .info = need_blur ? &node_snapshot->blur_info : NULL, .offset_x = node_snapshot->blur_offset_x, .offset_y = node_snapshot->blur_offset_y, .radius = &node_snapshot->blur_radius, }, }; ky_scene_render_target_add_texture(target, &opts); } static bool node_snapshot_mark_wants_update(struct snapshot *snapshot, bool wants) { struct node_snapshot *node_snapshot = wl_container_of(snapshot, node_snapshot, base); if (!node_snapshot->node) { kywc_log(KYWC_INFO, "snapshot node is null, the texture can't update"); return false; } /* do the following operations only when there is zero copy */ if (!node_snapshot->is_zerocopy) { return true; } /* use textures that are no longer updated during rendering */ if (!wants && snapshot->buffer) { snapshot->texture = wlr_texture_from_buffer(manager->server->renderer, snapshot->buffer); } /* real time texture acquisition during rendering */ if (wants) { wlr_texture_destroy(snapshot->texture); snapshot->texture = NULL; } return true; } static float node_snapshot_get_scale(const struct snapshot *snapshot) { struct node_snapshot *node_snapshot = wl_container_of(snapshot, node_snapshot, base); return node_snapshot->is_zerocopy ? node_snapshot->surface_scale : snapshot->scale; } static bool node_snapshot_ensure_buffer_bindable(struct snapshot *snapshot) { struct node_snapshot *node_snapshot = wl_container_of(snapshot, node_snapshot, base); if (!node_snapshot->is_zerocopy) { return true; } if (snapshot->buffer && wlr_client_buffer_get(snapshot->buffer)) { return true; } if (snapshot->detached && !node_snapshot->source.node) { return false; } struct thumbnail *thumbnail = NULL; if (node_snapshot->source.type == SNAPSHOT_SOURCE_TYPE_VIEW) { uint32_t thumbnail_options = (snapshot->options & SNAPSHOT_DISABLE_DECOR) | (snapshot->options & SNAPSHOT_DISABLE_SHADOW) | (snapshot->options & SNAPSHOT_DISABLE_ROUND_CORNER); thumbnail = thumbnail_create_from_view(node_snapshot->source.view, thumbnail_options, snapshot->scale); } else { thumbnail = thumbnail_create_from_node(node_snapshot->node, snapshot->scale, node_snapshot->without_subeffect_node); } if (!thumbnail) { return false; } bool old_wants_update = snapshot->wants_update; node_snapshot->is_zerocopy = false; snapshot->wants_update = true; snapshot->thumbnail = thumbnail; snapshot->update.notify = node_snapshot_handle_thumbnail_update; thumbnail_add_update_listener(thumbnail, &snapshot->update); snapshot->destroy.notify = node_snapshot_handle_thumbnail_destroy; thumbnail_add_destroy_listener(thumbnail, &snapshot->destroy); thumbnail_update(thumbnail); /* if the update fails, a destroy message will be sent */ if (!snapshot->thumbnail) { snapshot->thumbnail = NULL; node_snapshot->is_zerocopy = true; snapshot->wants_update = old_wants_update; return false; } /* just in case */ if (!snapshot->buffer) { snapshot_remove_thumbnail(snapshot); node_snapshot->is_zerocopy = true; snapshot->wants_update = old_wants_update; return false; } return true; } static void node_snapshot_detach(struct snapshot *snapshot) { struct node_snapshot *node_snapshot = wl_container_of(snapshot, node_snapshot, base); wl_list_remove(&node_snapshot->node_damage.link); wl_list_remove(&node_snapshot->node_destroy.link); wl_list_init(&node_snapshot->node_damage.link); wl_list_init(&node_snapshot->node_destroy.link); node_snapshot->node = NULL; } static void node_snapshot_destroy(struct snapshot *snapshot) { struct node_snapshot *node_snapshot = wl_container_of(snapshot, node_snapshot, base); wl_list_remove(&node_snapshot->buffer_destroy.link); pixman_region32_fini(&node_snapshot->blur_info.region); } static struct snapshot_impl node_snapshot_impl = { .snapshot_ensure_buffer_bindable = node_snapshot_ensure_buffer_bindable, .snapshot_mark_wants_update = node_snapshot_mark_wants_update, .snapshot_get_scale = node_snapshot_get_scale, .snapshot_render = node_snapshot_render, .snapshot_detach = node_snapshot_detach, .snapshot_destroy = node_snapshot_destroy, }; static void node_snapshot_handle_buffer_destroy(struct wl_listener *listener, void *data) { struct node_snapshot *node_snapshot = wl_container_of(listener, node_snapshot, buffer_destroy); wl_list_remove(&node_snapshot->buffer_destroy.link); wl_list_init(&node_snapshot->buffer_destroy.link); node_snapshot->base.buffer = NULL; } static void node_snapshot_handle_node_damage(struct wl_listener *listener, void *data) { struct node_snapshot *node_snapshot = wl_container_of(listener, node_snapshot, node_damage); if (node_snapshot->node->type == KY_SCENE_NODE_BUFFER) { node_snapshot->base.was_damaged = true; if (node_snapshot->is_zerocopy && node_snapshot->base.wants_update) { node_snapshot_set_zerocopy_buffer(node_snapshot, ky_scene_buffer_from_node(node_snapshot->node)); } return; } struct ky_scene_node *damage_node = data; if (node_snapshot->without_subeffect_node && damage_node != node_snapshot->node && (damage_node->has_effect || ky_scene_node_find_upper_effect_node(damage_node, node_snapshot->node))) { return; } node_snapshot->base.was_damaged = true; } static void node_snapshot_handle_node_destroy(struct wl_listener *listener, void *data) { struct node_snapshot *node_snapshot = wl_container_of(listener, node_snapshot, node_destroy); node_snapshot->source.node = NULL; snapshot_detach(&node_snapshot->base); } void snapshot_destroy(struct snapshot *snapshot) { wl_signal_emit_mutable(&snapshot->events.destroy, NULL); assert(wl_list_empty(&snapshot->events.destroy.listener_list)); struct snapshot_binder *binder, *tmp; wl_list_for_each_safe(binder, tmp, &snapshot->binders, link) { snapshot_binder_destroy(binder); } snapshot_detach(snapshot); wlr_texture_destroy(snapshot->texture); if (snapshot->impl->snapshot_destroy) { snapshot->impl->snapshot_destroy(snapshot); } free(snapshot); } struct ky_scene_buffer *view_try_get_single_buffer(struct view *view) { if (view->base.ssd != KYWC_SSD_NONE || !xwayland_check_view(view)) { return NULL; } return ky_scene_buffer_try_from_surface(view->surface); } static struct snapshot *snapshot_create(struct snapshot_source *source, float scale, uint32_t options) { struct node_snapshot *node_snapshot = calloc(1, sizeof(*node_snapshot)); if (!node_snapshot) { return NULL; } node_snapshot->base.scale = scale; node_snapshot->base.opacity = 1.0f; node_snapshot->base.options = options; node_snapshot->base.impl = &node_snapshot_impl; wl_signal_init(&node_snapshot->base.events.destroy); wl_list_init(&node_snapshot->base.update.link); wl_list_init(&node_snapshot->base.destroy.link); wl_list_init(&node_snapshot->base.binders); if (source->type == SNAPSHOT_SOURCE_TYPE_VIEW) { struct ky_scene_buffer *buffer = view_try_get_single_buffer(source->view); node_snapshot->node = buffer ? &buffer->node : &source->view->tree->node; } else if (source->type == SNAPSHOT_SOURCE_TYPE_NODE) { node_snapshot->node = source->node; } node_snapshot->source = *source; node_snapshot->surface_scale = 1.0f; node_snapshot->need_blur = !(options & SNAPSHOT_RENDER_DISABLE_BLUR); node_snapshot->without_subeffect_node = !(options & SNAPSHOT_ENABLE_SUBEFFECT_NODE); node_snapshot->buffer_destroy.notify = node_snapshot_handle_buffer_destroy; wl_list_init(&node_snapshot->buffer_destroy.link); pixman_region32_init(&node_snapshot->blur_info.region); node_snapshot_update_blur_info(node_snapshot); if (node_snapshot->node->type == KY_SCENE_NODE_BUFFER) { struct ky_scene_buffer *buffer = ky_scene_buffer_from_node(node_snapshot->node); /** * don't care about scale, all rendering results are derived from the original buffer. * if options are set, they can be converted into rendering parameters and applied * during rendering. */ if (!ky_scene_buffer_is_clipped(buffer)) { node_snapshot->is_zerocopy = true; /* update texture */ node_snapshot->base.was_damaged = true; node_snapshot->base.wants_update = true; /* TODO: set up src_box based on options */ node_snapshot_set_zerocopy_buffer(node_snapshot, buffer); goto final; } } struct thumbnail *thumbnail = NULL; if (source->type == SNAPSHOT_SOURCE_TYPE_VIEW) { uint32_t thumbnail_options = (options & (SNAPSHOT_DISABLE_DECOR | SNAPSHOT_DISABLE_SHADOW | SNAPSHOT_DISABLE_ROUND_CORNER)); thumbnail = thumbnail_create_from_view(source->view, thumbnail_options, scale); } else { thumbnail = thumbnail_create_from_node(node_snapshot->node, scale, node_snapshot->without_subeffect_node); } if (!thumbnail || !snapshot_init_thumbnail(&node_snapshot->base, thumbnail)) { thumbnail_destroy(thumbnail); pixman_region32_fini(&node_snapshot->blur_info.region); free(node_snapshot); return NULL; } final: node_snapshot->node_damage.notify = node_snapshot_handle_node_damage; wl_signal_add(&node_snapshot->node->events.damage, &node_snapshot->node_damage); node_snapshot->node_destroy.notify = node_snapshot_handle_node_destroy; wl_signal_add(&node_snapshot->node->events.destroy, &node_snapshot->node_destroy); return &node_snapshot->base; } struct snapshot *snapshot_create_from_view(struct view *view, float scale, uint32_t options) { struct snapshot_source source = { .type = SNAPSHOT_SOURCE_TYPE_VIEW, .view = view, }; return snapshot_create(&source, scale, options); } struct snapshot *snapshot_create_from_node(struct ky_scene_node *node, float scale, uint32_t options) { struct snapshot_source source = { .type = SNAPSHOT_SOURCE_TYPE_NODE, .node = node, }; return snapshot_create(&source, scale, options); } struct ky_scene_buffer *snapshot_create_scene_buffer(struct ky_scene_tree *parent, struct snapshot *snapshot) { if (snapshot->impl->snapshot_ensure_buffer_bindable && !snapshot->impl->snapshot_ensure_buffer_bindable(snapshot)) { return NULL; } struct ky_scene_buffer *buffer = ky_scene_buffer_create(parent, snapshot->buffer); if (!buffer) { return NULL; } if (!snapshot_binder_create(snapshot, buffer)) { ky_scene_node_destroy(&buffer->node); return NULL; } return buffer; } void snapshot_set_dst_box(struct snapshot *snapshot, const struct kywc_box *box) { if (box != NULL) { snapshot->dst_box = *box; } else { snapshot->dst_box = (struct kywc_box){ 0 }; } } void snapshot_set_anchors(struct snapshot *snapshot, uint32_t anchors) { snapshot->anchors = anchors; } void snapshot_set_opacity(struct snapshot *snapshot, float opacity) { snapshot->opacity = opacity; } void snapshot_set_mixed_snapshot(struct snapshot *snapshot, struct snapshot *mix_snapshot, float blend_factor) { snapshot->blend_factor = blend_factor; snapshot->mix_snapshot = mix_snapshot; } void snapshot_get_render_box(struct snapshot *snapshot, struct kywc_box *dst_box) { if (!snapshot->buffer) { *dst_box = snapshot->dst_box; return; } snapshot_get_render_dst_box(snapshot, snapshot->buffer->width, snapshot->buffer->height, dst_box); } void snapshot_mark_wants_update(struct snapshot *snapshot, bool wants) { if (snapshot->wants_update == wants) { return; } if (snapshot->impl->snapshot_mark_wants_update) { if (!snapshot->impl->snapshot_mark_wants_update(snapshot, wants)) { return; } } snapshot->wants_update = wants; if (!snapshot->thumbnail) { return; } thumbnail_mark_wants_update(snapshot->thumbnail, wants); } void snapshot_detach(struct snapshot *snapshot) { if (snapshot->detached) { return; } snapshot->was_damaged = false; snapshot_mark_wants_update(snapshot, false); if (snapshot->impl->snapshot_detach) { snapshot->impl->snapshot_detach(snapshot); } snapshot->detached = true; if (!snapshot->thumbnail) { return; } thumbnail_destroy(snapshot->thumbnail); } void snapshot_add_destroy_listener(struct snapshot *snapshot, struct wl_listener *listener) { wl_signal_add(&snapshot->events.destroy, listener); } void snapshot_render(struct snapshot *snapshot, struct ky_scene_render_target *target) { assert(snapshot->impl->snapshot_render); snapshot->impl->snapshot_render(snapshot, target); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->destroy.link); free(manager); manager = NULL; } bool snapshot_manager_create(struct server *server) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->server = server; manager->destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->destroy); return true; } kylin-wayland-compositor/src/effect/action.c0000664000175000017500000001131215160460057020125 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "effect/action.h" #include "server.h" #include "view/view.h" static struct action_effect_manager { struct wl_list effects; struct wl_listener server_destroy; } *action_effects = NULL; struct action_effect { enum action_effect_type type; uint32_t support_stages; struct wl_list link; const struct action_effect_interface *impl; void *data; }; struct action_effect *action_effect_create(enum action_effect_type type, uint32_t support_stages, void *data, const struct action_effect_interface *impl) { if (!action_effects) { return NULL; } struct action_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return NULL; } effect->impl = impl; effect->type = type; effect->data = data; effect->support_stages = support_stages; wl_list_insert(&action_effects->effects, &effect->link); return effect; } void action_effect_destroy(struct action_effect *effect) { wl_list_remove(&effect->link); free(effect); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&action_effects->server_destroy.link); struct action_effect *effect, *tmp; wl_list_for_each_safe(effect, tmp, &action_effects->effects, link) { wl_list_remove(&effect->link); wl_list_init(&effect->link); } free(action_effects); action_effects = NULL; } bool action_effect_manager_create(struct server *server) { action_effects = calloc(1, sizeof(*action_effects)); if (!action_effects) { return false; } wl_list_init(&action_effects->effects); action_effects->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &action_effects->server_destroy); return true; } struct action_effect *action_effect_manager_get_effect(enum action_effect_type type) { struct action_effect *pos; wl_list_for_each(pos, &action_effects->effects, link) { if (pos->type == type) { return pos; } } return NULL; } bool action_effect_init_options(struct action_effect *action_effect, struct action_effect_options *options) { if (!action_effect->impl->init_options) { return false; } action_effect->impl->init_options(action_effect, options); return true; } static void get_action_effect_options(struct action_effect *effect, enum effect_action action, struct action_effect_options *options, action_effect_options_adjust_func_t options_adjust, void *user_data) { action_effect_init_options(effect, options); if (!options_adjust) { view_manager_adjust_effect_options(action, options, NULL); return; } options_adjust(ACTION_EFFECT_OPTIONS_ADD, action, options, user_data); view_manager_adjust_effect_options(action, options, NULL); options_adjust(ACTION_EFFECT_OPTIONS_CONFIRM, action, options, user_data); } bool node_add_action_effect(struct ky_scene_node *node, enum effect_action action, enum action_effect_type default_type, action_effect_options_adjust_func_t options_adjust, void *user_data) { struct action_effect *effect = action_effect_manager_get_effect(default_type); if (!effect || !effect->impl || !effect->impl->add_to_node) { return false; } struct action_effect_options options = { 0 }; get_action_effect_options(effect, action, &options, options_adjust, user_data); struct action_effect *new_effect = action_effect_manager_get_effect(options.effect_type); effect = new_effect ? new_effect : effect; return effect->impl->add_to_node(effect, node, action, &options); } bool view_add_action_effect(struct view *view, enum effect_action action, enum action_effect_type default_type, action_effect_options_adjust_func_t options_adjust, void *user_data) { struct action_effect *effect = action_effect_manager_get_effect(default_type); if (!effect || !effect->impl || !effect->impl->add_to_node) { return false; } struct action_effect_options options = { 0 }; get_action_effect_options(effect, action, &options, options_adjust, user_data); struct action_effect *new_effect = action_effect_manager_get_effect(options.effect_type); effect = new_effect ? new_effect : effect; return effect->impl->add_to_view(effect, view, action, &options); } kylin-wayland-compositor/src/effect/magic_lamp.c0000664000175000017500000007077115160461067020761 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include "effect/animator.h" #include "effect/magic_lamp.h" #include "effect_p.h" #include "output.h" #include "render/opengl.h" #include "scene/scene.h" #include "scene/surface.h" #include "scene/thumbnail.h" #include "util/macros.h" #include "util/matrix.h" #include "util/time.h" #include "magic_lamp_frag.h" #include "magic_lamp_vert.h" enum spout_location { SPOUT_LOCATION_LEFT = 0, SPOUT_LOCATION_TOP = 1, SPOUT_LOCATION_RIGHT = 2, SPOUT_LOCATION_BOTTOM = 3, }; enum PointAxis { POINT_AXIS_X = 0, POINT_AXIS_Y = 1 }; typedef float Point[2]; // Cubic Bezier curve struct BezierCurve { Point sp; // start point Point cp1; // control point 1 Point cp2; // control point 2 Point ep; // end point }; struct vertex { float x; float y; float u; float v; }; // 0 top-left // 1 top-right // 2 bottom-right // 3 bottom-left typedef struct vertex quad[4]; static struct magic_lamp_gl_shader { int32_t program; // vs GLint in_position; GLint in_texcoord; GLint logic2ndc; GLint texture; } gl_shader = { 0 }; struct magic_lamp_entry { struct effect_entity *effect_entity; struct view *window_view; // mesh uint32_t subquads_size; quad *subquads; quad *subquads_cache; uint32_t vertices_size; struct vertex *vertices; // animate bool overtime; bool reversed; enum spout_location spout_location; struct kywc_fbox spout; struct kywc_fbox window; uint32_t start_time; float progress; // two part animation. deformation + track // screenshot struct thumbnail *thumbnail; struct wlr_texture *thumbnail_texture; struct wl_listener thumbnail_update; struct wl_listener thumbnail_destroy; // bbox struct kywc_box bbox; }; struct magic_lamp_effect { struct effect *effect; struct wl_listener destroy; struct effect_manager *manager; struct ky_opengl_renderer *renderer; struct animation *animation; uint32_t animate_duration; uint32_t subdiv_count; uint32_t subdiv_count2; }; static struct magic_lamp_effect *magic_lamp_effect = NULL; static inline float lerp(float a, float b, float t) { return a + t * (b - a); } static void subdivide_quad(quad quad0, uint32_t x, uint32_t y, quad *quads) { float step_x = 1.f / x; float step_y = 1.f / y; for (uint32_t i = 0; i < x; i++) { for (uint32_t j = 0; j < y; j++) { float u1 = i * step_x; float u2 = (i + 1) * step_x; float v1 = j * step_y; float v2 = (j + 1) * step_y; quad *sub_quad = &quads[y * i + j]; (*sub_quad)[0].u = u1; (*sub_quad)[0].v = v1; (*sub_quad)[1].u = u2; (*sub_quad)[1].v = v1; (*sub_quad)[2].u = u2; (*sub_quad)[2].v = v2; (*sub_quad)[3].u = u1; (*sub_quad)[3].v = v2; // lerp y axis float top1x = lerp(quad0[0].x, quad0[1].x, u1); float top1y = lerp(quad0[0].y, quad0[1].y, u1); float top2x = lerp(quad0[0].x, quad0[1].x, u2); float top2y = lerp(quad0[0].y, quad0[1].y, u2); float bottom1x = lerp(quad0[3].x, quad0[2].x, u1); float bottom1y = lerp(quad0[3].y, quad0[2].y, u1); float bottom2x = lerp(quad0[3].x, quad0[2].x, u2); float bottom2y = lerp(quad0[3].y, quad0[2].y, u2); // lerp x axis get 4 vertex (*sub_quad)[0].x = lerp(top1x, bottom1x, v1); (*sub_quad)[0].y = lerp(top1y, bottom1y, v1); (*sub_quad)[1].x = lerp(top2x, bottom2x, v1); (*sub_quad)[1].y = lerp(top2y, bottom2y, v1); (*sub_quad)[3].x = lerp(top1x, bottom1x, v2); (*sub_quad)[3].y = lerp(top1y, bottom1y, v2); (*sub_quad)[2].x = lerp(top2x, bottom2x, v2); (*sub_quad)[2].y = lerp(top2y, bottom2y, v2); } } } static inline float bezier_point_axis(struct BezierCurve *curve, float t, enum PointAxis axis) { float u = 1.f - t; float tt = t * t; float uu = u * u; float uuu = uu * u; float ttt = tt * t; return uuu * curve->sp[axis] + 3.f * uu * t * curve->cp1[axis] + 3.f * u * tt * curve->cp2[axis] + ttt * curve->ep[axis]; } static inline float bezier_derivative_axis(struct BezierCurve *curve, float t, enum PointAxis axis) { float u = 1.f - t; float tt = t * t; float uu = u * u; return 3.f * uu * (curve->cp1[axis] - curve->sp[axis]) + 6.f * u * t * (curve->cp2[axis] - curve->cp1[axis]) + 3.f * tt * (curve->ep[axis] - curve->cp2[axis]); } static inline float bezier_axis_intersection(struct BezierCurve *curve, enum PointAxis axis, float axisPos) { // Initial guess value float t = (axisPos - curve->sp[axis]) / (curve->ep[axis] - curve->sp[axis]); for (uint32_t i = 0; i < 3; i++) { float current = bezier_point_axis(curve, t, axis); float df = bezier_derivative_axis(curve, t, axis); if (fabsf(df) < 0.001f) { break; } // Newton-Raphson iterations. (Derivative correction) t -= (current - axisPos) / df; } return t; } // Determine the curve trajectory static inline void calculate_bezier_control_points(struct BezierCurve *curve) { curve->cp1[POINT_AXIS_X] = curve->sp[POINT_AXIS_X] + (curve->ep[POINT_AXIS_X] - curve->sp[POINT_AXIS_X]) * 0.3f; curve->cp1[POINT_AXIS_Y] = curve->sp[POINT_AXIS_Y] + (curve->ep[POINT_AXIS_Y] - curve->sp[POINT_AXIS_Y]) * 0.8f; curve->cp2[POINT_AXIS_X] = curve->ep[POINT_AXIS_X] - (curve->ep[POINT_AXIS_X] - curve->sp[POINT_AXIS_X]) * 0.2f; curve->cp2[POINT_AXIS_Y] = curve->ep[POINT_AXIS_Y] - (curve->ep[POINT_AXIS_Y] - curve->sp[POINT_AXIS_Y]) * 0.5f; } static void calculate_vertex_curve_position(struct magic_lamp_entry *entry, float progress) { if (entry->spout_location == SPOUT_LOCATION_BOTTOM) { struct BezierCurve curve0 = { .sp = { entry->window.x, entry->window.y }, .ep = { entry->spout.x, entry->spout.y } }; calculate_bezier_control_points(&curve0); struct BezierCurve curve1 = { .sp = { entry->window.x + entry->window.width, entry->window.y }, .ep = { entry->spout.x + entry->spout.width, entry->spout.y } }; calculate_bezier_control_points(&curve1); for (uint32_t i = 0; i < entry->subquads_size; i++) { quad *subquad = &entry->subquads[i]; for (uint32_t j = 0; j < 4; j++) { struct vertex *vtx = &(*subquad)[j]; float y = lerp(curve0.sp[POINT_AXIS_Y], curve0.ep[POINT_AXIS_Y], progress) + vtx->v * entry->window.height; y = fmin(y, curve0.ep[POINT_AXIS_Y]); float t0 = bezier_axis_intersection(&curve0, POINT_AXIS_Y, y); float x0 = bezier_point_axis(&curve0, t0, POINT_AXIS_X); float t1 = bezier_axis_intersection(&curve1, POINT_AXIS_Y, y); float x1 = bezier_point_axis(&curve1, t1, POINT_AXIS_X); vtx->x = lerp(x0, x1, vtx->u); vtx->y = y; } } } else if (entry->spout_location == SPOUT_LOCATION_TOP) { struct BezierCurve curve0 = { .sp = { entry->window.x, entry->window.y + entry->window.height }, .ep = { entry->spout.x, entry->spout.y + entry->spout.height } }; calculate_bezier_control_points(&curve0); struct BezierCurve curve1 = { .sp = { entry->window.x + entry->window.width, entry->window.y + entry->window.height }, .ep = { entry->spout.x + entry->spout.width, entry->spout.y + entry->spout.height } }; calculate_bezier_control_points(&curve1); for (uint32_t i = 0; i < entry->subquads_size; i++) { quad *subquad = &entry->subquads[i]; for (uint32_t j = 0; j < 4; j++) { struct vertex *vtx = &(*subquad)[j]; float y = lerp(curve0.sp[POINT_AXIS_Y], curve0.ep[POINT_AXIS_Y], progress) - (1.f - vtx->v) * entry->window.height; y = fmax(y, curve0.ep[POINT_AXIS_Y]); float t0 = bezier_axis_intersection(&curve0, POINT_AXIS_Y, y); float x0 = bezier_point_axis(&curve0, t0, POINT_AXIS_X); float t1 = bezier_axis_intersection(&curve1, POINT_AXIS_Y, y); float x1 = bezier_point_axis(&curve1, t1, POINT_AXIS_X); vtx->x = lerp(x0, x1, vtx->u); vtx->y = y; } } } else if (entry->spout_location == SPOUT_LOCATION_LEFT) { struct BezierCurve curve0 = { .sp = { entry->window.x + entry->window.width, entry->window.y }, .ep = { entry->spout.x + entry->spout.width, entry->spout.y } }; calculate_bezier_control_points(&curve0); struct BezierCurve curve1 = { .sp = { entry->window.x + entry->window.width, entry->window.y + entry->window.height }, .ep = { entry->spout.x + entry->spout.width, entry->spout.y + entry->spout.height } }; calculate_bezier_control_points(&curve1); for (uint32_t i = 0; i < entry->subquads_size; i++) { quad *subquad = &entry->subquads[i]; for (uint32_t j = 0; j < 4; j++) { struct vertex *vtx = &(*subquad)[j]; float x = lerp(curve0.sp[POINT_AXIS_X], curve0.ep[POINT_AXIS_X], progress) - (1.f - vtx->u) * entry->window.width; x = fmax(x, curve0.ep[POINT_AXIS_X]); float t0 = bezier_axis_intersection(&curve0, POINT_AXIS_X, x); float y0 = bezier_point_axis(&curve0, t0, POINT_AXIS_Y); float t1 = bezier_axis_intersection(&curve1, POINT_AXIS_X, x); float y1 = bezier_point_axis(&curve1, t1, POINT_AXIS_Y); vtx->x = x; vtx->y = lerp(y0, y1, vtx->v); } } } else if (entry->spout_location == SPOUT_LOCATION_RIGHT) { struct BezierCurve curve0 = { .sp = { entry->window.x, entry->window.y }, .ep = { entry->spout.x, entry->spout.y } }; calculate_bezier_control_points(&curve0); struct BezierCurve curve1 = { .sp = { entry->window.x, entry->window.y + entry->window.height }, .ep = { entry->spout.x, entry->spout.y + entry->spout.height } }; calculate_bezier_control_points(&curve1); for (uint32_t i = 0; i < entry->subquads_size; i++) { quad *subquad = &entry->subquads[i]; for (uint32_t j = 0; j < 4; j++) { struct vertex *vtx = &(*subquad)[j]; float x = lerp(curve0.sp[POINT_AXIS_X], curve0.ep[POINT_AXIS_X], progress) + vtx->u * entry->window.width; x = fmin(x, curve0.ep[POINT_AXIS_X]); float t0 = bezier_axis_intersection(&curve0, POINT_AXIS_X, x); float y0 = bezier_point_axis(&curve0, t0, POINT_AXIS_Y); float t1 = bezier_axis_intersection(&curve1, POINT_AXIS_X, x); float y1 = bezier_point_axis(&curve1, t1, POINT_AXIS_Y); vtx->x = x; vtx->y = lerp(y0, y1, vtx->v); } } } } static void compute_boundbox(struct magic_lamp_entry *entry, struct ky_scene_output *output) { float min_x = FLT_MAX; float min_y = FLT_MAX; float max_x = FLT_MIN; float max_y = FLT_MIN; for (uint32_t i = 0; i < entry->subquads_size; i++) { quad *subquad = &entry->subquads[i]; for (uint32_t j = 0; j < 4; j++) { min_x = MIN(min_x, (*subquad)[j].x); min_y = MIN(min_y, (*subquad)[j].y); max_x = MAX(max_x, (*subquad)[j].x); max_y = MAX(max_y, (*subquad)[j].y); } } // avoid float to int precision issue entry->bbox.x = floorf(min_x); entry->bbox.y = floorf(min_y); entry->bbox.width = MAX(1, ceilf(max_x) - entry->bbox.x); entry->bbox.height = MAX(1, ceilf(max_y) - entry->bbox.y); } static void create_opengl_shader(struct wlr_renderer *renderer) { struct ky_opengl_renderer *gl_renderer = ky_opengl_renderer_from_wlr_renderer(renderer); ky_egl_make_current(gl_renderer->egl, NULL); GLuint prog = ky_opengl_create_program(gl_renderer, magic_lamp_vert, magic_lamp_frag); if (prog == 0) { return; } gl_shader.program = prog; gl_shader.in_position = glGetAttribLocation(prog, "in_position"); gl_shader.in_texcoord = glGetAttribLocation(prog, "in_texcoord"); gl_shader.logic2ndc = glGetUniformLocation(prog, "logic2ndc"); gl_shader.texture = glGetUniformLocation(prog, "tex"); ky_egl_unset_current(gl_renderer->egl); } static void handle_thumbnail_update(struct wl_listener *listener, void *data) { struct thumbnail_update_event *event = data; if (!event->buffer_changed) { return; } struct magic_lamp_entry *entry = wl_container_of(listener, entry, thumbnail_update); if (entry->thumbnail_texture) { wlr_texture_destroy(entry->thumbnail_texture); } struct wlr_renderer *renderer = magic_lamp_effect->manager->server->renderer; entry->thumbnail_texture = wlr_texture_from_buffer(renderer, event->buffer); } static void handle_thumbnail_destroy(struct wl_listener *listener, void *data) { struct magic_lamp_entry *entry = wl_container_of(listener, entry, thumbnail_destroy); wl_list_remove(&entry->thumbnail_destroy.link); wl_list_remove(&entry->thumbnail_update.link); entry->thumbnail = NULL; } static void handle_effect_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&magic_lamp_effect->destroy.link); free(magic_lamp_effect); magic_lamp_effect = NULL; } static void entity_destroy(struct effect_entity *entity) { struct magic_lamp_entry *entry = entity->user_data; if (!entry) { return; } if (entry->thumbnail_texture) { wlr_texture_destroy(entry->thumbnail_texture); } if (entry->thumbnail) { wl_list_remove(&entry->thumbnail_update.link); wl_list_remove(&entry->thumbnail_destroy.link); thumbnail_destroy(entry->thumbnail); } free(entry->vertices); free(entry->subquads); free(entry->subquads_cache); free(entry); } static bool entity_bounding_box(struct effect_entity *entity, struct kywc_box *box) { struct magic_lamp_entry *entry = entity->user_data; if (!entry) { box->x = box->y = box->width = box->height = 0; return false; } *box = (struct kywc_box){ entry->bbox.x, entry->bbox.y, entry->bbox.width, entry->bbox.height }; struct effect_chain *chain = entity->slot.chain; struct node_effect_chain *node_chain = wl_container_of(chain, node_chain, base); int lx, ly; ky_scene_node_coords(node_chain->node, &lx, &ly); box->x -= lx; box->y -= ly; return false; } static bool node_push_damage(struct effect_entity *entity, struct ky_scene_node *damage_node, uint32_t *damage_type, pixman_region32_t *damage) { struct kywc_box box; entity_bounding_box(entity, &box); pixman_region32_union_rect(damage, damage, box.x, box.y, box.width, box.height); return false; } static bool frame_render_pre(struct effect_entity *entity, struct ky_scene_render_target *target) { struct magic_lamp_entry *entry = entity->user_data; if (!entry) { return true; } if (entry->overtime) { effect_entity_destroy(entry->effect_entity); return true; } // timer uint32_t diff_time = current_time_msec() - entry->start_time; if (diff_time > magic_lamp_effect->animate_duration) { diff_time = magic_lamp_effect->animate_duration; entry->overtime = true; } float percent = diff_time / (float)magic_lamp_effect->animate_duration; entry->progress = animation_value(magic_lamp_effect->animation, percent); if (entry->reversed) { entry->progress = 1.f - entry->progress; } // reset subdivide quad memcpy(entry->subquads, entry->subquads_cache, entry->subquads_size * sizeof(quad)); // calculate progress. modify quad vertex const float first_anim_dura = 0.3f; const float second_anim_start = 0.2f; if (entry->progress <= first_anim_dura) { // sub animation 1: deformation animation = window rect -> sub animation 2 first frame float progress = entry->progress / first_anim_dura; calculate_vertex_curve_position(entry, second_anim_start); for (uint32_t i = 0; i < entry->subquads_size; i++) { quad *subquad = &entry->subquads[i]; for (uint32_t j = 0; j < 4; j++) { struct vertex *vtx = &(*subquad)[j]; vtx->x = lerp(entry->window.x + vtx->u * entry->window.width, vtx->x, progress); vtx->y = lerp(entry->window.y + vtx->v * entry->window.height, vtx->y, progress); } } } else { // sub animation 2: track animation float progress = (entry->progress - first_anim_dura) / (1.f - first_anim_dura); // 0~1 -> second_anim_start~1 progress = lerp(second_anim_start, 1.f, progress); calculate_vertex_curve_position(entry, progress); } // compute boundbox for damage region struct ky_scene_output *scene_output = target->output; compute_boundbox(entry, scene_output); // triangulate uint32_t vtx_count = 0; for (uint32_t i = 0; i < entry->subquads_size; i++) { quad *subquad = &entry->subquads[i]; entry->vertices[vtx_count++] = (*subquad)[0]; entry->vertices[vtx_count++] = (*subquad)[2]; entry->vertices[vtx_count++] = (*subquad)[1]; entry->vertices[vtx_count++] = (*subquad)[0]; entry->vertices[vtx_count++] = (*subquad)[3]; entry->vertices[vtx_count++] = (*subquad)[2]; } effect_entity_push_damage(entity, KY_SCENE_DAMAGE_BOTH); return true; } static bool node_render(struct effect_entity *entity, int lx, int ly, struct ky_scene_render_target *target) { struct magic_lamp_entry *entry = entity->user_data; if (!entry) { return true; } if (!entry->thumbnail_texture && !wlr_texture_is_opengl(entry->thumbnail_texture)) { return true; } struct wlr_box dst_box = { .x = entry->bbox.x - target->logical.x, .y = entry->bbox.y - target->logical.y, .width = entry->bbox.width, .height = entry->bbox.height, }; ky_scene_render_box(&dst_box, target); pixman_region32_t render_region; pixman_region32_init(&render_region); pixman_region32_copy(&render_region, &target->damage); pixman_region32_translate(&render_region, -target->logical.x, -target->logical.y); ky_scene_render_region(&render_region, target); pixman_region32_intersect_rect(&render_region, &render_region, dst_box.x, dst_box.y, dst_box.width, dst_box.height); int rects_len; const pixman_box32_t *rects = pixman_region32_rectangles(&render_region, &rects_len); if (rects_len == 0) { pixman_region32_fini(&render_region); return false; } struct ky_mat3 transform; ky_mat3_identity(&transform); ky_mat3_init_translate(&transform, -target->logical.x, -target->logical.y); struct ky_mat3 to_ndc; ky_mat3_logic_to_ndc(&to_ndc, target->logical.width, target->logical.height, target->transform); struct ky_mat3 logic2ndc; ky_mat3_multiply(&to_ndc, &transform, &logic2ndc); struct ky_opengl_texture *ky_tex = ky_opengl_texture_from_wlr_texture(entry->thumbnail_texture); GLfloat *verts = (GLfloat *)entry->vertices; glUseProgram(gl_shader.program); glEnableVertexAttribArray(gl_shader.in_position); glVertexAttribPointer(gl_shader.in_position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), verts); glEnableVertexAttribArray(gl_shader.in_texcoord); glVertexAttribPointer(gl_shader.in_texcoord, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), verts + 2); glUniformMatrix3fv(gl_shader.logic2ndc, 1, GL_FALSE, logic2ndc.matrix); glUniform1i(gl_shader.texture, 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, ky_tex->tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glEnable(GL_SCISSOR_TEST); for (int i = 0; i < rects_len; i++) { const pixman_box32_t *rect = &rects[i]; glScissor(rect->x1, rect->y1, rect->x2 - rect->x1, rect->y2 - rect->y1); glDrawArrays(GL_TRIANGLES, 0, entry->vertices_size); } glUseProgram(0); glDisableVertexAttribArray(gl_shader.in_position); glDisableVertexAttribArray(gl_shader.in_texcoord); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_SCISSOR_TEST); pixman_region32_fini(&render_region); return false; } static bool frame_render_post(struct effect_entity *entity, struct ky_scene_render_target *target) { effect_entity_push_damage(entity, KY_SCENE_DAMAGE_BOTH); return true; } static bool handle_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } return false; } static const struct effect_interface effect_impl = { .entity_destroy = entity_destroy, .entity_bounding_box = entity_bounding_box, .node_push_damage = node_push_damage, .node_render = node_render, .frame_render_pre = frame_render_pre, .frame_render_post = frame_render_post, .configure = handle_effect_configure, }; bool magic_lamp_effect_create(struct effect_manager *manager) { if (!wlr_renderer_is_opengl(manager->server->renderer)) { return false; } magic_lamp_effect = calloc(1, sizeof(*magic_lamp_effect)); if (!magic_lamp_effect) { return false; } magic_lamp_effect->effect = effect_create("magic_lamp", 5, true, &effect_impl, NULL); if (!magic_lamp_effect->effect) { free(magic_lamp_effect); return false; } magic_lamp_effect->effect->support_actions = EFFECT_ACTION_MINIMIZE | EFFECT_ACTION_MINIMIZE_RESTORE; magic_lamp_effect->effect->category = EFFECT_CATEGORY_ACTION; magic_lamp_effect->destroy.notify = handle_effect_destroy; wl_signal_add(&magic_lamp_effect->effect->events.destroy, &magic_lamp_effect->destroy); magic_lamp_effect->manager = manager; magic_lamp_effect->renderer = ky_opengl_renderer_from_wlr_renderer(manager->server->renderer); magic_lamp_effect->animation = animation_manager_get(ANIMATION_TYPE_LINER); magic_lamp_effect->subdiv_count = 32; magic_lamp_effect->subdiv_count2 = 8; return true; } static void subdivide_window_quad(struct magic_lamp_entry *entry) { uint32_t subdiv_count = magic_lamp_effect->subdiv_count; uint32_t subdiv_count2 = magic_lamp_effect->subdiv_count2; quad window_quad = { { entry->window.x, entry->window.y, 0.0f, 0.0f }, { entry->window.x + entry->window.width, entry->window.y, 1.0f, 0.0f }, { entry->window.x + entry->window.width, entry->window.y + entry->window.height, 1.0f, 1.0f }, { entry->window.x, entry->window.y + entry->window.height, 0.0f, 1.0f } }; switch (entry->spout_location) { case SPOUT_LOCATION_BOTTOM: case SPOUT_LOCATION_TOP: subdivide_quad(window_quad, subdiv_count2, subdiv_count, entry->subquads_cache); break; case SPOUT_LOCATION_LEFT: case SPOUT_LOCATION_RIGHT: subdivide_quad(window_quad, subdiv_count, subdiv_count2, entry->subquads_cache); break; } } bool view_add_magic_lamp_effect(struct view *view) { if (!magic_lamp_effect || !magic_lamp_effect->effect->enabled) { return false; } magic_lamp_effect->animate_duration = effect_manager_scale_time(400); // create shader if (gl_shader.program == 0) { create_opengl_shader(magic_lamp_effect->manager->server->renderer); if (gl_shader.program <= 0) { return false; } } if (!view->minimized_geometry.panel_surface) { return false; } if (view->minimized_when_show_desktop) { return false; } // duplicate add struct effect_entity *entity = ky_scene_node_find_effect_entity(&view->tree->node, magic_lamp_effect->effect); if (entity) { struct magic_lamp_entry *entry = entity->user_data; if (entry && entry->window_view == view) { entry->overtime = false; entry->reversed = !view->base.minimized; if (entry->reversed) { entry->start_time = current_time_msec() - magic_lamp_effect->animate_duration * (1.f - entry->progress); } else { entry->start_time = current_time_msec() - magic_lamp_effect->animate_duration * entry->progress; } return true; } } struct thumbnail *thumbnail = thumbnail_create_from_view(view, THUMBNAIL_DISABLE_SHADOW, 1.0); if (!thumbnail) { return false; } entity = ky_scene_node_add_effect(&view->tree->node, magic_lamp_effect->effect, NULL); if (!entity) { return false; } struct magic_lamp_entry *entry = calloc(1, sizeof(*entry)); if (!entry) { effect_entity_destroy(entity); return false; } entity->user_data = entry; entry->effect_entity = entity; entry->window_view = view; // alloc buffer entry->subquads_size = magic_lamp_effect->subdiv_count * magic_lamp_effect->subdiv_count2; entry->subquads = malloc(entry->subquads_size * sizeof(quad)); entry->subquads_cache = malloc(entry->subquads_size * sizeof(quad)); entry->vertices_size = entry->subquads_size * 6; entry->vertices = malloc(entry->vertices_size * sizeof(struct vertex)); wl_list_init(&entry->thumbnail_update.link); wl_list_init(&entry->thumbnail_destroy.link); // screenshot window view entry->thumbnail = thumbnail; entry->thumbnail_update.notify = handle_thumbnail_update; thumbnail_add_update_listener(entry->thumbnail, &entry->thumbnail_update); entry->thumbnail_destroy.notify = handle_thumbnail_destroy; thumbnail_add_destroy_listener(entry->thumbnail, &entry->thumbnail_destroy); thumbnail_update(entry->thumbnail); entry->overtime = false; // animate reversed entry->reversed = !view->base.minimized; // calculate taskbar docking entry->spout_location = SPOUT_LOCATION_BOTTOM; struct output *output = output_from_kywc_output(view->output); if (output->geometry.height - output->usable_area.height > 1) { if (output->usable_area.y - output->scene_output->geometry.y > 1) { entry->spout_location = SPOUT_LOCATION_TOP; } else { entry->spout_location = SPOUT_LOCATION_BOTTOM; } } else if (output->geometry.width - output->usable_area.width > 1) { if (output->usable_area.x - output->scene_output->geometry.x > 1) { entry->spout_location = SPOUT_LOCATION_LEFT; } else { entry->spout_location = SPOUT_LOCATION_RIGHT; } } // spout quad int lx, ly; struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(view->minimized_geometry.panel_surface); ky_scene_node_coords(&buffer->node, &lx, &ly); entry->spout = (struct kywc_fbox){ view->minimized_geometry.geometry.x + lx, view->minimized_geometry.geometry.y + ly, view->minimized_geometry.geometry.width, view->minimized_geometry.geometry.height }; // window quad entry->window = (struct kywc_fbox){ view->base.geometry.x - view->base.margin.off_x, view->base.geometry.y - view->base.margin.off_y, view->base.geometry.width + view->base.margin.off_width, view->base.geometry.height + view->base.margin.off_height }; subdivide_window_quad(entry); entry->start_time = current_time_msec(); return true; } kylin-wayland-compositor/src/effect/mouse_click.c0000664000175000017500000001057315160460057021155 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "effect/shape.h" #include "effect_p.h" #include "input/cursor.h" #include "input/seat.h" struct mouse_click_effect { struct tap_ripple_effect *base; struct wl_listener new_seat; struct wl_list seat_mouses; }; struct seat_mouse { struct wl_list link; struct mouse_click_effect *effect; struct seat *seat; struct wl_listener mouse_button; struct wl_listener destroy; }; static void handle_mouse_button(struct wl_listener *listener, void *data) { struct wlr_pointer_button_event *event = data; if (event->state != WL_POINTER_BUTTON_STATE_PRESSED) { return; } struct seat_mouse *seat_mouse = wl_container_of(listener, seat_mouse, mouse_button); struct mouse_click_effect *effect = seat_mouse->effect; tap_ripple_effect_remove_all_points(effect->base); tap_ripple_effect_add_point(effect->base, 0, roundf(seat_mouse->seat->cursor->lx), roundf(seat_mouse->seat->cursor->ly)); } static void seat_mouse_destroy(struct seat_mouse *seat_mouse) { wl_list_remove(&seat_mouse->link); wl_list_remove(&seat_mouse->mouse_button.link); wl_list_remove(&seat_mouse->destroy.link); free(seat_mouse); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { struct seat_mouse *seat_mouse = wl_container_of(listener, seat_mouse, destroy); seat_mouse_destroy(seat_mouse); } static void seat_mouse_create(struct mouse_click_effect *effect, struct seat *seat) { struct seat_mouse *seat_mouse = calloc(1, sizeof(*seat_mouse)); if (!seat_mouse) { return; } seat_mouse->effect = effect; seat_mouse->seat = seat; seat_mouse->mouse_button.notify = handle_mouse_button; wl_signal_add(&seat->cursor->wlr_cursor->events.button, &seat_mouse->mouse_button); seat_mouse->destroy.notify = handle_seat_destroy; wl_signal_add(&seat->events.destroy, &seat_mouse->destroy); wl_list_insert(&effect->seat_mouses, &seat_mouse->link); } static void handle_new_seat(struct wl_listener *listener, void *data) { struct mouse_click_effect *effect = wl_container_of(listener, effect, new_seat); struct seat *seat = data; seat_mouse_create(effect, seat); } static bool handle_seat(struct seat *seat, int index, void *data) { struct mouse_click_effect *effect = data; seat_mouse_create(effect, seat); return false; } static void handle_effect_enable(struct tap_ripple_effect *base_effect, void *data) { struct mouse_click_effect *effect = data; input_manager_for_each_seat(handle_seat, effect); effect->new_seat.notify = handle_new_seat; seat_add_new_listener(&effect->new_seat); } static void handle_effect_disable(struct tap_ripple_effect *base_effect, void *data) { struct mouse_click_effect *effect = data; wl_list_remove(&effect->new_seat.link); wl_list_init(&effect->new_seat.link); struct seat_mouse *seat_mouse, *tmp0; wl_list_for_each_safe(seat_mouse, tmp0, &effect->seat_mouses, link) { seat_mouse_destroy(seat_mouse); } } static void handle_effect_destroy(struct tap_ripple_effect *base_effect, void *data) { struct mouse_click_effect *effect = data; free(effect); } static const struct tap_ripple_effect_interface effect_impl = { .enable = handle_effect_enable, .disable = handle_effect_disable, .destroy = handle_effect_destroy, }; bool mouse_click_effect_create(struct effect_manager *manager) { struct mouse_click_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } wl_list_init(&effect->new_seat.link); wl_list_init(&effect->seat_mouses); struct tap_ripple_effect_options options = {}; options.size = 50; options.color[0] = 1.0f; options.color[1] = 0.2f; options.color[2] = 0.2f; options.animate_duration = 600; options.start_radius = 0.5f; options.end_radius = 0.0f; options.start_attenuation = 0.4f; options.end_attenuation = 0.8f; effect->base = tap_ripple_effect_create(manager, &options, &effect_impl, "mouse_click", 100, false, effect); if (!effect->base) { free(effect); return false; } return true; } kylin-wayland-compositor/src/effect/watermark.c0000664000175000017500000004505115160461067020656 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "effect_p.h" #include "output.h" #include "painter.h" #include "util/dbus.h" #include "util/file.h" #include "util/string.h" #include "view/view.h" enum watermark_type { WATERMARK_TYPE_POSITION = 0, WATERMARK_TYPE_REPEAT, WATERMARK_TYPE_SCREEN, WATERMARK_TYPE_SCREEN_RATIO, WATERMARK_TYPE_LOCATION, WATERMARK_TYPE_LOCATION_PRIMARY, WATERMARK_TYPE_LAST, }; struct watermark_info { char *file; double opacity; union { struct { uint32_t left : 1; uint32_t right : 1; uint32_t left_dist : 15; uint32_t right_dist : 15; }; int x; }; union { struct { uint32_t top : 1; uint32_t bottom : 1; uint32_t top_dist : 15; uint32_t bottom_dist : 15; }; int y; }; enum watermark_type type; bool topmost; }; struct watermark_entry { struct wl_list link; struct watermark *watermark; struct ky_scene_buffer *scene_buffer; struct output *output; struct wl_listener output_geometry; struct wl_listener output_disable; }; struct watermark { struct wl_list link; const char *name; struct watermark_effect *effect; struct wl_list entries; // per output struct wlr_buffer *buffer; // from image file struct wl_listener new_enabled_output; struct wl_listener primary_output; struct ky_scene_tree *tree; struct watermark_info info; }; struct watermark_effect { struct effect *effect; struct wl_listener enable; struct wl_listener disable; struct wl_listener destroy; struct effect_manager *manager; struct dbus_object *dbus; // for dbus struct dbus_object *dbus_v2; struct watermark watermark; // default struct wl_list watermarks; }; static struct wlr_buffer *watermark_get_or_create_buffer(struct watermark *watermark) { if (watermark->buffer) { return watermark->buffer; } struct draw_info info = { .image = watermark->info.file }; struct wlr_buffer *buffer = painter_draw_buffer(&info); if (!buffer) { return NULL; } watermark->buffer = buffer; return buffer; } static void watermark_entry_destroy(struct watermark_entry *entry) { wl_list_remove(&entry->link); wl_list_remove(&entry->output_disable.link); wl_list_remove(&entry->output_geometry.link); ky_scene_node_destroy(&entry->scene_buffer->node); free(entry); } static void watermark_entry_handle_output_disable(struct wl_listener *listener, void *data) { struct watermark_entry *entry = wl_container_of(listener, entry, output_disable); watermark_entry_destroy(entry); } static void watermark_entry_update_buffer(struct watermark_entry *entry) { struct watermark *watermark = entry->watermark; struct wlr_buffer *buffer = watermark_get_or_create_buffer(watermark); if (!buffer) { return; } if (entry->scene_buffer->buffer != buffer) { ky_scene_buffer_set_buffer(entry->scene_buffer, buffer); } struct kywc_box geo = { 0 }; if (entry->output) { geo = entry->output->geometry; } int x = geo.x, y = geo.y; int width = buffer->width; int height = buffer->height; switch (watermark->info.type) { case WATERMARK_TYPE_POSITION: x = watermark->info.x; y = watermark->info.y; break; case WATERMARK_TYPE_REPEAT: ky_scene_buffer_set_repeated(entry->scene_buffer, true); /* fallthrough to screen */ case WATERMARK_TYPE_SCREEN: width = geo.width; height = geo.height; break; case WATERMARK_TYPE_SCREEN_RATIO:; float output_ratio = (float)geo.width / geo.height; float buffer_ratio = (float)width / height; if (output_ratio < buffer_ratio) { width = geo.width; height = geo.width / buffer_ratio; y += (geo.height - height) / 2; } else { height = geo.height; width = geo.height * buffer_ratio; x += (geo.width - width) / 2; } break; case WATERMARK_TYPE_LOCATION: case WATERMARK_TYPE_LOCATION_PRIMARY: if (watermark->info.left && watermark->info.right) { x += watermark->info.left_dist; width = geo.width - watermark->info.left_dist - watermark->info.right_dist; } else if (watermark->info.left) { x += watermark->info.left_dist; } else if (watermark->info.right) { x += geo.width - width - watermark->info.right_dist; } else { x += (geo.width - width) / 2; } if (watermark->info.top && watermark->info.bottom) { y += watermark->info.top_dist; height = geo.height - watermark->info.top_dist - watermark->info.bottom_dist; } else if (watermark->info.top) { y += watermark->info.top_dist; } else if (watermark->info.bottom) { y += geo.height - height - watermark->info.bottom_dist; } else { y += (geo.height - height) / 2; } if (width < 0 || width > geo.width) { width = geo.width; } if (height < 0 || height > geo.height) { height = geo.height; } if (x < geo.x || x + width > geo.x + geo.width) { x = geo.x; } if (y < geo.y || y + height > geo.y + geo.height) { y = geo.y; } break; default: break; } ky_scene_node_set_position(&entry->scene_buffer->node, x, y); ky_scene_buffer_set_dest_size(entry->scene_buffer, width, height); } static void watermark_entry_handle_output_geometry(struct wl_listener *listener, void *data) { struct watermark_entry *entry = wl_container_of(listener, entry, output_geometry); /* update buffer when output geometry changed */ watermark_entry_update_buffer(entry); } static bool watermark_entry_create(struct watermark *watermark, struct output *output) { struct watermark_entry *entry = calloc(1, sizeof(*entry)); if (!entry) { return false; } entry->watermark = watermark; wl_list_insert(&watermark->entries, &entry->link); entry->output = output; entry->output_disable.notify = watermark_entry_handle_output_disable; entry->output_geometry.notify = watermark_entry_handle_output_geometry; if (output) { wl_signal_add(&output->events.disable, &entry->output_disable); wl_signal_add(&output->events.geometry, &entry->output_geometry); } else { wl_list_init(&entry->output_disable.link); wl_list_init(&entry->output_geometry.link); } entry->scene_buffer = ky_scene_buffer_create(watermark->tree, NULL); ky_scene_node_lower_to_bottom(&entry->scene_buffer->node); ky_scene_buffer_set_opacity(entry->scene_buffer, watermark->info.opacity); ky_scene_node_set_input_bypassed(&entry->scene_buffer->node, true); watermark_entry_update_buffer(entry); return true; } static void watermark_destroy_entries(struct watermark *watermark) { wl_list_remove(&watermark->new_enabled_output.link); wl_list_init(&watermark->new_enabled_output.link); wl_list_remove(&watermark->primary_output.link); wl_list_init(&watermark->primary_output.link); struct watermark_entry *entry, *tmp; wl_list_for_each_safe(entry, tmp, &watermark->entries, link) { watermark_entry_destroy(entry); } wlr_buffer_drop(watermark->buffer); watermark->buffer = NULL; free(watermark->info.file); watermark->info.file = NULL; } static void handle_new_enabled_output(struct wl_listener *listener, void *data) { struct watermark *watermark = wl_container_of(listener, watermark, new_enabled_output); struct kywc_output *output = data; watermark_entry_create(watermark, output_from_kywc_output(output)); } static void handle_primary_output(struct wl_listener *listener, void *data) { struct watermark *watermark = wl_container_of(listener, watermark, primary_output); struct kywc_output *output = data; struct watermark_entry *entry, *tmp; wl_list_for_each_safe(entry, tmp, &watermark->entries, link) { watermark_entry_destroy(entry); } watermark_entry_create(watermark, output_from_kywc_output(output)); } static bool watermark_create_entries(struct watermark *watermark, struct watermark_info *info) { char *file = string_expand_path(info->file); if (!file || !file_exists(file)) { free(file); return false; } if (info->opacity < 0 || info->opacity > 1 || info->type < 0 || info->type >= WATERMARK_TYPE_LAST) { return false; } watermark->info = *info; watermark->info.file = file; enum layer layer = watermark->info.topmost ? LAYER_SCREEN_LOCK : LAYER_WATERMARK; struct view_layer *view_layer = view_manager_get_layer(layer, false); watermark->tree = view_layer->tree; /* no need to use output when WATERMARK_TYPE_POSITION */ if (info->type == WATERMARK_TYPE_POSITION) { watermark_entry_create(watermark, NULL); return true; } if (info->type == WATERMARK_TYPE_LOCATION_PRIMARY) { struct kywc_output *kywc_output = kywc_output_get_primary(); assert(kywc_output); watermark_entry_create(watermark, output_from_kywc_output(kywc_output)); kywc_output_add_primary_listener(&watermark->primary_output); return true; } struct ky_scene *scene = watermark->effect->manager->server->scene; struct ky_scene_output *output; wl_list_for_each(output, &scene->outputs, link) { watermark_entry_create(watermark, output_from_wlr_output(output->output)); } output_manager_add_new_enabled_listener(&watermark->new_enabled_output); return true; } static void watermark_init(struct watermark *watermark, struct watermark_effect *effect) { watermark->effect = effect; wl_list_init(&watermark->entries); wl_list_init(&watermark->new_enabled_output.link); wl_list_init(&watermark->primary_output.link); watermark->new_enabled_output.notify = handle_new_enabled_output; watermark->primary_output.notify = handle_primary_output; } static struct watermark *watermark_create(struct watermark_effect *effect) { struct watermark *watermark = calloc(1, sizeof(*watermark)); if (!watermark) { return NULL; } watermark_init(watermark, effect); wl_list_insert(&effect->watermarks, &watermark->link); watermark->name = kywc_identifier_uuid_generate(); return watermark; } static void watermark_destroy(struct watermark *watermark) { watermark_destroy_entries(watermark); wl_list_remove(&watermark->link); free((void *)watermark->name); free(watermark); } static struct watermark *watermark_by_name(struct watermark_effect *effect, const char *name) { struct watermark *watermark; wl_list_for_each(watermark, &effect->watermarks, link) { if (strcmp(watermark->name, name) == 0) { return watermark; } } return NULL; } static int watermark_update(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { char *file = NULL; double opacity = 0; CK(sd_bus_message_read(m, "sd", &file, &opacity)); struct watermark_effect *effect = userdata; watermark_destroy_entries(&effect->watermark); if (file && *file) { struct watermark_info info = { .file = file, .opacity = opacity, .type = WATERMARK_TYPE_SCREEN, .topmost = true, }; if (!watermark_create_entries(&effect->watermark, &info)) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid args."); return sd_bus_reply_method_error(m, &error); } } return sd_bus_reply_method_return(m, NULL); } static int watermark_update_ex(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { char *file = NULL; double opacity = 0; int x, y, type, topmost; CK(sd_bus_message_read(m, "sdiiib", &file, &opacity, &x, &y, &type, &topmost)); struct watermark_effect *effect = userdata; /* destroy current watermark entries, create new entries */ watermark_destroy_entries(&effect->watermark); if (file && *file) { struct watermark_info info = { .file = file, .opacity = opacity, .x = x, .y = y, .type = type, .topmost = topmost, }; if (!watermark_create_entries(&effect->watermark, &info)) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid args."); return sd_bus_reply_method_error(m, &error); } } return sd_bus_reply_method_return(m, NULL); } static const sd_bus_vtable watermark_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("updateWatermark", "sd", "", watermark_update, 0), SD_BUS_METHOD("updateWatermarkEx", "sdiiib", "", watermark_update_ex, 0), SD_BUS_VTABLE_END, }; static int watermark2_create(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { char *file = NULL; double opacity = 0; int x, y, type, topmost; CK(sd_bus_message_read(m, "sdiiib", &file, &opacity, &x, &y, &type, &topmost)); struct watermark_effect *effect = userdata; struct watermark *watermark = watermark_create(effect); if (!watermark) { return sd_bus_reply_method_return(m, "s", NULL); } if (file && *file) { struct watermark_info info = { .file = file, .opacity = opacity, .x = x, .y = y, .type = type, .topmost = topmost, }; if (!watermark_create_entries(watermark, &info)) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid args."); return sd_bus_reply_method_error(m, &error); } } return sd_bus_reply_method_return(m, "s", watermark->name); } static int watermark2_update(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { char *name = NULL, *file = NULL; double opacity = 0; int x, y, type, topmost; CK(sd_bus_message_read(m, "ssdiiib", &name, &file, &opacity, &x, &y, &type, &topmost)); struct watermark_effect *effect = userdata; struct watermark *watermark = watermark_by_name(effect, name); if (!watermark) { return sd_bus_reply_method_return(m, "b", false); } /* destroy current watermark entries, create new entries */ watermark_destroy_entries(watermark); if (file && *file) { struct watermark_info info = { .file = file, .opacity = opacity, .x = x, .y = y, .type = type, .topmost = topmost, }; if (!watermark_create_entries(watermark, &info)) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid args."); return sd_bus_reply_method_error(m, &error); } } return sd_bus_reply_method_return(m, "b", true); } static int watermark2_destroy(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { char *name = NULL; CK(sd_bus_message_read(m, "s", &name)); struct watermark_effect *effect = userdata; struct watermark *watermark = watermark_by_name(effect, name); if (!watermark) { return sd_bus_reply_method_return(m, NULL); } watermark_destroy(watermark); return sd_bus_reply_method_return(m, NULL); } static const sd_bus_vtable watermark2_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("createWatermark", "sdiiib", "s", watermark2_create, 0), SD_BUS_METHOD("updateWatermark", "ssdiiib", "b", watermark2_update, 0), SD_BUS_METHOD("destroyWatermark", "s", "", watermark2_destroy, 0), SD_BUS_VTABLE_END, }; static void handle_effect_enable(struct wl_listener *listener, void *data) { struct watermark_effect *effect = wl_container_of(listener, effect, enable); assert(wl_list_empty(&effect->watermarks)); effect->dbus = dbus_register_object("org.ukui.KWin", "/Watermark", "org.ukui.kwin.Watermark", watermark_vtable, effect); effect->dbus_v2 = dbus_register_object(NULL, "/com/kylin/Wlcom/Watermark", "com.kylin.Wlcom.Watermark", watermark2_vtable, effect); } static void handle_effect_disable(struct wl_listener *listener, void *data) { struct watermark_effect *effect = wl_container_of(listener, effect, disable); watermark_destroy_entries(&effect->watermark); struct watermark *watermark, *tmp; wl_list_for_each_safe(watermark, tmp, &effect->watermarks, link) { watermark_destroy(watermark); } dbus_unregister_object(effect->dbus); dbus_unregister_object(effect->dbus_v2); } static void handle_effect_destroy(struct wl_listener *listener, void *data) { struct watermark_effect *effect = wl_container_of(listener, effect, destroy); assert(wl_list_empty(&effect->watermarks)); wl_list_remove(&effect->destroy.link); wl_list_remove(&effect->enable.link); wl_list_remove(&effect->disable.link); free(effect); } static bool handle_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } return false; } static const struct effect_interface watemark_effect_impl = { .configure = handle_effect_configure, }; bool watermark_effect_create(struct effect_manager *manager) { struct watermark_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } /* enabled by default */ effect->effect = effect_create("watermark", 0, true, &watemark_effect_impl, NULL); if (!effect->effect) { free(effect); return false; } effect->manager = manager; wl_list_init(&effect->watermarks); effect->effect->category = EFFECT_CATEGORY_UTILS; watermark_init(&effect->watermark, effect); effect->enable.notify = handle_effect_enable; wl_signal_add(&effect->effect->events.enable, &effect->enable); effect->disable.notify = handle_effect_disable; wl_signal_add(&effect->effect->events.disable, &effect->disable); effect->destroy.notify = handle_effect_destroy; wl_signal_add(&effect->effect->events.destroy, &effect->destroy); if (effect->effect->enabled) { handle_effect_enable(&effect->enable, NULL); } return true; } kylin-wayland-compositor/src/effect/color_filter.c0000664000175000017500000002607415160460057021346 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "config.h" #include "effect_p.h" #include "output.h" #include "util/dbus.h" #include "util/macros.h" static const char *color_filter_dbus_interface = "org.ukui.KWin"; static const char *color_filter_path = "/ColorFilter"; struct color_filter_effect { struct effect *effect; struct config *config; struct effect_manager *manager; bool enabled; bool shortcut_enabled; enum kywc_output_color_filter type; struct wl_listener destroy; struct wl_listener new_output; struct wl_listener effect_enabled; struct wl_listener effect_disabled; }; static const char *color_filters[] = { "None", "GrayScale", "Invert", "Protanopia", "Deuteranopia", "Tritanopia" }; static enum kywc_output_color_filter get_color_filter_by_name(const char *name) { for (uint32_t index = 0; index < ARRAY_SIZE(color_filters); ++index) { if (strcmp(name, color_filters[index]) == 0) { return index; } } return KYWC_OUTPUT_COLOR_FILTER_NONE; } static bool update_output_color_filter(struct kywc_output *kywc_output, int index, void *data) { struct color_filter_effect *color_filter = data; if (!color_filter->effect->enabled || !color_filter->enabled) { output_set_color_filter(kywc_output, KYWC_OUTPUT_COLOR_FILTER_NONE); } else { output_set_color_filter(kywc_output, color_filter->type); } return false; } static void effect_update_color_filter(struct color_filter_effect *color_filter, bool status_changed, bool type_changed) { output_manager_for_each_output(update_output_color_filter, true, color_filter); if (status_changed) { dbus_emit_signal("/ColorFilter", "org.ukui.colorfilter", "statusChanged", "b", color_filter->effect->enabled && color_filter->enabled); effect_set_option_boolean(color_filter->effect, "color_filter_enabled", color_filter->enabled); } if (type_changed) { dbus_emit_signal("/ColorFilter", "org.ukui.colorfilter", "colorFilterTypeChanged", "s", color_filters[color_filter->type]); effect_set_option_int(color_filter->effect, "type", color_filter->type); } } static void effect_update_shortcut_enabled(struct color_filter_effect *color_filter) { dbus_emit_signal("/ColorFilter", "org.ukui.colorfilter", "colorFilterShortCutEnabledChanged", "b", color_filter->shortcut_enabled); effect_set_option_boolean(color_filter->effect, "shortcut_enabled", color_filter->shortcut_enabled); } static bool handle_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } return false; } static int color_filter_set(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct color_filter_effect *color_filter = userdata; if (!(color_filter->effect->enabled && color_filter->enabled)) { return sd_bus_reply_method_return(m, NULL); } const char *str_type = NULL; CK(sd_bus_message_read(m, "s", &str_type)); enum kywc_output_color_filter type = get_color_filter_by_name(str_type); if (type == KYWC_OUTPUT_COLOR_FILTER_NONE || color_filter->type == type) { return sd_bus_reply_method_return(m, NULL); } color_filter->type = type; effect_update_color_filter(color_filter, false, true); return sd_bus_reply_method_return(m, NULL); } static int color_filter_get_current(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct color_filter_effect *color_filter = userdata; return sd_bus_reply_method_return(m, "s", color_filters[color_filter->type]); } static int color_filter_set_enabled(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct color_filter_effect *color_filter = userdata; if (!color_filter->effect->enabled || color_filter->enabled) { return sd_bus_reply_method_return(m, NULL); } color_filter->enabled = true; effect_update_color_filter(color_filter, true, false); return sd_bus_reply_method_return(m, NULL); } static int color_filter_set_disabled(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct color_filter_effect *color_filter = userdata; if (!color_filter->effect->enabled || !color_filter->enabled) { return sd_bus_reply_method_return(m, NULL); } color_filter->enabled = false; effect_update_color_filter(color_filter, true, false); return sd_bus_reply_method_return(m, NULL); } static int color_filter_is_enabled(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct color_filter_effect *color_filter = userdata; bool enabled = color_filter->effect->enabled ? color_filter->enabled : false; return sd_bus_reply_method_return(m, "b", enabled); } static int color_filter_shortcut_set_enabled(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { int enabled; CK(sd_bus_message_read(m, "b", &enabled)); struct color_filter_effect *color_filter = userdata; if (enabled == color_filter->shortcut_enabled) { return sd_bus_reply_method_return(m, NULL); } color_filter->shortcut_enabled = enabled; effect_update_shortcut_enabled(color_filter); return sd_bus_reply_method_return(m, NULL); } static int color_filter_shortcut_is_enabled(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct color_filter_effect *color_filter = userdata; return sd_bus_reply_method_return(m, "b", color_filter->shortcut_enabled); } static int color_filter_get_available(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(m, &reply)); CK(sd_bus_message_open_container(reply, 'a', "s")); for (uint32_t i = 1; i < ARRAY_SIZE(color_filters); ++i) { CK(sd_bus_message_append(reply, "s", color_filters[i])); } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 0; } static const sd_bus_vtable color_filter_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("availableColorFilter", "", "as", color_filter_get_available, 0), SD_BUS_METHOD("setColorFilter", "s", "", color_filter_set, 0), SD_BUS_METHOD("getCurrentColorFilter", "", "s", color_filter_get_current, 0), SD_BUS_METHOD("openColorFilter", "", "", color_filter_set_enabled, 0), SD_BUS_METHOD("closeColorFilter", "", "", color_filter_set_disabled, 0), SD_BUS_METHOD("isColorFilterEnabled", "", "b", color_filter_is_enabled, 0), SD_BUS_METHOD("isColorFilterShortCutEnabled", "", "b", color_filter_shortcut_is_enabled, 0), SD_BUS_METHOD("setColorFilterShortCutEnabled", "b", "", color_filter_shortcut_set_enabled, 0), SD_BUS_VTABLE_END, }; static void load_color_filter_config(struct color_filter_effect *color_filter) { color_filter->type = KYWC_OUTPUT_COLOR_FILTER_PROTANOPIA; enum kywc_output_color_filter type = effect_get_option_int(color_filter->effect, "type", KYWC_OUTPUT_COLOR_FILTER_PROTANOPIA); if (type != KYWC_OUTPUT_COLOR_FILTER_NONE) { color_filter->type = type; } color_filter->enabled = effect_get_option_boolean(color_filter->effect, "color_filter_enabled", false); color_filter->shortcut_enabled = effect_get_option_boolean(color_filter->effect, "shortcut_enabled", false); } static void color_filter_shortcut_toggle(struct key_binding *binding, void *data) { struct color_filter_effect *color_filter = data; if (!color_filter->effect->enabled || !color_filter->shortcut_enabled) { return; } color_filter->enabled = !color_filter->enabled; effect_update_color_filter(color_filter, true, false); } static void handle_effect_destroy(struct wl_listener *listener, void *data) { struct color_filter_effect *color_filter = wl_container_of(listener, color_filter, destroy); wl_list_remove(&color_filter->destroy.link); wl_list_remove(&color_filter->effect_enabled.link); wl_list_remove(&color_filter->effect_disabled.link); wl_list_remove(&color_filter->new_output.link); free(color_filter); } static void handle_effect_enabled(struct wl_listener *listener, void *data) { struct color_filter_effect *color_filter = wl_container_of(listener, color_filter, effect_enabled); output_manager_add_new_enabled_listener(&color_filter->new_output); effect_update_color_filter(color_filter, true, false); } static void handle_effect_disabled(struct wl_listener *listener, void *data) { struct color_filter_effect *color_filter = wl_container_of(listener, color_filter, effect_disabled); wl_list_remove(&color_filter->new_output.link); wl_list_init(&color_filter->new_output.link); effect_update_color_filter(color_filter, true, false); } static void color_filter_handle_new_output(struct wl_listener *listener, void *data) { struct color_filter_effect *color_filter = wl_container_of(listener, color_filter, new_output); struct kywc_output *output = data; update_output_color_filter(output, 0, color_filter); } static const struct effect_interface effect_impl = { .configure = handle_effect_configure, }; bool color_filter_effect_create(struct effect_manager *manager) { struct color_filter_effect *color_filter = calloc(1, sizeof(*color_filter)); if (!color_filter) { return false; } color_filter->effect = effect_create("color_filter", 200, true, &effect_impl, color_filter); if (!color_filter->effect) { free(color_filter); return false; } color_filter->manager = manager; load_color_filter_config(color_filter); dbus_register_object(NULL, color_filter_path, color_filter_dbus_interface, color_filter_vtable, color_filter); struct key_binding *binding = kywc_key_binding_create("Win+Ctrl+c", "Open/Close ColorFilter"); if (binding && !kywc_key_binding_register(binding, KEY_BINDING_TYPE_COLOR_FILTER, color_filter_shortcut_toggle, color_filter)) { kywc_key_binding_destroy(binding); } wl_list_init(&color_filter->new_output.link); color_filter->new_output.notify = color_filter_handle_new_output; color_filter->destroy.notify = handle_effect_destroy; wl_signal_add(&color_filter->effect->events.destroy, &color_filter->destroy); color_filter->effect_enabled.notify = handle_effect_enabled; wl_signal_add(&color_filter->effect->events.enable, &color_filter->effect_enabled); color_filter->effect_disabled.notify = handle_effect_disabled; wl_signal_add(&color_filter->effect->events.disable, &color_filter->effect_disabled); if (color_filter->effect->enabled) { handle_effect_enabled(&color_filter->effect_enabled, NULL); } return true; } kylin-wayland-compositor/src/effect/fade.c0000664000175000017500000003112715160461067017557 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "effect/action.h" #include "effect/animator.h" #include "effect/fade.h" #include "effect/snapshot.h" #include "effect/transform.h" #include "effect_p.h" #include "scene/surface.h" #include "util/time.h" enum fade_center_type { FADE_TYPE_CENTER = 0, FADE_TYPE_TOPLEFT, }; struct fade_entity { struct transform *transform; bool fade_in; int x_offset, y_offset; float width_factor, height_factor; enum fade_center_type center_type; struct wl_listener destroy; }; static struct fade_effect { struct transform_effect *effect; struct action_effect *action_effect; } *fade_effect = NULL; static void fade_entity_calc_scale_geometry(const struct fade_entity *fade, struct kywc_box *current_geometry, struct kywc_box *geometry) { int x_offset = fade->x_offset, y_offset = fade->y_offset; geometry->width = current_geometry->width * fade->width_factor; geometry->height = current_geometry->height * fade->height_factor; switch (fade->center_type) { case FADE_TYPE_CENTER: geometry->x = current_geometry->x + (current_geometry->width - geometry->width) / 2 + x_offset; geometry->y = current_geometry->y + (current_geometry->height - geometry->height) / 2 + y_offset; break; case FADE_TYPE_TOPLEFT: geometry->x = current_geometry->x + x_offset; geometry->y = current_geometry->y + y_offset; break; } } static void fade_get_node_geometry(struct ky_scene_node *node, struct kywc_box *geometry) { struct kywc_box box; ky_scene_node_get_affected_bounding_box(node, KY_SCENE_BOUNDING_WITHOUT_EFFECT_NODE, &box); ky_scene_node_coords(node, &geometry->x, &geometry->y); geometry->x += box.x; geometry->y += box.y; geometry->width = box.width; geometry->height = box.height; } static void fade_entity_set_transform_options_geometry(struct fade_entity *fade_entity, struct ky_scene_node *node, struct transform_options *options) { struct kywc_box node_geometry; fade_get_node_geometry(node, &node_geometry); if (fade_entity->fade_in) { fade_entity_calc_scale_geometry(fade_entity, &node_geometry, &options->start.geometry); options->end.geometry = node_geometry; } else { options->start.geometry = node_geometry; fade_entity_calc_scale_geometry(fade_entity, &node_geometry, &options->end.geometry); } } static void fade_handle_transform_destroy(struct wl_listener *listener, void *data) { struct fade_entity *fade_entity = wl_container_of(listener, fade_entity, destroy); wl_list_remove(&fade_entity->destroy.link); free(fade_entity); } static void fade_update_transform_options(struct transform_effect *effect, struct transform *transform, struct ky_scene_node *node, struct transform_options *options, struct animation_data *current, void *data) { struct fade_entity *fade_entity = transform_get_user_data(transform); if (!fade_entity || !fade_entity->fade_in) { return; } /** * when xdg_popup request fade effect, the current geometry maybe wrong. * need to update start geometry. */ fade_entity_set_transform_options_geometry(fade_entity, node, options); } static void fade_effect_destroy(struct transform_effect *effect, void *data) { if (!fade_effect) { return; } if (fade_effect->action_effect) { action_effect_destroy(fade_effect->action_effect); } free(fade_effect); fade_effect = NULL; } static bool fade_entity_create_transform(struct fade_entity *fade_entity, struct ky_scene_node *node, struct transform_effect *effect, struct transform_options *options) { struct effect_render_data render_data = { 0 }; struct ky_scene_add_effect_event event = { .render_data = &render_data, }; struct effect_entity *entity = node_add_custom_transform_effect(node, effect, options, &event); if (!entity) { return false; } if (!entity->user_data) { fade_entity->transform = transform_effect_create_transform(effect, entity, options, node, fade_entity); if (!fade_entity->transform) { effect_entity_destroy(entity); return false; } } else { fade_entity->transform = entity->user_data; transform_set_user_data(entity->user_data, fade_entity); struct transform_options *transform_options = transform_get_options(entity->user_data); transform_options->start = *transform_get_current(entity->user_data); } /* prohibit updating thumbnail when fade out */ transform_block_source_update(fade_entity->transform, !fade_entity->fade_in); fade_entity->destroy.notify = fade_handle_transform_destroy; transform_add_destroy_listener(fade_entity->transform, &fade_entity->destroy); return true; } bool view_add_fade_effect(struct view *view, enum fade_action action) { if (!fade_effect) { return false; } struct fade_entity *fade_entity = calloc(1, sizeof(*fade_entity)); if (!fade_entity) { return false; } struct transform_options options = { 0 }; struct animation *animation = animation_manager_get(ANIMATION_TYPE_EASE); options.animations.alpha = animation; options.animations.geometry = animation; options.scale = view->surface ? ky_scene_surface_get_scale(view->surface) : 1.0f; options.start_time = current_time_msec(); struct ky_scene_node *node = &view->tree->node; if (action == FADE_IN) { fade_entity->fade_in = true; fade_entity->width_factor = 0.9; options.duration = 300; options.start.alpha = 0; options.end.alpha = 1.0; } else { fade_entity->fade_in = false; fade_entity->width_factor = 0.8; options.duration = 260; options.start.alpha = 1.0; options.end.alpha = 0; } fade_entity->height_factor = fade_entity->width_factor; fade_entity_set_transform_options_geometry(fade_entity, node, &options); if (action == FADE_IN) { options.buffer = view_try_get_single_buffer(view); } if (!fade_entity_create_transform(fade_entity, node, fade_effect->effect, &options)) { free(fade_entity); return false; } return true; } bool popup_add_fade_effect(struct ky_scene_node *node, enum fade_action action, bool topmost, bool seat, float scale) { if (!fade_effect) { return false; } struct fade_entity *fade_entity = calloc(1, sizeof(*fade_entity)); if (!fade_entity) { return false; } struct transform_options options = { 0 }; options.scale = scale; options.start_time = current_time_msec(); if (action == FADE_IN) { if (topmost && seat) { fade_entity->width_factor = 1.0; options.duration = 220; } else if (topmost && !seat) { fade_entity->width_factor = 0.85; options.duration = 200; } else if (!topmost) { fade_entity->width_factor = 1.0; fade_entity->y_offset = 4; options.duration = 220; } fade_entity->fade_in = true; options.start.alpha = 0; options.end.alpha = 1.0; } else { if (topmost && seat) { fade_entity->width_factor = 1.0; options.duration = 180; } else if (topmost && !seat) { fade_entity->width_factor = 0.85; options.duration = 150; } else if (!topmost) { fade_entity->width_factor = 1.0; fade_entity->y_offset = -4; options.duration = 180; } fade_entity->fade_in = false; options.start.alpha = 1.0; options.end.alpha = 0; } fade_entity->height_factor = fade_entity->width_factor; fade_entity_set_transform_options_geometry(fade_entity, node, &options); struct animation *animation; if (topmost && seat) { animation = animation_manager_get(ANIMATION_TYPE_30_2_8_100); options.animations.alpha = animation; options.animations.geometry = animation; } else { animation = animation_manager_get(ANIMATION_TYPE_EASE); options.animations.alpha = animation; options.animations.geometry = animation; } if (!fade_entity_create_transform(fade_entity, node, fade_effect->effect, &options)) { free(fade_entity); return false; } return true; } /** * The default parameters of fade can be very simple, without distinguishing between multiple * situations. But when switching from other effects to fade effects through the * protocol(ukui-effect-v1), more fade parameters need to be set. */ static void fade_init_options(struct action_effect *action_effect, struct action_effect_options *options) { options->alpha = 0.0f; options->y_offset = 0; options->x_offset = 0; options->width_scale = 0.85f; options->height_scale = 0.85f; options->duration = 200; options->effect_type = ACTION_EFFECT_FADE; options->style = FADE_TYPE_CENTER; struct animation *animation = animation_manager_get(ANIMATION_TYPE_EASE); options->animations.geometry = animation; options->animations.alpha = animation; options->animations.angle = NULL; options->buffer = NULL; options->new_parent = NULL; options->scale = 1.0f; } static bool add_fade_to_node(struct action_effect *action_effect, struct ky_scene_node *node, enum effect_action action, struct action_effect_options *options) { if (!action_effect || !node->enabled) { return false; } struct fade_entity *fade_entity = calloc(1, sizeof(*fade_entity)); if (!fade_entity) { return false; } fade_entity->y_offset = options->y_offset; fade_entity->x_offset = options->x_offset; fade_entity->fade_in = action == EFFECT_ACTION_MAP; fade_entity->center_type = options->style; fade_entity->width_factor = options->width_scale; fade_entity->height_factor = options->height_scale; struct transform_options fade_options = { 0 }; fade_options.buffer = options->buffer; fade_options.scale = options->scale; fade_options.start_time = current_time_msec(); fade_options.geometry_type = TRANSFORM_GEOMETRY_NODE_BOUNDING; fade_options.duration = options->duration; fade_options.start.alpha = fade_entity->fade_in ? options->alpha : 1.0f; fade_options.end.alpha = fade_entity->fade_in ? 1.0f : options->alpha; fade_options.animations = options->animations; fade_entity_set_transform_options_geometry(fade_entity, node, &fade_options); if (!fade_entity_create_transform(fade_entity, node, fade_effect->effect, &fade_options)) { free(fade_entity); return false; } return true; } static bool add_fade_to_view(struct action_effect *effect, struct view *view, enum effect_action action, struct action_effect_options *options) { options->scale = view->surface ? view->surface->current.scale : 1.0f; if (action == EFFECT_ACTION_MAP) { options->buffer = view_try_get_single_buffer(view); } return add_fade_to_node(effect, &view->tree->node, action, options); } const struct action_effect_interface fade_action_impl = { .init_options = fade_init_options, .add_to_node = add_fade_to_node, .add_to_view = add_fade_to_view, }; struct transform_effect_interface fade_impl = { .update_transform_options = fade_update_transform_options, .destroy = fade_effect_destroy, }; bool fade_effect_create(struct effect_manager *manager) { fade_effect = calloc(1, sizeof(*fade_effect)); if (!fade_effect) { return false; } uint32_t support_actions = EFFECT_ACTION_MAP | EFFECT_ACTION_UNMAP; fade_effect->action_effect = action_effect_create(ACTION_EFFECT_FADE, support_actions, fade_effect, &fade_action_impl); fade_effect->effect = transform_effect_create(manager, &fade_impl, support_actions, "fade", 10, NULL); if (!fade_effect->effect) { fade_effect_destroy(NULL, NULL); return false; } return true; } kylin-wayland-compositor/src/effect/wlr_screencopy.c0000664000175000017500000006456015160461067021725 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include #include #include #include "effect_p.h" #include "output.h" #include "render/pixel_format.h" #include "security.h" #include "wlr-screencopy-unstable-v1-protocol.h" struct wlr_screencopy_manager { struct wl_global *global; struct wl_list frames; // wlr_screencopy_frame.link struct wl_listener display_destroy; struct wl_listener server_destroy; }; struct wlr_screencopy_client { struct wlr_screencopy_manager *manager; struct wl_list damages; int ref; }; struct wlr_screencopy_frame { struct wl_resource *resource; struct wlr_screencopy_client *client; struct wl_list link; // wlr_screencopy_manager.frames uint32_t shm_format, dmabuf_format; // DRM format codes pixman_format_code_t pixman_format; struct wlr_box box; // in buffer int shm_stride; bool overlay_cursor, cursor_locked; bool with_damage; enum wlr_buffer_cap buffer_cap; struct wlr_buffer *buffer; struct wlr_output *output; struct wl_listener output_commit; struct wl_listener output_destroy; }; struct screencopy_damage { struct wl_list link; struct wlr_output *output; struct pixman_region32 damage; struct wl_listener output_precommit; struct wl_listener output_destroy; }; static struct screencopy_damage *screencopy_damage_find(struct wlr_screencopy_client *client, struct wlr_output *output) { struct screencopy_damage *damage; wl_list_for_each(damage, &client->damages, link) { if (damage->output == output) { return damage; } } return NULL; } static void screencopy_damage_accumulate(struct screencopy_damage *damage, const struct wlr_output_state *state) { struct pixman_region32 *region = &damage->damage; struct wlr_output *output = damage->output; if (state->committed & WLR_OUTPUT_STATE_DAMAGE) { // If the compositor submitted damage, copy it over pixman_region32_union(region, region, &state->damage); pixman_region32_intersect_rect(region, region, 0, 0, output->width, output->height); } else if (state->committed & WLR_OUTPUT_STATE_BUFFER) { // If the compositor did not submit damage but did submit a buffer damage everything pixman_region32_union_rect(region, region, 0, 0, output->width, output->height); } } static void screencopy_damage_handle_output_precommit(struct wl_listener *listener, void *data) { struct screencopy_damage *damage = wl_container_of(listener, damage, output_precommit); const struct wlr_output_event_precommit *event = data; screencopy_damage_accumulate(damage, event->state); } static void screencopy_damage_destroy(struct screencopy_damage *damage) { wl_list_remove(&damage->output_destroy.link); wl_list_remove(&damage->output_precommit.link); wl_list_remove(&damage->link); pixman_region32_fini(&damage->damage); free(damage); } static void screencopy_damage_handle_output_destroy(struct wl_listener *listener, void *data) { struct screencopy_damage *damage = wl_container_of(listener, damage, output_destroy); screencopy_damage_destroy(damage); } static struct screencopy_damage *screencopy_damage_create(struct wlr_screencopy_client *client, struct wlr_output *output) { struct screencopy_damage *damage = calloc(1, sizeof(*damage)); if (!damage) { return NULL; } damage->output = output; pixman_region32_init_rect(&damage->damage, 0, 0, output->width, output->height); wl_list_insert(&client->damages, &damage->link); wl_signal_add(&output->events.precommit, &damage->output_precommit); damage->output_precommit.notify = screencopy_damage_handle_output_precommit; wl_signal_add(&output->events.destroy, &damage->output_destroy); damage->output_destroy.notify = screencopy_damage_handle_output_destroy; return damage; } static struct screencopy_damage * screencopy_damage_get_or_create(struct wlr_screencopy_client *client, struct wlr_output *output) { struct screencopy_damage *damage = screencopy_damage_find(client, output); return damage ? damage : screencopy_damage_create(client, output); } static void client_unref(struct wlr_screencopy_client *client) { assert(client->ref > 0); if (--client->ref != 0) { return; } struct screencopy_damage *damage, *tmp_damage; wl_list_for_each_safe(damage, tmp_damage, &client->damages, link) { screencopy_damage_destroy(damage); } free(client); } static const struct zwlr_screencopy_frame_v1_interface wlr_screencopy_frame_impl; static struct wlr_screencopy_frame *frame_from_resource(struct wl_resource *resource) { assert(wl_resource_instance_of(resource, &zwlr_screencopy_frame_v1_interface, &wlr_screencopy_frame_impl)); return wl_resource_get_user_data(resource); } static void frame_destroy(struct wlr_screencopy_frame *frame) { if (!frame) { return; } if (frame->output && frame->buffer) { wlr_output_lock_attach_render(frame->output, false); if (frame->cursor_locked) { wlr_output_lock_software_cursors(frame->output, false); } } wl_list_remove(&frame->link); wl_list_remove(&frame->output_commit.link); wl_list_remove(&frame->output_destroy.link); // Make the frame resource inert wl_resource_set_user_data(frame->resource, NULL); wlr_buffer_unlock(frame->buffer); client_unref(frame->client); free(frame); } static void frame_send_damage(struct wlr_screencopy_frame *frame) { if (!frame->with_damage) { return; } struct screencopy_damage *damage = screencopy_damage_get_or_create(frame->client, frame->output); if (!damage) { return; } int n_boxes; const pixman_box32_t *boxes = pixman_region32_rectangles(&damage->damage, &n_boxes); for (int i = 0; i < n_boxes; i++) { const pixman_box32_t *box = &boxes[i]; int damage_x = box->x1; int damage_y = box->y1; int damage_width = box->x2 - box->x1; int damage_height = box->y2 - box->y1; zwlr_screencopy_frame_v1_send_damage(frame->resource, damage_x, damage_y, damage_width, damage_height); } pixman_region32_clear(&damage->damage); } static void frame_send_ready(struct wlr_screencopy_frame *frame, struct timespec *when) { time_t tv_sec = when->tv_sec; uint32_t tv_sec_hi = (sizeof(tv_sec) > 4) ? tv_sec >> 32 : 0; uint32_t tv_sec_lo = tv_sec & 0xFFFFFFFF; zwlr_screencopy_frame_v1_send_ready(frame->resource, tv_sec_hi, tv_sec_lo, when->tv_nsec); } static bool frame_is_security_touched(struct wlr_screencopy_frame *frame, pixman_region32_t *clip, pixman_region32_t *clear) { struct wlr_output *wlr_output = frame->output; struct output *output = output_from_wlr_output(wlr_output); pixman_region32_init_rect(clip, output->geometry.x, output->geometry.y, output->geometry.width, output->geometry.height); pixman_region32_init_rect(clear, frame->box.x, frame->box.y, frame->box.width, frame->box.height); bool has_security = security_check_output(output, clip); if (!has_security) { return false; } /* output capture is not allowed */ if (has_security && !pixman_region32_not_empty(clip)) { return true; } /* output capture is allowed in clip region */ pixman_region32_translate(clip, -output->geometry.x, -output->geometry.y); wlr_region_scale(clip, clip, wlr_output->scale); int width, height; wlr_output_transformed_resolution(wlr_output, &width, &height); enum wl_output_transform transform = wlr_output_transform_invert(wlr_output->transform); wlr_region_transform(clip, clip, transform, width, height); /* intersect the frame box */ pixman_region32_intersect(clip, clip, clear); pixman_region32_subtract(clear, clear, clip); return true; } static bool frame_shm_copy(struct wlr_screencopy_frame *frame, struct wlr_buffer *src_buffer) { struct wlr_output *output = frame->output; struct wlr_renderer *renderer = output->renderer; assert(renderer); pixman_region32_t clip, clear; bool ok = false; void *data; uint32_t format; size_t stride; if (!wlr_buffer_begin_data_ptr_access(frame->buffer, WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data, &format, &stride)) { goto out; } struct wlr_texture *texture = wlr_texture_from_buffer(renderer, src_buffer); if (!texture) { goto out; } ok = wlr_texture_read_pixels( texture, &(struct wlr_texture_read_pixels_options){ .data = data, .format = format, .stride = stride, .src_box = frame->box }); wlr_texture_destroy(texture); out: wlr_buffer_end_data_ptr_access(frame->buffer); pixman_region32_fini(&clip); pixman_region32_fini(&clear); if (!ok) { kywc_log(KYWC_DEBUG, "Failed to copy to destination during shm screencopy"); } return ok; } static bool frame_dma_copy(struct wlr_screencopy_frame *frame, struct wlr_buffer *src_buffer) { struct wlr_buffer *dst_buffer = frame->buffer; struct wlr_output *output = frame->output; struct wlr_renderer *renderer = output->renderer; assert(renderer); pixman_region32_t clip, clear; bool touched = frame_is_security_touched(frame, &clip, &clear); bool ok = false; struct wlr_texture *src_tex = wlr_texture_from_buffer(renderer, src_buffer); if (!src_tex) { goto out; } struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, dst_buffer, NULL); if (!pass) { goto out; } // clear all dst buffer if security touched if (touched) { wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ .color = { 0, 0, 0, 0 }, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, .clip = &clear, }); } if (!touched || pixman_region32_not_empty(&clip)) { wlr_render_pass_add_texture( pass, &(struct wlr_render_texture_options){ .texture = src_tex, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, .dst_box = (struct wlr_box){ 0, 0, dst_buffer->width, dst_buffer->height }, .src_box = (struct wlr_fbox){ frame->box.x, frame->box.y, frame->box.width, frame->box.height }, .clip = touched ? &clip : NULL, }); } ok = wlr_render_pass_submit(pass); out: wlr_texture_destroy(src_tex); pixman_region32_fini(&clip); pixman_region32_fini(&clear); if (!ok) { kywc_log(KYWC_DEBUG, "Failed to render to destination during dma screencopy"); } return ok; } static void frame_handle_output_commit(struct wl_listener *listener, void *data) { struct wlr_screencopy_frame *frame = wl_container_of(listener, frame, output_commit); struct wlr_output_event_commit *event = data; struct wlr_output *output = frame->output; if (event->state->committed & WLR_OUTPUT_STATE_ENABLED && !output->enabled) { goto err; } if (!(event->state->committed & WLR_OUTPUT_STATE_BUFFER)) { return; } if (!frame->buffer) { return; } if (frame->with_damage) { struct screencopy_damage *damage = screencopy_damage_get_or_create(frame->client, output); if (damage && !pixman_region32_not_empty(&damage->damage)) { return; } } wl_list_remove(&frame->output_commit.link); wl_list_init(&frame->output_commit.link); struct wlr_buffer *src_buffer = event->state->buffer; if (frame->box.x < 0 || frame->box.y < 0 || frame->box.x + frame->box.width > src_buffer->width || frame->box.y + frame->box.height > src_buffer->height) { goto err; } switch (frame->buffer_cap) { case WLR_BUFFER_CAP_DMABUF: if (!frame_dma_copy(frame, src_buffer)) { goto err; } break; case WLR_BUFFER_CAP_DATA_PTR: if (!frame_shm_copy(frame, src_buffer)) { goto err; } break; default: abort(); // unreachable } zwlr_screencopy_frame_v1_send_flags(frame->resource, 0); frame_send_damage(frame); frame_send_ready(frame, event->when); frame_destroy(frame); return; err: zwlr_screencopy_frame_v1_send_failed(frame->resource); frame_destroy(frame); } static void frame_handle_output_destroy(struct wl_listener *listener, void *data) { struct wlr_screencopy_frame *frame = wl_container_of(listener, frame, output_destroy); zwlr_screencopy_frame_v1_send_failed(frame->resource); frame_destroy(frame); } static void frame_handle_copy(struct wl_client *wl_client, struct wl_resource *frame_resource, struct wl_resource *buffer_resource) { struct wlr_screencopy_frame *frame = frame_from_resource(frame_resource); if (!frame) { return; } struct wlr_output *output = frame->output; if (!output->enabled) { zwlr_screencopy_frame_v1_send_failed(frame->resource); frame_destroy(frame); return; } struct wlr_buffer *buffer = wlr_buffer_try_from_resource(buffer_resource); if (!buffer) { wl_resource_post_error(frame->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); return; } if (buffer->width != frame->box.width || buffer->height != frame->box.height) { wl_resource_post_error(frame->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer dimensions"); return; } if (frame->buffer) { wl_resource_post_error(frame->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); return; } enum wlr_buffer_cap cap; struct wlr_dmabuf_attributes dmabuf; void *data; uint32_t format; size_t stride; if (wlr_buffer_get_dmabuf(buffer, &dmabuf)) { cap = WLR_BUFFER_CAP_DMABUF; if (dmabuf.format != frame->dmabuf_format) { wl_resource_post_error(frame->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); return; } } else if (wlr_buffer_begin_data_ptr_access(buffer, WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data, &format, &stride)) { wlr_buffer_end_data_ptr_access(buffer); cap = WLR_BUFFER_CAP_DATA_PTR; if (format != frame->shm_format) { wl_resource_post_error(frame->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); return; } if (stride != (size_t)frame->shm_stride) { wl_resource_post_error(frame->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer stride"); return; } } else { wl_resource_post_error(frame->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "unsupported buffer type"); return; } frame->buffer = buffer; frame->buffer_cap = cap; wl_signal_add(&output->events.commit, &frame->output_commit); frame->output_commit.notify = frame_handle_output_commit; // Request a frame because we can't assume that the current front buffer is still usable. It may // have been released already, and we shouldn't lock it here because compositors want to render // into the least damaged buffer. wlr_output_update_needs_frame(output); wlr_output_lock_attach_render(output, true); if (frame->overlay_cursor) { wlr_output_lock_software_cursors(output, true); frame->cursor_locked = true; } } static void frame_handle_copy_with_damage(struct wl_client *wl_client, struct wl_resource *frame_resource, struct wl_resource *buffer_resource) { struct wlr_screencopy_frame *frame = frame_from_resource(frame_resource); if (!frame) { return; } frame->with_damage = true; frame_handle_copy(wl_client, frame_resource, buffer_resource); } static void frame_handle_destroy(struct wl_client *wl_client, struct wl_resource *frame_resource) { wl_resource_destroy(frame_resource); } static const struct zwlr_screencopy_frame_v1_interface wlr_screencopy_frame_impl = { .copy = frame_handle_copy, .destroy = frame_handle_destroy, .copy_with_damage = frame_handle_copy_with_damage, }; static void frame_handle_resource_destroy(struct wl_resource *frame_resource) { struct wlr_screencopy_frame *frame = frame_from_resource(frame_resource); frame_destroy(frame); } static const struct zwlr_screencopy_manager_v1_interface wlr_screencopy_manager_impl; static struct wlr_screencopy_client *client_from_resource(struct wl_resource *resource) { assert(wl_resource_instance_of(resource, &zwlr_screencopy_manager_v1_interface, &wlr_screencopy_manager_impl)); return wl_resource_get_user_data(resource); } static enum wl_shm_format convert_drm_format_to_wl_shm(uint32_t fmt) { switch (fmt) { case DRM_FORMAT_XRGB8888: return WL_SHM_FORMAT_XRGB8888; case DRM_FORMAT_ARGB8888: return WL_SHM_FORMAT_ARGB8888; default: return (enum wl_shm_format)fmt; } } static void capture_output(struct wl_client *wl_client, struct wlr_screencopy_client *client, uint32_t version, uint32_t id, int32_t overlay_cursor, struct wlr_output *output, const struct wlr_box *box) { struct wlr_screencopy_frame *frame = calloc(1, sizeof(*frame)); if (!frame) { wl_client_post_no_memory(wl_client); return; } frame->resource = wl_resource_create(wl_client, &zwlr_screencopy_frame_v1_interface, version, id); if (!frame->resource) { free(frame); wl_client_post_no_memory(wl_client); return; } frame->output = output; frame->overlay_cursor = !!overlay_cursor; wl_resource_set_implementation(frame->resource, &wlr_screencopy_frame_impl, frame, frame_handle_resource_destroy); if (!output) { wl_resource_set_user_data(frame->resource, NULL); zwlr_screencopy_frame_v1_send_failed(frame->resource); free(frame); return; } frame->client = client; client->ref++; wl_list_insert(&client->manager->frames, &frame->link); wl_list_init(&frame->output_commit.link); wl_signal_add(&output->events.destroy, &frame->output_destroy); frame->output_destroy.notify = frame_handle_output_destroy; if (!output || !output->enabled) { goto error; } struct wlr_renderer *renderer = output->renderer; assert(renderer); struct wlr_buffer *buffer = wlr_swapchain_acquire(output->swapchain); if (buffer == NULL) { goto error; } struct wlr_texture *texture = wlr_texture_from_buffer(renderer, buffer); wlr_buffer_unlock(buffer); if (!texture) { goto error; } frame->shm_format = wlr_texture_preferred_read_format(texture); wlr_texture_destroy(texture); if (frame->shm_format == DRM_FORMAT_INVALID) { kywc_log(KYWC_ERROR, "Failed to capture output: no read format supported by renderer"); goto error; } const struct ky_pixel_format *shm_info = ky_pixel_format_from_drm(frame->shm_format); if (!shm_info) { kywc_log(KYWC_ERROR, "Failed to capture output: no pixel format info matching read format"); goto error; } if (output->allocator && (output->allocator->buffer_caps & WLR_BUFFER_CAP_DMABUF)) { frame->dmabuf_format = output->render_format; } else { frame->dmabuf_format = DRM_FORMAT_INVALID; } struct wlr_box buffer_box = { 0 }; if (!box) { buffer_box.width = output->width; buffer_box.height = output->height; } else { int ow, oh; wlr_output_effective_resolution(output, &ow, &oh); enum wl_output_transform transform = wlr_output_transform_invert(output->transform); buffer_box = *box; wlr_box_transform(&buffer_box, &buffer_box, transform, ow, oh); buffer_box.x *= output->scale; buffer_box.y *= output->scale; buffer_box.width *= output->scale; buffer_box.height *= output->scale; if (floor(output->scale) != output->scale) { wlr_box_intersection(&buffer_box, &buffer_box, &(struct wlr_box){ 0, 0, output->width, output->height }); } } frame->box = buffer_box; frame->shm_stride = ky_pixel_format_min_stride(shm_info, buffer_box.width); frame->pixman_format = shm_info->pixman_format; zwlr_screencopy_frame_v1_send_buffer(frame->resource, convert_drm_format_to_wl_shm(frame->shm_format), buffer_box.width, buffer_box.height, frame->shm_stride); if (version >= 3) { if (frame->dmabuf_format != DRM_FORMAT_INVALID) { zwlr_screencopy_frame_v1_send_linux_dmabuf(frame->resource, frame->dmabuf_format, buffer_box.width, buffer_box.height); } zwlr_screencopy_frame_v1_send_buffer_done(frame->resource); } return; error: zwlr_screencopy_frame_v1_send_failed(frame->resource); frame_destroy(frame); } static void manager_handle_capture_output(struct wl_client *wl_client, struct wl_resource *manager_resource, uint32_t id, int32_t overlay_cursor, struct wl_resource *output_resource) { struct wlr_screencopy_client *client = client_from_resource(manager_resource); uint32_t version = wl_resource_get_version(manager_resource); struct wlr_output *output = wlr_output_from_resource(output_resource); capture_output(wl_client, client, version, id, overlay_cursor, output, NULL); } static void manager_handle_capture_output_region(struct wl_client *wl_client, struct wl_resource *manager_resource, uint32_t id, int32_t overlay_cursor, struct wl_resource *output_resource, int32_t x, int32_t y, int32_t width, int32_t height) { struct wlr_screencopy_client *client = client_from_resource(manager_resource); uint32_t version = wl_resource_get_version(manager_resource); struct wlr_output *output = wlr_output_from_resource(output_resource); struct wlr_box box = { x, y, width, height }; capture_output(wl_client, client, version, id, overlay_cursor, output, &box); } static void manager_handle_destroy(struct wl_client *wl_client, struct wl_resource *manager_resource) { wl_resource_destroy(manager_resource); } static const struct zwlr_screencopy_manager_v1_interface wlr_screencopy_manager_impl = { .capture_output = manager_handle_capture_output, .capture_output_region = manager_handle_capture_output_region, .destroy = manager_handle_destroy, }; static void manager_handle_resource_destroy(struct wl_resource *resource) { struct wlr_screencopy_client *client = client_from_resource(resource); client_unref(client); } static void wlr_screencopy_manager_bind(struct wl_client *wl_client, void *data, uint32_t version, uint32_t id) { struct wlr_screencopy_client *client = calloc(1, sizeof(*client)); if (!client) { wl_client_post_no_memory(wl_client); return; } struct wl_resource *resource = wl_resource_create(wl_client, &zwlr_screencopy_manager_v1_interface, version, id); if (!resource) { free(client); wl_client_post_no_memory(wl_client); return; } struct wlr_screencopy_manager *manager = data; client->manager = manager; wl_list_init(&client->damages); client->ref = 1; wl_resource_set_implementation(resource, &wlr_screencopy_manager_impl, client, manager_handle_resource_destroy); return; } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct wlr_screencopy_manager *manager = wl_container_of(listener, manager, server_destroy); wl_list_remove(&manager->server_destroy.link); free(manager); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_screencopy_manager *manager = wl_container_of(listener, manager, display_destroy); wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); } bool wlr_screencopy_manager_create(struct server *server) { struct wlr_screencopy_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->global = wl_global_create(server->display, &zwlr_screencopy_manager_v1_interface, 3, manager, wlr_screencopy_manager_bind); if (!manager->global) { kywc_log(KYWC_WARN, "Wlr screencopy manager create failed"); free(manager); return false; } wl_list_init(&manager->frames); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &manager->display_destroy); return true; } kylin-wayland-compositor/src/effect/translation.c0000664000175000017500000010476515160460057021225 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include "effect/animator.h" #include "effect/translation.h" #include "effect_p.h" #include "output.h" #include "render/pass.h" #include "render/renderer.h" #include "scene/render.h" #include "scene/thumbnail.h" #include "util/time.h" #include "view/view.h" #define TRANSLATION_ENTITY_COUNT 2 struct translation_entity { struct workspace *workspace; struct animator *automatic_animator; struct wlr_texture *thumbnail_texture; int start_offset; float current_offset; bool is_manual_control; struct animation_data current; struct kywc_box end_geometry; struct kywc_box start_geometry; }; enum translation_order_type { TRANSLATION_AUTOMATIC, TRANSLATION_MANUAL, }; struct translation_order { struct wl_list link; enum translation_order_type type; enum direction direction; float offset; struct workspace *workspace; struct wlr_texture *thumbnail_texture; struct wl_listener thumbnail_update; }; struct workspace_output { struct wl_list link; struct ky_scene_output *scene_output; struct translation_entity *entities[TRANSLATION_ENTITY_COUNT]; int order_count; int width, height; bool is_translating; uint32_t start_time; uint32_t order_duration; struct wl_list orders; struct wl_listener output_destroy; }; struct workspace_translation { float last_offset; uint32_t duration; bool is_manual_control; struct workspace *center; /* only using for manual control */ struct workspace *next_workspace; struct workspace *displaying_workspace; struct wl_list workspace_outputs; struct wl_listener new_enabled_output; }; struct translation_effect { struct effect *effect; uint32_t duration; struct ky_scene *scene; struct wlr_renderer *renderer; struct wl_listener destroy; }; static struct translation_effect *translation_effect = NULL; static void workspace_output_apply_translation_order(struct workspace_output *ws_output); static void workspace_output_move_translation_entities(struct workspace_output *ws_output, uint32_t current_time); static struct workspace_output *find_workspace_output(struct workspace_translation *data, struct ky_scene_output *scene_output); static void workspace_translation_do_destroy(struct workspace_translation *data); static void translation_effect_entity_destroy(struct effect_entity *entity) { struct workspace_translation *data = entity->user_data; workspace_translation_do_destroy(data); entity->user_data = NULL; } static bool translation_frame_render_pre(struct effect_entity *entity, struct ky_scene_render_target *target) { struct ky_scene_output *scene_output = target->output; struct workspace_translation *data = entity->user_data; struct workspace_output *ws_output = find_workspace_output(data, scene_output); if (!ws_output) { return true; } if (data->is_manual_control) { workspace_output_move_translation_entities(ws_output, 0); /* surface maybe push damage */ ky_scene_output_damage_whole(scene_output); return true; } uint32_t current_time = current_time_msec(); if (ws_output->start_time + ws_output->order_duration < current_time) { if (ws_output->order_count == 0) { effect_entity_destroy(entity); return true; } workspace_output_apply_translation_order(ws_output); } workspace_output_move_translation_entities(ws_output, current_time); return true; } static void translation_entity_render(struct translation_entity *translation_entity, struct ky_scene_render_target *target) { if (!translation_entity->thumbnail_texture) { return; } struct wlr_box dst_box = { .x = translation_entity->current.geometry.x, .y = translation_entity->current.geometry.y, .width = translation_entity->current.geometry.width, .height = translation_entity->current.geometry.height, }; pixman_region32_t render_region; pixman_region32_init(&render_region); pixman_region32_copy(&render_region, &target->damage); pixman_region32_translate(&render_region, -target->logical.x, -target->logical.y); pixman_region32_intersect_rect(&render_region, &render_region, dst_box.x, dst_box.y, dst_box.width, dst_box.height); if (!pixman_region32_not_empty(&render_region)) { pixman_region32_fini(&render_region); return; } ky_scene_render_box(&dst_box, target); ky_scene_render_region(&render_region, target); struct ky_render_texture_options options = { .base = { .texture = translation_entity->thumbnail_texture, .alpha = &translation_entity->current.alpha, .dst_box = dst_box, .clip = &render_region, }, .radius = { 0 }, .repeated = false, .rotation_angle = translation_entity->current.angle, }; ky_render_pass_add_texture(target->render_pass, &options); pixman_region32_fini(&render_region); } static void translation_frame_render(struct effect_entity *entity, struct ky_scene_render_target *target) { /* TODO: translation entity maybe be displayed cross output when zooming */ struct workspace_output *ws_output = find_workspace_output(entity->user_data, target->output); if (!ws_output) { return; } /* clear the target buffer */ wlr_render_pass_add_rect(target->render_pass, &(struct wlr_render_rect_options){ .color = { 0, 0, 0, 0 }, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, }); for (int i = 0; i < TRANSLATION_ENTITY_COUNT; ++i) { if (ws_output->entities[i]) { translation_entity_render(ws_output->entities[i], target); } } } static bool translation_frame_render_post(struct effect_entity *entity, struct ky_scene_render_target *target) { struct workspace_translation *data = entity->user_data; if (!data->is_manual_control) { ky_scene_output_damage_whole(target->output); } return true; } static void handle_effect_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&translation_effect->destroy.link); free(translation_effect); translation_effect = NULL; } static bool handle_translation_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } return false; } static bool handle_allow_direct_scanout(struct effect *effect, struct ky_scene_render_target *target) { return !ky_scene_find_effect_entity(translation_effect->scene, effect); } static const struct effect_interface effect_impl = { .entity_destroy = translation_effect_entity_destroy, .frame_render_pre = translation_frame_render_pre, .frame_render = translation_frame_render, .frame_render_post = translation_frame_render_post, .configure = handle_translation_effect_configure, .allow_direct_scanout = handle_allow_direct_scanout, }; bool translation_effect_create(struct effect_manager *manager) { translation_effect = calloc(1, sizeof(*translation_effect)); if (!translation_effect) { return false; } translation_effect->duration = 400; translation_effect->scene = manager->server->scene; translation_effect->renderer = manager->server->renderer; bool enable = !ky_renderer_is_software(manager->server->renderer); translation_effect->effect = effect_create("translation", 105, enable, &effect_impl, NULL); if (!translation_effect->effect) { free(translation_effect); translation_effect = NULL; return false; } translation_effect->effect->category = EFFECT_CATEGORY_SCENE; translation_effect->destroy.notify = handle_effect_destroy; wl_signal_add(&translation_effect->effect->events.destroy, &translation_effect->destroy); return true; } static void handle_thumbnail_update(struct wl_listener *listener, void *data) { struct translation_order *order = wl_container_of(listener, order, thumbnail_update); struct thumbnail_update_event *event = data; if (!event->buffer_changed) { return; } if (order->thumbnail_texture) { wlr_texture_destroy(order->thumbnail_texture); } order->thumbnail_texture = wlr_texture_from_buffer(translation_effect->renderer, event->buffer); } static void translation_order_destroy(struct translation_order *translation_order) { if (translation_order->thumbnail_texture) { wlr_texture_destroy(translation_order->thumbnail_texture); } wl_list_remove(&translation_order->link); free(translation_order); } static struct translation_order *translation_order_create(struct workspace_output *ws_output, enum translation_order_type type, struct workspace *workspace, enum direction direction, float offset) { struct translation_order *order = calloc(1, sizeof(*order)); if (!order) { return NULL; } order->type = type; order->offset = offset; order->workspace = workspace; order->direction = direction; wl_list_init(&order->link); if (!workspace) { return order; } struct thumbnail *output_thumbnail = thumbnail_create_from_output(ws_output->scene_output, NULL, 1.0); if (!output_thumbnail) { free(order); return NULL; } order->thumbnail_update.notify = handle_thumbnail_update; thumbnail_add_update_listener(output_thumbnail, &order->thumbnail_update); thumbnail_update(output_thumbnail); wl_list_remove(&order->thumbnail_update.link); thumbnail_destroy(output_thumbnail); return order; } static void translation_entity_destroy(struct translation_entity *translation_entity) { if (translation_entity->thumbnail_texture) { wlr_texture_destroy(translation_entity->thumbnail_texture); } if (translation_entity->automatic_animator) { animator_destroy(translation_entity->automatic_animator); } free(translation_entity); } static struct translation_entity *translation_entity_create(struct workspace_output *ws_output, struct translation_order *order) { struct translation_entity *translation_entity = calloc(1, sizeof(*translation_entity)); if (!translation_entity) { return NULL; } translation_entity->start_offset = order->offset > 0 ? (int)(order->offset + 0.5) : (int)(order->offset - 0.5); translation_entity->current_offset = order->offset; translation_entity->workspace = order->workspace; translation_entity->is_manual_control = order->type == TRANSLATION_MANUAL; translation_entity->end_geometry.x = translation_entity->end_geometry.y = 0; translation_entity->end_geometry.width = ws_output->width; translation_entity->end_geometry.height = ws_output->height; translation_entity->start_geometry = translation_entity->end_geometry; translation_entity->current.alpha = 1; translation_entity->current.angle = 0; translation_entity->current.geometry = translation_entity->start_geometry; translation_entity->automatic_animator = NULL; translation_entity->thumbnail_texture = order->thumbnail_texture; return translation_entity; } static void workspace_output_move_translation_entities(struct workspace_output *ws_output, uint32_t current_time) { for (int i = 0; i < TRANSLATION_ENTITY_COUNT; ++i) { struct translation_entity *translation_entity = ws_output->entities[i]; if (!translation_entity) { continue; } if (translation_entity->is_manual_control) { struct kywc_box *current = &translation_entity->current.geometry; struct kywc_box *start = &translation_entity->start_geometry; struct kywc_box *end = &translation_entity->end_geometry; current->x = start->x + (end->x - start->x) * fabs(translation_entity->current_offset - translation_entity->start_offset); continue; } if (!translation_entity->automatic_animator) { continue; } const struct animation_data *current_data = animator_value(translation_entity->automatic_animator, current_time); translation_entity->current = *current_data; } } static void workspace_output_reset_entities_animator(struct workspace_output *ws_output) { uint32_t current_time = current_time_msec(); uint32_t end_time = current_time + ws_output->order_duration; ws_output->start_time = current_time; struct translation_entity *exist_entity; for (int i = 0; i < TRANSLATION_ENTITY_COUNT; ++i) { exist_entity = ws_output->entities[i]; if (!exist_entity) { continue; } if (exist_entity->automatic_animator) { animator_set_time_ex(exist_entity->automatic_animator, current_time, end_time); animator_set_position(exist_entity->automatic_animator, exist_entity->end_geometry.x, exist_entity->end_geometry.y); continue; } struct animation_group group = { .geometry = animation_manager_get(ANIMATION_TYPE_EASE_OUT), }; exist_entity->automatic_animator = animator_create(&exist_entity->current, &group, current_time, end_time); if (!exist_entity->automatic_animator) { continue; } animator_set_position(exist_entity->automatic_animator, exist_entity->end_geometry.x, exist_entity->end_geometry.y); } } static struct translation_entity * workspace_output_create_translation_entity(struct workspace_output *ws_output, struct translation_order *order) { struct translation_entity *translation_entity = translation_entity_create(ws_output, order); if (!translation_entity) { return NULL; } order->thumbnail_texture = NULL; struct translation_entity *exist_entity = ws_output->entities[0]; if (!exist_entity) { ws_output->entities[0] = translation_entity; ws_output->is_translating = true; return translation_entity; } struct workspace *workspace = order->workspace; enum direction direct = order->direction; if (direct == DIRECTION_LEFT) { translation_entity->start_geometry.x = exist_entity->current.geometry.x - exist_entity->current.geometry.width; if (!workspace) { translation_entity->end_geometry.x = translation_entity->start_geometry.x + ws_output->width * 0.2; } } else if (direct == DIRECTION_RIGHT) { translation_entity->start_geometry.x = exist_entity->current.geometry.x + exist_entity->current.geometry.width; if (!workspace) { translation_entity->end_geometry.x = translation_entity->start_geometry.x - ws_output->width * 0.2; } } exist_entity->end_geometry.x = exist_entity->current.geometry.x + translation_entity->end_geometry.x - translation_entity->start_geometry.x; translation_entity->current.geometry = translation_entity->start_geometry; ws_output->entities[1] = translation_entity; if (order->type == TRANSLATION_AUTOMATIC) { workspace_output_reset_entities_animator(ws_output); } return translation_entity; } static struct translation_entity * workspace_output_add_translation_entity(struct workspace_output *ws_output, struct workspace *workspace, enum direction direct) { struct translation_order *order = translation_order_create(ws_output, TRANSLATION_AUTOMATIC, workspace, direct, 0); if (!order) { return NULL; } if (ws_output->entities[0] && ws_output->entities[1]) { wl_list_insert(&ws_output->orders, &order->link); ws_output->order_count++; return NULL; } struct translation_entity *new_entity = workspace_output_create_translation_entity(ws_output, order); translation_order_destroy(order); return new_entity; } static struct translation_entity * workspace_output_add_manaul_translation_entity(struct workspace_output *ws_output, struct workspace *workspace, enum direction direction, float offset) { struct translation_entity *already_exsit = NULL; if (ws_output->entities[0] && ws_output->entities[0]->workspace == workspace) { already_exsit = ws_output->entities[0]; } else if (ws_output->entities[1] && ws_output->entities[1]->workspace == workspace) { already_exsit = ws_output->entities[1]; } if (already_exsit) { already_exsit->current_offset = offset; return already_exsit; } /* remove entity who doesn't refresh offset */ if (ws_output->entities[0] && ws_output->entities[1]) { assert(ws_output->entities[0]->current_offset != FLT_MAX || ws_output->entities[1]->current_offset != FLT_MAX); for (int i = 0; i < TRANSLATION_ENTITY_COUNT; i++) { if (ws_output->entities[i]->current_offset == FLT_MAX) { translation_entity_destroy(ws_output->entities[i]); ws_output->entities[i] = NULL; } else { ws_output->entities[i]->start_offset = offset > 0 ? (int)(offset + 0.5) : (int)(offset - 0.5); ws_output->entities[i]->end_geometry = (struct kywc_box){ 0, 0, ws_output->width, ws_output->height }; ws_output->entities[i]->start_geometry = ws_output->entities[i]->end_geometry; ws_output->entities[i]->current.geometry = ws_output->entities[i]->end_geometry; } } if (!ws_output->entities[0]) { ws_output->entities[0] = ws_output->entities[1]; ws_output->entities[1] = NULL; } } struct translation_order *order = translation_order_create(ws_output, TRANSLATION_MANUAL, workspace, direction, offset); if (!order) { return NULL; } struct translation_entity *ret = workspace_output_create_translation_entity(ws_output, order); translation_order_destroy(order); return ret; } static void workspace_output_apply_translation_order(struct workspace_output *ws_output) { assert(ws_output->order_count); if (ws_output->entities[1]) { if (ws_output->entities[0]) { translation_entity_destroy(ws_output->entities[0]); } ws_output->entities[0] = ws_output->entities[1]; ws_output->entities[1] = NULL; } struct translation_order *order, *tmp; wl_list_for_each_reverse_safe(order, tmp, &ws_output->orders, link) { workspace_output_create_translation_entity(ws_output, order); wl_list_remove(&order->link); ws_output->order_count--; free(order); break; } } static void workspace_output_destroy(struct workspace_output *ws_output) { wl_list_remove(&ws_output->output_destroy.link); struct translation_entity *exist_entity; for (int i = 0; i < TRANSLATION_ENTITY_COUNT; ++i) { exist_entity = ws_output->entities[i]; if (!exist_entity) { continue; } translation_entity_destroy(exist_entity); } struct translation_order *pos, *tmp; wl_list_for_each_safe(pos, tmp, &ws_output->orders, link) { translation_order_destroy(pos); } wl_list_remove(&ws_output->link); free(ws_output); } static struct workspace_output *workspace_output_create(struct ky_scene_output *output, int duration) { struct workspace_output *workspace_output = calloc(1, sizeof(*workspace_output)); if (!workspace_output) { return NULL; } workspace_output->order_count = 0; workspace_output->is_translating = false; workspace_output->order_duration = duration; workspace_output->scene_output = output; wlr_output_effective_resolution(output->output, &workspace_output->width, &workspace_output->height); wl_list_init(&workspace_output->orders); wl_list_init(&workspace_output->link); return workspace_output; } static struct workspace_output *find_workspace_output(struct workspace_translation *data, struct ky_scene_output *output) { struct workspace_output *ws_output; wl_list_for_each(ws_output, &data->workspace_outputs, link) { if (ws_output->scene_output == output) { return ws_output; } } return NULL; } static void workspace_translation_do_destroy(struct workspace_translation *data) { wl_list_remove(&data->new_enabled_output.link); struct workspace_output *ws_output, *tmp; wl_list_for_each_safe(ws_output, tmp, &data->workspace_outputs, link) { workspace_output_destroy(ws_output); } free(data); } static void workspace_output_handle_output_destroy(struct wl_listener *listener, void *data) { struct workspace_output *ws_output = wl_container_of(listener, ws_output, output_destroy); workspace_output_destroy(ws_output); } static void workspace_translation_add_output(struct workspace_translation *data, struct ky_scene_output *output) { struct workspace_output *ws_output = workspace_output_create(output, data->duration); if (!ws_output) { return; } ws_output->output_destroy.notify = workspace_output_handle_output_destroy; wl_signal_add(&output->events.destroy, &ws_output->output_destroy); wl_list_insert(&data->workspace_outputs, &ws_output->link); } static void workspace_translation_handle_new_output(struct wl_listener *listener, void *data) { struct workspace_translation *translation_data = wl_container_of(listener, translation_data, new_enabled_output); struct ky_scene_output *output = output_from_kywc_output(data)->scene_output; struct workspace_output *ws_output = find_workspace_output(translation_data, output); if (ws_output) { return; } workspace_translation_add_output(translation_data, output); } static struct workspace_translation *workspace_translation_create(struct ky_scene *scene, struct workspace *center, int duration, bool is_manual_control) { struct workspace_translation *data = calloc(1, sizeof(*data)); if (!data) { return NULL; } data->center = center; data->is_manual_control = is_manual_control; data->duration = duration; data->last_offset = 0; wl_list_init(&data->workspace_outputs); data->new_enabled_output.notify = workspace_translation_handle_new_output; output_manager_add_new_enabled_listener(&data->new_enabled_output); struct ky_scene_output *output; wl_list_for_each(output, &scene->outputs, link) { workspace_translation_add_output(data, output); } return data; } static struct workspace_translation *workspace_add_translation_effect(struct workspace *center, bool is_manual_control) { if (!translation_effect || !translation_effect->effect->enabled) { return NULL; } struct effect *effect = translation_effect->effect; struct ky_scene *scene = translation_effect->scene; struct effect_entity *entity = ky_scene_find_effect_entity(scene, effect); if (entity) { return entity->user_data; } /** * When switching workspaces, the damage region only contains * the damage regions generated by the view in the workspace layer. */ ky_scene_damage_whole(scene); entity = ky_scene_add_effect(scene, effect); if (!entity) { return NULL; } struct workspace_translation *data = workspace_translation_create( scene, center, effect_manager_scale_time(translation_effect->duration), is_manual_control); if (!data) { effect_entity_destroy(entity); return NULL; } entity->user_data = data; return data; } bool workspace_add_automatic_translation_effect(struct workspace *current, struct workspace *next, enum direction direction) { if (direction != DIRECTION_LEFT && direction != DIRECTION_RIGHT) { return false; } struct workspace_translation *ws_translation = workspace_add_translation_effect(NULL, false); if (!ws_translation) { return false; } if (ws_translation->is_manual_control) { return false; } next = next == current ? NULL : next; struct workspace_output *ws_output; wl_list_for_each(ws_output, &ws_translation->workspace_outputs, link) { if (!ws_output->is_translating) { workspace_output_add_translation_entity(ws_output, current, direction); } } if (next) { workspace_activate(next); } wl_list_for_each(ws_output, &ws_translation->workspace_outputs, link) { if (ws_output->order_count >= 10) { continue; } if (!wl_list_empty(&ws_output->orders)) { struct translation_order *last_order = wl_container_of(ws_output->orders.next, last_order, link); enum direction round_trip_direction = direction == DIRECTION_LEFT ? DIRECTION_RIGHT : DIRECTION_LEFT; if (next == NULL && last_order->direction == round_trip_direction && last_order->workspace == current) { continue; } } else if (next == NULL && ws_output->entities[0]->workspace == next && ws_output->entities[1]->workspace == current) { continue; } workspace_output_add_translation_entity(ws_output, next, direction); /* if current workspace is last workspace, need a short round trip */ if (!next) { workspace_output_add_translation_entity( ws_output, current, direction == DIRECTION_LEFT ? DIRECTION_RIGHT : DIRECTION_LEFT); } } return true; } struct workspace_translation *workspace_create_manual_translation_effect(struct workspace *center) { if (!center) { return NULL; } struct workspace_translation *ws_translation = workspace_add_translation_effect(center, true); if (!ws_translation) { return NULL; } if (ws_translation->center != center || !ws_translation->is_manual_control) { return NULL; } return ws_translation; } static void get_workspace_index(struct workspace *center_workspace, uint32_t workspace_count, double offset, int64_t *displaying, int64_t *next) { int32_t center = center_workspace->position; if (offset > 0) { *displaying = center + floor(offset); *next = center + ceil(offset); } else { *displaying = center - floor(-offset); *next = center + (int)(offset - 1); } if (*displaying <= 0 && *next <= 0) { *displaying = 0; *next = -1; } else if (*displaying >= workspace_count && *next >= workspace_count) { *displaying = (int64_t)workspace_count - 1; *next = workspace_count; } } static bool update_translation_workspace(struct workspace_translation *ws_translation, struct workspace *displaying1, struct workspace *displaying2) { if (!ws_translation->displaying_workspace && !ws_translation->next_workspace) { ws_translation->displaying_workspace = displaying1; ws_translation->next_workspace = displaying2; return true; } bool is_displaying1_exist = ws_translation->displaying_workspace == displaying1 || ws_translation->next_workspace == displaying1; bool is_displaying2_exist = ws_translation->displaying_workspace == displaying2 || ws_translation->next_workspace == displaying2; if (is_displaying1_exist && is_displaying2_exist) { return true; } if (is_displaying1_exist) { ws_translation->displaying_workspace = displaying1; ws_translation->next_workspace = displaying2; return true; } if (is_displaying2_exist) { ws_translation->displaying_workspace = displaying2; ws_translation->next_workspace = displaying1; return true; } return false; } bool workspace_translation_manual(struct workspace_translation *ws_translation, enum direction direction, float offset) { if (!ws_translation->is_manual_control || (direction != DIRECTION_LEFT && direction != DIRECTION_RIGHT)) { return false; } if (offset < 0) { offset = fmax(fmax(offset, ws_translation->last_offset - 0.01f), -2.0f); } else { offset = fmin(fmin(offset, ws_translation->last_offset + 0.01f), 2.0f); } if (ws_translation->last_offset == offset) { return false; } ws_translation->last_offset = offset; int64_t displaying1_index, displaying2_index; get_workspace_index(ws_translation->center, workspace_manager_get_count(), offset, &displaying1_index, &displaying2_index); struct workspace *displaying1_workspace = displaying1_index < 0 ? NULL : workspace_by_position(displaying1_index); struct workspace *displaying2_workspace = displaying2_index < 0 ? NULL : workspace_by_position(displaying2_index); if (!update_translation_workspace(ws_translation, displaying1_workspace, displaying2_workspace)) { /* workspace1 and workspace2 didn't display */ return false; } struct workspace *next = ws_translation->next_workspace; struct workspace *current = ws_translation->displaying_workspace; bool current_exist = false, next_exist = false; /* reset offset */ struct workspace_output *ws_output; wl_list_for_each(ws_output, &ws_translation->workspace_outputs, link) { for (int i = 0; i < TRANSLATION_ENTITY_COUNT; i++) { if (!ws_output->entities[i]) { continue; } ws_output->entities[i]->current_offset = FLT_MAX; if (ws_output->entities[i]->workspace == current) { current_exist = true; } else if (ws_output->entities[i]->workspace == next) { next_exist = true; } } /* don't know which entity show be deleted, if current workspace don't in array */ if (!current_exist && ws_output->entities[0] && ws_output->entities[1]) { return false; } } /** * When switching workspaces, the damage region only contains * the damage regions generated by the view in the workspace layer. */ ky_scene_damage_whole(translation_effect->scene); if (!current_exist && current) { workspace_activate(current); } wl_list_for_each(ws_output, &ws_translation->workspace_outputs, link) { workspace_output_add_manaul_translation_entity(ws_output, current, direction, offset); } if (!next_exist && next) { workspace_activate(next); } wl_list_for_each(ws_output, &ws_translation->workspace_outputs, link) { workspace_output_add_manaul_translation_entity(ws_output, next, direction, offset); } if (ws_translation->center) { workspace_activate(ws_translation->center); } return true; } struct workspace *workspace_translation_destroy(struct workspace_translation *ws_translation, float continue_switching_percent) { if (!ws_translation->is_manual_control) { return NULL; } ky_scene_damage_whole(translation_effect->scene); bool workspace_continue_switched = false; struct workspace_output *ws_output; wl_list_for_each(ws_output, &ws_translation->workspace_outputs, link) { bool is_can_continue_switching = ws_output->entities[0] && ws_output->entities[1] && ws_output->entities[0]->workspace && ws_output->entities[1]->workspace; for (int i = 0; i < TRANSLATION_ENTITY_COUNT; i++) { if (!ws_output->entities[i]) { continue; } ws_output->entities[i]->is_manual_control = false; if (!is_can_continue_switching) { /* back to start geometry */ ws_output->entities[i]->end_geometry = ws_output->entities[i]->start_geometry; continue; } float current_percent = fabs(ws_output->entities[i]->current_offset - ws_output->entities[i]->start_offset); if (current_percent < continue_switching_percent) { /* back to start geometry */ ws_output->entities[i]->end_geometry = ws_output->entities[i]->start_geometry; continue; } workspace_continue_switched = true; } workspace_output_reset_entities_animator(ws_output); } struct workspace *final_show = workspace_continue_switched ? ws_translation->next_workspace : ws_translation->displaying_workspace; ws_translation->is_manual_control = false; ws_translation->displaying_workspace = NULL; ws_translation->next_workspace = NULL; return final_show; } kylin-wayland-compositor/src/effect/touch_long.c0000664000175000017500000001314215160460057021014 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "effect/shape.h" #include "effect_p.h" #include "input/cursor.h" #include "input/seat.h" struct seat_touch { struct wl_list link; struct touch_long_effect *effect; struct seat *seat; struct wl_listener touch_up; struct wl_listener touch_down; struct wl_listener touch_motion; struct wl_listener destroy; }; struct touch_long_effect { struct circle_progressbar_effect *base; struct wl_listener new_seat; struct wl_list seat_touchs; double touch_x, touch_y; }; static void handle_touch_down(struct wl_listener *listener, void *data) { struct seat_touch *seat_touch = wl_container_of(listener, seat_touch, touch_down); struct wlr_touch_down_event *event = data; // only first finger touch if (event->touch_id != 0) { circle_progressbar_effect_end(seat_touch->effect->base); return; } seat_touch->effect->touch_x = event->x; seat_touch->effect->touch_y = event->y; int32_t lx = roundf(seat_touch->seat->cursor->lx); int32_t ly = roundf(seat_touch->seat->cursor->ly); circle_progressbar_effect_begin(seat_touch->effect->base, lx, ly); } static void handle_touch_up(struct wl_listener *listener, void *data) { struct seat_touch *seat_touch = wl_container_of(listener, seat_touch, touch_up); circle_progressbar_effect_end(seat_touch->effect->base); } static void handle_touch_motion(struct wl_listener *listener, void *data) { struct seat_touch *seat_touch = wl_container_of(listener, seat_touch, touch_motion); struct wlr_touch_motion_event *event = data; if (event->touch_id != 0) { circle_progressbar_effect_end(seat_touch->effect->base); return; } const float anti_acciden = 0.01f; // move diff > this then remove if (fabs(seat_touch->effect->touch_x - event->x) > anti_acciden || fabs(seat_touch->effect->touch_y - event->y) > anti_acciden) { circle_progressbar_effect_end(seat_touch->effect->base); } } static void seat_touch_destroy(struct seat_touch *seat_touch) { wl_list_remove(&seat_touch->link); wl_list_remove(&seat_touch->touch_up.link); wl_list_remove(&seat_touch->touch_down.link); wl_list_remove(&seat_touch->touch_motion.link); wl_list_remove(&seat_touch->destroy.link); free(seat_touch); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { struct seat_touch *seat_touch = wl_container_of(listener, seat_touch, destroy); seat_touch_destroy(seat_touch); } static void seat_touch_create(struct touch_long_effect *effect, struct seat *seat) { struct seat_touch *seat_touch = calloc(1, sizeof(*seat_touch)); if (!seat_touch) { return; } wl_list_insert(&effect->seat_touchs, &seat_touch->link); seat_touch->effect = effect; seat_touch->seat = seat; seat_touch->touch_up.notify = handle_touch_up; wl_signal_add(&seat->cursor->wlr_cursor->events.touch_up, &seat_touch->touch_up); seat_touch->touch_down.notify = handle_touch_down; wl_signal_add(&seat->cursor->wlr_cursor->events.touch_down, &seat_touch->touch_down); seat_touch->touch_motion.notify = handle_touch_motion; wl_signal_add(&seat->cursor->wlr_cursor->events.touch_motion, &seat_touch->touch_motion); seat_touch->destroy.notify = handle_seat_destroy; wl_signal_add(&seat->events.destroy, &seat_touch->destroy); } static void handle_new_seat(struct wl_listener *listener, void *data) { struct touch_long_effect *effect = wl_container_of(listener, effect, new_seat); struct seat *seat = data; seat_touch_create(effect, seat); } static bool handle_seat(struct seat *seat, int index, void *data) { struct touch_long_effect *effect = data; seat_touch_create(effect, seat); return false; } static void handle_effect_enable(struct circle_progressbar_effect *base_effect, void *user_data) { struct touch_long_effect *effect = user_data; input_manager_for_each_seat(handle_seat, effect); effect->new_seat.notify = handle_new_seat; seat_add_new_listener(&effect->new_seat); } static void handle_effect_disable(struct circle_progressbar_effect *base_effect, void *user_data) { struct touch_long_effect *effect = user_data; wl_list_remove(&effect->new_seat.link); wl_list_init(&effect->new_seat.link); struct seat_touch *seat_touch, *tmp0; wl_list_for_each_safe(seat_touch, tmp0, &effect->seat_touchs, link) { seat_touch_destroy(seat_touch); } } static void handle_effect_destroy(struct circle_progressbar_effect *base_effect, void *user_data) { struct touch_long_effect *effect = user_data; free(effect); } static const struct circle_progressbar_effect_interface effect_impl = { .enable = handle_effect_enable, .disable = handle_effect_disable, .destroy = handle_effect_destroy, }; bool touch_long_effect_create(struct effect_manager *manager) { struct touch_long_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } wl_list_init(&effect->new_seat.link); wl_list_init(&effect->seat_touchs); struct circle_progressbar_effect_options options = {}; options.size = 100; options.animate_delay = 200; options.animate_duration = 800; effect->base = circle_progressbar_effect_create(manager, &options, &effect_impl, "touch_long", 102, true, effect); if (!effect->base) { free(effect); return false; } return true; } kylin-wayland-compositor/src/effect/shake_view.c0000664000175000017500000001633315160461067021007 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "effect/shake_view.h" #include "effect_p.h" #include "scene/animation.h" #include "view/view.h" /* An animation time */ #define SHAKE_EFFECT_PERIOD (40) /* offset distance */ #define SHAKE_EFFECT_OFFSET (10) /* Run times */ #define SHAKE_EFFECT_TIMES (3) enum shake_effect_stage { SHAKE_EFFECT_ORIGIN = 0, SHAKE_EFFECT_LEFT_SIDE, SHAKE_EFFECT_RIGHT_SIDE, }; struct shake_view { struct wl_list link; struct view *view; /* view position and size before shake effect. */ struct kywc_box geo; /* attributes used for the shake effect. */ int current_stage; uint32_t period; bool enabled; int count; struct wl_listener view_unmap; struct wl_event_source *timer; }; static struct shake_view_effect { struct wl_list shake_views; struct effect *effect; struct wl_listener enable; struct wl_listener disable; struct wl_listener destroy; } *effect = NULL; static void shake_view_effect_enabled(struct shake_view *shake, bool enabled); static int get_shake_view_effect_pending_stage(enum shake_effect_stage stage) { enum shake_effect_stage pending_stage = SHAKE_EFFECT_ORIGIN; switch (stage) { case SHAKE_EFFECT_ORIGIN: pending_stage = SHAKE_EFFECT_LEFT_SIDE; break; case SHAKE_EFFECT_LEFT_SIDE: pending_stage = SHAKE_EFFECT_RIGHT_SIDE; break; case SHAKE_EFFECT_RIGHT_SIDE: pending_stage = SHAKE_EFFECT_ORIGIN; break; } return pending_stage; } static void set_shake_view_effect_animation_time(struct shake_view *shake, int time) { if (wl_event_source_timer_update(shake->timer, time) < 0) { kywc_log(KYWC_DEBUG, "Failed to set key view shake animation timer"); } } static void set_shake_view_effect_animation(struct shake_view *shake, enum shake_effect_stage stage, int time) { int temp_x = shake->geo.x; switch (stage) { case SHAKE_EFFECT_ORIGIN: temp_x = shake->geo.x; break; case SHAKE_EFFECT_LEFT_SIDE: temp_x = shake->geo.x - SHAKE_EFFECT_OFFSET; break; case SHAKE_EFFECT_RIGHT_SIDE: temp_x = shake->geo.x + SHAKE_EFFECT_OFFSET; break; } struct animation *animation = animation_manager_get(ANIMATION_TYPE_EASE_IN_OUT); ky_scene_node_set_position_with_animation(&shake->view->tree->node, temp_x, shake->geo.y, animation, time); } /* current.x --> left.x --> right.x --> current.x * current.x --> left.x and right.x --> current.x same distance * left.x --> right.x is twice the distance */ static int handle_shake_view_effect(void *data) { struct shake_view *shake = data; enum shake_effect_stage pending_stage = get_shake_view_effect_pending_stage(shake->current_stage); if (pending_stage == SHAKE_EFFECT_ORIGIN) { shake->count++; } uint32_t period = pending_stage == SHAKE_EFFECT_RIGHT_SIDE ? shake->period * 2 : shake->period; set_shake_view_effect_animation_time(shake, period); set_shake_view_effect_animation(shake, pending_stage, period); if (shake->count >= SHAKE_EFFECT_TIMES) { shake_view_effect_enabled(shake, false); } shake->current_stage = pending_stage; return 0; } static void shake_view_effect_enabled(struct shake_view *shake, bool enabled) { if (!shake || shake->enabled == enabled) { return; } if (shake->enabled) { kywc_view_move(&shake->view->base, shake->geo.x, shake->geo.y); } shake->enabled = enabled; shake->count = 0; shake->geo = shake->view->base.geometry; shake->current_stage = SHAKE_EFFECT_ORIGIN; if (enabled) { handle_shake_view_effect(shake); } else if (wl_event_source_timer_update(shake->timer, 0) < 0) { kywc_log(KYWC_DEBUG, "Failed to stop view shake timer"); } } static struct shake_view *shake_view_by_view(struct view *view) { struct shake_view *shake; wl_list_for_each(shake, &effect->shake_views, link) { if (shake->view == view) { return shake; } } return NULL; } static void handle_view_unmap(struct wl_listener *listener, void *data) { struct shake_view *shake = wl_container_of(listener, shake, view_unmap); wl_list_remove(&shake->link); wl_list_remove(&shake->view_unmap.link); wl_event_source_remove(shake->timer); free(shake); } void view_add_shake_effect(struct view *view) { if (!effect || !effect->effect->enabled) { return; } uint32_t period = effect_manager_scale_time(SHAKE_EFFECT_PERIOD); if (!period) { return; } struct shake_view *shake = shake_view_by_view(view); if (shake) { shake->period = period; shake_view_effect_enabled(shake, true); return; } shake = calloc(1, sizeof(*shake)); if (!shake) { return; } shake->view = view; shake->period = period; wl_list_insert(&effect->shake_views, &shake->link); shake->view_unmap.notify = handle_view_unmap; wl_signal_add(&view->base.events.unmap, &shake->view_unmap); /* create timer for shake effect */ struct seat *seat = input_manager_get_default_seat(); struct wl_event_loop *loop = wl_display_get_event_loop(seat->wlr_seat->display); shake->timer = wl_event_loop_add_timer(loop, handle_shake_view_effect, shake); shake_view_effect_enabled(shake, true); } static void handle_effect_enable(struct wl_listener *listener, void *data) { /* Do nothing, the effect is created by the module called independently. */ } static void handle_effect_disable(struct wl_listener *listener, void *data) { /* Do nothing, the effect is released by the module called independently. */ } static void handle_effect_destroy(struct wl_listener *listener, void *data) { assert(wl_list_empty(&effect->shake_views)); wl_list_remove(&effect->destroy.link); wl_list_remove(&effect->enable.link); wl_list_remove(&effect->disable.link); free(effect); effect = NULL; } static bool handle_effect_configure(struct effect *eff, const struct effect_option *option) { return true; // Always on } static const struct effect_interface effect_impl = { .configure = handle_effect_configure, }; bool shake_view_effect_create(struct effect_manager *effect_manager) { effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } effect->effect = effect_create("shake_view", 0, true, &effect_impl, NULL); if (!effect->effect) { free(effect); effect = NULL; return false; } effect->effect->support_actions = EFFECT_ACTION_SHAKE; effect->effect->category = EFFECT_CATEGORY_ACTION; wl_list_init(&effect->shake_views); effect->enable.notify = handle_effect_enable; wl_signal_add(&effect->effect->events.enable, &effect->enable); effect->disable.notify = handle_effect_disable; wl_signal_add(&effect->effect->events.disable, &effect->disable); effect->destroy.notify = handle_effect_destroy; wl_signal_add(&effect->effect->events.destroy, &effect->destroy); return true; } kylin-wayland-compositor/src/effect/shake_cursor.c0000664000175000017500000004210615160461067021347 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include "effect/animator.h" #include "effect_p.h" #include "input/cursor.h" #include "input/seat.h" #include "painter.h" #include "theme.h" #include "util/macros.h" #include "util/time.h" #define INTERVAL (500) #define DURATION (150) #define POINTS_SIZE (64) enum cursor_stage { CURSOR_STAGE_HIDDEN = 0, CURSOR_STAGE_SHOWING, CURSOR_STAGE_SHOWN, CURSOR_STAGE_HIDING, }; struct cursor_point { double lx, ly; uint32_t time_msec; }; struct seat_cursor { struct wl_list link; struct shake_cursor_effect *effect; struct seat *seat; struct wl_listener cursor_motion; struct wl_listener cursor_configure; struct wl_listener seat_destroy; struct cursor_point points[POINTS_SIZE]; /* index of head and tail entry */ size_t head, tail, count; /* buffer from cursor theme */ struct wlr_buffer *buffer; struct wlr_texture *texture; struct wlr_box damage; uint32_t off_x, off_y; uint32_t orig_off_x, orig_off_y; double lx, ly; uint32_t duration; uint32_t last_shaked_time; uint32_t last_motion_time; uint32_t animation_start_time; enum cursor_stage stage; }; struct shake_cursor_effect { struct effect *effect; struct wl_listener enable; struct wl_listener disable; struct wl_listener destroy; struct wl_list cursors; struct wl_listener new_seat; struct server *server; struct animation *animation; }; static bool in_same_sign(double a, double b) { double tolerance = 1; // movements less than tolerance count as movements in any direction return (a >= -tolerance && b >= -tolerance) || (a <= tolerance && b <= tolerance); } /** * From KWin: Shake gestures are detected by comparing the length of the trail of the cursor * within past N milliseconds with the length of the diagonal of the bounding rectangle of the * trail. If the trail is longer than the diagonal by certain preconfigured factor, it's assumed * that the user shook the pointer */ static bool cursor_shake_detect(struct seat_cursor *cursor, double lx, double ly, uint32_t time_msec) { // remove old point in the history points size_t index = cursor->head, count = cursor->count; for (size_t i = 0; i < count; i++) { if (time_msec - cursor->points[index].time_msec < INTERVAL) { break; } index = (index + 1) % POINTS_SIZE; cursor->head = index; cursor->count--; } // merge points in same sign if (cursor->count >= 2) { struct cursor_point *last = &cursor->points[(cursor->tail - 1) % POINTS_SIZE]; struct cursor_point *prev = &cursor->points[(cursor->tail - 2) % POINTS_SIZE]; if (in_same_sign(last->lx - prev->lx, lx - last->lx) && in_same_sign(last->ly - prev->ly, ly - last->ly)) { *last = (struct cursor_point){ lx, ly, time_msec }; return false; } } // move head if ring is full if (cursor->count == POINTS_SIZE) { cursor->head = (cursor->head + 1) % POINTS_SIZE; cursor->count--; } // insert to tail cursor->points[cursor->tail] = (struct cursor_point){ lx, ly, time_msec }; cursor->tail = (cursor->tail + 1) % POINTS_SIZE; cursor->count++; if (cursor->count < 2) { return false; } struct cursor_point *point = &cursor->points[cursor->head]; double left = point->lx, top = point->ly; double right = point->lx, bottom = point->ly; double distance = 0, dx, dy; struct cursor_point *current, *next; index = cursor->head; for (size_t i = 1; i < cursor->count; i++) { current = &cursor->points[index]; index = (index + 1) % POINTS_SIZE; next = &cursor->points[index]; dx = next->lx - current->lx; dy = next->ly - current->ly; distance += sqrt(dx * dx + dy * dy); left = MIN(left, next->lx); top = MIN(top, next->ly); right = MAX(right, next->lx); bottom = MAX(bottom, next->ly); } double width = right - left, height = bottom - top; double diagonal = sqrt(width * width + height * height); if (diagonal < 100) { return false; } if (distance / diagonal > 4) { // clear points history cursor->head = cursor->tail; cursor->count = 0; return true; } return false; } static bool cursor_get_or_create_buffer(struct seat_cursor *cursor) { if (cursor->buffer) { return true; } struct wlr_xcursor_manager *manager = cursor->seat->cursor->xcursor_manager; wlr_xcursor_manager_load(manager, 4.0); struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(manager, "default", 4.0); struct wlr_xcursor_image *image = xcursor->images[0]; struct draw_info info = { .width = image->width, .height = image->height, .pixel = { image->width, image->height, image->buffer }, }; cursor->buffer = painter_draw_buffer(&info); if (!cursor->buffer) { return false; } cursor->off_x = image->hotspot_x; cursor->off_y = image->hotspot_y; wlr_xcursor_manager_load(manager, 1.0); xcursor = wlr_xcursor_manager_get_xcursor(manager, "default", 1.0); cursor->orig_off_x = xcursor->images[0]->hotspot_x; cursor->orig_off_y = xcursor->images[0]->hotspot_y; struct server *server = cursor->effect->server; cursor->texture = wlr_texture_from_buffer(server->renderer, cursor->buffer); return true; } static void handle_cursor_motion(struct wl_listener *listener, void *data) { struct seat_cursor *cursor = wl_container_of(listener, cursor, cursor_motion); struct seat_cursor_motion_event *event = data; if (event->device && event->device->prop.type != WLR_INPUT_DEVICE_POINTER) { return; } cursor->lx = event->lx; cursor->ly = event->ly; cursor->last_motion_time = event->time_msec; if (cursor_shake_detect(cursor, event->lx, event->ly, event->time_msec)) { cursor->last_shaked_time = event->time_msec; } } static void handle_cursor_configure(struct wl_listener *listener, void *data) { struct seat_cursor *cursor = wl_container_of(listener, cursor, cursor_configure); wlr_buffer_drop(cursor->buffer); cursor->buffer = NULL; } static void seat_cursor_destroy(struct seat_cursor *cursor) { wl_list_remove(&cursor->seat_destroy.link); wl_list_remove(&cursor->cursor_configure.link); wl_list_remove(&cursor->cursor_motion.link); wl_list_remove(&cursor->link); wlr_buffer_drop(cursor->buffer); free(cursor); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { struct seat_cursor *cursor = wl_container_of(listener, cursor, seat_destroy); seat_cursor_destroy(cursor); } static void seat_cursor_create(struct shake_cursor_effect *effect, struct seat *seat) { struct seat_cursor *cursor = calloc(1, sizeof(*cursor)); if (!cursor) { return; } cursor->effect = effect; wl_list_insert(&effect->cursors, &cursor->link); cursor->duration = effect_manager_scale_time(DURATION); cursor->seat = seat; cursor->seat_destroy.notify = handle_seat_destroy; wl_signal_add(&seat->events.destroy, &cursor->seat_destroy); cursor->cursor_motion.notify = handle_cursor_motion; wl_signal_add(&cursor->seat->events.cursor_motion, &cursor->cursor_motion); cursor->cursor_configure.notify = handle_cursor_configure; wl_signal_add(&cursor->seat->events.cursor_configure, &cursor->cursor_configure); cursor->effect->animation = animation_manager_get(ANIMATION_TYPE_EASE_IN_OUT); cursor->stage = CURSOR_STAGE_HIDDEN; } static bool handle_seat(struct seat *seat, int index, void *data) { struct shake_cursor_effect *effect = data; seat_cursor_create(effect, seat); return false; } static void handle_new_seat(struct wl_listener *listener, void *data) { struct shake_cursor_effect *effect = wl_container_of(listener, effect, new_seat); struct seat *seat = data; seat_cursor_create(effect, seat); } static void handle_effect_enable(struct wl_listener *listener, void *data) { struct shake_cursor_effect *effect = wl_container_of(listener, effect, enable); input_manager_for_each_seat(handle_seat, effect); seat_add_new_listener(&effect->new_seat); } static void handle_effect_disable(struct wl_listener *listener, void *data) { struct shake_cursor_effect *effect = wl_container_of(listener, effect, disable); wl_list_remove(&effect->new_seat.link); wl_list_init(&effect->new_seat.link); struct seat_cursor *cursor, *tmp; wl_list_for_each_safe(cursor, tmp, &effect->cursors, link) { if (cursor->stage != CURSOR_STAGE_HIDDEN) { cursor_lock_image(cursor->seat->cursor, false); cursor_rebase(cursor->seat->cursor); } seat_cursor_destroy(cursor); } } static bool handle_frame_render_pre(struct effect_entity *entity, struct ky_scene_render_target *target) { struct shake_cursor_effect *effect = entity->user_data; uint32_t duration = effect_manager_scale_time(DURATION); struct seat_cursor *cursor; wl_list_for_each(cursor, &effect->cursors, link) { cursor->duration = duration; enum cursor_stage old_stage = cursor->stage; uint32_t time = current_time_msec(); if (time - cursor->last_shaked_time < INTERVAL) { if (cursor->stage == CURSOR_STAGE_HIDDEN || cursor->stage == CURSOR_STAGE_HIDING) { cursor->stage = CURSOR_STAGE_SHOWING; cursor->animation_start_time = time; } else if (cursor->stage == CURSOR_STAGE_SHOWING && time - cursor->animation_start_time > duration) { cursor->stage = CURSOR_STAGE_SHOWN; } } else if (time - cursor->last_motion_time > INTERVAL / 2) { if (cursor->stage == CURSOR_STAGE_SHOWING || cursor->stage == CURSOR_STAGE_SHOWN) { cursor->stage = CURSOR_STAGE_HIDING; cursor->animation_start_time = time; } else if (cursor->stage == CURSOR_STAGE_HIDING && time - cursor->animation_start_time > duration) { cursor->stage = CURSOR_STAGE_HIDDEN; } } if (cursor->stage != CURSOR_STAGE_HIDDEN && !cursor_get_or_create_buffer(cursor)) { cursor->stage = CURSOR_STAGE_HIDDEN; } if (old_stage == CURSOR_STAGE_HIDDEN && cursor->stage != CURSOR_STAGE_HIDDEN) { cursor->damage = (struct wlr_box){ cursor->lx - cursor->off_x, cursor->ly - cursor->off_y, cursor->buffer->width, cursor->buffer->height }; cursor_set_image(cursor->seat->cursor, CURSOR_NONE); cursor_lock_image(cursor->seat->cursor, true); } else if (old_stage != CURSOR_STAGE_HIDDEN && cursor->stage == CURSOR_STAGE_HIDDEN) { cursor->damage = (struct wlr_box){ 0 }; cursor_lock_image(cursor->seat->cursor, false); cursor_rebase(cursor->seat->cursor); } } return true; } static bool handle_frame_render_end(struct effect_entity *entity, struct ky_scene_render_target *target) { struct shake_cursor_effect *effect = entity->user_data; struct seat_cursor *cursor; wl_list_for_each(cursor, &effect->cursors, link) { if (cursor->stage == CURSOR_STAGE_HIDDEN) { continue; } struct wlr_box dst_box = { .x = cursor->lx - cursor->off_x, .y = cursor->ly - cursor->off_y, .width = cursor->buffer->width, .height = cursor->buffer->height, }; if (cursor->stage == CURSOR_STAGE_SHOWING || cursor->stage == CURSOR_STAGE_HIDING) { float percent = (float)(current_time_msec() - cursor->animation_start_time) / cursor->duration; float value = animation_value(cursor->effect->animation, percent); int size = cursor->seat->state.cursor_size; if (cursor->stage == CURSOR_STAGE_SHOWING) { dst_box.width = size + (dst_box.width - size) * value; dst_box.height = size + (dst_box.height - size) * value; } else { dst_box.x = dst_box.x + (cursor->off_x - cursor->orig_off_x) * value; dst_box.y = dst_box.y + (cursor->off_y - cursor->orig_off_y) * value; dst_box.width = dst_box.width - (dst_box.width - size) * value; dst_box.height = dst_box.height - (dst_box.height - size) * value; } } cursor->damage = dst_box; struct wlr_box box; if (!wlr_box_intersection(&box, &dst_box, &target->logical)) { continue; } /* add dst_box to target expand damage, because it will be add to frame damage */ pixman_region32_union_rect(&target->expand_damage, &target->expand_damage, dst_box.x, dst_box.y, dst_box.height, dst_box.width); dst_box.x -= target->logical.x; dst_box.y -= target->logical.y; ky_scene_render_box(&dst_box, target); struct wlr_render_texture_options options = { .texture = cursor->texture, .dst_box = dst_box, .transform = target->transform, }; wlr_render_pass_add_texture(target->render_pass, &options); } return true; } static bool handle_frame_render_post(struct effect_entity *entity, struct ky_scene_render_target *target) { struct shake_cursor_effect *effect = entity->user_data; struct seat_cursor *cursor; wl_list_for_each(cursor, &effect->cursors, link) { if (cursor->stage == CURSOR_STAGE_HIDDEN) { continue; } pixman_region32_t region; pixman_region32_init_rect(®ion, cursor->damage.x, cursor->damage.y, cursor->damage.width, cursor->damage.height); ky_scene_add_damage(cursor->effect->server->scene, ®ion); pixman_region32_fini(®ion); } return true; } static void handle_effect_destroy(struct wl_listener *listener, void *data) { struct shake_cursor_effect *effect = wl_container_of(listener, effect, destroy); wl_list_remove(&effect->destroy.link); wl_list_remove(&effect->enable.link); wl_list_remove(&effect->disable.link); wl_list_remove(&effect->new_seat.link); free(effect); } static bool handle_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } return false; } static bool handle_allow_direct_scanout(struct effect *effect, struct ky_scene_render_target *target) { struct shake_cursor_effect *shake_cursor = effect->user_data; struct seat_cursor *cursor; wl_list_for_each(cursor, &shake_cursor->cursors, link) { if (cursor->stage == CURSOR_STAGE_HIDDEN) { continue; } struct wlr_box damage = { cursor->lx - cursor->off_x, cursor->ly - cursor->off_y, cursor->buffer->width, cursor->buffer->height }; if (wlr_box_intersection(&damage, &target->logical, &damage)) { return false; } } return true; } static const struct effect_interface shake_cursor_effect_impl = { .frame_render_pre = handle_frame_render_pre, .frame_render_end = handle_frame_render_end, .frame_render_post = handle_frame_render_post, .configure = handle_effect_configure, .allow_direct_scanout = handle_allow_direct_scanout, }; bool shake_cursor_effect_create(struct effect_manager *manager) { struct shake_cursor_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } effect->effect = effect_create("shake_cursor", 110, false, &shake_cursor_effect_impl, effect); if (!effect->effect) { free(effect); return false; } effect->effect->category = EFFECT_CATEGORY_UTILS; struct effect_entity *entity = ky_scene_add_effect(manager->server->scene, effect->effect); if (!entity) { effect_destroy(effect->effect); free(effect); return false; } entity->user_data = effect; effect->server = manager->server; wl_list_init(&effect->cursors); effect->enable.notify = handle_effect_enable; wl_signal_add(&effect->effect->events.enable, &effect->enable); effect->disable.notify = handle_effect_disable; wl_signal_add(&effect->effect->events.disable, &effect->disable); effect->destroy.notify = handle_effect_destroy; wl_signal_add(&effect->effect->events.destroy, &effect->destroy); effect->new_seat.notify = handle_new_seat; wl_list_init(&effect->new_seat.link); if (effect->effect->enabled) { handle_effect_enable(&effect->enable, NULL); } return true; } kylin-wayland-compositor/src/effect/scale.c0000664000175000017500000002343215160461067017747 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "effect/scale.h" #include "effect/transform.h" #include "effect_p.h" #include "kywc/boxes.h" #include "output.h" #include "scene/surface.h" #include "util/time.h" struct scale_entity { struct transform *transform; struct wl_listener destroy; struct wl_listener view_destroy; }; struct scale_effect { struct transform_effect *effect; }; static struct scale_effect *scale_effect = NULL; static void scale_calc_view_box(struct kywc_view *view, struct kywc_box *geometry_box, struct kywc_box *box) { box->x = geometry_box->x - view->margin.off_x; box->y = geometry_box->y - view->margin.off_y; box->width = geometry_box->width + view->margin.off_width; box->height = geometry_box->height + view->margin.off_height; } static void scale_end_geometry_from_panel_surface(struct view *view, struct kywc_box *geometry) { int lx, ly; struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(view->minimized_geometry.panel_surface); ky_scene_node_coords(&buffer->node, &lx, &ly); *geometry = (struct kywc_box){ view->minimized_geometry.geometry.x + lx, view->minimized_geometry.geometry.y + ly, view->minimized_geometry.geometry.width, view->minimized_geometry.geometry.height }; } static void scale_end_geometry_from_parent_panel(struct view *view, struct kywc_box *geometry) { struct view *parent = view->parent; while (parent) { if (parent->minimized_geometry.panel_surface) { scale_end_geometry_from_panel_surface(parent, geometry); break; } parent = parent->parent; } } static void scale_calc_minimize_start_and_end_geometry(struct view *view, struct kywc_box *start, struct kywc_box *end) { *start = view->base.geometry; end->width = view->base.geometry.width * 0.4; end->height = view->base.geometry.height * 0.4; if (view->minimized_when_show_desktop) { struct output *output = output_from_kywc_output(view->output); end->x = output->geometry.x + (output->geometry.width - end->width) / 2; end->y = output->geometry.y + (output->geometry.height - end->height) / 2; struct kywc_box *start_geometry = view->base.minimized ? start : end; scale_calc_view_box(&view->base, start_geometry, start); return; } if (view->minimized_geometry.panel_surface) { scale_end_geometry_from_panel_surface(view, end); } else if (kywc_box_not_empty(&view->startup_geometry)) { *end = view->startup_geometry; } else { end->x = view->base.geometry.x + view->base.geometry.width / 2; end->y = view->base.geometry.y + view->base.geometry.height / 2; } if (view->parent && !view->minimized_geometry.panel_surface) { scale_end_geometry_from_parent_panel(view, end); } struct kywc_box original_start = *start; struct kywc_box *start_geometry = view->base.minimized ? start : end; scale_calc_view_box(&view->base, start_geometry, start); struct kywc_box *end_geometry = view->base.minimized ? end : &original_start; scale_calc_view_box(&view->base, end_geometry, end); } static void scale_handle_transform_destroy(struct wl_listener *listener, void *data) { struct scale_entity *scale_entity = wl_container_of(listener, scale_entity, destroy); wl_list_remove(&scale_entity->view_destroy.link); wl_list_remove(&scale_entity->destroy.link); free(scale_entity); } static void scale_handle_view_destroy(struct wl_listener *listener, void *data) { struct scale_entity *scale_entity = wl_container_of(listener, scale_entity, view_destroy); transform_destroy(scale_entity->transform); } static void scale_update_transform_options(struct transform_effect *effect, struct transform *transform, struct ky_scene_node *node, struct transform_options *options, struct animation_data *current, void *data) { struct view *view = options->view; if (!view || view->base.minimized) { return; } if (options->geometry_type == TRANSFORM_GEOMETRY_VIEW_BOX) { scale_calc_view_box(&view->base, &view->base.geometry, &options->end.geometry); } } static void scale_effect_destroy(struct transform_effect *effect, void *data) { struct scale_effect *scale_effect = data; free(scale_effect); scale_effect = NULL; } struct transform_effect_interface scale_impl = { .update_transform_options = scale_update_transform_options, .destroy = scale_effect_destroy, }; bool scale_effect_create(struct effect_manager *manager) { scale_effect = calloc(1, sizeof(*scale_effect)); if (!scale_effect) { return false; } uint32_t support_actions = EFFECT_ACTION_RESIZE | EFFECT_ACTION_MAXIMIZE | EFFECT_ACTION_MAXIMIZE_RESTORE | EFFECT_ACTION_MINIMIZE | EFFECT_ACTION_MINIMIZE_RESTORE; scale_effect->effect = transform_effect_create(manager, &scale_impl, support_actions, "scale", 5, scale_effect); if (!scale_effect->effect) { free(scale_effect); return false; } return true; } bool view_add_scale_effect(struct view *view, enum scale_action action) { if (!scale_effect) { return false; } /* do not display maximize effect when view minimized */ if (view->base.minimized && action == SCALE_MAXIMIZE) { return false; } struct transform_options options = { .view = view, .start_time = current_time_msec(), .geometry_type = TRANSFORM_GEOMETRY_VIEW_BOX, }; if (action == SCALE_MAXIMIZE || action == SCALE_FULLSCREEN) { options.start.alpha = 1.0; options.end.alpha = 1.0; options.animations.geometry = animation_manager_get(ANIMATION_TYPE_EASE); options.duration = view->base.maximized || view->base.fullscreen ? 300 : 260; if (action == SCALE_MAXIMIZE || (action == SCALE_FULLSCREEN && !view->base.fullscreen)) { scale_calc_view_box(&view->base, &view->pending.geometry, &options.start.geometry); scale_calc_view_box(&view->base, &view->base.geometry, &options.end.geometry); } else { /** * When in fullscreen mode, no need to calculate ssd and shadows. When exiting * fullscreen mode, ssd and shadows need to be calculated */ options.start.geometry = view->pending.geometry; options.end.geometry = view->base.geometry; } } else if (action == SCALE_RESIZE) { options.start.alpha = 1.0; options.end.alpha = 1.0; options.duration = 260; options.animations.geometry = animation_manager_get(ANIMATION_TYPE_EASE); scale_calc_view_box(&view->base, &view->pending.geometry, &options.end.geometry); scale_calc_view_box(&view->base, kywc_box_not_empty(&view->tile_start_geometry) ? &view->tile_start_geometry : &view->base.geometry, &options.start.geometry); } else if (action == SCALE_MINIMIZE) { if (view->base.minimized) { options.duration = 260; options.start.alpha = 1.0; options.end.alpha = 0; options.animations.geometry = view->minimized_when_show_desktop ? animation_manager_get(ANIMATION_TYPE_33_0_100_75) : animation_manager_get(ANIMATION_TYPE_0_40_20_100); options.animations.alpha = animation_manager_get(ANIMATION_TYPE_33_0_100_75); } else { options.start.alpha = 0; options.end.alpha = 1.0; options.duration = 300; options.animations.geometry = animation_manager_get(ANIMATION_TYPE_30_15_10_100); options.animations.alpha = animation_manager_get(ANIMATION_TYPE_0_40_20_100); } scale_calc_minimize_start_and_end_geometry(view, &options.start.geometry, &options.end.geometry); } struct scale_entity *scale_entity = calloc(1, sizeof(*scale_entity)); if (!scale_entity) { return false; } struct effect_render_data render_data = { 0 }; struct ky_scene_add_effect_event event = { .render_data = &render_data, }; struct effect_entity *entity = node_add_custom_transform_effect(&view->tree->node, scale_effect->effect, &options, &event); if (!entity) { free(scale_entity); return false; } if (kywc_box_not_empty(&view->tile_start_geometry) && render_data.is_valid) { scale_calc_view_box(&view->base, &view->tile_start_geometry, &options.start.geometry); } if (!entity->user_data) { scale_entity->transform = transform_effect_create_transform( scale_effect->effect, entity, &options, &view->tree->node, scale_entity); if (!scale_entity->transform) { effect_entity_destroy(entity); free(scale_entity); return false; } } else { scale_entity->transform = entity->user_data; transform_set_user_data(entity->user_data, scale_entity); } scale_entity->view_destroy.notify = scale_handle_view_destroy; wl_signal_add(&view->base.events.destroy, &scale_entity->view_destroy); scale_entity->destroy.notify = scale_handle_transform_destroy; transform_add_destroy_listener(scale_entity->transform, &scale_entity->destroy); return true; } kylin-wayland-compositor/src/effect/wallpaper.c0000664000175000017500000002233415160460057020645 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "effect_p.h" #include "output.h" #include "theme.h" #include "util/color.h" #include "view/view.h" struct wallpaper_output { struct wl_list link; struct wallpaper_effect *effect; struct output *output; struct wl_listener geometry; struct wl_listener disable; struct ky_scene_buffer *buffer; struct ky_scene_rect *rect; }; struct wallpaper_effect { struct effect *effect; struct wl_listener enable; struct wl_listener disable; struct wl_listener destroy; struct wl_list outputs; struct wl_listener new_enabled_output; struct wl_listener background_update; }; static void wallpaper_output_destroy(struct wallpaper_output *output) { wl_list_remove(&output->link); wl_list_remove(&output->geometry.link); wl_list_remove(&output->disable.link); if (output->buffer) { ky_scene_node_destroy(&output->buffer->node); } else if (output->rect) { ky_scene_node_destroy(&output->rect->node); } free(output); } static void wallpaper_output_update_buffer(struct wallpaper_output *output) { struct kywc_box *geo = &output->output->geometry; struct wlr_fbox dst = { geo->x, geo->y, geo->width, geo->height }; struct wlr_fbox src = { 0 }; int width, height; output_layout_get_size(&width, &height); bool repeated = theme_manager_get_background_box(&dst, &src, width, height); ky_scene_buffer_set_repeated(output->buffer, repeated); ky_scene_buffer_set_source_box(output->buffer, &src); ky_scene_node_set_position(&output->buffer->node, dst.x, dst.y); ky_scene_buffer_set_dest_size(output->buffer, dst.width, dst.height); pixman_region32_t region; pixman_region32_init_rect(®ion, 0, 0, dst.width, dst.height); ky_scene_buffer_set_opaque_region(output->buffer, ®ion); pixman_region32_fini(®ion); } static void wallpaper_output_update_rect(struct wallpaper_output *output) { struct kywc_box *geo = &output->output->geometry; ky_scene_node_set_position(&output->rect->node, geo->x, geo->y); ky_scene_rect_set_size(output->rect, geo->width, geo->height); } static void output_handle_geometry(struct wl_listener *listener, void *data) { struct wallpaper_output *output = wl_container_of(listener, output, geometry); if (output->buffer) { wallpaper_output_update_buffer(output); } else if (output->rect) { wallpaper_output_update_rect(output); } } static void output_handle_disable(struct wl_listener *listener, void *data) { struct wallpaper_output *output = wl_container_of(listener, output, disable); struct wallpaper_effect *effect = output->effect; wallpaper_output_destroy(output); // update all outputs if BACKGROUND_OPTION_SPANNED wl_list_for_each(output, &effect->outputs, link) { if (output->buffer) { wallpaper_output_update_buffer(output); } } } static void wallpaper_output_update_node(struct wallpaper_output *output, struct wlr_buffer *buffer, int32_t color) { if (buffer) { if (output->rect) { ky_scene_node_destroy(&output->rect->node); output->rect = NULL; } if (!output->buffer) { struct view_layer *layer = view_manager_get_layer(LAYER_DESKTOP, false); output->buffer = ky_scene_buffer_create(layer->tree, buffer); ky_scene_node_set_input_bypassed(&output->buffer->node, true); ky_scene_node_lower_to_bottom(&output->buffer->node); } else { ky_scene_buffer_set_buffer(output->buffer, buffer); } } else if (color >= 0) { if (output->buffer) { ky_scene_node_destroy(&output->buffer->node); output->buffer = NULL; } float solid[4]; color_uint24_to_float(solid, color); if (!output->rect) { struct view_layer *layer = view_manager_get_layer(LAYER_DESKTOP, false); output->rect = ky_scene_rect_create(layer->tree, 1, 1, solid); ky_scene_node_set_input_bypassed(&output->rect->node, true); ky_scene_node_lower_to_bottom(&output->rect->node); } else { ky_scene_rect_set_color(output->rect, solid); } } } static void wallpaper_output_create(struct wallpaper_effect *effect, struct kywc_output *kywc_output) { struct wallpaper_output *output = calloc(1, sizeof(*output)); if (!output) { return; } output->effect = effect; wl_list_insert(&effect->outputs, &output->link); output->output = output_from_kywc_output(kywc_output); output->geometry.notify = output_handle_geometry; wl_signal_add(&output->output->events.geometry, &output->geometry); output->disable.notify = output_handle_disable; wl_signal_add(&output->output->events.disable, &output->disable); int32_t color = -1; struct wlr_buffer *buffer = theme_manager_get_background(&color); if (!buffer && color < 0) { return; } wallpaper_output_update_node(output, buffer, color); if (output->buffer) { // update all outputs if BACKGROUND_OPTION_SPANNED wl_list_for_each(output, &effect->outputs, link) { wallpaper_output_update_buffer(output); } } else if (output->rect) { wallpaper_output_update_rect(output); } } static void handle_new_enabled_output(struct wl_listener *listener, void *data) { struct wallpaper_effect *effect = wl_container_of(listener, effect, new_enabled_output); struct kywc_output *output = data; wallpaper_output_create(effect, output); } static void handle_background_update(struct wl_listener *listener, void *data) { int32_t color = -1; struct wlr_buffer *buffer = theme_manager_get_background(&color); if (!buffer && color < 0) { return; } struct wallpaper_effect *effect = wl_container_of(listener, effect, background_update); struct wallpaper_output *output; wl_list_for_each(output, &effect->outputs, link) { wallpaper_output_update_node(output, buffer, color); if (output->buffer) { wallpaper_output_update_buffer(output); } else if (output->rect) { wallpaper_output_update_rect(output); } } } static bool create_request(struct kywc_output *kywc_output, int index, void *data) { struct wallpaper_effect *effect = data; wallpaper_output_create(effect, kywc_output); return false; } static void handle_effect_enable(struct wl_listener *listener, void *data) { struct wallpaper_effect *effect = wl_container_of(listener, effect, enable); /* get current enabled output */ output_manager_for_each_output(create_request, true, effect); output_manager_add_new_enabled_listener(&effect->new_enabled_output); theme_manager_add_background_update_listener(&effect->background_update); } static void handle_effect_disable(struct wl_listener *listener, void *data) { struct wallpaper_effect *effect = wl_container_of(listener, effect, disable); wl_list_remove(&effect->new_enabled_output.link); wl_list_init(&effect->new_enabled_output.link); wl_list_remove(&effect->background_update.link); wl_list_init(&effect->background_update.link); struct wallpaper_output *output, *tmp; wl_list_for_each_safe(output, tmp, &effect->outputs, link) { wallpaper_output_destroy(output); } } static void handle_effect_destroy(struct wl_listener *listener, void *data) { struct wallpaper_effect *effect = wl_container_of(listener, effect, destroy); wl_list_remove(&effect->new_enabled_output.link); wl_list_remove(&effect->background_update.link); wl_list_remove(&effect->destroy.link); wl_list_remove(&effect->enable.link); wl_list_remove(&effect->disable.link); free(effect); } static bool handle_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } return false; } static const struct effect_interface wallpaper_effect_impl = { .configure = handle_effect_configure, }; bool wallpaper_effect_create(struct effect_manager *manager) { struct wallpaper_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } effect->effect = effect_create("wallpaper", 0, false, &wallpaper_effect_impl, effect); if (!effect->effect) { free(effect); return false; } effect->enable.notify = handle_effect_enable; wl_signal_add(&effect->effect->events.enable, &effect->enable); effect->disable.notify = handle_effect_disable; wl_signal_add(&effect->effect->events.disable, &effect->disable); effect->destroy.notify = handle_effect_destroy; wl_signal_add(&effect->effect->events.destroy, &effect->destroy); wl_list_init(&effect->outputs); effect->new_enabled_output.notify = handle_new_enabled_output; wl_list_init(&effect->new_enabled_output.link); effect->background_update.notify = handle_background_update; wl_list_init(&effect->background_update.link); if (effect->effect->enabled) { handle_effect_enable(&effect->enable, NULL); } return true; } kylin-wayland-compositor/src/effect/capture.c0000664000175000017500000007533515160461067020334 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "effect/capture.h" #include "effect_p.h" #include "output.h" #include "painter.h" #include "render/renderer.h" #include "security.h" #include "server.h" #include "util/wayland.h" enum capture_type { CAPTURE_TYPE_OUTPUT = 0, CAPTURE_TYPE_AREA, CAPTURE_TYPE_FULLSCREEN, }; struct capture { struct wl_list link; struct capture_buffer *buffer; // xxx_capture->base struct { struct wl_signal update; // capture_update_event struct wl_signal destroy; } events; bool force_update, wants_update; }; struct capture_buffer { struct wlr_buffer *buffer; struct wl_list link; struct wl_list captures; // capture->link struct wl_list requests; // request->link enum capture_type type; uint32_t options; union { struct output *output; struct wlr_box area; }; float scale; bool was_damaged, buffer_changed; bool need_destroy, can_destroy; }; struct capture_request { struct wl_list link; struct wl_list output_link; struct capture_buffer *buffer; struct capture_output *output; struct wlr_fbox src_box; struct wlr_box dst_box; bool blit_done, cursor_locked; }; struct capture_output { struct wl_list link; struct wl_list requests; // capture_request->output_link; struct output *output; struct wl_listener commit; struct wl_listener geometry; struct wl_listener disable; }; struct capture_manager { struct wl_list buffers; // capture_buffer->link struct wl_list outputs; // capture_output->link struct wl_listener new_enabled_output; struct server *server; struct wl_listener destroy; }; static struct capture_manager *manager = NULL; static struct capture_request *capture_request_create(struct output *output, struct capture_buffer *buffer); static void capture_request_destroy(struct capture_request *request); static bool capture_buffer_clear(struct capture_buffer *buffer) { struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(manager->server->renderer, buffer->buffer, NULL); if (!pass) { return false; } wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ .color = { 0, 0, 0, 0 }, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, }); wlr_render_pass_submit(pass); /* mark buffer is damaged */ buffer->was_damaged = true; return true; } static struct wlr_buffer *capture_buffer_allocate(struct capture_buffer *capture_buffer) { assert(!wl_list_empty(&capture_buffer->requests)); /* calc buffer size used in capture_buffer */ int width = 0, height = 0; if (capture_buffer->type == CAPTURE_TYPE_OUTPUT || capture_buffer->type == CAPTURE_TYPE_FULLSCREEN) { pixman_region32_t region; pixman_region32_init(®ion); struct capture_request *request; wl_list_for_each(request, &capture_buffer->requests, link) { pixman_region32_union_rect(®ion, ®ion, request->dst_box.x, request->dst_box.y, request->dst_box.width, request->dst_box.height); } width = region.extents.x2 - region.extents.x1; height = region.extents.y2 - region.extents.y1; pixman_region32_fini(®ion); } else if (capture_buffer->type == CAPTURE_TYPE_AREA) { width = capture_buffer->area.width; height = capture_buffer->area.height; } bool need_create = !capture_buffer->buffer || (capture_buffer->buffer->width != width || capture_buffer->buffer->height != height); if (!need_create) { return capture_buffer->buffer; } struct wlr_buffer *buffer = ky_renderer_create_buffer( manager->server->renderer, manager->server->allocator, width, height, DRM_FORMAT_ARGB8888, capture_buffer->options & CAPTURE_NEED_SINGLE_PLANE); if (!buffer) { kywc_log(KYWC_ERROR, "Failed to create wlr buffer"); return NULL; } wlr_buffer_drop(capture_buffer->buffer); capture_buffer->buffer = buffer; capture_buffer->buffer_changed = true; /* re-blit all requests */ struct capture_request *request; wl_list_for_each(request, &capture_buffer->requests, link) { request->blit_done = false; } capture_buffer_clear(capture_buffer); return buffer; } static void fbox_intersection(struct wlr_fbox *dest, const struct wlr_fbox *box_a, const struct wlr_fbox *box_b) { int x1 = fmax(box_a->x, box_b->x); int y1 = fmax(box_a->y, box_b->y); int x2 = fmin(box_a->x + box_a->width, box_b->x + box_b->width); int y2 = fmin(box_a->y + box_a->height, box_b->y + box_b->height); dest->x = x1; dest->y = y1; dest->width = x2 - x1; dest->height = y2 - y1; } static bool capture_buffer_calc_box(struct capture_buffer *buffer, struct output *output, struct wlr_box *dst, struct wlr_fbox *src) { float scale = buffer->options & CAPTURE_NEED_UNSCALED ? output_manager_get_max_scale() : 1.0; buffer->scale = scale; struct kywc_box *geo = &output->geometry; *dst = (struct wlr_box){ 0 }; *src = (struct wlr_fbox){ 0 }; if (buffer->type == CAPTURE_TYPE_OUTPUT) { /* calc the dst box in request */ dst->width = geo->width * scale; dst->height = geo->height * scale; } else if (buffer->type == CAPTURE_TYPE_AREA) { *dst = (struct wlr_box){ geo->x * scale, geo->y * scale, geo->width * scale, geo->height * scale }; if (!wlr_box_intersection(dst, dst, &buffer->area)) { return false; } float output_scale = output->wlr_output->scale; src->x = (dst->x - geo->x * scale) / scale * output_scale; src->y = (dst->y - geo->y * scale) / scale * output_scale; src->width = dst->width / scale * output_scale; src->height = dst->height / scale * output_scale; int width, height; /* translate to buffer coord, otherwise assert failed in wlr_render_pass_add_texture */ wlr_output_transformed_resolution(output->wlr_output, &width, &height); wlr_fbox_transform(src, src, wlr_output_transform_invert(output->wlr_output->transform), width, height); if (floor(output_scale) != output_scale) { int32_t w = output->wlr_output->width; int32_t h = output->wlr_output->height; fbox_intersection(src, src, &(struct wlr_fbox){ 0, 0, w, h }); } dst->x -= buffer->area.x; dst->y -= buffer->area.y; } else if (buffer->type == CAPTURE_TYPE_FULLSCREEN) { /* assume that we always have zero coord */ *dst = (struct wlr_box){ geo->x * scale, geo->y * scale, geo->width * scale, geo->height * scale }; } return true; } static void capture_manager_schedule_frame(void) { struct capture_output *output; wl_list_for_each(output, &manager->outputs, link) { output_schedule_frame(output->output->wlr_output); } } static void capture_buffer_destroy(struct capture_buffer *buffer) { /* don't destroy if still have captures */ if (!buffer->need_destroy && !wl_list_empty(&buffer->captures)) { return; } /* mark need_destroy if cannot be destroyed current */ if (!buffer->can_destroy) { buffer->need_destroy = true; return; } struct capture_request *request, *request_tmp; wl_list_for_each_safe(request, request_tmp, &buffer->requests, link) { request->buffer = NULL; capture_request_destroy(request); } /* force destroy all captures */ struct capture *capture, *tmp; wl_list_for_each_safe(capture, tmp, &buffer->captures, link) { capture->buffer = NULL; capture_destroy(capture); } if (buffer->buffer) { wlr_buffer_drop(buffer->buffer); } wl_list_remove(&buffer->link); free(buffer); } static void capture_output_destroy(struct capture_output *output) { wl_list_remove(&output->commit.link); wl_list_remove(&output->geometry.link); wl_list_remove(&output->disable.link); wl_list_remove(&output->link); struct capture_request *request, *tmp; wl_list_for_each_safe(request, tmp, &output->requests, output_link) { capture_request_destroy(request); } free(output); } static void capture_request_destroy(struct capture_request *request) { wl_list_remove(&request->link); wl_list_remove(&request->output_link); /* destroy capture_buffer if no requests */ struct capture_buffer *buffer = request->buffer; if (buffer) { if (wl_list_empty(&buffer->requests)) { buffer->need_destroy = true; capture_buffer_destroy(buffer); } else if (buffer->type == CAPTURE_TYPE_AREA) { /* clear the buffer when area type */ capture_buffer_clear(buffer); } } struct wlr_output *wlr_output = request->output->output->wlr_output; wlr_output_lock_attach_render(wlr_output, false); if (request->cursor_locked) { wlr_output_lock_software_cursors(wlr_output, false); } free(request); } static void capture_output_handle_disable(struct wl_listener *listener, void *data) { struct capture_output *capture_output = wl_container_of(listener, capture_output, disable); capture_output_destroy(capture_output); /* re-allocate buffer */ struct capture_buffer *buffer; wl_list_for_each(buffer, &manager->buffers, link) { capture_buffer_allocate(buffer); } capture_manager_schedule_frame(); } static void capture_output_handle_geometry(struct wl_listener *listener, void *data) { struct capture_output *capture_output = wl_container_of(listener, capture_output, geometry); /* update requests src and dst box if output geometry changed */ struct capture_request *request, *tmp; wl_list_for_each_safe(request, tmp, &capture_output->requests, output_link) { if (capture_buffer_calc_box(request->buffer, capture_output->output, &request->dst_box, &request->src_box)) { capture_buffer_allocate(request->buffer); } else { capture_request_destroy(request); } } /* update capture_buffer with area type */ struct capture_buffer *buffer; wl_list_for_each(buffer, &manager->buffers, link) { if (buffer->type != CAPTURE_TYPE_AREA) { continue; } bool request_found = false; wl_list_for_each(request, &buffer->requests, link) { if (request->output == capture_output) { request_found = true; break; } } if (!request_found) { capture_request_create(capture_output->output, buffer); /* no need to re-allocate buffer when area type */ } } capture_manager_schedule_frame(); } static bool capture_request_is_security_touched(struct capture_request *request, pixman_region32_t *clip, pixman_region32_t *clear) { struct capture_buffer *buffer = request->buffer; struct output *output = request->output->output; if (buffer->type == CAPTURE_TYPE_AREA) { pixman_region32_init_rect(clip, request->dst_box.x + buffer->area.x, request->dst_box.y + buffer->area.y, request->dst_box.width, request->dst_box.height); } else { pixman_region32_init_rect(clip, output->geometry.x, output->geometry.y, output->geometry.width, output->geometry.height); } pixman_region32_init_rect(clear, 0, 0, buffer->buffer->width, buffer->buffer->height); bool has_security = security_check_output(output, clip); if (!has_security) { return false; } /* output capture is not allowed */ if (has_security && !pixman_region32_not_empty(clip)) { return true; } switch (buffer->type) { case CAPTURE_TYPE_OUTPUT: pixman_region32_translate(clip, -output->geometry.x, -output->geometry.y); // fallthrough to fullscreen case CAPTURE_TYPE_FULLSCREEN: wlr_region_scale(clip, clip, buffer->scale); break; case CAPTURE_TYPE_AREA: wlr_region_scale(clip, clip, buffer->scale); pixman_region32_translate(clip, -buffer->area.x, -buffer->area.y); break; } pixman_region32_intersect(clip, clip, clear); pixman_region32_subtract(clear, clear, clip); return true; } static bool capture_request_do_blit(struct capture_request *request, struct wlr_buffer *src) { struct capture_buffer *buffer = request->buffer; struct wlr_output *wlr_output = request->output->output->wlr_output; struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(wlr_output->renderer, buffer->buffer, NULL); if (!render_pass) { return false; } pixman_region32_t clip, clear; bool touched = capture_request_is_security_touched(request, &clip, &clear); // clear the buffer if security touched if (touched) { wlr_render_pass_add_rect(render_pass, &(struct wlr_render_rect_options){ .color = { 0, 0, 0, 0 }, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, .clip = &clear, }); } if (!touched || pixman_region32_not_empty(&clip)) { struct wlr_texture *src_tex = wlr_texture_from_buffer(wlr_output->renderer, src); struct wlr_render_texture_options options = { .texture = src_tex, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, .src_box = request->src_box, .dst_box = request->dst_box, .clip = touched ? &clip : NULL, .transform = wlr_output_transform_invert(wlr_output->transform), }; wlr_render_pass_add_texture(render_pass, &options); wlr_texture_destroy(src_tex); } wlr_render_pass_submit(render_pass); pixman_region32_fini(&clip); pixman_region32_fini(&clear); kywc_log(KYWC_DEBUG, "Capture output %s copy (%f, %f) %f x %f to (%d, %d) %d x %d", wlr_output->name, request->src_box.x, request->src_box.y, request->src_box.width, request->src_box.height, request->dst_box.x, request->dst_box.y, request->dst_box.width, request->dst_box.height); return true; } static void capture_request_mark_done(struct capture_request *request) { struct capture_buffer *buffer = request->buffer; request->blit_done = true; struct capture_request *req; wl_list_for_each(req, &buffer->requests, link) { if (!req->blit_done) { return; } } struct capture *capture, *tmp; if (!buffer->was_damaged) { /* must have a buffer because was_damaged == false */ struct capture_update_event event = { .buffer = buffer->buffer, .buffer_changed = true, }; buffer->can_destroy = false; wl_list_for_each_safe(capture, tmp, &buffer->captures, link) { if (capture->wants_update && capture->force_update) { capture->force_update = false; wl_signal_emit_oneshot(&capture->events.update, &event); } } /* capture may need be destroyed in update */ buffer->can_destroy = true; capture_buffer_destroy(buffer); return; } /* now all requests are done, emit update signal for all captures */ struct capture_update_event event = { .buffer = buffer->buffer }; buffer->can_destroy = false; wl_list_for_each_safe(capture, tmp, &buffer->captures, link) { if (capture->wants_update) { event.buffer_changed = buffer->buffer_changed || capture->force_update; capture->force_update = false; wl_signal_emit_oneshot(&capture->events.update, &event); } else { capture->force_update |= buffer->buffer_changed; } } wl_list_for_each(req, &buffer->requests, link) { req->blit_done = false; } buffer->buffer_changed = false; buffer->was_damaged = false; buffer->can_destroy = true; capture_buffer_destroy(buffer); } static bool capture_buffer_need_update(struct capture_buffer *buffer) { struct capture *capture; wl_list_for_each(capture, &buffer->captures, link) { if (capture->wants_update) { return true; } } return false; } static void capture_buffer_update_damage(struct capture_request *request, bool has_damage, const pixman_region32_t *damage) { struct capture_buffer *buffer = request->buffer; if (!has_damage || buffer->was_damaged) { return; } if (buffer->type != CAPTURE_TYPE_AREA) { buffer->was_damaged = true; return; } /* check damage region and src_box */ pixman_region32_t box; pixman_region32_init_rect(&box, request->src_box.x, request->src_box.y, request->src_box.width, request->src_box.height); pixman_region32_intersect(&box, &box, damage); if (pixman_region32_not_empty(&box)) { buffer->was_damaged = true; } pixman_region32_fini(&box); } static void capture_output_handle_commit(struct wl_listener *listener, void *data) { struct capture_output *capture_output = wl_container_of(listener, capture_output, commit); struct wlr_output_event_commit *event = data; if (wl_list_empty(&capture_output->requests)) { return; } if (!(event->state->committed & WLR_OUTPUT_STATE_BUFFER)) { return; } bool has_damage = event->state->committed & WLR_OUTPUT_STATE_DAMAGE && pixman_region32_not_empty(&event->state->damage); /* do blit for every request */ struct capture_buffer *buffer; struct capture_request *request, *tmp; wl_list_for_each_safe(request, tmp, &capture_output->requests, output_link) { buffer = request->buffer; capture_buffer_update_damage(request, has_damage, &event->state->damage); /* skip this request if all captures don't want update */ if (!capture_buffer_need_update(buffer)) { continue; } if (!buffer->was_damaged) { capture_request_mark_done(request); continue; } if (capture_request_do_blit(request, event->state->buffer)) { /* mark this request in capture_buffer is done */ capture_request_mark_done(request); } else { /* destroy capture_buffer */ buffer->need_destroy = true; capture_buffer_destroy(buffer); } } } static struct capture_output *capture_output_get(struct output *output) { struct capture_output *capture_output; wl_list_for_each(capture_output, &manager->outputs, link) { if (capture_output->output == output) { return capture_output; } } return NULL; } static struct capture_output *capture_output_create(struct output *output) { struct capture_output *capture_output = calloc(1, sizeof(*capture_output)); if (!capture_output) { return NULL; } wl_list_init(&capture_output->requests); wl_list_insert(&manager->outputs, &capture_output->link); capture_output->output = output; capture_output->commit.notify = capture_output_handle_commit; wl_signal_add(&output->wlr_output->events.commit, &capture_output->commit); capture_output->geometry.notify = capture_output_handle_geometry; wl_signal_add(&output->events.geometry, &capture_output->geometry); capture_output->disable.notify = capture_output_handle_disable; wl_signal_add(&output->events.disable, &capture_output->disable); return capture_output; } static struct capture_request *capture_request_create(struct output *output, struct capture_buffer *buffer) { /* create capture requests according to capture type */ struct wlr_fbox fbox; struct wlr_box box; if (!capture_buffer_calc_box(buffer, output, &box, &fbox)) { return NULL; } struct capture_output *capture_output = capture_output_get(output); if (!capture_output) { return NULL; } struct capture_request *request = calloc(1, sizeof(*request)); if (!request) { return NULL; } request->buffer = buffer; request->output = capture_output; request->src_box = fbox; request->dst_box = box; request->cursor_locked = buffer->options & CAPTURE_NEED_CURSOR; wl_list_insert(&buffer->requests, &request->link); wl_list_insert(&capture_output->requests, &request->output_link); if (request->cursor_locked) { wlr_output_lock_software_cursors(output->wlr_output, true); } /* Locks the output to only use rendering instead of direct scan-out */ wlr_output_lock_attach_render(output->wlr_output, true); return request; } static bool create_request(struct kywc_output *kywc_output, int index, void *data) { struct output *output = output_from_kywc_output(kywc_output); struct capture_buffer *buffer = data; capture_request_create(output, buffer); return false; } static struct capture_buffer *capture_buffer_get_or_create(enum capture_type type, uint32_t options, struct output *output, struct wlr_box *area) { struct capture_buffer *buffer; wl_list_for_each(buffer, &manager->buffers, link) { /* skip cursor option check */ if (buffer->type != type || (buffer->options ^ options) & (CAPTURE_NEED_UNSCALED | CAPTURE_NEED_SINGLE_PLANE)) { continue; } if (type == CAPTURE_TYPE_FULLSCREEN) { return buffer; } else if (type == CAPTURE_TYPE_OUTPUT && buffer->output == output) { return buffer; } else if (type == CAPTURE_TYPE_AREA && wlr_box_equal(&buffer->area, area)) { return buffer; } } buffer = calloc(1, sizeof(*buffer)); if (!buffer) { return NULL; } buffer->type = type; buffer->options = options; buffer->was_damaged = true; buffer->can_destroy = true; wl_list_init(&buffer->captures); wl_list_init(&buffer->requests); if (type == CAPTURE_TYPE_OUTPUT) { buffer->output = output; capture_request_create(buffer->output, buffer); } else if (type == CAPTURE_TYPE_AREA) { buffer->area = *area; output_manager_for_each_output(create_request, true, buffer); } else if (type == CAPTURE_TYPE_FULLSCREEN) { output_manager_for_each_output(create_request, true, buffer); } if (wl_list_empty(&buffer->requests) || !capture_buffer_allocate(buffer)) { free(buffer); return NULL; } wl_list_insert(&manager->buffers, &buffer->link); return buffer; } struct capture *capture_create_from_output(struct output *output, uint32_t options) { if (!manager || !output->scene_output) { return NULL; } struct capture *capture = calloc(1, sizeof(*capture)); if (!capture) { return NULL; } struct capture_buffer *buffer = capture_buffer_get_or_create(CAPTURE_TYPE_OUTPUT, options, output, NULL); if (!buffer) { free(capture); return NULL; } capture->buffer = buffer; wl_list_insert(&buffer->captures, &capture->link); wl_signal_init(&capture->events.update); wl_signal_init(&capture->events.destroy); capture->force_update = capture->wants_update = true; /* buffer update is needed */ capture_manager_schedule_frame(); return capture; } struct capture *capture_create_from_area(struct wlr_box *rect, uint32_t options) { if (!manager || wlr_box_empty(rect)) { return NULL; } struct capture *capture = calloc(1, sizeof(*capture)); if (!capture) { return NULL; } struct capture_buffer *buffer = capture_buffer_get_or_create(CAPTURE_TYPE_AREA, options, NULL, rect); if (!buffer) { free(capture); return NULL; } capture->buffer = buffer; wl_list_insert(&buffer->captures, &capture->link); wl_signal_init(&capture->events.update); wl_signal_init(&capture->events.destroy); capture->force_update = capture->wants_update = true; /* buffer update is needed */ capture_manager_schedule_frame(); return capture; } struct capture *capture_create_from_fullscreen(uint32_t options) { if (!manager) { return NULL; } struct capture *capture = calloc(1, sizeof(*capture)); if (!capture) { return NULL; } struct capture_buffer *buffer = capture_buffer_get_or_create(CAPTURE_TYPE_FULLSCREEN, options, NULL, NULL); if (!buffer) { free(capture); return NULL; } capture->buffer = buffer; wl_list_insert(&buffer->captures, &capture->link); wl_signal_init(&capture->events.update); wl_signal_init(&capture->events.destroy); capture->force_update = capture->wants_update = true; /* buffer update is needed */ capture_manager_schedule_frame(); return capture; } void capture_destroy(struct capture *capture) { if (!capture) { return; } wl_signal_emit_mutable(&capture->events.destroy, NULL); assert(wl_list_empty(&capture->events.destroy.listener_list)); assert(wl_list_empty(&capture->events.update.listener_list)); wl_list_remove(&capture->link); /* capture_buffer may not be destroyed caused by can_destroy == false */ if (capture->buffer) { capture_buffer_destroy(capture->buffer); } free(capture); } void capture_add_update_listener(struct capture *capture, struct wl_listener *listener) { assert(wl_list_empty(&capture->events.update.listener_list)); wl_signal_add(&capture->events.update, listener); } void capture_add_destroy_listener(struct capture *capture, struct wl_listener *listener) { assert(wl_list_empty(&capture->events.destroy.listener_list)); wl_signal_add(&capture->events.destroy, listener); } void capture_mark_wants_update(struct capture *capture, bool wants, bool force) { if (capture->wants_update == wants) { return; } capture->wants_update = wants; /* should send update if buffer was damaged */ if (wants) { capture->force_update |= force; capture_manager_schedule_frame(); } } /** * helpers to read buffer, and write to png file in another thread. */ void capture_read_buffer(struct wlr_buffer *buffer, uint32_t format, uint32_t stride, struct wlr_box *box, void *data) { struct wlr_texture *texture = wlr_texture_from_buffer(manager->server->renderer, buffer); if (!texture) { kywc_log(KYWC_DEBUG, "Failed to grab a texture from a buffer during capture"); return; } wlr_texture_read_pixels( texture, &(struct wlr_texture_read_pixels_options){ .data = data, .format = format, .stride = stride, .src_box = *box }); wlr_texture_destroy(texture); } struct capture_data { struct wlr_buffer *buffer; void (*done)(const char *path, void *data); char *path; void *user_data; }; static void capture_write_image(struct wlr_buffer *buffer, const char *path, void (*done)(const char *path, void *data), void *user_data) { painter_buffer_write_to_file(buffer, path); wlr_buffer_drop(buffer); if (done) { done(path, user_data); } } static void write_image(void *job, void *gdata, int index) { kywc_log(KYWC_DEBUG, "%s: in thread %d", __func__, index); struct capture_data *data = job; capture_write_image(data->buffer, data->path, data->done, data->user_data); free(data->path); free(data); } void capture_write_file(struct wlr_buffer *buffer, int width, int height, const char *path, void (*done)(const char *path, void *data), void *user_data) { uint32_t format; size_t stride; void *dst_ptr; struct wlr_buffer *dst_buf = painter_create_buffer(width, height, 1.0); wlr_buffer_begin_data_ptr_access(dst_buf, WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &dst_ptr, &format, &stride); capture_read_buffer(buffer, format, stride, &(struct wlr_box){ 0, 0, dst_buf->width, dst_buf->height }, dst_ptr); wlr_buffer_end_data_ptr_access(dst_buf); kywc_log(KYWC_DEBUG, "Capture copy buffer to memory"); struct capture_data *data = malloc(sizeof(*data)); if (!data) { capture_write_image(dst_buf, path, done, user_data); return; } data->buffer = dst_buf; data->path = strdup(path); data->done = done; data->user_data = user_data; if (!queue_add_job(manager->server->queue, data, NULL, write_image, NULL)) { free(data->path); free(data); capture_write_image(dst_buf, path, done, user_data); } } static void handle_new_enabled_output(struct wl_listener *listener, void *data) { struct kywc_output *kywc_output = data; struct output *output = output_from_kywc_output(kywc_output); if (!capture_output_create(output)) { return; } struct capture_buffer *buffer, *tmp; wl_list_for_each_safe(buffer, tmp, &manager->buffers, link) { if (buffer->type == CAPTURE_TYPE_OUTPUT) { continue; } if (capture_request_create(output, buffer)) { capture_buffer_allocate(buffer); } } /* blit done is reset, must blit again */ capture_manager_schedule_frame(); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->destroy.link); wl_list_remove(&manager->new_enabled_output.link); free(manager); manager = NULL; } bool capture_manager_create(struct server *server) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } wl_list_init(&manager->buffers); wl_list_init(&manager->outputs); manager->new_enabled_output.notify = handle_new_enabled_output; output_manager_add_new_enabled_listener(&manager->new_enabled_output); manager->server = server; manager->destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->destroy); return true; } kylin-wayland-compositor/src/effect/config.c0000664000175000017500000002362315160461067020127 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include "config.h" #include "effect_p.h" #include "util/dbus.h" #define ENABLED_KEY "enabled" static const char *service_path = "/com/kylin/Wlcom/Effect"; static const char *service_interface = "com.kylin.Wlcom.Effect"; static int list_effects(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct effect_manager *manager = userdata; sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(m, &reply)); CK(sd_bus_message_open_container(reply, 'a', "(sub)")); struct effect *effect; wl_list_for_each(effect, &manager->effects, link) { sd_bus_message_append(reply, "(sub)", effect->name, effect->priority, effect->enabled); } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } static int print_effect_options(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *name = NULL; CK(sd_bus_message_read(m, "s", &name)); struct effect *effect = effect_by_name(name); if (!effect || !effect->options) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid effect name or no option."); return sd_bus_reply_method_error(m, &error); } const char *config = json_object_to_json_string(effect->options); return sd_bus_reply_method_return(m, "s", config); } static int enable_effect(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *name = NULL; int enabled = false; CK(sd_bus_message_read(m, "sb", &name, &enabled)); struct effect *effect = effect_by_name(name); if (!effect) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid effect name."); return sd_bus_reply_method_error(m, &error); } effect_set_enabled(effect, enabled); return sd_bus_reply_method_return(m, NULL); } static int set_effect_option(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *name = NULL, *option = NULL, *type = NULL; CK(sd_bus_message_read(m, "ss", &name, &option)); struct effect *effect = effect_by_name(name); if (!effect) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid effect name."); return sd_bus_reply_method_error(m, &error); } struct effect_option opt = { option, { 0 } }; enum json_type opt_type = json_type_null; CK(sd_bus_message_peek_type(m, NULL, &type)); if (strcmp(type, "i") == 0) { CK(sd_bus_message_read(m, "v", "i", &opt.value.num)); opt_type = json_type_int; } else if (strcmp(type, "b") == 0) { CK(sd_bus_message_read(m, "v", "b", &opt.value.boolean)); opt_type = json_type_boolean; } else if (strcmp(type, "d") == 0) { CK(sd_bus_message_read(m, "v", "d", &opt.value.realnum)); opt_type = json_type_double; } else if (strcmp(type, "s") == 0) { CK(sd_bus_message_read(m, "v", "s", &opt.value.str)); opt_type = json_type_string; } else { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid option type."); return sd_bus_reply_method_error(m, &error); } json_object *data; if (!json_object_object_get_ex(effect->options, option, &data) || json_object_get_type(data) != opt_type) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "option not found or type error."); return sd_bus_reply_method_error(m, &error); } if (!effect->impl->configure) { #if 0 /* apply enabled key if no configure support */ if (strcmp(option, ENABLED_KEY) == 0) { json_object_set_boolean(data, opt.value.boolean); return sd_bus_reply_method_return(m, NULL); } #endif const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Effect is not support configure."); return sd_bus_reply_method_error(m, &error); } if (effect->impl->configure(effect, &opt)) { switch (opt_type) { case json_type_boolean: json_object_set_boolean(data, opt.value.boolean); break; case json_type_double: json_object_set_double(data, opt.value.realnum); break; case json_type_int: json_object_set_int(data, opt.value.num); break; case json_type_string: json_object_set_string(data, opt.value.str); break; default: break; } } return sd_bus_reply_method_return(m, NULL); } static int set_effect_time_scale(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { double time_scale; CK(sd_bus_message_read(m, "d", &time_scale)); if (time_scale > 20 || time_scale < 0.1) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST( SD_BUS_ERROR_INVALID_ARGS, "Time scale greater than 20 or less than 0.1."); return sd_bus_reply_method_error(m, &error); } struct effect_manager *manager = userdata; if (!manager) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "effect manager isn't ready."); return sd_bus_reply_method_error(m, &error); } manager->time_scale = round(time_scale * 10) / 10; return sd_bus_reply_method_return(m, "d", manager->time_scale); } static const sd_bus_vtable service_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("ListAllEffects", "", "a(sub)", list_effects, 0), /* enable or disable effect in runtime */ SD_BUS_METHOD("EnableEffect", "sb", "", enable_effect, 0), SD_BUS_METHOD("PrintEffectOptions", "s", "s", print_effect_options, 0), SD_BUS_METHOD("SetEffectOption", "ssv", "", set_effect_option, 0), // SD_BUS_METHOD("SetEffectOptions", "sa(sv)", "", set_effect_options, 0), SD_BUS_METHOD("SetEffectTimeScale", "d", "d", set_effect_time_scale, 0), SD_BUS_VTABLE_END, }; bool effect_manager_config_init(struct effect_manager *effect_manager) { effect_manager->config = config_manager_add_config("Effects"); if (!effect_manager->config) { return false; } return dbus_register_object(NULL, service_path, service_interface, service_vtable, effect_manager); } bool effect_init_config(struct effect *effect) { struct effect_manager *manager = effect->manager; if (!manager->config) { return effect->enabled; } effect->options = json_object_object_get(manager->config->json, effect->name); if (!effect->options) { effect->options = json_object_new_object(); json_object_object_add(manager->config->json, effect->name, effect->options); } bool enabled = effect->enabled; /* use state in configure file */ json_object *data; if (json_object_object_get_ex(effect->options, ENABLED_KEY, &data)) { enabled = json_object_get_boolean(data); } else { json_object_object_add(effect->options, ENABLED_KEY, json_object_new_boolean(enabled)); } return enabled; } bool effect_get_option_boolean(struct effect *effect, const char *key, bool value) { if (!effect || !effect->options) { return value; } json_object *data; if (json_object_object_get_ex(effect->options, key, &data)) { return json_object_get_boolean(data); } json_object_object_add(effect->options, key, json_object_new_boolean(value)); return value; } void effect_set_option_boolean(struct effect *effect, const char *key, bool value) { if (!effect || !effect->options) { return; } json_object_object_add(effect->options, key, json_object_new_boolean(value)); } int effect_get_option_int(struct effect *effect, const char *key, int value) { if (!effect || !effect->options) { return value; } json_object *data; if (json_object_object_get_ex(effect->options, key, &data)) { return json_object_get_int(data); } json_object_object_add(effect->options, key, json_object_new_int(value)); return value; } void effect_set_option_int(struct effect *effect, const char *key, int value) { if (!effect || !effect->options) { return; } json_object_object_add(effect->options, key, json_object_new_int(value)); } double effect_get_option_double(struct effect *effect, const char *key, double value) { if (!effect || !effect->options) { return value; } json_object *data; if (json_object_object_get_ex(effect->options, key, &data)) { return json_object_get_double(data); } json_object_object_add(effect->options, key, json_object_new_double(value)); return value; } void effect_set_option_double(struct effect *effect, const char *key, double value) { if (!effect || !effect->options) { return; } json_object_object_add(effect->options, key, json_object_new_double(value)); } const char *effect_get_option_string(struct effect *effect, const char *key, const char *value) { if (!effect || !effect->options) { return value; } json_object *data; if (json_object_object_get_ex(effect->options, key, &data)) { return json_object_get_string(data); } json_object_object_add(effect->options, key, json_object_new_string(value)); return value; } void effect_set_option_string(struct effect *effect, const char *key, const char *value) { if (!effect || !effect->options) { return; } json_object_object_add(effect->options, key, json_object_new_string(value)); } void effect_write_enabled_option(struct effect *effect, bool enabled) { effect_set_option_boolean(effect, ENABLED_KEY, enabled); } bool effect_option_is_enabled_option(const struct effect_option *option) { return option->key && strcmp(option->key, ENABLED_KEY) == 0; } kylin-wayland-compositor/src/effect/animator.c0000664000175000017500000003465615160461067020504 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "effect/animator.h" #include "render/opengl.h" #include "scene/render.h" #include "server.h" #include "util/macros.h" #include "util/time.h" #define UNUSED_ANIMATION_MAX_COUNT (30) static struct animation_manager { struct wl_list animations; // animation struct wl_list unused_animations; // animation struct wl_listener destroy; uint32_t unused_count; } *manager = NULL; static const struct curve { struct point p1, p2; } curves[ANIMATION_TYPES] = { { { 0, 0 }, { 0, 0 } }, { { 0, 0 }, { 1, 1 } }, { { 0.25, 0.1 }, { 0.25, 1 } }, { { 0.42, 0 }, { 1, 1 } }, { { 0, 0 }, { 0.58, 1 } }, { { 0.42, 0 }, { 0.58, 1 } }, { { 0.3, 0.15 }, { 0.1, 1 } }, { { 0, 0.4 }, { 0.2, 1 } }, { { 0.33, 0 }, { 1, 0.75 } }, { { 0.3, 0.02 }, { 0.08, 1 } }, }; enum animator_mask { ANIMATOR_NONE = 0, ANIMATOR_ALPHA = 1 << 0, ANIMATOR_ANGLE = 1 << 1, ANIMATOR_GEOMETRY_X = 1 << 2, ANIMATOR_GEOMETRY_Y = 1 << 3, ANIMATOR_GEOMETRY_W = 1 << 4, ANIMATOR_GEOMETRY_H = 1 << 5, ANIMATOR_ALL = (1 << 6) - 1, }; struct linear_function { float k; float b; }; struct animation_entity { struct animation *animation; float value; }; struct animator { int masks; int64_t start_time; int64_t current_time; int64_t end_time; struct animation_data current, end; struct animation_entity geometry; struct animation_entity alpha; struct animation_entity angle; struct linear_function angle_func; struct linear_function alpha_func; struct linear_function geometry_func[4]; }; static const float max_animation_value = 1; /* copied from Hyprland */ float animation_value(struct animation *animation, float x) { /* find x in points, then get y by x */ if (x >= 1.0) { return 1.0f; } int upper = POINTS - 1; int lower = 0; int mid = upper / 2; while (abs(upper - lower) > 1) { if (animation->points[mid].x > x) { upper = mid; } else { lower = mid; } mid = (upper + lower) / 2; } struct point *p1 = &animation->points[lower]; struct point *p2 = &animation->points[upper]; float delta = (x - p1->x) / (p2->x - p1->x); if (isnan(delta) || isinf(delta)) { return 0.f; } return p1->y + (p2->y - p1->y) * delta; } static void animation_destroy(struct animation *animation) { wl_list_remove(&animation->link); free(animation); } static struct animation *_animation_create(enum animation_type type, const struct point *p1, const struct point *p2) { struct animation *animation = calloc(1, sizeof(*animation)); if (!animation) { return NULL; } animation->type = type; animation->p1 = *p1; animation->p2 = *p2; animation->references = 1; wl_list_insert(&manager->animations, &animation->link); float t, a1, a2, a3; for (int i = 0; i < POINTS; i++) { t = (i + 1) / (float)POINTS; a1 = 3 * t * pow(1 - t, 2); a2 = 3 * pow(t, 2) * (1 - t); a3 = pow(t, 3); animation->points[i].x = a1 * p1->x + a2 * p2->x + a3; animation->points[i].y = a1 * p1->y + a2 * p2->y + a3; } return animation; } static void linear_function_init(struct linear_function *func, float x1, float y1, float x2, float y2) { float delta_x = x2 - x1; func->k = delta_x ? (y2 - y1) / delta_x : 0; func->b = y1 - func->k * x1; } struct animator *animator_create(struct animation_data *start, struct animation_group *group, int64_t start_time, int64_t end_time) { struct animator *animator = calloc(1, sizeof(*animator)); if (!animator) { return NULL; } animator->geometry.animation = group->geometry; animator->alpha.animation = group->alpha; animator->angle.animation = group->angle; animator->masks = ANIMATOR_NONE; animator->current = *start; animator->end = *start; animator->start_time = start_time; animator->current_time = start_time; animator->end_time = end_time; return animator; } static void handle_server_destroy(struct wl_listener *listener, void *data) { struct animation *animation, *tmp; wl_list_for_each_safe(animation, tmp, &manager->animations, link) { animation_destroy(animation); } wl_list_for_each_safe(animation, tmp, &manager->unused_animations, link) { animation_destroy(animation); } wl_list_remove(&manager->destroy.link); free(manager); manager = NULL; } struct animation *animation_manager_get(enum animation_type type) { if (!manager || type <= ANIMATION_TYPE_CUSTOM || type >= ANIMATION_TYPES) { return NULL; } struct animation *animation; wl_list_for_each(animation, &manager->animations, link) { if (animation->type == type) { return animation; } } return NULL; } bool animation_manager_create(struct server *server) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } wl_list_init(&manager->animations); wl_list_init(&manager->unused_animations); /* create all animations */ for (int i = ANIMATION_TYPE_LINER; i < ANIMATION_TYPES; i++) { _animation_create(i, &curves[i].p1, &curves[i].p2); } manager->destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->destroy); return true; } void animation_manager_for_each_builtin_animation(builtin_animation_iterator_func_t iterator, void *data) { struct animation *animation; wl_list_for_each(animation, &manager->animations, link) { if (animation->type == ANIMATION_TYPE_CUSTOM) { continue; } if (iterator(animation, data)) { return; } } } static bool is_point_equal(const struct point *p1, const struct point *p2) { return FLOAT_EQUAL(p1->x, p2->x) && FLOAT_EQUAL(p1->y, p2->y); } struct animation *animation_manager_create_animation(struct point p1, struct point p2) { struct animation *animation = NULL; wl_list_for_each(animation, &manager->animations, link) { if (is_point_equal(&animation->p1, &p1) && is_point_equal(&animation->p2, &p2)) { if (animation->type == ANIMATION_TYPE_CUSTOM) { animation->references++; } return animation; } } wl_list_for_each(animation, &manager->unused_animations, link) { if (is_point_equal(&animation->p1, &p1) && is_point_equal(&animation->p2, &p2)) { animation->references++; return animation; } } animation = _animation_create(ANIMATION_TYPE_CUSTOM, &p1, &p2); if (!animation) { return NULL; } return animation; } void animation_manager_destroy_animation(struct animation *animation) { if (animation->type != ANIMATION_TYPE_CUSTOM || animation->references <= 0) { return; } animation->references--; if (!animation->references) { wl_list_remove(&animation->link); wl_list_insert(&manager->unused_animations, &animation->link); manager->unused_count++; } if (manager->unused_count < UNUSED_ANIMATION_MAX_COUNT) { return; } struct animation *destroy_animation, *temp; wl_list_for_each_reverse_safe(destroy_animation, temp, &manager->unused_animations, link) { animation_destroy(destroy_animation); manager->unused_count--; break; } } void animator_destroy(struct animator *animator) { animator->geometry.animation = NULL; animator->angle.animation = NULL; animator->alpha.animation = NULL; free(animator); } static void animator_update_animation_value(struct animator *animator, int current_time) { animator->current_time = current_time; int64_t delta_time = animator->current_time - animator->start_time; int64_t delta_max_time = animator->end_time - animator->start_time; float x = delta_time * 1.0f / delta_max_time; if (animator->geometry.animation) { animator->geometry.value = animation_value(animator->geometry.animation, x); } if (animator->alpha.animation) { animator->alpha.value = animation_value(animator->alpha.animation, x); } if (animator->angle.animation) { animator->angle.value = animation_value(animator->angle.animation, x); } } void animator_set_time(struct animator *animator, int64_t end_time) { int64_t current_time = current_time_msec(); if (end_time < current_time) { return; } animator->end_time = end_time; animator_update_animation_value(animator, current_time); } /** * reset start time, the start animator data is current animator data. * used to synchronize time and data with other animators. */ void animator_set_time_ex(struct animator *animator, int64_t start_time, int64_t end_time) { if (end_time < start_time) { return; } animator->start_time = start_time; animator->end_time = end_time; animator_update_animation_value(animator, start_time); } void animator_set_angle(struct animator *animator, float end_angle) { if (!animator->angle.animation) { return; } if (end_angle == animator->end.angle) { return; } animator->masks |= ANIMATOR_ANGLE; animator->end.angle = end_angle; linear_function_init(&animator->angle_func, animator->angle.value, animator->current.angle, max_animation_value, end_angle); } void animator_set_alpha(struct animator *animator, float end_alpha) { if (!animator->alpha.animation) { return; } if (end_alpha == animator->end.alpha) { return; } animator->masks |= ANIMATOR_ALPHA; animator->end.alpha = end_alpha; linear_function_init(&animator->alpha_func, animator->alpha.value, animator->current.alpha, max_animation_value, end_alpha); } void animator_set_position(struct animator *animator, int end_x, int end_y) { if (!animator->geometry.animation) { return; } if (end_x != animator->end.geometry.x) { animator->masks |= ANIMATOR_GEOMETRY_X; animator->end.geometry.x = end_x; linear_function_init(&animator->geometry_func[0], animator->geometry.value, animator->current.geometry.x, max_animation_value, end_x); } if (end_y != animator->end.geometry.y) { animator->masks |= ANIMATOR_GEOMETRY_Y; animator->end.geometry.y = end_y; linear_function_init(&animator->geometry_func[1], animator->geometry.value, animator->current.geometry.y, max_animation_value, end_y); } } void animator_set_size(struct animator *animator, int end_width, int end_height) { if (!animator->geometry.animation) { return; } if (end_width != animator->end.geometry.width) { animator->masks |= ANIMATOR_GEOMETRY_W; animator->end.geometry.width = end_width; linear_function_init(&animator->geometry_func[2], animator->geometry.value, animator->current.geometry.width, max_animation_value, end_width); } if (end_height != animator->end.geometry.height) { animator->masks |= ANIMATOR_GEOMETRY_H; animator->end.geometry.height = end_height; linear_function_init(&animator->geometry_func[3], animator->geometry.value, animator->current.geometry.height, max_animation_value, end_height); } } const struct animation_data *animator_value(struct animator *animator, int64_t current_time) { animator_update_animation_value(animator, current_time); float geometry_value = animator->geometry.value; float alpha_value = animator->alpha.value; float angle_value = animator->angle.value; if (animator->masks & ANIMATOR_ANGLE) { animator->current.angle = angle_value * animator->angle_func.k + animator->angle_func.b; } if (animator->masks & ANIMATOR_ALPHA) { animator->current.alpha = alpha_value * animator->alpha_func.k + animator->alpha_func.b; } if (animator->masks & ANIMATOR_GEOMETRY_X) { animator->current.geometry.x = geometry_value * animator->geometry_func[0].k + animator->geometry_func[0].b; } if (animator->masks & ANIMATOR_GEOMETRY_Y) { animator->current.geometry.y = geometry_value * (animator->geometry_func[1].k) + animator->geometry_func[1].b; } if (animator->masks & ANIMATOR_GEOMETRY_W) { animator->current.geometry.width = geometry_value * animator->geometry_func[2].k + animator->geometry_func[2].b; } if (animator->masks & ANIMATOR_GEOMETRY_H) { animator->current.geometry.height = geometry_value * animator->geometry_func[3].k + animator->geometry_func[3].b; } return &animator->current; } void animator_render_texture(struct animation_data *animation_data, struct ky_scene_render_target *target, struct wlr_texture *texture) { struct wlr_box dst_box = { .x = animation_data->geometry.x - target->logical.x, .y = animation_data->geometry.y - target->logical.y, .width = animation_data->geometry.width, .height = animation_data->geometry.height, }; ky_scene_render_box(&dst_box, target); pixman_region32_t render_region; pixman_region32_init(&render_region); pixman_region32_copy(&render_region, &target->damage); pixman_region32_translate(&render_region, -target->logical.x, -target->logical.y); ky_scene_render_region(&render_region, target); pixman_region32_intersect_rect(&render_region, &render_region, dst_box.x, dst_box.y, dst_box.width, dst_box.height); if (!pixman_region32_not_empty(&render_region)) { pixman_region32_fini(&render_region); return; } struct ky_render_texture_options options = { .base = { .texture = texture, .dst_box = dst_box, .transform = target->transform, .alpha = &animation_data->alpha, .clip = &render_region, }, .rotation_angle = animation_data->angle, }; ky_render_pass_add_texture(target->render_pass, &options); pixman_region32_fini(&render_region); } kylin-wayland-compositor/src/effect/circle_progressbar.c0000664000175000017500000002436515160461067022540 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "effect/shape.h" #include "effect_p.h" #include "render/opengl.h" #include "scene/scene.h" #include "util/matrix.h" #include "util/time.h" #include "circle_progressbar_frag.h" #include "circle_progressbar_vert.h" #define M_2_PI 6.28318530717958647692f struct circle_info { struct wl_list link; int32_t x, y; uint32_t start_time; bool rendering; float angle; }; struct circle_progressbar_effect { struct effect *base; struct effect_manager *manager; struct wl_listener enable; struct wl_listener disable; struct wl_listener destroy; const struct circle_progressbar_effect_interface *impl; struct circle_progressbar_effect_options options; void *user_data; struct circle_info *circle_info; }; static struct circle_progressbar_gl_shader { int32_t program; // vs GLint in_uv; GLint uv2ndc; GLint output_invert; // fs GLint anti_aliasing; GLint angle; } gl_shader = { 0 }; static bool create_opengl_shader(struct ky_opengl_renderer *renderer) { if (gl_shader.program == 0) { GLuint prog = ky_opengl_create_program(renderer, circle_progressbar_vert, circle_progressbar_frag); if (prog == 0) { return false; } gl_shader.program = prog; gl_shader.in_uv = glGetAttribLocation(prog, "inUV"); gl_shader.uv2ndc = glGetUniformLocation(prog, "uv2ndc"); gl_shader.output_invert = glGetUniformLocation(prog, "outputInvert"); gl_shader.anti_aliasing = glGetUniformLocation(prog, "antiAliasing"); gl_shader.angle = glGetUniformLocation(prog, "endAngle"); } return true; } static void free_circle_info(struct circle_progressbar_effect *effect) { if (effect->circle_info) { free(effect->circle_info); effect->circle_info = NULL; } } static void add_damage(struct circle_progressbar_effect *effect) { if (!effect->circle_info) { return; } struct ky_scene *scene = effect->manager->server->scene; pixman_region32_t region; pixman_region32_init_rect(®ion, effect->circle_info->x, effect->circle_info->y, effect->options.size, effect->options.size); ky_scene_add_damage(scene, ®ion); pixman_region32_fini(®ion); } static void gl_render_finger_effect(struct circle_progressbar_effect *effect, struct circle_info *finger, struct ky_scene_render_target *target) { if (!effect->circle_info->rendering) { return; } static GLfloat verts[8] = { 0.0f, 0.0f, // v0 1.0f, 0.0f, // v1 1.0f, 1.0f, // v2 0.0f, 1.0f, // v3 }; struct wlr_box box = { .x = finger->x - target->logical.x, .y = finger->y - target->logical.y, .width = effect->options.size, .height = effect->options.size, }; ky_scene_render_box(&box, target); int height = (target->transform & WL_OUTPUT_TRANSFORM_90) ? box.width : box.height; float one_pixel_distance = 1.0f / height; struct ky_mat3 projection; ky_mat3_framebuffer_to_ndc(&projection, target->buffer->width, target->buffer->height); struct ky_mat3 uv2pos; ky_mat3_init_scale_translate(&uv2pos, box.width, box.height, box.x, box.y); struct ky_mat3 uv2ndc; ky_mat3_multiply(&projection, &uv2pos, &uv2ndc); struct ky_mat3 output_invert; ky_mat3_invert_output_transform(&output_invert, target->transform); glEnable(GL_BLEND); glUseProgram(gl_shader.program); glEnableVertexAttribArray(gl_shader.in_uv); glVertexAttribPointer(gl_shader.in_uv, 2, GL_FLOAT, GL_FALSE, 0, verts); glUniformMatrix3fv(gl_shader.uv2ndc, 1, GL_FALSE, uv2ndc.matrix); glUniformMatrix3fv(gl_shader.output_invert, 1, GL_FALSE, output_invert.matrix); // square not need aspect ratio glUniform1f(gl_shader.anti_aliasing, one_pixel_distance); glUniform1f(gl_shader.angle, finger->angle); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glUseProgram(0); glDisableVertexAttribArray(gl_shader.in_uv); } static bool frame_render_begin(struct effect_entity *entity, struct ky_scene_render_target *target) { struct circle_progressbar_effect *effect = entity->user_data; struct circle_info *finger = effect->circle_info; if (!finger) { return true; } // timer uint32_t time = current_time_msec(); uint32_t duration = effect_manager_scale_time(effect->options.animate_duration); if (time > finger->start_time + effect->options.animate_delay) { uint32_t diff_time = time - effect->options.animate_delay - finger->start_time; if (diff_time > duration && finger->angle > M_2_PI) { free_circle_info(effect); } else { finger->rendering = true; float t = diff_time * 1.0f / duration; finger->angle = t * M_2_PI; } } return true; } static bool frame_render_post(struct effect_entity *entity, struct ky_scene_render_target *target) { struct circle_progressbar_effect *effect = entity->user_data; // add damage to trigger render event add_damage(effect); return true; } static bool frame_render_end(struct effect_entity *entity, struct ky_scene_render_target *target) { struct circle_progressbar_effect *effect = entity->user_data; struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(effect->manager->server->renderer); if (!create_opengl_shader(renderer)) { return true; } if (effect->circle_info) { gl_render_finger_effect(effect, effect->circle_info, target); /* add circle bounding box to target expand damage, it should be add to frame damage. */ pixman_region32_union_rect(&target->expand_damage, &target->expand_damage, effect->circle_info->x, effect->circle_info->y, effect->options.size, effect->options.size); } return true; } static bool handle_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } return false; } static bool handle_allow_direct_scanout(struct effect *effect, struct ky_scene_render_target *target) { struct circle_progressbar_effect *circle_progressbar = effect->user_data; if (circle_progressbar->circle_info) { struct wlr_box damage = { circle_progressbar->circle_info->x, circle_progressbar->circle_info->y, circle_progressbar->options.size, circle_progressbar->options.size, }; if (wlr_box_intersection(&damage, &target->logical, &damage)) { return false; } } return true; } static void handle_effect_enable(struct wl_listener *listener, void *data) { struct circle_progressbar_effect *effect = wl_container_of(listener, effect, enable); if (effect->impl->enable) { effect->impl->enable(effect, effect->user_data); } } static void handle_effect_disable(struct wl_listener *listener, void *data) { struct circle_progressbar_effect *effect = wl_container_of(listener, effect, disable); if (effect->impl->disable) { effect->impl->disable(effect, effect->user_data); } free_circle_info(effect); } static void handle_effect_destroy(struct wl_listener *listener, void *data) { struct circle_progressbar_effect *effect = wl_container_of(listener, effect, destroy); if (effect->impl->destroy) { effect->impl->destroy(effect, effect->user_data); } wl_list_remove(&effect->destroy.link); wl_list_remove(&effect->enable.link); wl_list_remove(&effect->disable.link); free(effect); } static const struct effect_interface effect_impl = { .frame_render_begin = frame_render_begin, .frame_render_end = frame_render_end, .frame_render_post = frame_render_post, .configure = handle_effect_configure, .allow_direct_scanout = handle_allow_direct_scanout, }; struct circle_progressbar_effect * circle_progressbar_effect_create(struct effect_manager *manager, struct circle_progressbar_effect_options *options, const struct circle_progressbar_effect_interface *impl, const char *name, int priority, bool enabled, void *user_data) { if (!wlr_renderer_is_opengl(manager->server->renderer)) { return NULL; } struct circle_progressbar_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return NULL; } effect->base = effect_create(name, priority, enabled, &effect_impl, effect); if (!effect->base) { free(effect); return NULL; } effect->base->category = EFFECT_CATEGORY_SCENE; struct effect_entity *entity = ky_scene_add_effect(manager->server->scene, effect->base); if (!entity) { effect_destroy(effect->base); free(effect); return NULL; } entity->user_data = effect; effect->manager = manager; effect->enable.notify = handle_effect_enable; wl_signal_add(&effect->base->events.enable, &effect->enable); effect->disable.notify = handle_effect_disable; wl_signal_add(&effect->base->events.disable, &effect->disable); effect->destroy.notify = handle_effect_destroy; wl_signal_add(&effect->base->events.destroy, &effect->destroy); effect->impl = impl; effect->options = *options; effect->user_data = user_data; if (effect->base->enabled) { handle_effect_enable(&effect->enable, NULL); } return effect; } void circle_progressbar_effect_begin(struct circle_progressbar_effect *effect, int32_t x, int32_t y) { free_circle_info(effect); effect->circle_info = calloc(1, sizeof(*effect->circle_info)); effect->circle_info->x = x - effect->options.size * 0.5f; effect->circle_info->y = y - effect->options.size * 0.5f; effect->circle_info->start_time = current_time_msec(); effect->circle_info->rendering = false; add_damage(effect); } void circle_progressbar_effect_end(struct circle_progressbar_effect *effect) { free_circle_info(effect); } kylin-wayland-compositor/src/effect/mouse_trail.c0000664000175000017500000001011615160461067021176 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "effect/shape.h" #include "effect_p.h" #include "input/cursor.h" #include "input/seat.h" struct seat_mouse { struct wl_list link; struct mouse_trail_effect *effect; struct seat *seat; struct wl_listener mouse_motion; struct wl_listener destroy; }; struct mouse_trail_effect { struct trail_effect *base; struct wl_listener new_seat; struct wl_list seat_mouses; }; static void handle_mouse_motion(struct wl_listener *listener, void *data) { struct seat_mouse *seat_mouse = wl_container_of(listener, seat_mouse, mouse_motion); struct mouse_trail_effect *effect = seat_mouse->effect; int32_t lx = roundf(seat_mouse->seat->cursor->lx); int32_t ly = roundf(seat_mouse->seat->cursor->ly); trail_effect_add_trail(effect->base, 0, lx, ly); trail_effect_trail_add_point(effect->base, 0, lx, ly); } static void seat_mouse_destroy(struct seat_mouse *seat_mouse) { wl_list_remove(&seat_mouse->link); wl_list_remove(&seat_mouse->mouse_motion.link); wl_list_remove(&seat_mouse->destroy.link); free(seat_mouse); } static void handle_seat_destroy(struct wl_listener *listener, void *data) { struct seat_mouse *seat_mouse = wl_container_of(listener, seat_mouse, destroy); seat_mouse_destroy(seat_mouse); } static void seat_mouse_create(struct mouse_trail_effect *effect, struct seat *seat) { struct seat_mouse *seat_mouse = calloc(1, sizeof(*seat_mouse)); if (!seat_mouse) { return; } wl_list_insert(&effect->seat_mouses, &seat_mouse->link); seat_mouse->effect = effect; seat_mouse->seat = seat; seat_mouse->mouse_motion.notify = handle_mouse_motion; wl_signal_add(&seat->cursor->wlr_cursor->events.motion, &seat_mouse->mouse_motion); seat_mouse->destroy.notify = handle_seat_destroy; wl_signal_add(&seat->events.destroy, &seat_mouse->destroy); } static void handle_new_seat(struct wl_listener *listener, void *data) { struct mouse_trail_effect *effect = wl_container_of(listener, effect, new_seat); struct seat *seat = data; seat_mouse_create(effect, seat); } static bool handle_seat(struct seat *seat, int index, void *data) { struct mouse_trail_effect *effect = data; seat_mouse_create(effect, seat); return false; } static void handle_effect_enable(struct trail_effect *base_effect, void *user_data) { struct mouse_trail_effect *effect = user_data; input_manager_for_each_seat(handle_seat, effect); effect->new_seat.notify = handle_new_seat; seat_add_new_listener(&effect->new_seat); } static void handle_effect_disable(struct trail_effect *base_effect, void *user_data) { struct mouse_trail_effect *effect = user_data; wl_list_remove(&effect->new_seat.link); wl_list_init(&effect->new_seat.link); struct seat_mouse *seat_mouse, *tmp0; wl_list_for_each_safe(seat_mouse, tmp0, &effect->seat_mouses, link) { seat_mouse_destroy(seat_mouse); } } static void handle_effect_destroy(struct trail_effect *base_effect, void *user_data) { struct mouse_trail_effect *effect = user_data; free(effect); } static const struct trail_effect_interface effect_impl = { .enable = handle_effect_enable, .disable = handle_effect_disable, .destroy = handle_effect_destroy, }; bool mouse_trail_effect_create(struct effect_manager *manager) { struct mouse_trail_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } wl_list_init(&effect->new_seat.link); wl_list_init(&effect->seat_mouses); struct trail_effect_options options = {}; options.color[0] = 0.6f; options.color[1] = 0.12f; options.color[2] = 0.12f; options.color[3] = 0.6f; options.thickness = 8.0f; options.life_time = 200; effect->base = trail_effect_create(manager, &options, &effect_impl, "mouse_trail", 100, false, effect); if (!effect->base) { free(effect); return false; } return true; } kylin-wayland-compositor/src/effect/blur.c0000664000175000017500000014131415160461067017624 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _DEFAULT_SOURCE #include #include #include #include #include #include #include "blur_down_frag.h" #include "blur_first_down_frag.h" #include "blur_first_down_vert.h" #include "blur_tex_rgba_frag.h" #include "blur_tex_vert.h" #include "blur_up_frag.h" #include "blur_vert.h" #include "effect/blur.h" #include "effect/effect.h" #include "effect_p.h" #include "render/opengl.h" #include "render/profile.h" #include "scene/scene.h" #include "util/macros.h" #include "util/matrix.h" #include "util/time.h" // rtt = render to texture = fbo + texture struct glrtt_pool_texture { struct wl_list link; struct glrtt_pool *pool; size_t size; GLuint fbo; GLuint texture; GLsizei width; GLsizei height; bool used; uint32_t time; }; struct glrtt_pool { size_t max_size; uint32_t bytes_per_pixel; GLenum format; GLint filter; uint32_t timeout; struct wl_list textures; size_t size; }; static struct glrtt_pool *glrtt_pool_create(size_t max_size, uint32_t bytes_per_pixel, GLenum format, GLint filter, uint32_t timeout) { struct glrtt_pool *pool = calloc(1, sizeof(*pool)); if (!pool) { return NULL; } pool->max_size = max_size; pool->bytes_per_pixel = bytes_per_pixel; pool->format = format; pool->filter = filter; pool->timeout = timeout; wl_list_init(&pool->textures); return pool; } static void glrtt_pool_texture_destroy(struct glrtt_pool_texture *texture) { texture->pool->size -= texture->size; glDeleteFramebuffers(1, &texture->fbo); glDeleteTextures(1, &texture->texture); wl_list_remove(&texture->link); free(texture); } static void glrtt_pool_destroy(struct glrtt_pool *pool) { struct glrtt_pool_texture *texture, *tmp; wl_list_for_each_safe(texture, tmp, &pool->textures, link) { glrtt_pool_texture_destroy(texture); } free(pool); } static void glrtt_pool_check_size(struct glrtt_pool *pool) { struct glrtt_pool_texture *texture, *tmp; wl_list_for_each_reverse_safe(texture, tmp, &pool->textures, link) { if (pool->size < pool->max_size) { break; } if (!texture->used) { glrtt_pool_texture_destroy(texture); } } } static struct glrtt_pool_texture *glrtt_pool_get_texture(struct glrtt_pool *pool, GLsizei width, GLsizei height) { struct glrtt_pool_texture *texture = NULL; // find cached texture bool cached = false; struct glrtt_pool_texture *texture_tmp; wl_list_for_each(texture_tmp, &pool->textures, link) { if (!texture_tmp->used && texture_tmp->width == width && texture_tmp->height == height) { texture = texture_tmp; texture->used = true; cached = true; break; } } // new texture if (!cached) { texture = calloc(1, sizeof(*texture)); if (!texture) { return NULL; } texture->pool = pool; texture->size = width * height * pool->bytes_per_pixel; glGenTextures(1, &texture->texture); glBindTexture(GL_TEXTURE_2D, texture->texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, pool->filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, pool->filter); glTexImage2D(GL_TEXTURE_2D, 0, pool->format, width, height, 0, pool->format, GL_UNSIGNED_BYTE, 0); GLint old_fbo; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &old_fbo); glGenFramebuffers(1, &texture->fbo); glBindFramebuffer(GL_FRAMEBUFFER, texture->fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->texture, 0); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { kywc_log(KYWC_ERROR, "Glrtt pool failed to fbo(%d) attach texture(%d). status=%d", texture->fbo, texture->texture, status); } glBindFramebuffer(GL_FRAMEBUFFER, old_fbo); texture->width = width; texture->height = height; texture->used = true; wl_list_insert(&pool->textures, &texture->link); pool->size += texture->size; glrtt_pool_check_size(pool); } return texture; } static void glrtt_pool_release_texture(struct glrtt_pool *pool, struct glrtt_pool_texture *texture) { if (!texture) { return; } texture->used = false; texture->time = current_time_msec(); } static void glrtt_pool_release_timeout_texture(struct glrtt_pool *pool) { struct glrtt_pool_texture *texture, *tmp; wl_list_for_each_safe(texture, tmp, &pool->textures, link) { if (!texture->used && (current_time_msec() - texture->time) > pool->timeout) { glrtt_pool_texture_destroy(texture); } } } struct blur_tex_program { GLuint id; struct { GLint uv2ndc; GLint uv2texcoord; GLint uv2shapecoord; GLint pos_attrib; GLint sdfpos_attrib; GLint tex; GLint alpha; GLint anti_aliasing; GLint aspect; GLint rounded_corner_radius; GLint offset; GLint halfpixel; } shaders; }; struct first_blur_shaders { GLint uv2tex; GLint min_uv; GLint max_uv; }; struct blur_program { GLuint id; struct { GLint texture; GLint offset; GLint position; GLint halfpixel; } shaders; void *extension_data; }; struct blur_output_data { struct wl_list link; pixman_region32_t unaffected_region; struct ky_scene_output *output; struct wl_listener output_destroy; struct wlr_buffer *output_buffer; struct wl_listener buffer_destroy; }; enum blur_program_type { BLUR_PROGRAM_FIRST_DOWN, BLUR_PROGRAM_DOWN, BLUR_PROGRAM_UP, BLUR_PROGRAM_TYPE_COUNT }; static struct blur_data { struct blur_effect *effect; struct wl_list output_data; struct blur_output_data *current_output_data; struct ky_opengl_texture *current_output_texture; float offset; uint32_t iterations; struct blur_program blur_prog[BLUR_PROGRAM_TYPE_COUNT]; struct blur_tex_program blur_tex_prog; } effect_blur_data = { 0 }; struct blur_node { struct wl_list link; struct ky_scene_node *node; struct blur_info blur; pixman_region32_t visible_blur; pixman_region32_t damaged_blur; }; struct blur_render_config { struct ky_opengl_renderer *renderer; }; static struct blur_render_config blur_config = { .renderer = NULL, }; struct blur_effect { struct effect *effect; struct ky_scene *scene; struct glrtt_pool *glrtt_pool; struct wl_listener enable; struct wl_listener disable; struct wl_listener destroy; }; #define MAX_QUADS 86 // 4kb static void blur_output_data_destroy(struct blur_output_data *data); static int calculate_blur_radius(int iterations, float offset) { return ceil(pow(2, iterations + 1) * offset); } #if 0 static void save_texture_to_rgba(int frame_count, GLuint texture_id, GLuint fb, int width, int height, const char *prefix) { char path[255] = { 0 }; snprintf(path, 50, "/tmp/ky_%dx%d_rgba_%s_%d_%d%s", width, height, prefix, fb, frame_count, ".rgb"); mkstemps(path, 4); int fileszie = width * height * 4; void *pixles = calloc(fileszie, sizeof(char)); GLuint fbo; glGenFramebuffers(1, &fbo); GLuint pbo; glGenBuffers(1, &pbo); glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); glBufferData(GL_PIXEL_PACK_BUFFER, fileszie, NULL, GL_STREAM_READ); if (fb > 0 && texture_id <= 0) { glBindFramebuffer(GL_READ_FRAMEBUFFER, fb); } else { glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); /* FBO attach texture */ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id, 0); } /** * read texture data to PBO. * nullptr indicates reading data to PBO, not to cpu memory. */ glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, NULL); /* Map PBO to CPU address space */ glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); void *pixels = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, fileszie, GL_MAP_READ_BIT); if (pixels) { FILE *f = fopen(path, "wb+"); if (!f) { goto failed; } fwrite(pixels, fileszie, 1, f); fclose(f); } failed: /* unmap PBO */ glUnmapBuffer(GL_PIXEL_PACK_BUFFER); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); glDeleteBuffers(1, &pbo); glDeleteFramebuffers(1, &fbo); free(pixles); } #endif static struct blur_node *create_blur_node(struct ky_scene_node *node, struct blur_info *blur, pixman_region32_t *visible_blur) { struct blur_node *blur_node = calloc(1, sizeof(*blur_node)); if (!blur_node) { return NULL; } blur_node->node = node; blur_node->blur.offset = blur->offset; blur_node->blur.iterations = blur->iterations; pixman_region32_init(&blur_node->blur.region); pixman_region32_init(&blur_node->damaged_blur); pixman_region32_init(&blur_node->visible_blur); pixman_region32_copy(&blur_node->blur.region, &blur->region); pixman_region32_copy(&blur_node->visible_blur, visible_blur); return blur_node; } static void destroy_blur_node(struct blur_node *node) { wl_list_remove(&node->link); pixman_region32_fini(&node->damaged_blur); pixman_region32_fini(&node->blur.region); pixman_region32_fini(&node->visible_blur); free(node); } static void destroy_blur_nodes(struct wl_list *nodes) { struct blur_node *blur_node, *tmp; wl_list_for_each_safe(blur_node, tmp, nodes, link) { kywc_log(KYWC_DEBUG, "node: %p skiped. when blur calc extend_render_region, the blur region of " "the node may have been overlooked", blur_node->node); destroy_blur_node(blur_node); } } static void blur_program_generate(struct blur_program *prog, const char *vertex_source, const char *frag_source) { GLuint prog_id = ky_opengl_create_program(blur_config.renderer, vertex_source, frag_source); if (prog_id > 0) { prog->id = prog_id; prog->shaders.position = glGetAttribLocation(prog_id, "position"); prog->shaders.texture = glGetUniformLocation(prog_id, "texture"); prog->shaders.offset = glGetUniformLocation(prog_id, "offset"); prog->shaders.halfpixel = glGetUniformLocation(prog_id, "halfpixel"); } } static void first_blur_program_generate(struct blur_program *prog, const char *vertex_source, const char *frag_source) { blur_program_generate(prog, vertex_source, frag_source); if (prog->id <= 0) { return; } struct first_blur_shaders *shaders = calloc(1, sizeof(*shaders)); if (shaders) { shaders->uv2tex = glGetUniformLocation(prog->id, "uv2tex"); shaders->max_uv = glGetUniformLocation(prog->id, "max_uv"); shaders->min_uv = glGetUniformLocation(prog->id, "min_uv"); prog->extension_data = shaders; } } static struct blur_program *get_or_generate_blur_program(void) { struct blur_program *blur_prog = effect_blur_data.blur_prog; if (blur_prog[BLUR_PROGRAM_FIRST_DOWN].id <= 0) { first_blur_program_generate(&blur_prog[BLUR_PROGRAM_FIRST_DOWN], blur_first_down_vert, blur_first_down_frag); } if (blur_prog[BLUR_PROGRAM_DOWN].id <= 0) { blur_program_generate(&blur_prog[BLUR_PROGRAM_DOWN], blur_vert, blur_down_frag); } if (blur_prog[BLUR_PROGRAM_UP].id <= 0) { blur_program_generate(&blur_prog[BLUR_PROGRAM_UP], blur_vert, blur_up_frag); } for (int i = 0; i < BLUR_PROGRAM_TYPE_COUNT; ++i) { if (blur_prog[i].id <= 0) { kywc_log(KYWC_ERROR, "Blur shader compile error!"); return NULL; } } return blur_prog; } static struct blur_tex_program *get_or_generate_blur_text_program(void) { struct blur_tex_program *blur_tex_prog = &effect_blur_data.blur_tex_prog; if (blur_tex_prog->id > 0) { return blur_tex_prog; } GLuint prog = ky_opengl_create_program(blur_config.renderer, blur_tex_vert, blur_tex_rgba_frag); if (prog > 0) { blur_tex_prog->id = prog; blur_tex_prog->shaders.uv2ndc = glGetUniformLocation(prog, "uv2ndc"); blur_tex_prog->shaders.uv2texcoord = glGetUniformLocation(prog, "uv2texcoord"); blur_tex_prog->shaders.uv2shapecoord = glGetUniformLocation(prog, "uv2shapecoord"); blur_tex_prog->shaders.tex = glGetUniformLocation(prog, "tex"); blur_tex_prog->shaders.alpha = glGetUniformLocation(prog, "alpha"); blur_tex_prog->shaders.anti_aliasing = glGetUniformLocation(prog, "antiAliasing"); blur_tex_prog->shaders.aspect = glGetUniformLocation(prog, "aspect"); blur_tex_prog->shaders.rounded_corner_radius = glGetUniformLocation(prog, "roundedCornerRadius"); blur_tex_prog->shaders.pos_attrib = glGetAttribLocation(prog, "pos"); blur_tex_prog->shaders.sdfpos_attrib = glGetAttribLocation(prog, "sdfpos"); blur_tex_prog->shaders.offset = glGetUniformLocation(prog, "offset"); blur_tex_prog->shaders.halfpixel = glGetUniformLocation(prog, "halfpixel"); return blur_tex_prog; } kywc_log(KYWC_ERROR, "Blur shader compile error!"); return NULL; } static void blur_data_destroy(struct blur_data *data) { ky_egl_make_current(blur_config.renderer->egl, NULL); for (int i = 0; i < BLUR_PROGRAM_TYPE_COUNT; ++i) { if (data->blur_prog[i].id <= 0) { continue; } if (data->blur_prog[i].extension_data) { free(data->blur_prog[i].extension_data); } glDeleteProgram(data->blur_prog[i].id); } if (data->blur_tex_prog.id > 0) { glDeleteProgram(data->blur_tex_prog.id); } struct blur_output_data *output_data, *tmp; wl_list_for_each_safe(output_data, tmp, &data->output_data, link) { blur_output_data_destroy(output_data); } } static void set_pos_matrix(GLint loc, int target_buffer_width, int target_buffer_height, const struct kywc_box *box) { struct ky_mat3 uv2ndc; ky_mat3_uvofbox_to_ndc(&uv2ndc, target_buffer_width, target_buffer_height, 0, box); glUniformMatrix3fv(loc, 1, GL_FALSE, uv2ndc.matrix); } static void set_tex_matrix(GLint loc, enum wl_output_transform trans, const struct kywc_fbox *box) { struct ky_mat3 uv_rotation; ky_mat3_invert_output_transform(&uv_rotation, trans); struct ky_mat3 uv2texcoord; struct ky_mat3 tex_matrix; ky_mat3_init_scale(&tex_matrix, box->width, box->height); ky_mat3_translate(&tex_matrix, box->x, box->y); ky_mat3_multiply(&tex_matrix, &uv_rotation, &uv2texcoord); glUniformMatrix3fv(loc, 1, GL_FALSE, uv2texcoord.matrix); } static void render_iteration(struct glrtt_pool_texture *in, struct glrtt_pool_texture *out, int width, int height) { KY_PROFILE_RENDER_ZONE(&blur_config.renderer->wlr_renderer, gzone, __func__); glBindFramebuffer(GL_FRAMEBUFFER, out->fbo); glViewport(0, 0, out->width, out->height); glBindTexture(GL_TEXTURE_2D, in->texture); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); KY_PROFILE_RENDER_ZONE_END(&blur_config.renderer->wlr_renderer); } static struct glrtt_pool_texture *blur_first_down(struct blur_data *data, struct ky_opengl_buffer *gl_buffer, struct kywc_box *blur_src_box) { struct blur_program *first_down_prog = &data->blur_prog[BLUR_PROGRAM_FIRST_DOWN]; struct first_blur_shaders *extension_shaders = first_down_prog->extension_data; if (!extension_shaders) { return NULL; } KY_PROFILE_RENDER_ZONE(&blur_config.renderer->wlr_renderer, gzone, __func__); int sample_width = blur_src_box->width / 2; int sample_height = blur_src_box->height / 2; if (sample_width <= 0 || sample_height <= 0) { return NULL; } struct glrtt_pool *glrtt_pool = data->effect->glrtt_pool; struct glrtt_pool_texture *out_tex = glrtt_pool_get_texture(glrtt_pool, sample_width, sample_height); if (!out_tex) { return NULL; } GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); glDisable(GL_BLEND); // down scale glUseProgram(first_down_prog->id); glEnableVertexAttribArray(first_down_prog->shaders.position); GLfloat pos_vertex[8] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; glVertexAttribPointer(first_down_prog->shaders.position, 2, GL_FLOAT, GL_FALSE, 0, pos_vertex); struct ky_opengl_texture *gl_texture = data->current_output_texture; if (!gl_texture) { struct wlr_texture *src_tex = ky_opengl_texture_from_buffer(&blur_config.renderer->wlr_renderer, gl_buffer->buffer); gl_texture = ky_opengl_texture_from_wlr_texture(src_tex); } if (!gl_texture) { return NULL; } int width = gl_texture->wlr_texture.width; int height = gl_texture->wlr_texture.height; struct kywc_fbox src_fbox = { .x = blur_src_box->x * 1.0f / width, .y = blur_src_box->y * 1.0f / height, .width = blur_src_box->width * 1.0f / width, .height = blur_src_box->height * 1.0f / height, }; set_tex_matrix(extension_shaders->uv2tex, WL_OUTPUT_TRANSFORM_NORMAL, &src_fbox); glUniform2f(extension_shaders->min_uv, src_fbox.x, src_fbox.y); /** * the srcbox may be greater than 1, and texture sampling may exceed the texture boundary. * using GL_CAMP_TO_EDGE can ensure correct blur results. */ glUniform2f(extension_shaders->max_uv, src_fbox.x + src_fbox.width, src_fbox.y + src_fbox.height); glUniform1i(first_down_prog->shaders.texture, 0); glUniform1f(first_down_prog->shaders.offset, data->offset); glUniform2f(first_down_prog->shaders.halfpixel, 0.5f / width, 0.5f / height); glActiveTexture(GL_TEXTURE0); glBindTexture(gl_texture->target, gl_texture->tex); glTexParameteri(gl_texture->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(gl_texture->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(gl_texture->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(gl_texture->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindFramebuffer(GL_FRAMEBUFFER, out_tex->fbo); glViewport(0, 0, out_tex->width, out_tex->height); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(gl_texture->target, 0); glDisableVertexAttribArray(first_down_prog->shaders.position); glUseProgram(0); glEnable(GL_BLEND); if (!data->current_output_texture) { wlr_texture_destroy(&gl_texture->wlr_texture); } KY_PROFILE_RENDER_ZONE_END(&blur_config.renderer->wlr_renderer); return out_tex; } static void blur_fb0(struct blur_data *data, struct glrtt_pool_texture *src_tex) { struct glrtt_pool *glrtt_pool = data->effect->glrtt_pool; // iterations must > 0 int iterations = data->iterations - 1; float offset = data->offset; int width = src_tex->width; int height = src_tex->height; KY_PROFILE_RENDER_ZONE(&blur_config.renderer->wlr_renderer, gzone, __func__); GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); struct blur_program *prog = data->blur_prog; struct blur_program *down_prog = &prog[BLUR_PROGRAM_DOWN]; struct blur_program *up_prog = &prog[BLUR_PROGRAM_UP]; glDisable(GL_BLEND); // down scale glUseProgram(down_prog->id); glEnableVertexAttribArray(down_prog->shaders.position); GLfloat pos_vertex[8] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; glVertexAttribPointer(down_prog->shaders.position, 2, GL_FLOAT, GL_FALSE, 0, pos_vertex); glUniform1f(down_prog->shaders.offset, offset); glUniform1i(down_prog->shaders.texture, 0); struct glrtt_pool_texture *last_tex = src_tex; for (int i = 0; i < iterations; i++) { int sample_width = MAX(width / (1 << (i + 1)), 1); int sample_height = MAX(height / (1 << (i + 1)), 1); // first iter input use src_tex struct glrtt_pool_texture *in_tex = last_tex; struct glrtt_pool_texture *out_tex = glrtt_pool_get_texture(glrtt_pool, sample_width, sample_height); glUniform2f(down_prog->shaders.halfpixel, 0.5f / sample_width, 0.5f / sample_height); render_iteration(in_tex, out_tex, sample_width, sample_height); if (i != 0) { glrtt_pool_release_texture(glrtt_pool, in_tex); } last_tex = out_tex; } glDisableVertexAttribArray(down_prog->shaders.position); glBindFramebuffer(GL_FRAMEBUFFER, 0); glUseProgram(0); // up scale glUseProgram(up_prog->id); glEnableVertexAttribArray(up_prog->shaders.position); glVertexAttribPointer(up_prog->shaders.position, 2, GL_FLOAT, GL_FALSE, 0, pos_vertex); glUniform1f(up_prog->shaders.offset, offset); glUniform1i(up_prog->shaders.texture, 0); for (int i = iterations - 1; i >= 0; i--) { int sample_width = MAX(width / (1 << i), 1); int sample_height = MAX(height / (1 << i), 1); // last iter output use src_tex struct glrtt_pool_texture *in_tex = last_tex; struct glrtt_pool_texture *out_tex = i == 0 ? src_tex : glrtt_pool_get_texture(glrtt_pool, sample_width, sample_height); glUniform2f(up_prog->shaders.halfpixel, 0.5f / sample_width, 0.5f / sample_height); render_iteration(in_tex, out_tex, sample_width, sample_height); glrtt_pool_release_texture(glrtt_pool, in_tex); last_tex = out_tex; } glDisableVertexAttribArray(up_prog->shaders.position); glBindFramebuffer(GL_FRAMEBUFFER, 0); glUseProgram(0); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBindTexture(GL_TEXTURE_2D, 0); glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); KY_PROFILE_RENDER_ZONE_END(&blur_config.renderer->wlr_renderer); } static void render(const struct kywc_box *box, const pixman_region32_t *blur, GLint attrib, const struct wlr_box *sdf_box, GLint sdfpos_attrib) { int rects_len; const pixman_box32_t *rects = pixman_region32_rectangles(blur, &rects_len); KY_PROFILE_RENDER_ZONE(&blur_config.renderer->wlr_renderer, gzone, __func__); glEnableVertexAttribArray(sdfpos_attrib); glEnableVertexAttribArray(attrib); for (int i = 0; i < rects_len;) { int batch = rects_len - i < MAX_QUADS ? rects_len - i : MAX_QUADS; int batch_end = batch + i; size_t vert_index = 0; GLfloat verts[MAX_QUADS * 6 * 4]; for (; i < batch_end; i++) { const pixman_box32_t *rect = &rects[i]; verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x1 - sdf_box->x) / sdf_box->width; verts[vert_index++] = (GLfloat)(rect->y1 - sdf_box->y) / sdf_box->height; verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x2 - sdf_box->x) / sdf_box->width; verts[vert_index++] = (GLfloat)(rect->y1 - sdf_box->y) / sdf_box->height; verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x1 - sdf_box->x) / sdf_box->width; verts[vert_index++] = (GLfloat)(rect->y2 - sdf_box->y) / sdf_box->height; verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x2 - sdf_box->x) / sdf_box->width; verts[vert_index++] = (GLfloat)(rect->y1 - sdf_box->y) / sdf_box->height; verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x2 - sdf_box->x) / sdf_box->width; verts[vert_index++] = (GLfloat)(rect->y2 - sdf_box->y) / sdf_box->height; verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x1 - sdf_box->x) / sdf_box->width; verts[vert_index++] = (GLfloat)(rect->y2 - sdf_box->y) / sdf_box->height; } glVertexAttribPointer(attrib, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), verts); glVertexAttribPointer(sdfpos_attrib, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), verts + 2); glDrawArrays(GL_TRIANGLES, 0, batch * 6); } glDisableVertexAttribArray(attrib); glDisableVertexAttribArray(sdfpos_attrib); KY_PROFILE_RENDER_ZONE_END(&blur_config.renderer->wlr_renderer); } static void blur_render(struct ky_scene_render_target *target, const struct blur_render_options *options, const pixman_region32_t *blur_region, struct kywc_box *buffer_cpy_box) { KY_PROFILE_RENDER_ZONE(&blur_config.renderer->wlr_renderer, gzone, __func__); GLint old_fbo; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &old_fbo); struct ky_opengl_render_pass *gl_pass = ky_opengl_render_pass_from_wlr_render_pass(target->render_pass); struct blur_data *data = &effect_blur_data; struct glrtt_pool *glrtt_pool = data->effect->glrtt_pool; data->iterations = options->blur->iterations; data->offset = options->blur->offset; struct glrtt_pool_texture *src_tex = blur_first_down(data, gl_pass->buffer, buffer_cpy_box); if (!src_tex) { goto final; } blur_fb0(data, src_tex); struct blur_tex_program *prog = &data->blur_tex_prog; if (!prog) { goto final; } glUseProgram(prog->id); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, src_tex->texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glUniform1i(prog->shaders.tex, 0); glUniform1f(prog->shaders.offset, data->offset); glUniform2f(prog->shaders.halfpixel, 0.5f / src_tex->width, 0.5f / src_tex->height); float alpha = options->alpha ? *options->alpha : 1.0f; glUniform1f(prog->shaders.alpha, alpha * options->blur->alpha); set_pos_matrix(prog->shaders.uv2ndc, target->buffer->width, target->buffer->height, buffer_cpy_box); struct kywc_fbox tex_src_fbox = { .x = 0, .y = 0, .width = 1.0f, .height = 1.0f }; set_tex_matrix(prog->shaders.uv2texcoord, WL_OUTPUT_TRANSFORM_NORMAL, &tex_src_fbox); struct kywc_fbox shape_fbox = { .x = 0, .y = 0, .width = 1.0f, .height = 1.0f }; set_tex_matrix(prog->shaders.uv2shapecoord, target->transform, &shape_fbox); int width = options->dst_box->width; int height = options->dst_box->height; if (target->transform & WL_OUTPUT_TRANSFORM_90) { width = options->dst_box->height; height = options->dst_box->width; } glUniform1f(prog->shaders.aspect, width / (float)height); float half_height = (float)height * 0.5f; float one_pixel_distance = 1.0f / half_height; // shader distance scale glUniform1f(prog->shaders.anti_aliasing, one_pixel_distance * 0.5f); if (options->radius) { glUniform4f(prog->shaders.rounded_corner_radius, options->radius->rb * one_pixel_distance, options->radius->rt * one_pixel_distance, options->radius->lb * one_pixel_distance, options->radius->lt * one_pixel_distance); } else { glUniform4f(prog->shaders.rounded_corner_radius, 0, 0, 0, 0); } glBindFramebuffer(GL_FRAMEBUFFER, old_fbo); render(buffer_cpy_box, blur_region, prog->shaders.pos_attrib, options->dst_box, prog->shaders.sdfpos_attrib); glrtt_pool_release_texture(glrtt_pool, src_tex); // call release_timeout in blur, better than frame_pre() // in frame_pre will pre release blur need use texture glrtt_pool_release_timeout_texture(glrtt_pool); glBindTexture(GL_TEXTURE_2D, 0); glUseProgram(0); final: KY_PROFILE_RENDER_ZONE_END(&blur_config.renderer->wlr_renderer); } static void get_copy_box(const pixman_region32_t *blur_region, const struct blur_info *blur_info, const pixman_region32_t *damaged_blur_region, struct kywc_box *copy_box) { /** * the copy area may be larger than the damage area, but it has no effect. * because it is outside the radius of blur. */ int align_num = calculate_blur_radius(blur_info->iterations, blur_info->offset); pixman_box32_t *damage_bbox = pixman_region32_extents(damaged_blur_region); /* pos in frame_buffer */ struct kywc_box buffer_cpy_box = { .x = align_num * (int)(damage_bbox->x1 / align_num), .y = align_num * (int)(damage_bbox->y1 / align_num), }; /* copybox larger than damage blur bounding box */ buffer_cpy_box.width = align_num * ((damage_bbox->x2 - buffer_cpy_box.x) / align_num + 1); buffer_cpy_box.height = align_num * ((damage_bbox->y2 - buffer_cpy_box.y) / align_num + 1); /* copybox smaller than blur box */ pixman_box32_t *blur_box = pixman_region32_extents(blur_region); copy_box->x = buffer_cpy_box.x > blur_box->x1 ? buffer_cpy_box.x : blur_box->x1; copy_box->y = buffer_cpy_box.y > blur_box->y1 ? buffer_cpy_box.y : blur_box->y1; copy_box->width = buffer_cpy_box.x + buffer_cpy_box.width < blur_box->x2 ? buffer_cpy_box.width : blur_box->x2 - copy_box->x; copy_box->height = buffer_cpy_box.y + buffer_cpy_box.height < blur_box->y2 ? buffer_cpy_box.height : blur_box->y2 - copy_box->y; } void blur_render_with_target(struct ky_scene_render_target *target, const struct blur_render_options *options) { if (!options->blur || options->blur->alpha <= 0.0f || options->blur->offset <= 0 || !blur_config.renderer || !effect_blur_data.effect->effect->enabled || !get_or_generate_blur_text_program() || !get_or_generate_blur_program() || (options->alpha && *options->alpha <= 0.0f)) { return; } pixman_region32_t blur_region; if (pixman_region32_not_empty(&options->blur->region)) { pixman_region32_init(&blur_region); pixman_region32_copy(&blur_region, &options->blur->region); pixman_region32_translate(&blur_region, options->lx - target->logical.x, options->ly - target->logical.y); ky_scene_render_region(&blur_region, target); } else { pixman_region32_init_rect(&blur_region, options->dst_box->x, options->dst_box->y, options->dst_box->width, options->dst_box->height); } pixman_region32_t damaged_blur_region; pixman_region32_init(&damaged_blur_region); pixman_region32_intersect(&damaged_blur_region, &blur_region, options->clip); if (pixman_region32_not_empty(&blur_region)) { struct kywc_box buffer_cpy_box; get_copy_box(&blur_region, options->blur, &damaged_blur_region, &buffer_cpy_box); blur_render(target, options, &damaged_blur_region, &buffer_cpy_box); } pixman_region32_fini(&damaged_blur_region); pixman_region32_fini(&blur_region); } static void blur_expand_damage(const struct blur_info *blur, pixman_region32_t *damaged_blur, pixman_region32_t *half_expand_damage, struct ky_scene_render_target *target) { int distance = calculate_blur_radius(blur->iterations, blur->offset); distance = ceil(distance / target->scale); /* blur expand region, region for blur */ wlr_region_expand(damaged_blur, damaged_blur, distance); pixman_region32_intersect(damaged_blur, damaged_blur, &blur->region); pixman_region32_union(half_expand_damage, half_expand_damage, damaged_blur); /* region for copyback and blur */ wlr_region_expand(damaged_blur, damaged_blur, distance); pixman_region32_intersect(damaged_blur, damaged_blur, &blur->region); /* blur expand region add to damage */ if (target->scale == floor(target->scale)) { pixman_region32_union(&target->damage, &target->damage, damaged_blur); return; } /* fractional scaling extends by one more pixel */ pixman_region32_t add_damage; pixman_region32_init(&add_damage); wlr_region_expand(&add_damage, damaged_blur, 1); pixman_region32_intersect(damaged_blur, &add_damage, &blur->region); pixman_region32_union(&target->damage, &target->damage, &add_damage); pixman_region32_fini(&add_damage); } static void node_for_each_blur_region(struct ky_scene_node *node, struct ky_scene_render_target *target, int lx, int ly, struct wl_list *nodes, pixman_region32_t *half_expand_damage) { if (!ky_scene_node_is_visible(node)) { return; } if (node->type == KY_SCENE_NODE_TREE) { struct ky_scene_tree *tree = ky_scene_tree_from_node(node); struct ky_scene_node *child; wl_list_for_each_reverse(child, &tree->children, link) { node_for_each_blur_region(child, target, lx + child->x, ly + child->y, nodes, half_expand_damage); } return; } if (!node->has_blur) { return; } struct blur_info blur_info; pixman_region32_init(&blur_info.region); ky_scene_node_get_blur_info(node, &blur_info); if (!pixman_region32_not_empty(&blur_info.region)) { pixman_region32_fini(&blur_info.region); kywc_log(KYWC_DEBUG, "node has blur, but region is empty."); return; } pixman_region32_translate(&blur_info.region, lx, ly); pixman_region32_intersect_rect(&blur_info.region, &blur_info.region, target->logical.x, target->logical.y, target->logical.width, target->logical.height); pixman_region32_t visible_blur; pixman_region32_init(&visible_blur); pixman_region32_intersect(&visible_blur, &blur_info.region, &node->visible_region); struct blur_node *blur_node = create_blur_node(node, &blur_info, &visible_blur); if (!blur_node) { pixman_region32_fini(&blur_info.region); pixman_region32_fini(&visible_blur); return; } /* blur in damage and visible */ pixman_region32_t damaged_visible_blur; pixman_region32_init(&damaged_visible_blur); pixman_region32_intersect(&damaged_visible_blur, &visible_blur, &target->damage); pixman_region32_fini(&visible_blur); if (!pixman_region32_not_empty(&damaged_visible_blur)) { wl_list_insert(nodes, &blur_node->link); pixman_region32_fini(&blur_info.region); pixman_region32_fini(&damaged_visible_blur); return; } /* node damaged blur region */ pixman_region32_t new_half_expand_damage; pixman_region32_init(&new_half_expand_damage); pixman_region32_copy(&new_half_expand_damage, half_expand_damage); blur_expand_damage(&blur_info, &damaged_visible_blur, half_expand_damage, target); pixman_region32_subtract(&new_half_expand_damage, half_expand_damage, &new_half_expand_damage); pixman_region32_copy(&blur_node->damaged_blur, &damaged_visible_blur); pixman_region32_fini(&damaged_visible_blur); pixman_region32_fini(&blur_info.region); /** * expand region maybe out the damaged blur region of up node * and in the visible blur region of up node. the region like the damage * region from blur's backgroud. */ pixman_region32_t node_expand_blur_damage; pixman_region32_init(&node_expand_blur_damage); struct blur_node *up_blur_node; wl_list_for_each(up_blur_node, nodes, link) { pixman_region32_intersect(&node_expand_blur_damage, &up_blur_node->visible_blur, &new_half_expand_damage); if (!pixman_region32_not_empty(&node_expand_blur_damage)) { continue; } blur_expand_damage(&up_blur_node->blur, &node_expand_blur_damage, &new_half_expand_damage, target); pixman_region32_union(&up_blur_node->damaged_blur, &up_blur_node->damaged_blur, &node_expand_blur_damage); pixman_region32_union(half_expand_damage, half_expand_damage, &new_half_expand_damage); } wl_list_insert(nodes, &blur_node->link); pixman_region32_fini(&new_half_expand_damage); pixman_region32_fini(&node_expand_blur_damage); return; } static void node_for_each_visible_region(struct ky_scene_node *node, int lx, int ly, struct wl_list *nodes, pixman_region32_t *damaged_blur) { pixman_region32_clear(&node->extend_render_region); if (!node->enabled) { return; } if (node->type == KY_SCENE_NODE_TREE) { struct ky_scene_tree *tree = ky_scene_tree_from_node(node); struct ky_scene_node *child; wl_list_for_each_reverse(child, &tree->children, link) { if (child->has_blur) { struct blur_node *blur_node, *tmp; wl_list_for_each_safe(blur_node, tmp, nodes, link) { if (blur_node->node != child) { continue; } pixman_region32_union(damaged_blur, damaged_blur, &blur_node->damaged_blur); destroy_blur_node(blur_node); break; } } node_for_each_visible_region(child, lx + child->x, ly + child->y, nodes, damaged_blur); } return; } /* the blur damage must be refresh */ if (!pixman_region32_not_empty(damaged_blur)) { return; } struct wlr_box box; node->impl.get_bounding_box(node, &box); pixman_region32_t blur_update_region; pixman_region32_init_rect(&blur_update_region, 0, 0, box.width, box.height); if (pixman_region32_not_empty(&node->clip_region)) { pixman_region32_intersect(&blur_update_region, &blur_update_region, &node->clip_region); } pixman_region32_translate(&blur_update_region, box.x + lx, box.y + ly); pixman_region32_intersect(&blur_update_region, &blur_update_region, damaged_blur); pixman_region32_copy(&node->extend_render_region, &blur_update_region); pixman_region32_fini(&blur_update_region); pixman_region32_t node_opaque; pixman_region32_init(&node_opaque); node->impl.get_opaque_region(node, &node_opaque); if (!pixman_region32_not_empty(&node_opaque)) { pixman_region32_fini(&node_opaque); return; } pixman_region32_translate(&node_opaque, lx, ly); pixman_region32_subtract(damaged_blur, damaged_blur, &node_opaque); pixman_region32_fini(&node_opaque); } static bool blur_frame_render_begin(struct effect_entity *entity, struct ky_scene_render_target *target) { struct blur_data *data = entity->user_data; struct ky_opengl_render_pass *gl_pass = ky_opengl_render_pass_from_wlr_render_pass(target->render_pass); struct wlr_texture *src_tex = ky_opengl_texture_from_buffer(&blur_config.renderer->wlr_renderer, gl_pass->buffer->buffer); data->current_output_texture = ky_opengl_texture_from_wlr_texture(src_tex); struct blur_output_data *output_data = NULL, *exits_data; wl_list_for_each(exits_data, &data->output_data, link) { if (exits_data->output == target->output) { output_data = exits_data; break; } } data->current_output_data = output_data; if (!output_data) { return true; } pixman_region32_clear(&output_data->unaffected_region); pixman_region32_t all_blur, damaged_blur, half_expand_damage; pixman_region32_init(&all_blur); pixman_region32_init(&damaged_blur); pixman_region32_init(&half_expand_damage); pixman_region32_copy(&half_expand_damage, &target->damage); struct wl_list blur_nodes; wl_list_init(&blur_nodes); struct ky_scene_node *node = &data->effect->scene->tree.node; node_for_each_blur_region(node, target, 0, 0, &blur_nodes, &half_expand_damage); node_for_each_visible_region(node, 0, 0, &blur_nodes, &damaged_blur); destroy_blur_nodes(&blur_nodes); /* the target must be output in render begin */ pixman_region32_intersect_rect(&target->damage, &target->damage, target->logical.x, target->logical.y, target->logical.width, target->logical.height); pixman_region32_subtract(&output_data->unaffected_region, &target->damage, &half_expand_damage); pixman_region32_fini(&all_blur); pixman_region32_fini(&damaged_blur); pixman_region32_fini(&half_expand_damage); return true; } static bool blur_frame_render_end(struct effect_entity *entity, struct ky_scene_render_target *target) { struct blur_data *data = entity->user_data; if (data->current_output_texture) { wlr_texture_destroy(&data->current_output_texture->wlr_texture); data->current_output_texture = NULL; } struct blur_output_data *output_data = data->current_output_data; if (!output_data || !output_data->output_buffer || !pixman_region32_not_empty(&output_data->unaffected_region)) { return true; } /** * if sub the unaffected region from target damage, * the new target damage (to framebuffer coord) union (+) unaffected region (to framebuffer * coord) maybe not equal (!=) target old framebuffer damage (to framebuffer coord), when output * scale is not integer. */ pixman_region32_union(&target->excluded_damage, &target->excluded_damage, &output_data->unaffected_region); pixman_region32_translate(&output_data->unaffected_region, -target->logical.x, -target->logical.y); ky_scene_render_region(&output_data->unaffected_region, target); KY_PROFILE_RENDER_ZONE(&blur_config.renderer->wlr_renderer, gzone, __func__); struct wlr_texture *texture = wlr_texture_from_buffer(target->output->output->renderer, output_data->output_buffer); struct wlr_render_texture_options options = { .texture = texture, .clip = &output_data->unaffected_region, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, }; wlr_render_pass_add_texture(target->render_pass, &options); wlr_texture_destroy(texture); KY_PROFILE_RENDER_ZONE_END(&blur_config.renderer->wlr_renderer); return true; } static void blur_output_data_buffer_destroy(struct blur_output_data *data) { if (!data->output_buffer) { return; } wl_list_remove(&data->buffer_destroy.link); wl_list_init(&data->buffer_destroy.link); data->output_buffer = NULL; } static void blur_output_data_destroy(struct blur_output_data *data) { wl_list_remove(&data->output_destroy.link); blur_output_data_buffer_destroy(data); pixman_region32_fini(&data->unaffected_region); wl_list_remove(&data->link); free(data); } static void handle_output_buffer_destroy(struct wl_listener *listener, void *data) { struct blur_output_data *_data = wl_container_of(listener, _data, buffer_destroy); blur_output_data_buffer_destroy(_data); } static void handle_output_destroy(struct wl_listener *listener, void *data) { struct blur_output_data *_data = wl_container_of(listener, _data, output_destroy); blur_output_data_destroy(_data); } static bool blur_frame_render_post(struct effect_entity *entity, struct ky_scene_render_target *target) { struct blur_data *data = entity->user_data; struct wlr_buffer *buffer = target->buffer; struct blur_output_data *output_data = NULL, *exits_data; wl_list_for_each(exits_data, &data->output_data, link) { if (exits_data->output == target->output) { output_data = exits_data; break; } } if (output_data) { if (output_data->output_buffer == buffer) { return true; } wl_list_remove(&output_data->buffer_destroy.link); output_data->output_buffer = buffer; wl_signal_add(&buffer->events.destroy, &output_data->buffer_destroy); return true; } output_data = calloc(1, sizeof(*output_data)); if (!output_data) { return true; } wl_list_insert(&data->output_data, &output_data->link); pixman_region32_init(&output_data->unaffected_region); output_data->output = target->output; output_data->output_destroy.notify = handle_output_destroy; wl_signal_add(&target->output->events.destroy, &output_data->output_destroy); output_data->output_buffer = buffer; output_data->buffer_destroy.notify = handle_output_buffer_destroy; wl_signal_add(&buffer->events.destroy, &output_data->buffer_destroy); return true; } static void handle_effect_enable(struct wl_listener *listener, void *data) { struct blur_effect *effect = wl_container_of(listener, effect, enable); ky_scene_damage_whole(effect->scene); } static void handle_effect_disable(struct wl_listener *listener, void *data) { struct blur_effect *effect = wl_container_of(listener, effect, disable); ky_scene_damage_whole(effect->scene); } static bool handle_blur_effect_configure(struct effect *effect, const struct effect_option *option) { if (effect_option_is_enabled_option(option)) { return true; } return false; } static void handle_effect_destroy(struct wl_listener *listener, void *data) { struct blur_effect *effect = wl_container_of(listener, effect, destroy); wl_list_remove(&effect->destroy.link); wl_list_remove(&effect->enable.link); wl_list_remove(&effect->disable.link); blur_data_destroy(&effect_blur_data); glrtt_pool_destroy(effect->glrtt_pool); free(effect); } static const struct effect_interface blur_impl = { .frame_render_begin = blur_frame_render_begin, .frame_render_end = blur_frame_render_end, .frame_render_post = blur_frame_render_post, .configure = handle_blur_effect_configure, }; bool blur_effect_create(struct effect_manager *effect_manager) { /* check blur if opengl renderer support */ struct wlr_renderer *renderer = effect_manager->server->renderer; if (!wlr_renderer_is_opengl(renderer)) { blur_config.renderer = NULL; return false; } blur_config.renderer = ky_opengl_renderer_from_wlr_renderer(renderer); struct blur_effect *effect = calloc(1, sizeof(*effect)); if (!effect) { return false; } /* the priority very high, ensure correct display before other effect paint. */ effect->effect = effect_create("blur", 999, true, &blur_impl, NULL); if (!effect->effect) { free(effect); return false; } effect->effect->category = EFFECT_CATEGORY_STYLE; effect->glrtt_pool = glrtt_pool_create(64 * 1024 * 1024, 4, GL_RGBA, GL_LINEAR, 3000); if (!effect->glrtt_pool) { free(effect); return false; } effect->scene = effect_manager->server->scene; effect->enable.notify = handle_effect_enable; wl_signal_add(&effect->effect->events.enable, &effect->enable); effect->disable.notify = handle_effect_disable; wl_signal_add(&effect->effect->events.disable, &effect->disable); effect->destroy.notify = handle_effect_destroy; wl_signal_add(&effect->effect->events.destroy, &effect->destroy); effect_blur_data.effect = effect; wl_list_init(&effect_blur_data.output_data); struct effect_entity *entity = ky_scene_add_effect(effect->scene, effect->effect); if (!entity) { effect_destroy(effect->effect); return false; } entity->user_data = &effect_blur_data; return true; } kylin-wayland-compositor/src/security/0000775000175000017500000000000015160461067017123 5ustar fengfengkylin-wayland-compositor/src/security/security.c0000664000175000017500000001133215160461067021136 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include "security.h" #include "server.h" struct security_global_filter { struct wl_list link; struct wl_global *global; security_global_filter_func_t filter; void *filter_data; }; struct security_manager { struct wl_listener new_client; struct wl_list global_filters; struct wl_listener display_destroy; struct wl_listener server_destroy; }; static struct security_manager *manager = NULL; static char *get_path_from_pid(pid_t pid) { char link_file[32]; snprintf(link_file, sizeof(link_file), "/proc/%d/exe", (int)pid); char link_target[PATH_MAX]; // PATH_MAX includes the terminating NULL byte ssize_t read_len = readlink(link_file, link_target, PATH_MAX - 1); if (read_len < 0) { kywc_log_errno(KYWC_ERROR, "Failed to readlink() %s", link_file); return NULL; } /* should not assume that the returned contents are null-terminated */ link_target[read_len] = '\0'; return strdup(link_target); } static void handle_client_destroy(void *data) { struct security_client *client = data; kywc_log(KYWC_DEBUG, "Client(pid=%d) %s is disconnect", client->pid, client->path); free(client->path); free(client); } static void handle_new_client(struct wl_listener *listener, void *data) { struct security_client *client = calloc(1, sizeof(*client)); if (!client) { return; } struct wl_client *wl_client = data; client->fd = wl_client_get_fd(wl_client); wl_client_get_credentials(wl_client, &client->pid, &client->uid, &client->gid); client->path = get_path_from_pid(client->pid); kywc_log(KYWC_DEBUG, "Client(pid=%d) %s is connect", client->pid, client->path); client->client = wl_client; wl_client_set_user_data(wl_client, client, handle_client_destroy); } static struct security_global_filter *global_filter_from_global(const struct wl_global *global) { struct security_global_filter *global_filter; wl_list_for_each(global_filter, &manager->global_filters, link) { if (global_filter->global == global) { return global_filter; } } return NULL; } static bool filter_global(const struct wl_client *wl_client, const struct wl_global *wl_global, void *data) { // TODO: add some builtin filter rules struct security_global_filter *filter = global_filter_from_global(wl_global); if (!filter) { return true; } struct security_client *client = wl_client_get_user_data((struct wl_client *)wl_client); return filter->filter(client, filter->filter_data); } bool security_add_global_filter(struct wl_global *global, security_global_filter_func_t filter, void *data) { struct security_global_filter *global_filter = global_filter_from_global(global); if (!global_filter) { global_filter = calloc(1, sizeof(*global_filter)); if (!global_filter) { return false; } wl_list_insert(&manager->global_filters, &global_filter->link); } global_filter->global = global; global_filter->filter = filter; global_filter->filter_data = data; return true; } void security_remove_global_filter(struct wl_global *global) { struct security_global_filter *filter = global_filter_from_global(global); if (filter) { wl_list_remove(&filter->link); free(filter); } } static void handle_server_destroy(struct wl_listener *listener, void *data) { /* global fliter should be removed when global destroy */ assert(wl_list_empty(&manager->global_filters)); wl_list_remove(&manager->server_destroy.link); free(manager); manager = NULL; } static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->display_destroy.link); wl_list_remove(&manager->new_client.link); } bool security_manager_create(struct server *server) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } manager->new_client.notify = handle_new_client; wl_display_add_client_created_listener(server->display, &manager->new_client); manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(server->display, &manager->display_destroy); manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->server_destroy); wl_list_init(&manager->global_filters); wl_display_set_global_filter(server->display, filter_global, NULL); return true; } kylin-wayland-compositor/src/security/meson.build0000664000175000017500000000005215160460057021260 0ustar fengfengwlcom_sources += files( 'security.c', ) kylin-wayland-compositor/src/plugin/0000775000175000017500000000000015160461067016552 5ustar fengfengkylin-wayland-compositor/src/plugin/config.c0000664000175000017500000002673415160461067020177 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include "config.h" #include "plugin.h" #include "util/dbus.h" static const char *service_path = "/com/kylin/Wlcom/Plugin"; static const char *service_interface = "com.kylin.Wlcom.Plugin"; static void option_to_json(struct kywc_plugin_option *option, json_object *parent) { json_object *value; switch (option->type) { case option_type_boolean: value = json_object_new_boolean(option->value.boolean); break; case option_type_double: value = json_object_new_double(option->value.realnum); break; case option_type_int: value = json_object_new_int(option->value.num); break; case option_type_string: value = json_object_new_string(option->value.str); break; default: return; } json_object_object_add(parent, option->name, value); } static int list_plugins(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct plugin_manager *pm = userdata; int rescan = false; CK(sd_bus_message_read(m, "b", &rescan)); if (rescan) { plugin_manager_rescan_plugin(); } sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(m, &reply)); CK(sd_bus_message_open_container(reply, 'a', "(sbb)")); struct plugin *plugin; wl_list_for_each(plugin, &pm->plugins, link) { CK(sd_bus_message_append(reply, "(sbb)", plugin->name, plugin->loaded, plugin->enabled)); } CK(sd_bus_message_close_container(reply)); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } static int print_plugin_info(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *name = NULL; CK(sd_bus_message_read(m, "s", &name)); struct plugin *plugin = plugin_manager_get_plugin(name); if (!plugin) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid plugin name."); return sd_bus_reply_method_error(m, &error); } if (!plugin->loaded) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Plugin is not loaded."); return sd_bus_reply_method_error(m, &error); } return sd_bus_reply_method_return(m, "sssuu", plugin->info->vendor, plugin->info->class, plugin->info->description, plugin->info->version, plugin->info->abi_version); } static int print_plugin_config(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *name = NULL; CK(sd_bus_message_read(m, "s", &name)); struct plugin *plugin = plugin_manager_get_plugin(name); if (!plugin) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid plugin name."); return sd_bus_reply_method_error(m, &error); } json_object *config = json_object_new_object(); json_object_object_add(config, "enabled", json_object_new_boolean(plugin->enabled)); json_object *options = json_object_new_object(); json_object_object_add(config, "options", options); struct plugin_config_entry *entry; wl_list_for_each_reverse(entry, &plugin->entries, link) { option_to_json(&entry->option, options); } sd_bus_message *reply = NULL; CK(sd_bus_message_new_method_return(m, &reply)); const char *cfg = json_object_to_json_string(config); sd_bus_message_append_basic(reply, 's', cfg); json_object_put(config); CK(sd_bus_send(NULL, reply, NULL)); sd_bus_message_unref(reply); return 1; } static int load_plugin(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *name = NULL; int loaded = false; CK(sd_bus_message_read(m, "sb", &name, &loaded)); struct plugin *plugin = plugin_manager_get_plugin(name); if (!plugin) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid plugin name."); return sd_bus_reply_method_error(m, &error); } if (loaded) { plugin_manager_load_plugin(plugin); } else { plugin_manager_unload_plugin(plugin); } return sd_bus_reply_method_return(m, NULL); } static int enable_plugin(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *name = NULL; int enabled = false; CK(sd_bus_message_read(m, "sb", &name, &enabled)); struct plugin *plugin = plugin_manager_get_plugin(name); if (!plugin) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid plugin name."); return sd_bus_reply_method_error(m, &error); } if (!plugin->loaded) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Plugin is not loaded."); return sd_bus_reply_method_error(m, &error); } if (plugin_manager_enable_plugin(plugin, enabled)) { plugin_write_config(plugin); } return sd_bus_reply_method_return(m, NULL); } static int set_plugin_option(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { const char *name = NULL, *option = NULL, *type = NULL; CK(sd_bus_message_read(m, "ss", &name, &option)); struct plugin *plugin = plugin_manager_get_plugin(name); if (!plugin) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid plugin name."); return sd_bus_reply_method_error(m, &error); } CK(sd_bus_message_peek_type(m, NULL, &type)); struct kywc_plugin_option opt = { option, { 0 }, option_type_null, -1 }; if (!strcmp(type, "i")) { CK(sd_bus_message_read(m, "v", "i", &opt.value.num)); opt.type = option_type_int; } else if (!strcmp(type, "b")) { CK(sd_bus_message_read(m, "v", "b", &opt.value.boolean)); opt.type = option_type_boolean; } else if (!strcmp(type, "d")) { CK(sd_bus_message_read(m, "v", "d", &opt.value.realnum)); opt.type = option_type_double; } else if (!strcmp(type, "s")) { CK(sd_bus_message_read(m, "v", "s", &opt.value.str)); opt.type = option_type_string; } else { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid option type."); return sd_bus_reply_method_error(m, &error); } if (!plugin->loaded || !plugin->enabled) { const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Plugin is not loaded or enabled."); return sd_bus_reply_method_error(m, &error); } /* find config entry in entries */ struct plugin_config_entry *entry; wl_list_for_each(entry, &plugin->entries, link) { if (!kywc_plugin_option_match(&entry->option, &opt)) { continue; } /* option value is not changed */ if (kywc_plugin_option_value_equal(entry->option.value, opt.value, entry->option.type)) { return sd_bus_reply_method_return(m, NULL); } if (opt.type == option_type_string) { free((void *)entry->option.value.str); entry->option.value.str = strdup(opt.value.str); } else { entry->option.value.str = opt.value.str; } plugin->option(&entry->option); plugin_write_config(plugin); return sd_bus_reply_method_return(m, NULL); } const sd_bus_error error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "option not found or type error."); return sd_bus_reply_method_error(m, &error); } static const sd_bus_vtable service_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("ListAllPlugins", "b", "a(sbb)", list_plugins, 0), SD_BUS_METHOD("PrintPluginInfo", "s", "sssuu", print_plugin_info, 0), SD_BUS_METHOD("PrintPluginConfig", "s", "s", print_plugin_config, 0), SD_BUS_METHOD("LoadPlugin", "sb", "", load_plugin, 0), SD_BUS_METHOD("EnablePlugin", "sb", "", enable_plugin, 0), SD_BUS_METHOD("SetPluginOption", "ssv", "", set_plugin_option, 0), SD_BUS_VTABLE_END, }; bool plugin_manager_config_init(struct plugin_manager *plugin_manager) { plugin_manager->config = config_manager_add_config("plugins"); if (!plugin_manager->config) { return false; } return dbus_register_object(NULL, service_path, service_interface, service_vtable, plugin_manager); } static void option_from_json(struct kywc_plugin_option *option, json_object *value) { enum json_type type = json_object_get_type(value); switch (type) { case json_type_boolean: option->type = option_type_boolean; option->value.boolean = json_object_get_boolean(value); break; case json_type_double: option->type = option_type_double; option->value.realnum = json_object_get_double(value); break; case json_type_int: option->type = option_type_int; option->value.num = json_object_get_int(value); break; case json_type_string: option->type = option_type_string; option->value.str = strdup(json_object_get_string(value)); break; default: break; } } bool plugin_need_load(struct plugin *plugin) { struct plugin_manager *pm = plugin->manager; json_object *config, *data; if (pm->config && pm->config->json && (config = json_object_object_get(pm->config->json, plugin->name)) && json_object_object_get_ex(config, "enabled", &data)) { return json_object_get_boolean(data); } return true; } void plugin_read_config(struct plugin *plugin) { struct plugin_manager *pm = plugin->manager; if (!pm->config || !pm->config->json) { return; } /* get this plugin's config */ json_object *config = json_object_object_get(pm->config->json, plugin->name); /* if no config, default to enable */ if (!config) { return; } json_object *data; /* no options found in config */ if (!json_object_object_get_ex(config, "options", &data)) { return; } json_object_object_foreach(data, key, value) { struct plugin_config_entry *entry = calloc(1, sizeof(*entry)); if (!entry) { continue; } entry->option.name = key; option_from_json(&entry->option, value); wl_list_insert(&plugin->entries, &entry->link); } } void plugin_write_config(struct plugin *plugin) { struct plugin_manager *pm = plugin->manager; if (!pm->config || !pm->config->json) { return; } json_object *config = json_object_object_get(pm->config->json, plugin->name); if (!config) { config = json_object_new_object(); json_object_object_add(pm->config->json, plugin->name, config); } json_object_object_add(config, "enabled", json_object_new_boolean(plugin->enabled)); json_object *options = json_object_object_get(config, "options"); if (!options) { options = json_object_new_object(); json_object_object_add(config, "options", options); } struct plugin_config_entry *entry; wl_list_for_each(entry, &plugin->entries, link) { /* filter default option */ if (kywc_plugin_option_value_equal(entry->option.value, entry->default_value, entry->option.type)) { json_object_object_del(options, entry->option.name); continue; } option_to_json(&entry->option, options); } } kylin-wayland-compositor/src/plugin/plugin.c0000664000175000017500000002361315160461067020221 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include "plugin.h" #include "server.h" #include "util/file.h" static struct plugin_manager *plugin_manager = NULL; static struct plugin *plugin_create(const char *fullpath, const char *name, bool rescan) { int index = (int)strlen(name) - 3; if (index <= 0) { return NULL; } if (strcasecmp(name + index, ".so") != 0) { return NULL; } struct plugin *plugin; if (rescan) { wl_list_for_each(plugin, &plugin_manager->plugins, link) { if (strcmp(plugin->name, name) == 0) { return plugin; } } } /* create a plugin and insert to plugin manager */ plugin = calloc(1, sizeof(*plugin)); if (!plugin) { return NULL; } plugin->name = strndup(name, index); plugin->path = strdup(fullpath); plugin->manager = plugin_manager; wl_list_init(&plugin->entries); wl_list_insert(&plugin_manager->plugins, &plugin->link); return plugin; } static bool plugin_filter(const char *fullpath, const char *subpath, const char *name, void *data) { if (name == DIR_NOT_EXIST) { return false; } if (name) { plugin_create(fullpath, name, *(bool *)data); return false; } /* only search in one level */ if (subpath) { return true; } return false; } static void scan_all_plugins(bool rescan) { kywc_log(KYWC_DEBUG, "Search all plugins in %s", PLUGIN_DIR); if (!file_exists(PLUGIN_DIR)) { kywc_log(KYWC_WARN, "Plugin dir is not exists"); return; } file_for_each(PLUGIN_DIR, NULL, plugin_filter, &rescan); } void plugin_manager_rescan_plugin(void) { scan_all_plugins(true); } static struct kywc_plugin_option *find_option_by_token(struct plugin *plugin, int32_t token, option_type type) { struct plugin_config_entry *entry; wl_list_for_each(entry, &plugin->entries, link) { struct kywc_plugin_option *option = &entry->option; if (option->token == token && option->type == type) { return option; } } return NULL; } bool kywc_plugin_get_option_boolean(void *plugin, int32_t token, bool *value) { struct kywc_plugin_option *option = find_option_by_token(plugin, token, option_type_boolean); if (!option) { return false; } if (value) { *value = option->value.boolean; } return true; } bool kywc_plugin_get_option_double(void *plugin, int32_t token, double *value) { struct kywc_plugin_option *option = find_option_by_token(plugin, token, option_type_double); if (!option) { return false; } if (value) { *value = option->value.realnum; } return true; } bool kywc_plugin_get_option_int(void *plugin, int32_t token, int *value) { struct kywc_plugin_option *option = find_option_by_token(plugin, token, option_type_int); if (!option) { return false; } if (value) { *value = option->value.num; } return true; } const char *kywc_plugin_get_option_string(void *plugin, int32_t token) { struct kywc_plugin_option *option = find_option_by_token(plugin, token, option_type_string); if (!option) { return NULL; } return option->value.str; } static void plugin_destroy(struct plugin *plugin) { wl_list_remove(&plugin->link); free((void *)plugin->name); free((void *)plugin->path); free(plugin); } static void plugin_destroy_config_entry(struct plugin_config_entry *entry) { wl_list_remove(&entry->link); if (entry->option.type == option_type_string) { free((void *)entry->option.value.str); } free(entry); } static struct kywc_plugin_option *check_option(struct plugin *plugin, struct kywc_plugin_option *option) { struct kywc_plugin_option *default_option = plugin->options; if (!default_option) { return NULL; } /* search this option in plugin->options */ while (default_option->name) { if (!kywc_plugin_option_match(default_option, option)) { default_option++; continue; } return default_option; } return NULL; } static void plugin_merge_options(struct plugin *plugin) { uint64_t mask = 0; /* read options from config */ plugin_read_config(plugin); struct kywc_plugin_option *option; struct plugin_config_entry *entry, *entry_tmp; wl_list_for_each_safe(entry, entry_tmp, &plugin->entries, link) { option = check_option(plugin, &entry->option); /* remove if not support */ if (!option) { plugin_destroy_config_entry(entry); continue; } entry->option.token = option->token; entry->default_value = option->value; mask |= 0x1ull << (option - plugin->options); } /* merge plugin default options */ option = plugin->options; while (option && option->name) { /* this option was not configured */ if (!((mask >> (option - plugin->options)) & 0x1ull)) { struct plugin_config_entry *entry = calloc(1, sizeof(*entry)); if (!entry) { continue; } entry->default_value = option->value; entry->option = *option; if (option->type == option_type_string) { entry->option.value.str = strdup(option->value.str); } wl_list_insert(&plugin->entries, &entry->link); } option++; } } static bool load_plugin(struct plugin *plugin, bool force) { /* disabled in config */ if (!plugin_need_load(plugin) && !force) { return false; } kywc_log(KYWC_INFO, "Load plugin %s", plugin->path); void *handle = dlopen(plugin->path, RTLD_LAZY | RTLD_GLOBAL); if (!handle) { kywc_log(KYWC_ERROR, "Failed to load %s: %s", plugin->name, dlerror()); return false; } size_t len = strlen(plugin->name) + strlen("_plugin_data") + 1; char *symbol = calloc(len, sizeof(char)); snprintf(symbol, len, "%s_plugin_data", plugin->name); struct kywc_plugin_data *pdata = dlsym(handle, symbol); free(symbol); /* check plugin data */ if (!pdata || !pdata->info || !pdata->setup || !pdata->teardown) { kywc_log(KYWC_ERROR, "%s: invalid symbol", plugin->name); dlclose(handle); return false; } // TODO: check version plugin->info = pdata->info; plugin->options = pdata->options; plugin->setup = pdata->setup; plugin->option = pdata->option; plugin->teardown = pdata->teardown; plugin->handle = handle; plugin->loaded = true; /* merge plugin options with config and default */ plugin_merge_options(plugin); kywc_log(KYWC_INFO, "Plugin %s: %d.%d.%d %s", plugin->info->name, GET_PLUGIN_MAJOR_VERSION(plugin->info->version), GET_PLUGIN_MINOR_VERSION(plugin->info->version), GET_PLUGIN_MICRO_VERSION(plugin->info->version), plugin->info->vendor); return true; } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&plugin_manager->server_destroy.link); struct plugin *plugin, *plugin_tmp; wl_list_for_each_safe(plugin, plugin_tmp, &plugin_manager->plugins, link) { plugin_manager_unload_plugin(plugin); plugin_destroy(plugin); } free(plugin_manager); plugin_manager = NULL; } struct plugin_manager *plugin_manager_create(struct server *server) { plugin_manager = calloc(1, sizeof(*plugin_manager)); if (!plugin_manager) { return NULL; } wl_list_init(&plugin_manager->plugins); plugin_manager->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &plugin_manager->server_destroy); plugin_manager_config_init(plugin_manager); /* scan all plugins in plugin dir */ scan_all_plugins(false); struct plugin *plugin, *plugin_tmp; wl_list_for_each_safe(plugin, plugin_tmp, &plugin_manager->plugins, link) { /* load plugin, remove if invalid */ if (!load_plugin(plugin, false) || !plugin_manager_enable_plugin(plugin, true)) { plugin_manager_unload_plugin(plugin); // cleanup_plugin(plugin); } } return plugin_manager; } struct plugin *plugin_manager_get_plugin(const char *name) { if (!name || !strlen(name)) { return NULL; } struct plugin *plugin; wl_list_for_each(plugin, &plugin_manager->plugins, link) { if (!strcmp(name, plugin->name)) { return plugin; } } return NULL; } struct plugin *plugin_manager_load_plugin(struct plugin *plugin) { if (!plugin->loaded && !load_plugin(plugin, true)) { return NULL; } return plugin; } bool plugin_manager_enable_plugin(struct plugin *plugin, bool enable) { if (enable == plugin->enabled) { return false; } if (!plugin->loaded) { kywc_log(KYWC_WARN, "Plugin %s is not loaded when enable", plugin->name); return false; } if (enable) { if (!plugin->setup(plugin, &plugin->teardown_data)) { kywc_log(KYWC_ERROR, "Plugin %s setup failed", plugin->name); return false; } } else { plugin->teardown(plugin->teardown_data); } plugin->enabled = enable; return true; } void plugin_manager_unload_plugin(struct plugin *plugin) { if (plugin->loaded) { plugin_manager_enable_plugin(plugin, false); dlclose(plugin->handle); } /* free all config entries */ struct plugin_config_entry *entry, *tmp_entry; wl_list_for_each_safe(entry, tmp_entry, &plugin->entries, link) { plugin_destroy_config_entry(entry); } plugin->loaded = false; } kylin-wayland-compositor/src/xwayland/0000775000175000017500000000000015160461067017103 5ustar fengfengkylin-wayland-compositor/src/xwayland/meson.build0000664000175000017500000000037015160460057021243 0ustar fengfengshape = dependency('xcb-shape') xfixes = dependency('xcb-xfixes') sync = dependency('xcb-sync') wlcom_deps += [shape, xfixes, sync] wlcom_sources += files( 'dnd.c', 'drag_x11.c', 'selection.c', 'unmanaged.c', 'view.c', 'xwayland.c', ) kylin-wayland-compositor/src/xwayland/drag_x11.c0000664000175000017500000004554615160461067020673 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include #include "input/cursor.h" #include "output.h" #include "scene/surface.h" #include "view/view.h" #include "xwayland_p.h" static void handle_cursor_motion(struct wl_listener *listener, void *data) { struct xwayland_drag_x11 *drag_x11 = wl_container_of(listener, drag_x11, cursor_motion); struct seat_cursor_motion_event *event = data; struct xwayland_server *xwayland = drag_x11->xwayland; struct seat *seat = seat_from_wlr_seat(drag_x11->xwayland->wlr_xwayland->seat); /* send wayland motion */ if (drag_x11->hovered_surface != NULL && drag_x11->hovered_client != NULL) { struct wl_resource *resource; wl_resource_for_each(resource, &drag_x11->hovered_client->data_devices) { wl_data_device_send_motion(resource, event->time_msec, wl_fixed_from_double(seat->cursor->sx), wl_fixed_from_double(seat->cursor->sy)); } } else if (!drag_x11->hovered_surface) { // send no action to source window when hover NULL xwayland_send_dnd_status(xwayland, xwayland->window_catcher, drag_x11->source_window, 0); } struct wlr_surface *old_surface = drag_x11->hovered_surface; struct wlr_surface *new_surface = seat->cursor->hover.node ? wlr_surface_try_from_node(seat->cursor->hover.node) : NULL; struct view *old_view = old_surface ? view_try_from_wlr_surface(old_surface) : NULL; struct view *new_view = new_surface ? view_try_from_wlr_surface(new_surface) : NULL; if (old_surface == new_surface) { return; } if (old_view && !xwayland_check_view(old_view)) { kywc_log(KYWC_DEBUG, "Leave wayland surface"); xwayland_map_selection_window(xwayland, xwayland->window_catcher, NULL, false); } if (new_view && !xwayland_check_view(new_view)) { kywc_log(KYWC_DEBUG, "Enter wayalnd surface"); int width, height; output_layout_get_size(&width, &height); struct kywc_box box = { .x = 0, .y = 0, .width = width, .height = height }; xwayland_map_selection_window(xwayland, xwayland->window_catcher, &box, true); } drag_set_focus(drag_x11, new_surface, seat->cursor->sx, seat->cursor->sy); } static void handle_cursor_button(struct wl_listener *listener, void *data) { struct xwayland_drag_x11 *drag_x11 = wl_container_of(listener, drag_x11, cursor_button); // default left button drag, TODO: other button drag struct wlr_pointer_button_event *event = data; if (event->button != BTN_LEFT || event->state != WL_POINTER_BUTTON_STATE_RELEASED) { return; } /* some x11 source app do not send drop, so we end drag early if !accpepted */ struct view *hover_view = drag_x11->hovered_surface ? view_try_from_wlr_surface(drag_x11->hovered_surface) : NULL; if (hover_view && !xwayland_check_view(hover_view) && drag_x11->data_source && drag_x11->data_source->base.accepted && drag_x11->data_source->base.current_dnd_action) { return; } struct xwayland_server *xwayland = drag_x11->xwayland; xwayland_end_drag_x11(xwayland); } static void handle_drag_x11_surface_destroy(struct wl_listener *listener, void *data) { struct xwayland_drag_x11 *drag_x11 = wl_container_of(listener, drag_x11, surface_destroy); drag_set_focus(drag_x11, NULL, 0, 0); } static void drag_handle_seat_client_destroy(struct wl_listener *listener, void *data) { struct xwayland_drag_x11 *drag_x11 = wl_container_of(listener, drag_x11, seat_client_destroy); drag_x11->hovered_client = NULL; wl_list_remove(&drag_x11->seat_client_destroy.link); } bool drag_x11_has_data_source(struct xwayland_drag_x11 *drag_x11) { return drag_x11->data_source; } #define DATA_DEVICE_ALL_ACTIONS \ (WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | \ WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) static const struct wl_data_offer_interface data_offer_impl; static struct wlr_data_offer *data_offer_from_resource(struct wl_resource *resource) { assert(wl_resource_instance_of(resource, &wl_data_offer_interface, &data_offer_impl)); return wl_resource_get_user_data(resource); } static void data_offer_source_dnd_finish(struct wlr_data_offer *offer); static void data_offer_destroy(struct wlr_data_offer *offer) { if (offer == NULL) { return; } wl_list_remove(&offer->WLR_PRIVATE.source_destroy.link); wl_list_remove(&offer->link); if (offer->type == WLR_DATA_OFFER_DRAG && offer->source) { // If the drag destination has version < 3, wl_data_offer.finish // won't be called, so do this here as a safety net, because // we still want the version >= 3 drag source to be happy. if (wl_resource_get_version(offer->resource) < WL_DATA_OFFER_ACTION_SINCE_VERSION) { data_offer_source_dnd_finish(offer); } wlr_data_source_destroy(offer->source); } // Make the resource inert wl_resource_set_user_data(offer->resource, NULL); free(offer); } static uint32_t data_offer_choose_action(struct wlr_data_offer *offer) { uint32_t offer_actions, preferred_action = 0; if (wl_resource_get_version(offer->resource) >= WL_DATA_OFFER_ACTION_SINCE_VERSION) { offer_actions = offer->actions; preferred_action = offer->preferred_action; } else { offer_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; } uint32_t source_actions; if (offer->source->actions >= 0) { source_actions = offer->source->actions; } else { source_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; } uint32_t available_actions = offer_actions & source_actions; if (!available_actions) { return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; } if (offer->source->compositor_action & available_actions) { return offer->source->compositor_action; } // If the dest side has a preferred DnD action, use it if ((preferred_action & available_actions) != 0) { return preferred_action; } // Use the first found action, in bit order return 1 << (ffs(available_actions) - 1); } static void data_offer_update_action(struct wlr_data_offer *offer) { assert(offer->type == WLR_DATA_OFFER_DRAG); uint32_t action = data_offer_choose_action(offer); if (offer->source->current_dnd_action == action) { return; } offer->source->current_dnd_action = action; if (offer->in_ask) { return; } wlr_data_source_dnd_action(offer->source, action); if (wl_resource_get_version(offer->resource) >= WL_DATA_OFFER_ACTION_SINCE_VERSION) { wl_data_offer_send_action(offer->resource, action); } } static void data_offer_handle_accept(struct wl_client *client, struct wl_resource *resource, uint32_t serial, const char *mime_type) { struct wlr_data_offer *offer = data_offer_from_resource(resource); if (offer == NULL) { return; } if (offer->type != WLR_DATA_OFFER_DRAG) { kywc_log(KYWC_DEBUG, "Ignoring wl_data_offer.accept request on a " "non-drag-and-drop offer"); return; } wlr_data_source_accept(offer->source, serial, mime_type); } static void data_offer_handle_receive(struct wl_client *client, struct wl_resource *resource, const char *mime_type, int32_t fd) { struct wlr_data_offer *offer = data_offer_from_resource(resource); if (offer == NULL) { close(fd); return; } wlr_data_source_send(offer->source, mime_type, fd); } static void data_offer_source_dnd_finish(struct wlr_data_offer *offer) { struct wlr_data_source *source = offer->source; if (source->actions < 0) { return; } if (offer->in_ask) { wlr_data_source_dnd_action(source, source->current_dnd_action); } wlr_data_source_dnd_finish(source); } static void data_offer_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void data_offer_handle_finish(struct wl_client *client, struct wl_resource *resource) { struct wlr_data_offer *offer = data_offer_from_resource(resource); if (offer == NULL) { return; } // TODO: also fail while we have a drag-and-drop grab if (offer->type != WLR_DATA_OFFER_DRAG) { wl_resource_post_error(offer->resource, WL_DATA_OFFER_ERROR_INVALID_FINISH, "Offer is not drag-and-drop"); return; } if (!offer->source->accepted) { wl_resource_post_error(offer->resource, WL_DATA_OFFER_ERROR_INVALID_FINISH, "Premature finish request"); return; } enum wl_data_device_manager_dnd_action action = offer->source->current_dnd_action; if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE || action == WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { wl_resource_post_error(offer->resource, WL_DATA_OFFER_ERROR_INVALID_FINISH, "Offer finished with an invalid action"); return; } data_offer_source_dnd_finish(offer); data_offer_destroy(offer); } static void data_offer_handle_set_actions(struct wl_client *client, struct wl_resource *resource, uint32_t actions, uint32_t preferred_action) { struct wlr_data_offer *offer = data_offer_from_resource(resource); if (offer == NULL) { return; } if (actions & ~DATA_DEVICE_ALL_ACTIONS) { wl_resource_post_error(offer->resource, WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK, "invalid action mask %x", actions); return; } if (preferred_action && (!(preferred_action & actions) || __builtin_popcount(preferred_action) > 1)) { wl_resource_post_error(offer->resource, WL_DATA_OFFER_ERROR_INVALID_ACTION, "invalid action %x", preferred_action); return; } if (offer->type != WLR_DATA_OFFER_DRAG) { wl_resource_post_error(offer->resource, WL_DATA_OFFER_ERROR_INVALID_OFFER, "set_action can only be sent to drag-and-drop offers"); return; } offer->actions = actions; offer->preferred_action = preferred_action; data_offer_update_action(offer); } static const struct wl_data_offer_interface data_offer_impl = { .accept = data_offer_handle_accept, .receive = data_offer_handle_receive, .destroy = data_offer_handle_destroy, .finish = data_offer_handle_finish, .set_actions = data_offer_handle_set_actions, }; static void data_offer_handle_resource_destroy(struct wl_resource *resource) { struct wlr_data_offer *offer = data_offer_from_resource(resource); data_offer_destroy(offer); } static void data_offer_handle_source_destroy(struct wl_listener *listener, void *data) { struct wlr_data_offer *offer = wl_container_of(listener, offer, WLR_PRIVATE.source_destroy); // Prevent data_offer_destroy from destroying the source again offer->source = NULL; data_offer_destroy(offer); } static struct wlr_data_offer *data_offer_create(struct wlr_seat *wlr_seat, struct wl_resource *device_resource, struct wlr_data_source *source, enum wlr_data_offer_type type) { assert(wlr_seat != NULL); assert(source != NULL); // a NULL source means no selection struct wlr_data_offer *offer = calloc(1, sizeof(*offer)); if (offer == NULL) { return NULL; } offer->source = source; offer->type = type; struct wl_client *client = wl_resource_get_client(device_resource); uint32_t version = wl_resource_get_version(device_resource); offer->resource = wl_resource_create(client, &wl_data_offer_interface, version, 0); if (offer->resource == NULL) { free(offer); return NULL; } wl_resource_set_implementation(offer->resource, &data_offer_impl, offer, data_offer_handle_resource_destroy); switch (type) { case WLR_DATA_OFFER_SELECTION: wl_list_insert(&wlr_seat->selection_offers, &offer->link); break; case WLR_DATA_OFFER_DRAG: wl_list_insert(&wlr_seat->drag_offers, &offer->link); break; } offer->WLR_PRIVATE.source_destroy.notify = data_offer_handle_source_destroy; wl_signal_add(&source->events.destroy, &offer->WLR_PRIVATE.source_destroy); wl_data_device_send_data_offer(device_resource, offer->resource); char **p; wl_array_for_each(p, &source->mime_types) { wl_data_offer_send_offer(offer->resource, *p); } return offer; } void drag_set_focus(struct xwayland_drag_x11 *drag, struct wlr_surface *surface, double sx, double sy) { if (drag->hovered_surface == surface) { return; } if (drag->hovered_client) { wl_list_remove(&drag->seat_client_destroy.link); wl_list_init(&drag->seat_client_destroy.link); // If we're switching focus to another client, we want to destroy all // offers without destroying the source. If the drag operation ends, we // want to keep the offer around for the data transfer. struct wlr_data_offer *offer, *tmp; wl_list_for_each_safe(offer, tmp, &drag->hovered_client->seat->drag_offers, link) { struct wl_client *client = wl_resource_get_client(offer->resource); if (offer->source == &drag->data_source->base && client == drag->hovered_client->client) { offer->source = NULL; data_offer_destroy(offer); } } struct wl_resource *resource; wl_resource_for_each(resource, &drag->hovered_client->data_devices) { wl_data_device_send_leave(resource); } drag->hovered_client = NULL; } wl_list_remove(&drag->surface_destroy.link); wl_list_init(&drag->surface_destroy.link); drag->hovered_surface = NULL; if (!surface) { return; } struct wlr_seat *seat = drag->xwayland->wlr_xwayland->seat; struct wlr_seat_client *focus_client = wlr_seat_client_for_wl_client(seat, wl_resource_get_client(surface->resource)); if (!focus_client) { return; } if (drag->data_source != NULL) { drag->data_source->base.accepted = false; uint32_t serial = wl_display_next_serial(seat->display); struct wl_resource *device_resource; wl_resource_for_each(device_resource, &focus_client->data_devices) { struct wlr_data_offer *offer = data_offer_create( seat, device_resource, &drag->data_source->base, WLR_DATA_OFFER_DRAG); if (offer == NULL) { wl_resource_post_no_memory(device_resource); return; } data_offer_update_action(offer); if (wl_resource_get_version(offer->resource) >= WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION) { wl_data_offer_send_source_actions(offer->resource, drag->data_source->base.actions); } wl_data_device_send_enter(device_resource, serial, surface->resource, wl_fixed_from_double(sx), wl_fixed_from_double(sy), offer->resource); } } drag->hovered_surface = surface; drag->hovered_client = focus_client; wl_signal_add(&surface->events.destroy, &drag->surface_destroy); drag->seat_client_destroy.notify = drag_handle_seat_client_destroy; wl_signal_add(&focus_client->events.destroy, &drag->seat_client_destroy); } void xwayland_end_drag_x11(struct xwayland_server *xwayland) { if (!xwayland_is_dragging_x11(NULL)) { return; } kywc_log(KYWC_DEBUG, "End drag X11 "); struct xwayland_drag_x11 *drag_x11 = xwayland->drag_x11; if (drag_x11->data_source) { // This will end the grab_x11 wlr_data_source_destroy(&drag_x11->data_source->base); return; } wl_list_remove(&drag_x11->cursor_motion.link); wl_list_remove(&drag_x11->cursor_button.link); wl_list_remove(&drag_x11->surface_destroy.link); wl_list_remove(&drag_x11->seat_client_destroy.link); struct xwayland_data_transfer *transfer, *tmp; wl_list_for_each_safe(transfer, tmp, &drag_x11->transfers, link) { xwayland_data_transfer_destroy(transfer); } if (xwayland->wlr_xwayland->xwm) { xwayland_map_selection_window(xwayland, xwayland->window_catcher, NULL, false); } free(drag_x11); xwayland->drag_x11 = NULL; } bool xwayland_start_drag_x11(struct xwayland_server *xwayland, xcb_window_t source_window) { if (xwayland_is_dragging_x11(NULL)) { if (xwayland->drag_x11->source_window != source_window) { kywc_log(KYWC_WARN, "DND change owner?"); } return false; } struct seat *seat = seat_from_wlr_seat(xwayland->wlr_xwayland->seat); struct wlr_surface *current_surface = wlr_surface_try_from_node(seat->cursor->hover.node); struct view *current_view = current_surface ? view_try_from_wlr_surface(current_surface) : NULL; if (!current_view) { return false; } /** in wlroots, the window is xwm.dnd_selection.window, be created for wayland to x11 * TODO: use window id to compare */ if (current_view && !xwayland_check_view(current_view)) { return false; } kywc_log(KYWC_DEBUG, "start drag X11"); struct xwayland_drag_x11 *drag_x11 = calloc(1, sizeof(*drag_x11)); if (!drag_x11) { return false; } drag_x11->source_window = source_window; drag_x11->hovered_surface = current_surface; wl_list_init(&drag_x11->seat_client_destroy.link); wl_signal_add(&drag_x11->hovered_surface->events.destroy, &drag_x11->surface_destroy); drag_x11->surface_destroy.notify = handle_drag_x11_surface_destroy; drag_x11->cursor_motion.notify = handle_cursor_motion; wl_signal_add(&seat->events.cursor_motion, &drag_x11->cursor_motion); drag_x11->cursor_button.notify = handle_cursor_button; wl_signal_add(&seat->cursor->wlr_cursor->events.button, &drag_x11->cursor_button); wl_list_init(&drag_x11->transfers); drag_x11->xwayland = xwayland; xwayland->drag_x11 = drag_x11; int width, height; output_layout_get_size(&width, &height); struct kywc_box box = { .x = 0, .y = 0, .width = width, .height = height }; xwayland_map_selection_window(xwayland, xwayland->window_catcher, &box, true); return true; } kylin-wayland-compositor/src/xwayland/unmanaged.c0000664000175000017500000005427215160461067021220 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include "input/event.h" #include "input/seat.h" #include "scene/surface.h" #include "view/view.h" #include "xwayland_p.h" struct xwayland_unmanaged { struct wl_list link; struct xwayland_server *xwayland; struct wlr_xwayland_surface *wlr_xwayland_surface; struct ky_scene_node *surface_node; struct wl_listener node_destroy; struct wl_listener client_commit; struct wl_listener associate; struct wl_listener dissociate; struct wl_listener map; struct wl_listener unmap; struct wl_listener destroy; struct wl_listener request_activate; struct wl_listener request_configure; // struct wl_listener request_fullscreen; struct wl_listener set_geometry; struct wl_listener set_override_redirect; struct wlr_seat_pointer_grab pointer_grab; }; static bool xwayland_unmanaged_hover(struct seat *seat, struct ky_scene_node *node, double x, double y, uint32_t time, bool first, bool hold, void *data) { struct wlr_surface *surface = wlr_surface_try_from_node(node); xwayland_update_seat(seat); xwayland_update_hovered_surface(surface); if (!hold) { seat_notify_motion(seat, surface, time, xwayland_scale(x), xwayland_scale(y), first); return false; } struct xwayland_unmanaged *unmanaged = data; int lx, ly; ky_scene_node_coords(unmanaged->surface_node, &lx, &ly); seat_notify_motion(seat, surface, time, xwayland_scale(x - lx), xwayland_scale(y - ly), first); return true; } static bool xwayland_unmanaged_is_focusable(struct xwayland_unmanaged *unmanaged) { struct wlr_xwayland_surface *wlr_xwayland_surface = unmanaged->wlr_xwayland_surface; if (!wlr_xwayland_surface_override_redirect_wants_focus(wlr_xwayland_surface)) { return false; } /* No Input and Globally Active clients set the input field to False, * which requests that the window manager not set the input focus to their top-level window. */ if (wlr_xwayland_surface->hints && !wlr_xwayland_surface->hints->input) { return false; } return xwayland_surface_has_input(wlr_xwayland_surface, INPUT_MASK_KEYBOARD); } static void xwayland_unmanaged_focus(struct xwayland_unmanaged *unmanaged) { if (!xwayland_unmanaged_is_focusable(unmanaged)) { return; } struct seat *seat = seat_from_wlr_seat(unmanaged->xwayland->wlr_xwayland->seat); seat_focus_surface(seat, unmanaged->wlr_xwayland_surface->surface); } static void xwayland_unmanaged_click(struct seat *seat, struct ky_scene_node *node, uint32_t button, bool pressed, uint32_t time, enum click_state state, void *data) { xwayland_update_seat(seat); seat_notify_button(seat, time, button, pressed); /* only do activated when button pressed */ if (!pressed) { return; } /* only activate and focus top surface */ struct xwayland_unmanaged *unmanaged = data; xwayland_unmanaged_focus(unmanaged); } static void xwayland_unmanaged_leave(struct seat *seat, struct ky_scene_node *node, bool last, void *data) { /* so surface will call set_cursor when enter again */ struct wlr_surface *surface = wlr_surface_try_from_node(node); seat_notify_leave(seat, surface); } static struct ky_scene_node *xwayland_unmanaged_get_root(void *data) { struct xwayland_unmanaged *unmanaged = data; return unmanaged->surface_node; } static struct wlr_surface *xwayland_unmanaged_get_toplevel(void *data) { struct xwayland_unmanaged *unmanaged = data; /* only return surface if focusable */ if (!xwayland_unmanaged_is_focusable(unmanaged)) { return NULL; } return unmanaged->wlr_xwayland_surface->surface; } static const struct input_event_node_impl xwayland_unmanaged_event_node_impl = { .hover = xwayland_unmanaged_hover, .click = xwayland_unmanaged_click, .leave = xwayland_unmanaged_leave, }; static void unmanaged_handle_request_activate(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, request_activate); xwayland_unmanaged_focus(unmanaged); } static void unmanaged_handle_request_configure(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, request_configure); struct wlr_xwayland_surface *wlr_xwayland_surface = unmanaged->wlr_xwayland_surface; struct wlr_xwayland_surface_configure_event *event = data; wlr_xwayland_surface_configure(wlr_xwayland_surface, event->x, event->y, event->width, event->height); if (unmanaged->surface_node) { ky_scene_node_set_position(unmanaged->surface_node, xwayland_unscale(event->x), xwayland_unscale(event->y)); } } static void unmanaged_handle_set_geometry(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, set_geometry); struct wlr_xwayland_surface *wlr_xwayland_surface = unmanaged->wlr_xwayland_surface; if (unmanaged->surface_node) { ky_scene_node_set_position(unmanaged->surface_node, xwayland_unscale(wlr_xwayland_surface->x), xwayland_unscale(wlr_xwayland_surface->y)); } } static void unmanaged_pointer_grab_enter(struct wlr_seat_pointer_grab *grab, struct wlr_surface *surface, double sx, double sy) { if (wlr_xwayland_surface_try_from_wlr_surface(surface)) { wlr_seat_pointer_enter(grab->seat, surface, sx, sy); } else { wlr_seat_pointer_clear_focus(grab->seat); } } static void unmanaged_pointer_grab_clear_focus(struct wlr_seat_pointer_grab *grab) { wlr_seat_pointer_clear_focus(grab->seat); } static void unmanaged_pointer_grab_motion(struct wlr_seat_pointer_grab *grab, uint32_t time, double sx, double sy) { wlr_seat_pointer_send_motion(grab->seat, time, sx, sy); } static uint32_t unmanaged_pointer_grab_button(struct wlr_seat_pointer_grab *grab, uint32_t time, uint32_t button, uint32_t state) { uint32_t serial = wlr_seat_pointer_send_button(grab->seat, time, button, state); if (serial) { return serial; } struct xwayland_unmanaged *unmanaged = grab->data; struct wlr_surface *surface = unmanaged->wlr_xwayland_surface->surface; wlr_seat_pointer_enter(grab->seat, surface, FLT_MAX, FLT_MAX); wlr_seat_pointer_send_button(grab->seat, time, button, state); /* clear focus to eat the release button event */ wlr_seat_pointer_clear_focus(grab->seat); return 0; } static void unmanaged_pointer_grab_axis(struct wlr_seat_pointer_grab *grab, uint32_t time, enum wl_pointer_axis orientation, double value, int32_t value_discrete, enum wl_pointer_axis_source source, enum wl_pointer_axis_relative_direction relative_direction) { wlr_seat_pointer_send_axis(grab->seat, time, orientation, value, value_discrete, source, relative_direction); } static void unmanaged_pointer_grab_frame(struct wlr_seat_pointer_grab *grab) { wlr_seat_pointer_send_frame(grab->seat); } static void unmanaged_pointer_grab_cancel(struct wlr_seat_pointer_grab *grab) { kywc_log(KYWC_DEBUG, "Unmanaged popup menu pointer grab cancel"); grab->seat = NULL; } static const struct wlr_pointer_grab_interface unmanaged_pointer_grab_impl = { .enter = unmanaged_pointer_grab_enter, .clear_focus = unmanaged_pointer_grab_clear_focus, .motion = unmanaged_pointer_grab_motion, .button = unmanaged_pointer_grab_button, .cancel = unmanaged_pointer_grab_cancel, .axis = unmanaged_pointer_grab_axis, .frame = unmanaged_pointer_grab_frame, }; static void xwayland_unmanaged_grab_pointer(struct xwayland_unmanaged *unmanaged, struct wlr_seat *wlr_seat, bool grab) { struct wlr_seat_pointer_grab *pointer_grab = wlr_seat->pointer_state.grab; if (&unmanaged->pointer_grab != pointer_grab && grab) { unmanaged->pointer_grab.interface = &unmanaged_pointer_grab_impl; unmanaged->pointer_grab.data = unmanaged; wlr_seat_pointer_start_grab(wlr_seat, &unmanaged->pointer_grab); } else if (&unmanaged->pointer_grab == pointer_grab && !grab) { wlr_seat_pointer_end_grab(wlr_seat); } } static void xwayland_unmanaged_try_grab_pointer(struct xwayland_unmanaged *unmanaged) { struct wlr_xwayland_surface *wlr_xwayland_surface = unmanaged->wlr_xwayland_surface; if (wlr_xwayland_surface->parent && (xwayland_surface_has_type(wlr_xwayland_surface, NET_WM_WINDOW_TYPE_DIALOG) || xwayland_surface_has_type(wlr_xwayland_surface, NET_WM_WINDOW_TYPE_POPUP_MENU) || xwayland_surface_has_type(wlr_xwayland_surface, NET_WM_WINDOW_TYPE_DROPDOWN_MENU) || xwayland_surface_has_type(wlr_xwayland_surface, NET_WM_WINDOW_TYPE_COMBO))) { xwayland_unmanaged_grab_pointer(unmanaged, unmanaged->xwayland->wlr_xwayland->seat, true); kywc_log(KYWC_DEBUG, "Unmanaged popup menu start grab pointer"); } } static void unmanaged_handle_map(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, map); struct wlr_xwayland_surface *wlr_xwayland_surface = unmanaged->wlr_xwayland_surface; xwayland_unmanaged_focus(unmanaged); xwayland_unmanaged_try_grab_pointer(unmanaged); unmanaged->set_geometry.notify = unmanaged_handle_set_geometry; wl_signal_add(&wlr_xwayland_surface->events.set_geometry, &unmanaged->set_geometry); ky_scene_node_set_enabled(unmanaged->surface_node, true); ky_scene_node_set_position(unmanaged->surface_node, xwayland_unscale(wlr_xwayland_surface->x), xwayland_unscale(wlr_xwayland_surface->y)); /* workaround: fixup xwayland pointer position when no hovered surface */ xwayland_fixup_pointer_position(wlr_xwayland_surface->surface); /* workaround: wechat drag icon has no NET_WM_WINDOW_TYPE_DND */ if ((xwayland_is_dragging_x11(NULL) && !xwayland_surface_has_input(wlr_xwayland_surface, INPUT_MASK_POINTER) && xwayland_surface_has_type(wlr_xwayland_surface, NET_WM_WINDOW_TYPE_TOOLTIP)) || xwayland_surface_has_type(wlr_xwayland_surface, NET_WM_WINDOW_TYPE_DND)) { ky_scene_node_set_input_bypassed(unmanaged->surface_node, true); } } static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, unmap); struct wlr_seat *wlr_seat = unmanaged->pointer_grab.seat; if (wlr_seat) { xwayland_unmanaged_grab_pointer(unmanaged, wlr_seat, false); } wl_list_remove(&unmanaged->set_geometry.link); if (unmanaged->surface_node) { ky_scene_node_set_enabled(unmanaged->surface_node, false); } struct wlr_surface *wlr_surface = unmanaged->wlr_xwayland_surface->surface; wlr_seat = unmanaged->xwayland->wlr_xwayland->seat; if (wlr_seat && wlr_seat->keyboard_state.focused_surface == wlr_surface) { struct wlr_surface *parent_surface = unmanaged->wlr_xwayland_surface->parent ? unmanaged->wlr_xwayland_surface->parent->surface : NULL; struct view *parent = parent_surface ? view_try_from_wlr_surface(parent_surface) : NULL; struct view *activated_view = view_manager_get_activated(); if (activated_view && activated_view != parent) { return; } if (parent && KYWC_VIEW_IS_ACTIVATABLE(&parent->base) && KYWC_VIEW_IS_FOCUSABLE(&parent->base)) { view_do_activate(parent); view_set_focus(parent, seat_from_wlr_seat(wlr_seat)); } else { view_activate_topmost(false); } } } static void unmanaged_handle_node_destroy(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, node_destroy); wl_list_remove(&unmanaged->node_destroy.link); unmanaged->surface_node = NULL; } static void unmanaged_handle_client_commit(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, client_commit); if (unmanaged->xwayland->scale == 1.0) { return; } struct wlr_surface_state *pending = &unmanaged->wlr_xwayland_surface->surface->pending; pending->width = xwayland_unscale(pending->width); pending->height = xwayland_unscale(pending->height); float scale = 1.0 / unmanaged->xwayland->scale; if (pending->committed & WLR_SURFACE_STATE_SURFACE_DAMAGE) { wlr_region_scale(&pending->surface_damage, &pending->surface_damage, scale); } if (pending->committed & WLR_SURFACE_STATE_OPAQUE_REGION) { wlr_region_scale(&pending->opaque, &pending->opaque, scale); } if (pending->committed & WLR_SURFACE_STATE_INPUT_REGION) { wlr_region_scale(&pending->input, &pending->input, scale); } if (pending->committed & WLR_SURFACE_STATE_OFFSET) { pending->dx = xwayland_unscale(pending->dx); pending->dy = xwayland_unscale(pending->dy); } } static void unmanaged_handle_associate(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, associate); struct wlr_xwayland_surface *wlr_xwayland_surface = unmanaged->wlr_xwayland_surface; unmanaged->unmap.notify = unmanaged_handle_unmap; wl_signal_add(&wlr_xwayland_surface->surface->events.unmap, &unmanaged->unmap); struct view_layer *layer = view_manager_get_layer(LAYER_UNMANAGED, false); struct ky_scene_surface *scene_surface = ky_scene_surface_create(layer->tree, wlr_xwayland_surface->surface); unmanaged->surface_node = &scene_surface->buffer->node; ky_scene_node_set_enabled(unmanaged->surface_node, false); input_event_node_create(unmanaged->surface_node, &xwayland_unmanaged_event_node_impl, xwayland_unmanaged_get_root, xwayland_unmanaged_get_toplevel, unmanaged); xwayland_surface_shape_select_input(wlr_xwayland_surface, true); xwayland_surface_apply_shape_region(wlr_xwayland_surface); xwayland_apply_wm_window_opacity(wlr_xwayland_surface->window_id, false); xwayland_apply_blur_region(wlr_xwayland_surface->window_id, false); xwayland_apply_opaque_region(wlr_xwayland_surface->window_id, false); unmanaged->client_commit.notify = unmanaged_handle_client_commit; wl_signal_add(&wlr_xwayland_surface->surface->events.client_commit, &unmanaged->client_commit); unmanaged->map.notify = unmanaged_handle_map; wl_signal_add(&wlr_xwayland_surface->surface->events.map, &unmanaged->map); unmanaged->node_destroy.notify = unmanaged_handle_node_destroy; wl_signal_add(&unmanaged->surface_node->events.destroy, &unmanaged->node_destroy); } static void unmanaged_handle_dissociate(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, dissociate); ky_scene_node_destroy(unmanaged->surface_node); wl_list_remove(&unmanaged->client_commit.link); wl_list_remove(&unmanaged->map.link); wl_list_remove(&unmanaged->unmap.link); } static void unmanaged_handle_destroy(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, destroy); wl_list_remove(&unmanaged->link); wl_list_remove(&unmanaged->request_configure.link); wl_list_remove(&unmanaged->set_override_redirect.link); wl_list_remove(&unmanaged->request_activate.link); wl_list_remove(&unmanaged->associate.link); wl_list_remove(&unmanaged->dissociate.link); wl_list_remove(&unmanaged->destroy.link); free(unmanaged); } static void unmanaged_handle_set_override_redirect(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, set_override_redirect); struct wlr_xwayland_surface *wlr_xwayland_surface = unmanaged->wlr_xwayland_surface; struct xwayland_server *xwayland = unmanaged->xwayland; if (wlr_xwayland_surface->surface) { if (wlr_xwayland_surface->surface->mapped) { unmanaged_handle_unmap(&unmanaged->unmap, NULL); } unmanaged_handle_dissociate(&unmanaged->dissociate, NULL); } unmanaged_handle_destroy(&unmanaged->destroy, NULL); xwayland_view_create(xwayland, wlr_xwayland_surface); } void xwayland_unmanaged_create(struct xwayland_server *xwayland, struct wlr_xwayland_surface *wlr_xwayland_surface) { struct xwayland_unmanaged *unmanaged = calloc(1, sizeof(*unmanaged)); if (!unmanaged) { return; } unmanaged->xwayland = xwayland; wl_list_insert(&xwayland->unmanaged_surfaces, &unmanaged->link); unmanaged->wlr_xwayland_surface = wlr_xwayland_surface; unmanaged->associate.notify = unmanaged_handle_associate; wl_signal_add(&wlr_xwayland_surface->events.associate, &unmanaged->associate); unmanaged->dissociate.notify = unmanaged_handle_dissociate; wl_signal_add(&wlr_xwayland_surface->events.dissociate, &unmanaged->dissociate); unmanaged->destroy.notify = unmanaged_handle_destroy; wl_signal_add(&wlr_xwayland_surface->events.destroy, &unmanaged->destroy); unmanaged->request_activate.notify = unmanaged_handle_request_activate; wl_signal_add(&wlr_xwayland_surface->events.request_activate, &unmanaged->request_activate); unmanaged->request_configure.notify = unmanaged_handle_request_configure; wl_signal_add(&wlr_xwayland_surface->events.request_configure, &unmanaged->request_configure); unmanaged->set_override_redirect.notify = unmanaged_handle_set_override_redirect; wl_signal_add(&wlr_xwayland_surface->events.set_override_redirect, &unmanaged->set_override_redirect); wl_list_init(&unmanaged->client_commit.link); wl_list_init(&unmanaged->map.link); wl_list_init(&unmanaged->unmap.link); if (wlr_xwayland_surface->surface) { unmanaged_handle_associate(&unmanaged->associate, NULL); if (wlr_xwayland_surface->surface->mapped) { unmanaged_handle_map(&unmanaged->map, NULL); } } } static struct xwayland_unmanaged *xwayland_unmanaged_look_surface(struct xwayland_server *xwayland, xcb_window_t window_id) { struct xwayland_unmanaged *unmanaged; wl_list_for_each(unmanaged, &xwayland->unmanaged_surfaces, link) { if (unmanaged->wlr_xwayland_surface->window_id == window_id) { return unmanaged; } } return NULL; } bool xwayland_unmanaged_set_opacity(struct xwayland_server *xwayland, xcb_window_t window_id, float opacity) { struct xwayland_unmanaged *unmanaged = xwayland_unmanaged_look_surface(xwayland, window_id); if (!unmanaged) { return false; } if (unmanaged->surface_node) { ky_scene_buffer_set_opacity(ky_scene_buffer_from_node(unmanaged->surface_node), opacity); } return true; } bool xwayland_unmanaged_set_shape_region(struct xwayland_server *xwayland, xcb_window_t window_id, xcb_shape_sk_t kind, const pixman_region32_t *region) { struct xwayland_unmanaged *unmanaged = xwayland_unmanaged_look_surface(xwayland, window_id); if (!unmanaged) { return false; } if (!unmanaged->surface_node) { return true; } if (kind == XCB_SHAPE_SK_BOUNDING || kind == XCB_SHAPE_SK_CLIP) { ky_scene_node_set_clip_region(unmanaged->surface_node, region); } if (kind == XCB_SHAPE_SK_BOUNDING || kind == XCB_SHAPE_SK_INPUT) { ky_scene_node_set_input_region(unmanaged->surface_node, region); /* empty input region means no input support */ bool need_bypassed = kind == XCB_SHAPE_SK_INPUT && !pixman_region32_not_empty(region); ky_scene_node_set_input_bypassed(unmanaged->surface_node, need_bypassed); } return true; } bool xwayland_unmanaged_set_blur_region(struct xwayland_server *xwayland, xcb_window_t window_id, const pixman_region32_t *region) { struct xwayland_unmanaged *unmanaged = xwayland_unmanaged_look_surface(xwayland, window_id); if (!unmanaged) { return false; } if (unmanaged->surface_node) { ky_scene_node_set_blur_region(unmanaged->surface_node, region); } return true; } bool xwayland_unmanaged_set_opaque_region(struct xwayland_server *xwayland, xcb_window_t window_id, const pixman_region32_t *region) { struct xwayland_unmanaged *unmanaged = xwayland_unmanaged_look_surface(xwayland, window_id); if (!unmanaged) { return false; } if (unmanaged->surface_node) { ky_scene_buffer_set_opaque_region(ky_scene_buffer_from_node(unmanaged->surface_node), region); } return true; } bool xwayland_unmanaged_set_grab_pointer(struct xwayland_server *xwayland, struct wlr_xwayland_surface *wlr_xwayland_surface, struct wlr_seat *wlr_seat, bool grab) { if (!wlr_xwayland_surface || !wlr_seat) { return false; } struct xwayland_unmanaged *tmp, *unmanaged = NULL; wl_list_for_each(tmp, &xwayland->unmanaged_surfaces, link) { if (tmp->wlr_xwayland_surface == wlr_xwayland_surface) { unmanaged = tmp; break; } } if (!unmanaged) { return false; } xwayland_unmanaged_grab_pointer(unmanaged, wlr_seat, grab); return true; } kylin-wayland-compositor/src/xwayland/xwayland.c0000664000175000017500000012031115160461067021074 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "input/cursor.h" #include "input/seat.h" #include "output.h" #include "scene/surface.h" #include "security.h" #include "server.h" #include "util/macros.h" #include "util/string.h" #include "view/view.h" #include "xwayland_p.h" static const char *const atom_map[ATOM_LAST] = { [NET_WM_WINDOW_TYPE_DESKTOP] = "_NET_WM_WINDOW_TYPE_DESKTOP", [NET_WM_WINDOW_TYPE_DOCK] = "_NET_WM_WINDOW_TYPE_DOCK", [NET_WM_WINDOW_TYPE_TOOLBAR] = "_NET_WM_WINDOW_TYPE_TOOLBAR", [NET_WM_WINDOW_TYPE_MENU] = "_NET_WM_WINDOW_TYPE_MENU", [NET_WM_WINDOW_TYPE_UTILITY] = "_NET_WM_WINDOW_TYPE_UTILITY", [NET_WM_WINDOW_TYPE_SPLASH] = "_NET_WM_WINDOW_TYPE_SPLASH", [NET_WM_WINDOW_TYPE_DIALOG] = "_NET_WM_WINDOW_TYPE_DIALOG", [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_COMBO] = "_NET_WM_WINDOW_TYPE_COMBO", [NET_WM_WINDOW_TYPE_TOOLTIP] = "_NET_WM_WINDOW_TYPE_TOOLTIP", [NET_WM_WINDOW_TYPE_DND] = "_NET_WM_WINDOW_TYPE_DND", [NET_WM_WINDOW_TYPE_NOTIFICATION] = "_NET_WM_WINDOW_TYPE_NOTIFICATION", [NET_WM_WINDOW_TYPE_NORMAL] = "_NET_WM_WINDOW_TYPE_NORMAL", [NET_MOVERESIZE_WINDOW] = "_NET_MOVERESIZE_WINDOW", [KDE_NET_WM_WINDOW_TYPE_OVERRIDE] = "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE", [UKUI_NET_WM_WINDOW_TYPE_SYSTEMWINDOW] = "_UKUI_NET_WM_WINDOW_TYPE_SYSTEMWINDOW", [UKUI_NET_WM_WINDOW_TYPE_INPUTPANEL] = "_UKUI_NET_WM_WINDOW_TYPE_INPUTPANEL", [UKUI_NET_WM_WINDOW_TYPE_LOGOUT] = "_UKUI_NET_WM_WINDOW_TYPE_LOGOUT", [UKUI_NET_WM_WINDOW_TYPE_SCREENLOCK] = "_UKUI_NET_WM_WINDOW_TYPE_SCREENLOCK", [UKUI_NET_WM_WINDOW_TYPE_SCREENLOCKNOTIFICATION] = "_UKUI_NET_WM_WINDOW_TYPE_SCREENLOCKNOTIFICATION", [UKUI_NET_WM_WINDOW_TYPE_WATERMARK] = "_UKUI_NET_WM_WINDOW_TYPE_WATERMARK", [KWIN_UKUI_DECORATION] = "_KWIN_UKUI_DECORAION", // it has been misspelled since V10 [NET_WM_STATE] = "_NET_WM_STATE", [KDE_NET_WM_STATE_SKIP_SWITCHER] = "_KDE_NET_WM_STATE_SKIP_SWITCHER", [KDE_NET_WM_BLUR_BEHIND_REGION] = "_KDE_NET_WM_BLUR_BEHIND_REGION", [NET_WM_ICON] = "_NET_WM_ICON", [NET_WM_WINDOW_OPACITY] = "_NET_WM_WINDOW_OPACITY", [NET_WM_OPAQUE_REGION] = "_NET_WM_OPAQUE_REGION", [UTF8_STRING] = "UTF8_STRING", [NET_WM_NAME] = "_NET_WM_NAME", [NET_SUPPORTING_WM_CHECK] = "_NET_SUPPORTING_WM_CHECK", [INCR] = "INCR", [TEXT] = "TEXT", [WL_SELECTION] = "_WL_SELECTION", [DND_SELECTION] = "XdndSelection", [DND_AWARE] = "XdndAware", [DND_STATUS] = "XdndStatus", [DND_POSITION] = "XdndPosition", [DND_ENTER] = "XdndEnter", [DND_LEAVE] = "XdndLeave", [DND_DROP] = "XdndDrop", [DND_FINISHED] = "XdndFinished", [DND_PROXY] = "XdndProxy", [DND_TYPE_LIST] = "XdndTypeList", [DND_ACTION_MOVE] = "XdndActionMove", [DND_ACTION_COPY] = "XdndActionCopy", [DND_ACTION_ASK] = "XdndActionAsk", [DND_ACTION_PRIVATE] = "XdndActionPrivate", [WM_PROTOCOLS] = "WM_PROTOCOLS", [NET_WM_SYNC_REQUEST] = "_NET_WM_SYNC_REQUEST", [NET_WM_SYNC_REQUEST_COUNTER] = "_NET_WM_SYNC_REQUEST_COUNTER", [XWAYLAND_ALLOW_COMMITS] = "_XWAYLAND_ALLOW_COMMITS", }; static struct xwayland_server *xwayland = NULL; static void handle_new_xwayland_surface(struct wl_listener *listener, void *data) { struct wlr_xwayland_surface *wlr_xwayland_surface = data; /* dnd window catcher no need map */ if (wlr_xwayland_surface->window_id == xwayland->window_catcher) { return; } if (wlr_xwayland_surface->override_redirect) { xwayland_unmanaged_create(xwayland, wlr_xwayland_surface); return; } xwayland_view_create(xwayland, wlr_xwayland_surface); } static void xwayland_update_xresources(xcb_connection_t *xcb_conn) { struct seat *seat = seat_from_wlr_seat(xwayland->wlr_xwayland->seat); const char *prop_str = string_create("Xft.dpi:\t%d\nXcursor.size:\t%d\nXcursor.theme:\t%s\n", (int)(xwayland->scale * 96), (int)(seat->state.cursor_size * xwayland->scale), seat->state.cursor_theme ? seat->state.cursor_theme : "default"); if (!prop_str) { return; } xcb_change_property(xcb_conn, XCB_PROP_MODE_REPLACE, xwayland->screen->root, XCB_ATOM_RESOURCE_MANAGER, XCB_ATOM_STRING, 8, strlen(prop_str), prop_str); xcb_flush(xcb_conn); free((void *)prop_str); } static void handle_max_scale(struct wl_listener *listener, void *data) { float scale = output_manager_get_max_scale(); if (xwayland->scale == scale) { return; } xwayland->scale = scale; kywc_log(KYWC_INFO, "Xwayland set scale to %f", xwayland->scale); /* xwayland server is destroyed or not ready */ if (!xwayland->wlr_xwayland || !xwayland->wlr_xwayland->xwm) { return; } /* update default cursor with current scale */ xwayland_set_cursor(seat_from_wlr_seat(xwayland->wlr_xwayland->seat)); /* configure xwayland view geometry by the new scale */ struct kywc_output *output = data; xwayland_view_configure_all(xwayland, output); } static void xwayland_get_atoms(xcb_connection_t *xcb_conn) { xcb_intern_atom_cookie_t cookies[ATOM_LAST]; for (size_t i = 0; i < ATOM_LAST; i++) { cookies[i] = xcb_intern_atom(xcb_conn, 0, strlen(atom_map[i]), atom_map[i]); } for (size_t i = 0; i < ATOM_LAST; i++) { xcb_generic_error_t *error = NULL; xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(xcb_conn, cookies[i], &error); if (reply != NULL && error == NULL) { xwayland->atoms[i] = reply->atom; } free(reply); if (error != NULL) { kywc_log(KYWC_ERROR, "Could not resolve atom %s, X11 error code %d", atom_map[i], error->error_code); free(error); break; } } } static void xwayland_get_shape_extension(xcb_connection_t *xcb_conn) { xwayland->shape = xcb_get_extension_data(xcb_conn, &xcb_shape_id); if (!xwayland->shape || !xwayland->shape->present) { kywc_log(KYWC_WARN, "Shape not available"); xwayland->shape = NULL; return; } xcb_shape_query_version_cookie_t shape_cookie; xcb_shape_query_version_reply_t *shape_reply; shape_cookie = xcb_shape_query_version(xcb_conn); shape_reply = xcb_shape_query_version_reply(xcb_conn, shape_cookie, NULL); kywc_log(KYWC_INFO, "Shape version: %" PRIu32 ".%" PRIu32, shape_reply->major_version, shape_reply->minor_version); free(shape_reply); } static void xwayland_get_xfixes_extension(xcb_connection_t *xcb_conn) { xwayland->xfixes = xcb_get_extension_data(xcb_conn, &xcb_xfixes_id); if (!xwayland->xfixes || !xwayland->xfixes->present) { kywc_log(KYWC_WARN, "Xfixes not available"); xwayland->xfixes = NULL; return; } xcb_xfixes_query_version_cookie_t xfixes_cookie; xcb_xfixes_query_version_reply_t *xfixes_reply; xfixes_cookie = xcb_xfixes_query_version(xcb_conn, XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION); xfixes_reply = xcb_xfixes_query_version_reply(xcb_conn, xfixes_cookie, NULL); kywc_log(KYWC_INFO, "Xfixes version: %" PRIu32 ".%" PRIu32, xfixes_reply->major_version, xfixes_reply->minor_version); free(xfixes_reply); } static void xwayland_get_sync_extension(xcb_connection_t *xcb_conn) { xwayland->sync = xcb_get_extension_data(xcb_conn, &xcb_sync_id); if (!xwayland->sync || !xwayland->sync->present) { kywc_log(KYWC_WARN, "Sync not available"); xwayland->sync = NULL; return; } xcb_sync_initialize_cookie_t sync_cookie = xcb_sync_initialize(xcb_conn, XCB_SYNC_MAJOR_VERSION, XCB_SYNC_MINOR_VERSION); xcb_sync_initialize_reply_t *sync_reply = xcb_sync_initialize_reply(xcb_conn, sync_cookie, NULL); kywc_log(KYWC_INFO, "Sync version: %" PRIu32 ".%" PRIu32, sync_reply->major_version, sync_reply->minor_version); free(sync_reply); } static void xwayland_get_resources(xcb_connection_t *xcb_conn) { xcb_prefetch_extension_data(xcb_conn, &xcb_shape_id); xcb_prefetch_extension_data(xcb_conn, &xcb_sync_id); xwayland_get_atoms(xcb_conn); /** * workaround to fix java apps show blank window * wlcom is not a reparenting window manager * or set _JAVA_AWT_WM_NONREPARENTING=1 */ const char name[] = "LG3D"; xcb_change_property(xcb_conn, XCB_PROP_MODE_REPLACE, xwayland->screen->root, xwayland->atoms[NET_SUPPORTING_WM_CHECK], XCB_ATOM_WINDOW, 32, 1, &xwayland->screen->root); xcb_change_property(xcb_conn, XCB_PROP_MODE_REPLACE, xwayland->screen->root, xwayland->atoms[NET_WM_NAME], xwayland->atoms[UTF8_STRING], 8, strlen(name), name); xwayland_get_shape_extension(xcb_conn); xwayland_get_xfixes_extension(xcb_conn); xwayland_get_sync_extension(xcb_conn); } void xwayland_surface_shape_select_input(struct wlr_xwayland_surface *surface, bool enabled) { if (xwayland->shape) { xcb_shape_select_input(xwayland->xcb_conn, surface->window_id, enabled); xcb_flush(xwayland->xcb_conn); } } static bool xwayland_get_shape_region(xcb_window_t window, xcb_shape_kind_t kind, pixman_region32_t *region, int *count) { xcb_shape_get_rectangles_reply_t *reply = xcb_shape_get_rectangles_reply( xwayland->xcb_conn, xcb_shape_get_rectangles_unchecked(xwayland->xcb_conn, window, kind), NULL); if (!reply) { return false; } xcb_rectangle_t *rects = xcb_shape_get_rectangles_rectangles(reply); int len = xcb_shape_get_rectangles_rectangles_length(reply); if (region) { for (int i = 0; i < len; i++) { pixman_region32_union_rect( region, region, xwayland_unscale(rects[i].x), xwayland_unscale(rects[i].y), xwayland_unscale(rects[i].width), xwayland_unscale(rects[i].height)); } } if (count) { *count = len; } free(reply); return true; } void xwayland_surface_apply_shape_region(struct wlr_xwayland_surface *surface) { if (!xwayland->shape || !surface->surface) { return; } struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(surface->surface); if (!buffer) { return; } xcb_shape_query_extents_reply_t *reply = xcb_shape_query_extents_reply( xwayland->xcb_conn, xcb_shape_query_extents_unchecked(xwayland->xcb_conn, surface->window_id), NULL); if (!reply) { return; } pixman_region32_t region; pixman_region32_init(®ion); if (reply->clip_shaped) { if (xwayland_get_shape_region(surface->window_id, XCB_SHAPE_SK_CLIP, ®ion, NULL)) { ky_scene_node_set_clip_region(&buffer->node, ®ion); } } else if (reply->bounding_shaped) { if (xwayland_get_shape_region(surface->window_id, XCB_SHAPE_SK_BOUNDING, ®ion, NULL)) { ky_scene_node_set_clip_region(&buffer->node, ®ion); ky_scene_node_set_input_region(&buffer->node, ®ion); } } int count = 0; xwayland_get_shape_region(surface->window_id, XCB_SHAPE_SK_INPUT, NULL, &count); ky_scene_node_set_input_bypassed(&buffer->node, count == 0); pixman_region32_fini(®ion); free(reply); } static int xwayland_handle_shape_notify(xcb_shape_notify_event_t *notify) { pixman_region32_t region; pixman_region32_init(®ion); if (!xwayland_get_shape_region(notify->affected_window, notify->shape_kind, ®ion, NULL)) { pixman_region32_fini(®ion); return 1; } if (!xwayland_unmanaged_set_shape_region(xwayland, notify->affected_window, notify->shape_kind, ®ion)) { xwayland_view_set_shape_region(xwayland, notify->affected_window, notify->shape_kind, ®ion); } pixman_region32_fini(®ion); return 1; } void xwayland_set_cursor(struct seat *seat) { if (!xwayland || !xwayland->wlr_xwayland || xwayland->wlr_xwayland->seat != seat->wlr_seat) { return; } wlr_xcursor_manager_load(seat->cursor->xcursor_manager, xwayland->scale); struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(seat->cursor->xcursor_manager, "left_ptr", xwayland->scale); if (xcursor) { struct wlr_xcursor_image *image = xcursor->images[0]; wlr_xwayland_set_cursor(xwayland->wlr_xwayland, image->buffer, image->width * 4, image->width, image->height, image->hotspot_x, image->hotspot_y); } xwayland_update_xresources(xwayland->xcb_conn); } static void xwayland_set_seat(struct seat *seat) { if (xwayland->wlr_xwayland->seat == seat->wlr_seat) { return; } wlr_xwayland_set_seat(xwayland->wlr_xwayland, seat->wlr_seat); wl_list_remove(&xwayland->seat_destroy.link); wl_signal_add(&seat->events.destroy, &xwayland->seat_destroy); /* update xwayland cursor */ xwayland_set_cursor(seat); } void xwayland_update_seat(struct seat *seat) { // hover by input_rebase will run here when xwm_destroy if (xwayland->server->terminate || !xwayland->wlr_xwayland || !xwayland->wlr_xwayland->seat) { return; } xwayland_set_seat(seat); } void xwayland_update_hovered_surface(struct wlr_surface *surface) { if (!xwayland || xwayland->hoverd_surface == surface) { return; } if (xwayland->hoverd_surface) { wl_list_remove(&xwayland->surface_unmap.link); wl_list_remove(&xwayland->view_minimized.link); } xwayland->hoverd_surface = surface; wl_signal_add(&surface->events.unmap, &xwayland->surface_unmap); struct view *view = view_try_from_wlr_surface(surface); if (view) { wl_signal_add(&view->base.events.minimize, &xwayland->view_minimized); } else { wl_list_init(&xwayland->view_minimized.link); } } static void handle_xwayland_ready(struct wl_listener *listener, void *data) { kywc_log(KYWC_INFO, "Xwayland is ready"); xwayland->xcb_conn = wlr_xwayland_get_xwm_connection(xwayland->wlr_xwayland); xcb_screen_iterator_t screen_iterator = xcb_setup_roots_iterator(xcb_get_setup(xwayland->xcb_conn)); xwayland->screen = screen_iterator.data; /* use the default seat0 */ xwayland_set_seat(input_manager_get_default_seat()); xwayland_get_resources(xwayland->xcb_conn); // for selection int width = 0, height = 0; output_layout_get_size(&width, &height); xwayland_create_seletion_window(xwayland, &xwayland->window_catcher, 0, 0, width, height); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&xwayland->server_destroy.link); wl_list_remove(&xwayland->max_scale.link); wl_list_remove(&xwayland->surface_unmap.link); wl_list_remove(&xwayland->view_minimized.link); wl_list_remove(&xwayland->activate_view.link); free(xwayland); xwayland = NULL; } static bool xwayland_filter_global(const struct security_client *client, void *data) { /* only expose this global to Xwayland clients */ return xwayland_check_client(client->client); } xcb_sync_counter_t xwayland_get_sync_counter(xcb_window_t window_id) { if (!xwayland->sync) { return XCB_NONE; } xcb_get_property_cookie_t cookie = xcb_get_property(xwayland->xcb_conn, 0, window_id, xwayland->atoms[NET_WM_SYNC_REQUEST_COUNTER], XCB_ATOM_CARDINAL, 0, 1); xcb_get_property_reply_t *reply = xcb_get_property_reply(xwayland->xcb_conn, cookie, NULL); if (reply == NULL || reply->value_len == 0) { kywc_log(KYWC_INFO, "Failed to get window 0x%x sync request counter", window_id); free(reply); return XCB_NONE; } uint32_t *data = (uint32_t *)xcb_get_property_value(reply); xcb_sync_counter_t counter = data[0]; free(reply); if (counter == XCB_NONE) { return XCB_NONE; } xcb_sync_int64_t value = { .hi = 0, .lo = 0 }; xcb_sync_set_counter(xwayland->xcb_conn, counter, value); xcb_flush(xwayland->xcb_conn); xwayland_view_reset_sync_counter(xwayland, window_id); return counter; } xcb_sync_alarm_t xwayland_create_sync_alarm(xcb_window_t window_id, xcb_sync_counter_t counter) { if (!xwayland->sync || counter == XCB_NONE) { return XCB_NONE; } const uint32_t mask = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE_TYPE | XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_EVENTS; const uint32_t values[] = { counter, XCB_SYNC_VALUETYPE_RELATIVE, XCB_SYNC_TESTTYPE_POSITIVE_COMPARISON, 1 }; xcb_sync_alarm_t alarm = xcb_generate_id(xwayland->xcb_conn); xcb_void_cookie_t cookie = xcb_sync_create_alarm_checked(xwayland->xcb_conn, alarm, mask, values); xcb_generic_error_t *error = xcb_request_check(xwayland->xcb_conn, cookie); if (error) { alarm = XCB_NONE; } else { xcb_sync_change_alarm_value_list_t value = { .value.lo = 1, .delta.lo = 1 }; xcb_sync_change_alarm_aux(xwayland->xcb_conn, alarm, XCB_SYNC_CA_VALUE | XCB_SYNC_CA_DELTA, &value); xcb_flush(xwayland->xcb_conn); } return alarm; } void xwayland_send_sync_request(xcb_window_t window_id, xcb_sync_int64_t *value) { const uint32_t lo = value->lo++; if (lo > value->lo) { value->hi++; } xcb_client_message_event_t event = { .response_type = XCB_CLIENT_MESSAGE, .format = 32, .window = window_id, .type = xwayland->atoms[WM_PROTOCOLS], .data.data32[0] = xwayland->atoms[NET_WM_SYNC_REQUEST], .data.data32[1] = XCB_TIME_CURRENT_TIME, .data.data32[2] = value->lo, .data.data32[3] = value->hi, }; xcb_send_event(xwayland->xcb_conn, false, window_id, XCB_EVENT_MASK_NO_EVENT, (const char *)&event); xcb_flush(xwayland->xcb_conn); } void xwayland_set_allow_commits(xcb_window_t window_id, bool allow) { uint32_t value = allow; xcb_change_property(xwayland->xcb_conn, XCB_PROP_MODE_REPLACE, window_id, xwayland->atoms[XWAYLAND_ALLOW_COMMITS], XCB_ATOM_CARDINAL, 32, 1, &value); xcb_flush(xwayland->xcb_conn); } int xwayland_read_wm_state(xcb_window_t window_id) { xcb_get_property_cookie_t cookie = xcb_get_property( xwayland->xcb_conn, 0, window_id, xwayland->atoms[NET_WM_STATE], XCB_ATOM_ANY, 0, 2048); xcb_get_property_reply_t *reply = xcb_get_property_reply(xwayland->xcb_conn, cookie, NULL); if (reply == NULL) { kywc_log(KYWC_ERROR, "Failed to get window state property"); return 0; } /* nothing to be handled */ if (reply->value_len == 0) { free(reply); return 1; } struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, window_id); if (!surface) { free(reply); return 0; } xcb_atom_t *atoms = xcb_get_property_value(reply); int ret = 0; for (uint32_t i = 0; i < reply->value_len; i++) { if (atoms[i] == xwayland->atoms[KDE_NET_WM_STATE_SKIP_SWITCHER]) { xwayland_view_set_skip_switcher(surface, true); ret = reply->value_len == 1; break; } } free(reply); return ret; } int xwayland_read_wm_icon(xcb_window_t window_id) { xcb_get_property_cookie_t cookie = xcb_get_property(xwayland->xcb_conn, 0, window_id, xwayland->atoms[NET_WM_ICON], XCB_ATOM_CARDINAL, 0, 0xffffffff); xcb_get_property_reply_t *reply = xcb_get_property_reply(xwayland->xcb_conn, cookie, NULL); if (!reply || reply->value_len < 3 || reply->format != 32) { free(reply); return 0; } struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, window_id); if (!surface || !surface->surface) { free(reply); return 0; } /* clear icon first */ xwayland_view_clear_wm_icon(surface); uint32_t *data = (uint32_t *)xcb_get_property_value(reply); for (unsigned int j = 0; j < reply->value_len - 2;) { uint32_t width = data[j++], height = data[j++]; uint32_t size = width * height * sizeof(uint32_t); if (j + width * height > reply->value_len) { kywc_log(KYWC_WARN, "Proposed size leads to out of bounds (%d x %d) ", width, height); break; } if (width > 1024 || height > 1024) { kywc_log(KYWC_WARN, "Found huge icon may be ill-encoded (%d x %d)", width, height); } xwayland_view_add_new_wm_icon(surface, width, height, size, &data[j]); j += width * height; } xwayland_view_update_icon(surface); free(reply); return 1; } int xwayland_apply_wm_window_opacity(xcb_window_t window_id, bool delete) { if (delete) { if (!xwayland_unmanaged_set_opacity(xwayland, window_id, 1)) { return xwayland_view_set_opacity(xwayland, window_id, 1); } return 1; } xcb_get_property_cookie_t cookie = xcb_get_property(xwayland->xcb_conn, 0, window_id, xwayland->atoms[NET_WM_WINDOW_OPACITY], XCB_ATOM_CARDINAL, 0, 1); xcb_get_property_reply_t *reply = xcb_get_property_reply(xwayland->xcb_conn, cookie, NULL); if (!reply || reply->value_len != 1 || reply->format != 32) { free(reply); return 0; } uint32_t *value = (uint32_t *)xcb_get_property_value(reply); float opacity = value[0] == 0xffffffff ? 1.0 : value[0] * 1.0 / 0xffffffff; free(reply); if (!xwayland_unmanaged_set_opacity(xwayland, window_id, opacity)) { return xwayland_view_set_opacity(xwayland, window_id, opacity); } return 1; } int xwayland_apply_blur_region(xcb_window_t window_id, bool delete) { if (delete) { if (!xwayland_unmanaged_set_blur_region(xwayland, window_id, NULL)) { xwayland_view_set_blur_region(xwayland, window_id, NULL); } return 1; } xcb_get_property_cookie_t cookie = xcb_get_property( xwayland->xcb_conn, 0, window_id, xwayland->atoms[KDE_NET_WM_BLUR_BEHIND_REGION], XCB_ATOM_CARDINAL, 0, 0xffffffff); xcb_get_property_reply_t *reply = xcb_get_property_reply(xwayland->xcb_conn, cookie, NULL); if (!reply || reply->format != 32) { free(reply); return 0; } /* from V10sp1, the last value is the blur strength.we ignore it. */ uint32_t len; if (reply->value_len % 4 == 0) { len = reply->value_len; } else if (reply->value_len % 4 == 1) { len = reply->value_len - 1; } else { return 0; } pixman_region32_t region; pixman_region32_init(®ion); uint32_t *value = (uint32_t *)xcb_get_property_value(reply); for (uint32_t i = 0; i < len; i += 4) { pixman_region32_union_rect(®ion, ®ion, xwayland_unscale(value[i]), xwayland_unscale(value[i + 1]), xwayland_unscale(value[i + 2]), xwayland_unscale(value[i + 3])); } if (!xwayland_unmanaged_set_blur_region(xwayland, window_id, ®ion)) { xwayland_view_set_blur_region(xwayland, window_id, ®ion); } free(reply); pixman_region32_fini(®ion); return 1; } int xwayland_apply_opaque_region(xcb_window_t window_id, bool delete) { pixman_region32_t region; pixman_region32_init(®ion); if (delete) { if (!xwayland_unmanaged_set_opaque_region(xwayland, window_id, ®ion)) { xwayland_view_set_opaque_region(xwayland, window_id, ®ion); } pixman_region32_fini(®ion); return 1; } xcb_get_property_cookie_t cookie = xcb_get_property(xwayland->xcb_conn, 0, window_id, xwayland->atoms[NET_WM_OPAQUE_REGION], XCB_ATOM_CARDINAL, 0, 0xffffffff); xcb_get_property_reply_t *reply = xcb_get_property_reply(xwayland->xcb_conn, cookie, NULL); if (!reply || reply->format != 32 || reply->value_len % 4) { free(reply); return 0; } uint32_t *data = (uint32_t *)xcb_get_property_value(reply); for (unsigned int i = 0; i < reply->value_len; i += 4) { pixman_region32_union_rect(®ion, ®ion, xwayland_unscale(data[i]), xwayland_unscale(data[i + 1]), xwayland_unscale(data[i + 2]), xwayland_unscale(data[i + 3])); } if (!xwayland_unmanaged_set_opaque_region(xwayland, window_id, ®ion)) { xwayland_view_set_opaque_region(xwayland, window_id, ®ion); } free(reply); pixman_region32_fini(®ion); return 1; } int xwayland_apply_ukui_decoration(xcb_window_t window_id, bool delete) { if (delete) { xwayland_view_set_no_title(xwayland, window_id, false); return 1; } xcb_get_property_cookie_t cookie = xcb_get_property(xwayland->xcb_conn, 0, window_id, xwayland->atoms[KWIN_UKUI_DECORATION], XCB_ATOM_ANY, 0, 1); xcb_get_property_reply_t *reply = xcb_get_property_reply(xwayland->xcb_conn, cookie, NULL); if (!reply || reply->value_len == 0) { free(reply); return 0; } uint8_t *data = (uint8_t *)xcb_get_property_value(reply); if (data && data[0]) { xwayland_view_set_no_title(xwayland, window_id, true); } free(reply); return 1; } static void xwayland_handle_moveresize_message(struct xwayland_server *xwayland, xcb_client_message_event_t *client_message) { struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, client_message->window); if (!surface || !surface->surface) { return; } // gravity = client_message->data.data32[0] & 0xff); int value_mask = (client_message->data.data32[0] & 0xf00) >> 8; // source = (client_message->data.data32[0] & 0xf000) >> 12; struct kywc_box box = { .x = value_mask & XCB_CONFIG_WINDOW_X ? (int)client_message->data.data32[1] : surface->x, .y = value_mask & XCB_CONFIG_WINDOW_Y ? (int)client_message->data.data32[2] : surface->y, .width = value_mask & XCB_CONFIG_WINDOW_WIDTH ? (int)client_message->data.data32[3] : surface->width, .height = value_mask & XCB_CONFIG_WINDOW_HEIGHT ? (int)client_message->data.data32[4] : surface->height }; xwayland_view_move_resize(surface, &box); } /* return 0 as we only handle few things */ static bool xwayland_handle_event(struct wlr_xwayland *xwm, xcb_generic_event_t *event) { const uint8_t response_type = event->response_type & 0x7f; if (response_type == XCB_PROPERTY_NOTIFY) { xcb_property_notify_event_t *ev = (xcb_property_notify_event_t *)event; bool delete = ev->state == XCB_PROPERTY_DELETE; if (ev->atom == xwayland->atoms[NET_WM_STATE]) { return xwayland_read_wm_state(ev->window); } else if (ev->atom == xwayland->atoms[NET_WM_ICON]) { return xwayland_read_wm_icon(ev->window); } else if (ev->atom == xwayland->atoms[NET_WM_WINDOW_OPACITY]) { return xwayland_apply_wm_window_opacity(ev->window, delete); } else if (ev->atom == xwayland->atoms[NET_WM_SYNC_REQUEST_COUNTER]) { xwayland_get_sync_counter(ev->window); return true; } else if (ev->atom == xwayland->atoms[KDE_NET_WM_BLUR_BEHIND_REGION]) { return xwayland_apply_blur_region(ev->window, delete); } else if (ev->atom == xwayland->atoms[NET_WM_OPAQUE_REGION]) { return xwayland_apply_opaque_region(ev->window, delete); } else if (ev->atom == xwayland->atoms[KWIN_UKUI_DECORATION]) { return xwayland_apply_ukui_decoration(ev->window, delete); } } else if (response_type == XCB_CLIENT_MESSAGE) { xcb_client_message_event_t *ev = (xcb_client_message_event_t *)event; if (ev->type == xwayland->atoms[NET_MOVERESIZE_WINDOW]) { xwayland_handle_moveresize_message(xwayland, ev); return true; } else if (xwayland_handle_dnd_message(xwayland, ev)) { return true; } } else if (xwayland->shape && response_type == xwayland->shape->first_event + XCB_SHAPE_NOTIFY) { return xwayland_handle_shape_notify((xcb_shape_notify_event_t *)event); } else if (xwayland->sync && response_type == xwayland->sync->first_event + XCB_SYNC_ALARM_NOTIFY) { xcb_sync_alarm_notify_event_t *ev = (xcb_sync_alarm_notify_event_t *)event; xwayland_view_ack_sync(xwayland, ev->alarm, ev->counter_value); return true; } else if (xwayland_handle_selection_event(xwayland, event)) { return true; } return false; } static void handle_surface_unmap(struct wl_listener *listener, void *data) { wl_list_remove(&xwayland->surface_unmap.link); wl_list_init(&xwayland->surface_unmap.link); wl_list_remove(&xwayland->view_minimized.link); wl_list_init(&xwayland->view_minimized.link); xwayland->hoverd_surface = NULL; } static void handle_view_minimized(struct wl_listener *listener, void *data) { struct view *view = view_try_from_wlr_surface(xwayland->hoverd_surface); if (!view->base.minimized) { return; } wl_list_remove(&xwayland->surface_unmap.link); wl_list_init(&xwayland->surface_unmap.link); wl_list_remove(&xwayland->view_minimized.link); wl_list_init(&xwayland->view_minimized.link); xwayland->hoverd_surface = NULL; } static void handle_seat_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&xwayland->seat_destroy.link); wl_list_init(&xwayland->seat_destroy.link); xwayland_update_seat(input_manager_get_default_seat()); } static void handle_activate_view(struct wl_listener *listener, void *data) { /* xwayland server is destroyed or not ready */ if (!xwayland->wlr_xwayland || !xwayland->wlr_xwayland->xwm) { return; } if (!xwayland->activated_surface) { return; } struct kywc_view *view = data; if (view && xwayland_check_view(view_from_kywc_view(view))) { return; } wlr_xwayland_surface_activate(xwayland->activated_surface, false); xwayland->activated_surface = NULL; } static void handle_shell_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&xwayland->shell_destroy.link); security_remove_global_filter(xwayland->shell->global); xwayland->shell = NULL; } bool xwayland_server_create(struct server *server) { if (!server->options.enable_xwayland) { kywc_log(KYWC_INFO, "Xwayland is disabled by cmdline"); return false; } xwayland = calloc(1, sizeof(*xwayland)); if (!xwayland) { kywc_log(KYWC_ERROR, "Cannot create xwayland server"); return false; } xwayland->wlr_xwayland = wlr_xwayland_create(server->display, server->compositor, server->options.lazy_xwayland); if (!xwayland->wlr_xwayland) { kywc_log(KYWC_ERROR, "Cannot create wlroots xwayland server"); free(xwayland); xwayland = NULL; return false; } xwayland->scale = 1.0; xwayland->server = server; wl_list_init(&xwayland->surfaces); wl_list_init(&xwayland->unmanaged_surfaces); xwayland->wlr_xwayland->user_event_handler = xwayland_handle_event; xwayland->new_xwayland_surface.notify = handle_new_xwayland_surface; wl_signal_add(&xwayland->wlr_xwayland->events.new_surface, &xwayland->new_xwayland_surface); xwayland->xwayland_ready.notify = handle_xwayland_ready; wl_signal_add(&xwayland->wlr_xwayland->events.ready, &xwayland->xwayland_ready); xwayland->server_destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &xwayland->server_destroy); xwayland->max_scale.notify = handle_max_scale; output_manager_add_max_scale_listener(&xwayland->max_scale); xwayland->seat_destroy.notify = handle_seat_destroy; wl_list_init(&xwayland->seat_destroy.link); xwayland->activate_view.notify = handle_activate_view; view_manager_add_activate_view_listener(&xwayland->activate_view); xwayland->surface_unmap.notify = handle_surface_unmap; wl_list_init(&xwayland->surface_unmap.link); xwayland->view_minimized.notify = handle_view_minimized; wl_list_init(&xwayland->view_minimized.link); xwayland->shell = xwayland->wlr_xwayland->shell_v1; xwayland->shell_destroy.notify = handle_shell_destroy; wl_signal_add(&xwayland->shell->events.destroy, &xwayland->shell_destroy); security_add_global_filter(xwayland->shell->global, xwayland_filter_global, NULL); setenv("DISPLAY", xwayland->wlr_xwayland->display_name, true); kywc_log(KYWC_INFO, "Xwayland is running on display %s", xwayland->wlr_xwayland->display_name); return true; } void xwayland_server_destroy(void) { if (!xwayland) { return; } wl_list_remove(&xwayland->xwayland_ready.link); wl_list_remove(&xwayland->new_xwayland_surface.link); wl_list_remove(&xwayland->seat_destroy.link); xwayland_end_drag_x11(xwayland); struct wlr_xwayland *wlr_xwayland = xwayland->wlr_xwayland; /* prevent xwayland_update_seat in hover */ xwayland->wlr_xwayland = NULL; wlr_xwayland_destroy(wlr_xwayland); } bool xwayland_check_client(const struct wl_client *client) { return xwayland && xwayland->wlr_xwayland && xwayland->wlr_xwayland->server && xwayland->wlr_xwayland->server->client == client; } int xwayland_unscale(int value) { if (!xwayland) { return value; } int32_t K = (int32_t)(xwayland->scale * 256.0f); int64_t n = (int64_t)value * 256; int64_t b = n >= 0 ? (n + K / 2) / K : (n - K / 2) / K; return CLAMP(b, INT32_MIN, INT32_MAX); } int xwayland_scale(int value) { if (!xwayland) { return value; } int32_t K = (int32_t)(xwayland->scale * 256.0f); int64_t p = (int64_t)value * K; int64_t a = (p >= 0) ? (p + 128) / 256 : (p - 128) / 256; return CLAMP(a, INT32_MIN, INT32_MAX); } float xwayland_get_scale(void) { return xwayland ? xwayland->scale : 1.0; } bool xwayland_surface_has_type(struct wlr_xwayland_surface *wlr_xwayland_surface, int type) { for (size_t i = 0; i < wlr_xwayland_surface->window_type_len; ++i) { xcb_atom_t atom = wlr_xwayland_surface->window_type[i]; if (atom == xwayland->atoms[type]) { return true; } } return false; } bool xwayland_surface_has_input(struct wlr_xwayland_surface *wlr_xwayland_surface, uint32_t input) { xcb_get_window_attributes_reply_t *reply = xcb_get_window_attributes_reply( xwayland->xcb_conn, xcb_get_window_attributes_unchecked(xwayland->xcb_conn, wlr_xwayland_surface->window_id), NULL); if (!reply) { return true; } uint32_t input_mask = 0; if (input & INPUT_MASK_POINTER) { input_mask |= XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_POINTER_MOTION; } if (input & INPUT_MASK_KEYBOARD) { input_mask |= XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE; } bool has_input = reply->all_event_masks & input_mask; free(reply); return has_input; } static char *xwayland_get_atom_name(xcb_atom_t atom) { xcb_get_atom_name_cookie_t name_cookie = xcb_get_atom_name(xwayland->xcb_conn, atom); xcb_get_atom_name_reply_t *name_reply = xcb_get_atom_name_reply(xwayland->xcb_conn, name_cookie, NULL); if (name_reply == NULL) { return strdup("UNKNOWN"); } size_t len = xcb_get_atom_name_name_length(name_reply); char *buf = xcb_get_atom_name_name(name_reply); // not a C string char *name = strndup(buf, len); free(name_reply); return name; } char *xwayland_mime_type_from_atom(xcb_atom_t atom) { if (atom == xwayland->atoms[UTF8_STRING]) { return strdup("text/plain;charset=utf-8"); } else if (atom == xwayland->atoms[TEXT]) { return strdup("text/plain"); } else { return xwayland_get_atom_name(atom); } } void xwayland_fixup_pointer_position(struct wlr_surface *surface) { struct wlr_seat *seat = xwayland->wlr_xwayland->seat; if (xwayland->hoverd_surface || !xwayland->wlr_xwayland->seat) { return; } struct wl_client *wl_client = wl_resource_get_client(surface->resource); struct wlr_seat_client *client = wlr_seat_client_for_wl_client(seat, wl_client); if (!client) { return; } uint32_t serial = wlr_seat_client_next_serial(client); struct wl_resource *resource; wl_resource_for_each(resource, &client->pointers) { if (!wlr_seat_client_from_pointer_resource(resource)) { continue; } wl_pointer_send_enter(resource, serial, surface->resource, wl_fixed_from_double(FLT_MAX), wl_fixed_from_double(FLT_MAX)); wl_pointer_send_leave(resource, serial, surface->resource); if (wl_resource_get_version(resource) >= WL_POINTER_FRAME_SINCE_VERSION) { wl_pointer_send_frame(resource); } } } void xwayland_surface_debug_type(struct wlr_xwayland_surface *wlr_xwayland_surface) { for (size_t i = 0; i < wlr_xwayland_surface->window_type_len; ++i) { xcb_atom_t atom = wlr_xwayland_surface->window_type[i]; char *atom_name = xwayland_get_atom_name(atom); kywc_log(KYWC_INFO, "%s: type atom %s %ld(%ld)", wlr_xwayland_surface->class, atom_name, i, wlr_xwayland_surface->window_type_len); free(atom_name); } kywc_log(KYWC_INFO, "%s: OR %d size %d x %d %d", wlr_xwayland_surface->class, wlr_xwayland_surface->override_redirect, wlr_xwayland_surface->width, wlr_xwayland_surface->height, wlr_xwayland_surface->fullscreen); } void xwayland_update_workarea(void) { if (!xwayland || !xwayland->wlr_xwayland || !xwayland->wlr_xwayland->xwm) { return; } struct wlr_box box; output_layout_get_workarea(&box); box.x = xwayland_scale(box.x); box.y = xwayland_scale(box.y); box.width = xwayland_scale(box.width); box.height = xwayland_scale(box.height); wlr_xwayland_set_workareas(xwayland->wlr_xwayland, &box, 1); } enum wl_data_device_manager_dnd_action data_device_manager_dnd_action_from_atom(enum atom_name atom) { if (atom == xwayland->atoms[DND_ACTION_COPY] || atom == xwayland->atoms[DND_ACTION_PRIVATE]) { return WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; } else if (atom == xwayland->atoms[DND_ACTION_MOVE]) { return WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; } else if (atom == xwayland->atoms[DND_ACTION_ASK]) { return WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; } return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; } xcb_atom_t data_device_manager_dnd_action_to_atom(enum wl_data_device_manager_dnd_action action) { if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) { return xwayland->atoms[DND_ACTION_COPY]; } else if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) { return xwayland->atoms[DND_ACTION_MOVE]; } else if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { return xwayland->atoms[DND_ACTION_ASK]; } return XCB_ATOM_NONE; } bool xwayland_is_dragging_x11(struct seat *seat) { if (!xwayland || !xwayland->wlr_xwayland || !xwayland->wlr_xwayland->seat) { return false; } struct seat *xwayland_seat = seat_from_wlr_seat(xwayland->wlr_xwayland->seat); return (!seat || xwayland_seat == seat) && xwayland->drag_x11; } void xwayland_surface_set_grab_pointer(struct wlr_xwayland_surface *wlr_xwayland_surface, struct wlr_seat *wlr_seat, bool grab) { if (!wlr_xwayland_surface || !wlr_seat) { return; } /* currently, only unmanaged is supported */ xwayland_unmanaged_set_grab_pointer(xwayland, wlr_xwayland_surface, wlr_seat, grab); } kylin-wayland-compositor/src/xwayland/view.c0000664000175000017500000016132115160461067020225 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include "input/event.h" #include "input/seat.h" #include "output.h" #include "painter.h" #include "scene/surface.h" #include "theme.h" #include "util/macros.h" #include "view/action.h" #include "xwayland_p.h" struct xwayland_view { struct view view; struct wl_list link; struct xwayland_server *xwayland; struct wlr_xwayland_surface *wlr_xwayland_surface; struct wl_listener surface_tree_destroy; bool minimizable, maximizable; bool focusable, activatable, movable, resizable; struct wl_listener view_update_capabilities; struct wl_listener client_commit; struct wl_listener commit; struct wl_listener associate; struct wl_listener dissociate; struct wl_listener map; struct wl_listener unmap; struct wl_listener destroy; struct wl_listener request_configure; struct wl_listener request_move; struct wl_listener request_resize; struct wl_listener request_minimize; struct wl_listener request_maximize; struct wl_listener request_fullscreen; struct wl_listener request_activate; struct wl_listener request_sticky; struct wl_listener request_skip_taskbar; struct wl_listener request_above; struct wl_listener request_below; struct wl_listener request_demands_attention; /* view state need sync with xwayland */ struct wl_listener view_above; struct wl_listener view_below; struct wl_listener view_sticky; struct wl_listener view_skip_taskbar; struct wl_listener view_demands_attention; struct wl_listener set_title; struct wl_listener set_class; // struct wl_listener set_role; struct wl_listener set_parent; // struct wl_listener set_startup_id; // struct wl_listener set_window_type; struct wl_listener set_hints; struct wl_listener set_decorations; struct wl_listener set_strut_partial; struct wl_listener set_override_redirect; // struct wl_listener set_geometry; struct wl_listener ping_timeout; struct wl_list net_wm_icons; // from net_wm_icon struct { xcb_sync_counter_t counter; xcb_sync_int64_t value; xcb_sync_alarm_t alarm; bool blocked; } sync; }; struct net_wm_icon { struct wl_list link; uint32_t width, height; unsigned char *data; struct wlr_buffer *buffer; }; static bool xwayland_view_hover(struct seat *seat, struct ky_scene_node *node, double x, double y, uint32_t time, bool first, bool hold, void *data) { struct wlr_surface *surface = wlr_surface_try_from_node(node); struct xwayland_view *xwayland_view = data; if (first) { view_hover(seat, &xwayland_view->view); } xwayland_update_seat(seat); xwayland_update_hovered_surface(surface); if (!hold) { seat_notify_motion(seat, surface, time, xwayland_scale(x), xwayland_scale(y), first); return false; } double sx = x - xwayland_view->view.base.geometry.x; double sy = y - xwayland_view->view.base.geometry.y; seat_notify_motion(seat, surface, time, xwayland_scale(sx), xwayland_scale(sy), first); return true; } static void xwayland_view_click(struct seat *seat, struct ky_scene_node *node, uint32_t button, bool pressed, uint32_t time, enum click_state state, void *data) { xwayland_update_seat(seat); seat_notify_button(seat, time, button, pressed); /* only do activated when button pressed */ if (!pressed) { return; } struct xwayland_view *xwayland_view = data; view_click(seat, &xwayland_view->view, button, pressed, state); } static void xwayland_view_leave(struct seat *seat, struct ky_scene_node *node, bool last, void *data) { /* so surface will call set_cursor when enter again */ struct wlr_surface *surface = wlr_surface_try_from_node(node); seat_notify_leave(seat, surface); } static struct ky_scene_node *xwayland_view_get_root(void *data) { struct xwayland_view *xwayland_view = data; return &xwayland_view->view.tree->node; } static struct wlr_surface *xwayland_view_get_toplevel(void *data) { struct xwayland_view *xwayland_view = data; return xwayland_view->view.surface; } static const struct input_event_node_impl xwayland_view_event_node_impl = { .hover = xwayland_view_hover, .click = xwayland_view_click, .leave = xwayland_view_leave, }; static struct xwayland_view *xwayland_view_from_view(struct view *view) { struct xwayland_view *xwayland_view = wl_container_of(view, xwayland_view, view); return xwayland_view; } static void xwayland_view_close(struct view *view) { struct xwayland_view *xwayland_view = xwayland_view_from_view(view); wlr_xwayland_surface_close(xwayland_view->wlr_xwayland_surface); } static void xwayland_view_destroy(struct view *view) { struct xwayland_view *xwayland_view = xwayland_view_from_view(view); free(xwayland_view); } static void xwayland_view_move(struct xwayland_view *xwayland_view, int x, int y) { struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface; struct view *view = &xwayland_view->view; /* xwayland views need sync position except in moving */ if (!view->interactive_moving) { wlr_xwayland_surface_configure(wlr_xwayland_surface, xwayland_scale(x), xwayland_scale(y), wlr_xwayland_surface->width, wlr_xwayland_surface->height); } view_helper_move(view, x, y); } static void xwayland_restack_view(struct xwayland_view *xwayland_view) { struct wlr_xwayland_surface *surface = xwayland_view->wlr_xwayland_surface; if (xwayland_view->view.base.kept_below) { wlr_xwayland_surface_restack(surface, NULL, XCB_STACK_MODE_BELOW); return; } else if (xwayland_view->view.base.kept_above) { wlr_xwayland_surface_restack(surface, NULL, XCB_STACK_MODE_ABOVE); return; } wlr_xwayland_surface_restack(surface, NULL, XCB_STACK_MODE_ABOVE); struct xwayland_view *view; wl_list_for_each(view, &xwayland_view->xwayland->surfaces, link) { surface = view->wlr_xwayland_surface; if (xwayland_view->view.base.kept_above) { wlr_xwayland_surface_restack(surface, NULL, XCB_STACK_MODE_ABOVE); } } } static void xwayland_view_ping(struct view *view) { struct xwayland_view *xwayland_view = xwayland_view_from_view(view); struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface; wlr_xwayland_surface_ping(wlr_xwayland_surface); } static void xwayland_view_configure(struct view *view) { struct xwayland_view *xwayland_view = xwayland_view_from_view(view); struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface; struct kywc_view *kywc_view = &xwayland_view->view.base; bool fixup_pointer_position = false; if (view->pending.action & VIEW_ACTION_MINIMIZE) { view->pending.action &= ~VIEW_ACTION_MINIMIZE; fixup_pointer_position = !kywc_view->minimized; wlr_xwayland_surface_set_minimized(wlr_xwayland_surface, kywc_view->minimized); } if (view->pending.action & VIEW_ACTION_ACTIVATE) { view->pending.action &= ~VIEW_ACTION_ACTIVATE; if (kywc_view->activated && wlr_xwayland_surface->minimized) { fixup_pointer_position = true; wlr_xwayland_surface_set_minimized(wlr_xwayland_surface, false); } if (kywc_view->activated) { wlr_xwayland_surface_activate(wlr_xwayland_surface, true); xwayland_view->xwayland->activated_surface = wlr_xwayland_surface; xwayland_restack_view(xwayland_view); } } if (fixup_pointer_position) { xwayland_fixup_pointer_position(wlr_xwayland_surface->surface); } /* direct move when not changed size */ if (view->pending.action & VIEW_ACTION_MOVE) { view->pending.action &= ~VIEW_ACTION_MOVE; if (!view_action_change_size(view->pending.configure_action)) { xwayland_view_move(xwayland_view, view->pending.geometry.x, view->pending.geometry.y); } else { kywc_log(KYWC_DEBUG, "Skip move when pending configure action 0x%x", view->pending.configure_action); } } if (view->pending.action == VIEW_ACTION_NOP) { return; } /* now, only changed size action left */ assert(view_action_change_size(view->pending.action)); if (view->pending.action & VIEW_ACTION_FULLSCREEN) { wlr_xwayland_surface_set_fullscreen(wlr_xwayland_surface, kywc_view->fullscreen); } if (view->pending.action & VIEW_ACTION_MAXIMIZE) { wlr_xwayland_surface_set_maximized(wlr_xwayland_surface, kywc_view->maximized, kywc_view->maximized); } struct kywc_box *pending = &view->pending.geometry; /* If no need to resizing, process the move immediately */ if (!view_action_change_size(view->pending.configure_action) && wlr_xwayland_surface->width == xwayland_scale(pending->width) && wlr_xwayland_surface->height == xwayland_scale(pending->height)) { view->pending.action &= ~VIEW_ACTION_RESIZE; xwayland_view_move(xwayland_view, pending->x, pending->y); view_configure(&xwayland_view->view, 0); view_configured(&xwayland_view->view, true); return; } /* there is no commit after map */ if (view->base.has_initial_position) { view_helper_move(view, pending->x, pending->y); } if (xwayland_view->sync.counter) { /* skip the configure if prev one is not acked when resizing */ if (xwayland_view->sync.blocked && view->current_resize_edges != KYWC_EDGE_NONE) { view_configure(&xwayland_view->view, xwayland_view->sync.counter); return; } if (!xwayland_view->sync.blocked) { xwayland_set_allow_commits(wlr_xwayland_surface->window_id, false); xwayland_view->sync.blocked = true; } xwayland_send_sync_request(wlr_xwayland_surface->window_id, &xwayland_view->sync.value); } wlr_xwayland_surface_configure(wlr_xwayland_surface, xwayland_scale(pending->x), xwayland_scale(pending->y), xwayland_scale(pending->width), xwayland_scale(pending->height)); view_configure(&xwayland_view->view, xwayland_view->sync.counter); } static void xwayland_view_configure_timeout(struct view *view) { struct xwayland_view *xwayland_view = xwayland_view_from_view(view); struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface; assert(xwayland_view->sync.counter); /* ack_synced but not committed */ if (!xwayland_view->sync.blocked) { return; } xwayland_set_allow_commits(wlr_xwayland_surface->window_id, true); xwayland_view->sync.blocked = false; } static struct wlr_buffer *xwayland_view_get_wm_icon_buffer(struct view *view, int size, float scale) { struct xwayland_view *xwayland_view = xwayland_view_from_view(view); struct draw_info info = { .width = size, .height = size, .scale = scale }; float scale_width = info.width * info.scale; float min_abs = FLT_MAX; float tmp_abs; struct net_wm_icon *icon_similar = NULL; struct net_wm_icon *icon; wl_list_for_each(icon, &xwayland_view->net_wm_icons, link) { tmp_abs = fabs(icon->width - scale_width); if (tmp_abs < min_abs) { min_abs = tmp_abs; icon_similar = icon; } } if (!icon_similar) { return NULL; } if (icon_similar->buffer) { return icon_similar->buffer; } info.pixel.width = icon_similar->width; info.pixel.height = icon_similar->height; info.pixel.data = icon_similar->data; icon_similar->buffer = painter_draw_buffer(&info); return icon_similar->buffer; } static void xwayland_view_update_usable_area(struct view *view, struct kywc_output *output, struct kywc_box *usable_area, enum kywc_edges edge) { struct xwayland_view *xwayland_view = wl_container_of(view, xwayland_view, view); struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface; struct kywc_box geo; kywc_output_effective_geometry(xwayland_view->view.output, &geo); xcb_ewmh_wm_strut_partial_t *strut = wlr_xwayland_surface->strut_partial; if (strut->left_start_y != strut->left_end_y) { int scaled_left = xwayland_unscale(strut->left); geo.x += scaled_left; geo.width -= scaled_left; } if (strut->right_start_y != strut->right_end_y) { geo.width -= xwayland_unscale(strut->right); } if (strut->top_start_x != strut->top_end_x) { int scaled_top = xwayland_unscale(strut->top); geo.y += scaled_top; geo.height -= scaled_top; } if (strut->bottom_start_x != strut->bottom_end_x) { geo.height -= xwayland_unscale(strut->bottom); } /* intersect usable_area and geo */ usable_area->x = MAX(geo.x, usable_area->x); usable_area->y = MAX(geo.y, usable_area->y); usable_area->width = MIN(geo.width, usable_area->width); usable_area->height = MIN(geo.height, usable_area->height); } static const struct view_impl xwl_surface_impl = { .ping = xwayland_view_ping, .configure = xwayland_view_configure, .close = xwayland_view_close, .destroy = xwayland_view_destroy, .configure_timeout = xwayland_view_configure_timeout, .update_usable_area = xwayland_view_update_usable_area, .get_icon_buffer = xwayland_view_get_wm_icon_buffer, }; static void xwayland_view_update_geometry(struct xwayland_view *xwayland_view) { struct wlr_surface_state *state = &xwayland_view->wlr_xwayland_surface->surface->current; xcb_size_hints_t *size_hints = xwayland_view->wlr_xwayland_surface->size_hints; if (!size_hints) { view_update_size(&xwayland_view->view, state->width, state->height, 0, 0, 0, 0); } else { /* convert -1 to zero followed by xdg-shell */ view_update_size(&xwayland_view->view, state->width, state->height, size_hints->min_width < 0 ? 0 : xwayland_unscale(size_hints->min_width), size_hints->min_height < 0 ? 0 : xwayland_unscale(size_hints->min_height), size_hints->max_width < 0 ? 0 : xwayland_unscale(size_hints->max_width), size_hints->max_height < 0 ? 0 : xwayland_unscale(size_hints->max_height)); } } static void xwayland_view_handle_commit(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, commit); struct kywc_box geo = xwayland_view->view.base.geometry; struct kywc_box *current = &xwayland_view->view.base.geometry; uint32_t resize_edges = xwayland_view->view.current_resize_edges; xwayland_view_update_geometry(xwayland_view); enum view_action pending_action = xwayland_view->view.pending.configure_action; if (pending_action == VIEW_ACTION_NOP) { /* fix position when resizing by left or top edges */ int x = resize_edges & KYWC_EDGE_LEFT ? geo.x + geo.width - current->width : geo.x; int y = resize_edges & KYWC_EDGE_TOP ? geo.y + geo.height - current->height : geo.y; if (x != geo.x || y != geo.y) { xwayland_view_move(xwayland_view, x, y); } return; } assert(view_action_change_size(pending_action)); struct kywc_box *pending = &xwayland_view->view.pending.configure_geometry; int x = pending->x, y = pending->y; if (pending_action & VIEW_ACTION_RESIZE) { if (resize_edges & KYWC_EDGE_LEFT) { x += pending->width - current->width; } if (resize_edges & KYWC_EDGE_TOP) { y += pending->height - current->height; } } xwayland_view_move(xwayland_view, x, y); /* don't clear configure timer if this commit is handled after block */ view_configured(&xwayland_view->view, !xwayland_view->sync.blocked); } static void xwayland_view_handle_request_move(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_move); struct seat *seat = seat_from_wlr_seat(xwayland_view->xwayland->wlr_xwayland->seat); if (view_validate_move_or_resize_request(&xwayland_view->view, seat)) { window_begin_move(&xwayland_view->view, seat); } } static void xwayland_view_handle_request_resize(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_resize); struct wlr_xwayland_resize_event *event = data; struct seat *seat = seat_from_wlr_seat(xwayland_view->xwayland->wlr_xwayland->seat); if (view_validate_move_or_resize_request(&xwayland_view->view, seat)) { window_begin_resize(&xwayland_view->view, event->edges, seat); } } static void xwayland_view_handle_request_minimize(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_minimize); struct wlr_xwayland_minimize_event *event = data; kywc_view_set_minimized(&xwayland_view->view.base, event->minimize); } static void xwayland_view_handle_request_maximize(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_maximize); bool maximized = xwayland_view->wlr_xwayland_surface->maximized_horz && xwayland_view->wlr_xwayland_surface->maximized_vert; kywc_view_set_maximized(&xwayland_view->view.base, maximized, NULL); } static void xwayland_view_handle_request_fullscreen(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_fullscreen); bool fullscreen = xwayland_view->wlr_xwayland_surface->fullscreen; kywc_view_set_fullscreen(&xwayland_view->view.base, fullscreen, NULL); } static void xwayland_view_handle_request_activate(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_activate); /* force sync activate surface and focus surface */ if (xwayland_view->xwayland->activated_surface == xwayland_view->wlr_xwayland_surface) { wlr_xwayland_surface_activate(xwayland_view->wlr_xwayland_surface, true); } kywc_view_activate(&xwayland_view->view.base); struct wlr_seat *wlr_seat = xwayland_view->xwayland->wlr_xwayland->seat; struct seat *seat = wlr_seat ? seat_from_wlr_seat(wlr_seat) : input_manager_get_default_seat(); view_set_focus(&xwayland_view->view, seat); } static void xwayland_view_handle_request_sticky(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_sticky); bool sticky = xwayland_view->wlr_xwayland_surface->sticky; kywc_view_set_sticky(&xwayland_view->view.base, sticky); } static void xwayland_view_handle_request_skip_taskbar(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_skip_taskbar); bool skip_taskbar = xwayland_view->wlr_xwayland_surface->skip_taskbar; kywc_view_set_skip_taskbar(&xwayland_view->view.base, skip_taskbar); } static void xwayland_view_handle_request_above(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_above); bool above = xwayland_view->wlr_xwayland_surface->above; kywc_view_set_kept_above(&xwayland_view->view.base, above); } static void xwayland_view_handle_request_below(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_below); bool below = xwayland_view->wlr_xwayland_surface->below; kywc_view_set_kept_below(&xwayland_view->view.base, below); } static void xwayland_view_handle_request_demands_attention(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_demands_attention); bool demands_attention = xwayland_view->wlr_xwayland_surface->demands_attention; kywc_view_set_demands_attention(&xwayland_view->view.base, demands_attention); } static void xwayland_view_handle_set_title(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_title); view_set_title(&xwayland_view->view, xwayland_view->wlr_xwayland_surface->title); } static void xwayland_view_handle_set_class(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_class); view_set_app_id(&xwayland_view->view, xwayland_view->wlr_xwayland_surface->class); } static void xwayland_view_handle_set_parent(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_parent); struct wlr_xwayland_surface *parent = xwayland_view->wlr_xwayland_surface->parent; struct xwayland_view *parent_xwayland_view = parent ? parent->data : NULL; struct view *parent_view = parent_xwayland_view ? &parent_xwayland_view->view : NULL; view_set_parent(&xwayland_view->view, parent_view); } static void xwayland_view_handle_set_hints(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_hints); enum wlr_xwayland_icccm_input_model input_model = wlr_xwayland_surface_icccm_input_model(xwayland_view->wlr_xwayland_surface); if (input_model == WLR_ICCCM_INPUT_MODEL_NONE) { xwayland_view->focusable = xwayland_view->activatable = false; } else { xwayland_view->focusable = xwayland_view->activatable = true; } view_update_capabilities(&xwayland_view->view, KYWC_VIEW_FOCUSABLE | KYWC_VIEW_ACTIVATABLE); } static void xwayland_view_handle_set_decorations(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_decorations); struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface; if (xwayland_surface_has_type(wlr_xwayland_surface, NET_WM_WINDOW_TYPE_DOCK) || xwayland_surface_has_type(wlr_xwayland_surface, NET_WM_WINDOW_TYPE_SPLASH) || xwayland_surface_has_type(wlr_xwayland_surface, KDE_NET_WM_WINDOW_TYPE_OVERRIDE)) { view_set_decoration(&xwayland_view->view, KYWC_SSD_NONE); return; } /* disable ssd if the window has clip region */ if (xwayland_view->view.surface) { struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(xwayland_view->view.surface); if (pixman_region32_not_empty(&buffer->node.clip_region)) { view_set_decoration(&xwayland_view->view, KYWC_SSD_NONE); return; } } enum kywc_ssd ssd = KYWC_SSD_ALL; if (wlr_xwayland_surface->decorations & WLR_XWAYLAND_SURFACE_DECORATIONS_NO_BORDER) { ssd &= ~KYWC_SSD_BORDER; } if (wlr_xwayland_surface->decorations & WLR_XWAYLAND_SURFACE_DECORATIONS_NO_TITLE) { ssd &= ~KYWC_SSD_TITLE; } /* don't add shadow if surface decoration is 0 */ if (ssd == KYWC_SSD_RESIZE) { ssd = KYWC_SSD_NONE; } view_set_decoration(&xwayland_view->view, ssd); } static void xwayland_view_handle_ping_timeout(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, ping_timeout); view_show_hang_window(&xwayland_view->view); } static void xwayland_view_set_strut_partial(struct xwayland_view *xwayland_view) { struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface; xcb_ewmh_wm_strut_partial_t *strut = wlr_xwayland_surface->strut_partial; bool has_area = strut && (strut->left_start_y != strut->left_end_y || strut->right_start_y != strut->right_end_y || strut->top_start_x != strut->top_end_x || strut->bottom_start_x != strut->bottom_end_x); xwayland_view->movable = !has_area; view_update_capabilities(&xwayland_view->view, KYWC_VIEW_MOVABLE); xwayland_view->view.base.unconstrained = has_area; view_set_exclusive(&xwayland_view->view, has_area); } static void xwayland_view_handle_set_strut_partial(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_strut_partial); xwayland_view_set_strut_partial(xwayland_view); } static void xwayland_view_adjust_geometry(struct xwayland_view *xwayland_view, struct kywc_box *geo) { struct kywc_view *kywc_view = &xwayland_view->view.base; struct kywc_output *kywc_output = kywc_output_at_point(geo->x, geo->y); if (kywc_view->fullscreen) { kywc_output_effective_geometry(kywc_output, geo); return; } else if (kywc_view->maximized) { view_get_tiled_geometry(&xwayland_view->view, geo, kywc_output, KYWC_TILE_ALL); return; } else if (kywc_view->tiled) { view_get_tiled_geometry(&xwayland_view->view, geo, kywc_output, kywc_view->tiled); return; } int max_width = kywc_view->max_width; int max_height = kywc_view->max_height; int width = 0, height = 0; output_layout_get_size(&width, &height); if (max_width <= 0 || max_width > width - kywc_view->margin.off_width) { max_width = width - kywc_view->margin.off_width; } if (max_height <= 0 || max_height > height - kywc_view->margin.off_height) { max_height = height - kywc_view->margin.off_height; } geo->width = CLAMP(geo->width, kywc_view->min_width, max_width); geo->height = CLAMP(geo->height, kywc_view->min_height, max_height); if (xwayland_view->view.exclusive_zone) { return; } struct output *output = output_from_kywc_output(kywc_output); if (kywc_view->has_initial_position) { int min_x = output->usable_area.x + kywc_view->margin.off_x; int min_y = output->usable_area.y + kywc_view->margin.off_y; int max_x = output->usable_area.x + output->usable_area.width; int max_y = output->usable_area.y + output->usable_area.height; geo->x = MAX(min_x, geo->x); geo->y = MAX(min_y, geo->y); if (geo->x > max_x) { geo->x = geo->width > output->usable_area.width ? min_x : max_x - geo->width; } if (geo->y > max_y) { geo->y = geo->height > output->usable_area.height ? min_y : max_y - geo->height; } } else { window_move_constraints(kywc_view, output, &geo->x, &geo->y, geo->width, geo->height); } } static void xwayland_view_apply_type(struct xwayland_view *xwayland_view) { struct wlr_xwayland_surface *surface = xwayland_view->wlr_xwayland_surface; struct view_layer *layer = NULL; bool removed_from_workspace = false; if (xwayland_surface_has_type(surface, NET_WM_WINDOW_TYPE_DESKTOP)) { layer = view_manager_get_layer(LAYER_DESKTOP, false); xwayland_view->resizable = false; view_update_capabilities(&xwayland_view->view, KYWC_VIEW_RESIZABLE); xwayland_view->view.base.role = KYWC_VIEW_ROLE_DESKTOP; removed_from_workspace = true; } else if (xwayland_surface_has_type(surface, NET_WM_WINDOW_TYPE_DOCK)) { layer = view_manager_get_layer(LAYER_DOCK, false); xwayland_view->focusable = xwayland_view->activatable = xwayland_view->resizable = false; view_update_capabilities(&xwayland_view->view, KYWC_VIEW_FOCUSABLE | KYWC_VIEW_ACTIVATABLE | KYWC_VIEW_RESIZABLE); xwayland_view->view.base.role = KYWC_VIEW_ROLE_PANEL; removed_from_workspace = true; } else if (xwayland_surface_has_type(surface, NET_WM_WINDOW_TYPE_UTILITY) && !xwayland_surface_has_input(surface, INPUT_MASK_KEYBOARD)) { xwayland_view->focusable = xwayland_view->activatable = false; view_update_capabilities(&xwayland_view->view, KYWC_VIEW_FOCUSABLE | KYWC_VIEW_ACTIVATABLE); } if (removed_from_workspace) { view_unset_workspace(&xwayland_view->view, layer); } xwayland_view->view.base.has_round_corner = xwayland_surface_has_type(surface, NET_WM_WINDOW_TYPE_NORMAL) || xwayland_surface_has_type(surface, NET_WM_WINDOW_TYPE_DIALOG); } void xwayland_view_set_skip_switcher(struct wlr_xwayland_surface *surface, bool skip_switcher) { struct xwayland_view *xwayland_view = surface->data; if (!xwayland_view) { return; } kywc_view_set_skip_switcher(&xwayland_view->view.base, skip_switcher); } static void xwayland_view_fixup_geometry(struct xwayland_view *xwayland_view) { struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface; struct kywc_view *kywc_view = &xwayland_view->view.base; struct kywc_box geo = kywc_view->geometry; if (wlr_xwayland_surface->x != 0 || wlr_xwayland_surface->y != 0) { geo.x = xwayland_unscale(wlr_xwayland_surface->x); geo.y = xwayland_unscale(wlr_xwayland_surface->y); kywc_view->has_initial_position = true; } else { /* apply the position in size_hints */ xcb_size_hints_t *size_hints = wlr_xwayland_surface->size_hints; if (size_hints && size_hints->flags & (XCB_ICCCM_SIZE_HINT_US_POSITION | XCB_ICCCM_SIZE_HINT_P_POSITION)) { geo.x = xwayland_unscale(size_hints->x); geo.y = xwayland_unscale(size_hints->y); kywc_view->has_initial_position = true; } } xwayland_view_adjust_geometry(xwayland_view, &geo); view_do_resize(&xwayland_view->view, &geo); } static bool wlr_xwayland_surface_is_window(struct wlr_xwayland_surface *surface) { return surface && surface->surface && surface->class && *surface->class && (xwayland_surface_has_type(surface, NET_WM_WINDOW_TYPE_NORMAL) || xwayland_surface_has_type(surface, NET_WM_WINDOW_TYPE_DIALOG)); } static bool xwayland_view_fixup_parent(struct xwayland_view *xwayland_view) { if (wlr_xwayland_surface_is_window(xwayland_view->wlr_xwayland_surface->parent)) { return true; } struct view *parent = NULL; struct xwayland_view *xview; wl_list_for_each(xview, &xwayland_view->xwayland->surfaces, link) { if (!xview->view.base.mapped || xview == xwayland_view || xview->wlr_xwayland_surface->pid != xwayland_view->wlr_xwayland_surface->pid) { continue; } if (wlr_xwayland_surface_is_window(xview->wlr_xwayland_surface)) { parent = &xview->view; } } view_set_parent(&xwayland_view->view, parent); return !!parent; } static void xwayland_view_handle_map(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, map); struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface; xwayland_view_update_geometry(xwayland_view); view_set_app_id(&xwayland_view->view, wlr_xwayland_surface->class); view_set_title(&xwayland_view->view, wlr_xwayland_surface->title); xwayland_view_handle_set_parent(&xwayland_view->set_parent, NULL); xwayland_view_handle_set_decorations(&xwayland_view->set_decorations, NULL); xwayland_view_handle_request_maximize(&xwayland_view->request_maximize, NULL); xwayland_view_handle_request_fullscreen(&xwayland_view->request_fullscreen, NULL); xwayland_view_handle_request_sticky(&xwayland_view->request_sticky, NULL); xwayland_view_handle_request_skip_taskbar(&xwayland_view->request_skip_taskbar, NULL); xwayland_view_handle_request_above(&xwayland_view->request_above, NULL); xwayland_view_handle_request_below(&xwayland_view->request_below, NULL); xwayland_view_handle_request_demands_attention(&xwayland_view->request_demands_attention, NULL); xwayland_view_handle_set_hints(&xwayland_view->set_hints, NULL); xwayland_apply_ukui_decoration(wlr_xwayland_surface->window_id, false); assert(wlr_xwayland_surface->surface == xwayland_view->view.surface); xwayland_view->commit.notify = xwayland_view_handle_commit; wl_signal_add(&wlr_xwayland_surface->surface->events.commit, &xwayland_view->commit); xwayland_view->set_strut_partial.notify = xwayland_view_handle_set_strut_partial; wl_signal_add(&wlr_xwayland_surface->events.set_strut_partial, &xwayland_view->set_strut_partial); xwayland_view->request_move.notify = xwayland_view_handle_request_move; wl_signal_add(&wlr_xwayland_surface->events.request_move, &xwayland_view->request_move); xwayland_view->request_resize.notify = xwayland_view_handle_request_resize; wl_signal_add(&wlr_xwayland_surface->events.request_resize, &xwayland_view->request_resize); xwayland_view->request_maximize.notify = xwayland_view_handle_request_maximize; wl_signal_add(&wlr_xwayland_surface->events.request_maximize, &xwayland_view->request_maximize); xwayland_view->request_fullscreen.notify = xwayland_view_handle_request_fullscreen; wl_signal_add(&wlr_xwayland_surface->events.request_fullscreen, &xwayland_view->request_fullscreen); xwayland_view->request_activate.notify = xwayland_view_handle_request_activate; wl_signal_add(&wlr_xwayland_surface->events.request_activate, &xwayland_view->request_activate); xwayland_view->request_sticky.notify = xwayland_view_handle_request_sticky; wl_signal_add(&wlr_xwayland_surface->events.request_sticky, &xwayland_view->request_sticky); xwayland_view->request_skip_taskbar.notify = xwayland_view_handle_request_skip_taskbar; wl_signal_add(&wlr_xwayland_surface->events.request_skip_taskbar, &xwayland_view->request_skip_taskbar); xwayland_view->request_above.notify = xwayland_view_handle_request_above; wl_signal_add(&wlr_xwayland_surface->events.request_above, &xwayland_view->request_above); xwayland_view->request_below.notify = xwayland_view_handle_request_below; wl_signal_add(&wlr_xwayland_surface->events.request_below, &xwayland_view->request_below); xwayland_view->request_demands_attention.notify = xwayland_view_handle_request_demands_attention; wl_signal_add(&wlr_xwayland_surface->events.request_demands_attention, &xwayland_view->request_demands_attention); xwayland_view->set_title.notify = xwayland_view_handle_set_title; wl_signal_add(&wlr_xwayland_surface->events.set_title, &xwayland_view->set_title); xwayland_view->set_class.notify = xwayland_view_handle_set_class; wl_signal_add(&wlr_xwayland_surface->events.set_class, &xwayland_view->set_class); xwayland_view->set_parent.notify = xwayland_view_handle_set_parent; wl_signal_add(&wlr_xwayland_surface->events.set_parent, &xwayland_view->set_parent); xwayland_view->ping_timeout.notify = xwayland_view_handle_ping_timeout; wl_signal_add(&wlr_xwayland_surface->events.ping_timeout, &xwayland_view->ping_timeout); xwayland_view->set_hints.notify = xwayland_view_handle_set_hints; wl_signal_add(&wlr_xwayland_surface->events.set_hints, &xwayland_view->set_hints); xwayland_view->set_decorations.notify = xwayland_view_handle_set_decorations; wl_signal_add(&wlr_xwayland_surface->events.set_decorations, &xwayland_view->set_decorations); xwayland_view_apply_type(xwayland_view); /* we should stack above the new window always */ if (!KYWC_VIEW_IS_ACTIVATABLE(&xwayland_view->view.base)) { xwayland_restack_view(xwayland_view); } xwayland_view_fixup_geometry(xwayland_view); xwayland_view->view.pid = wlr_xwayland_surface->pid; bool modal = wlr_xwayland_surface->modal; if (modal && wlr_xwayland_surface->parent) { modal = xwayland_view_fixup_parent(xwayland_view); } kywc_view_set_modal(&xwayland_view->view.base, modal); view_map(&xwayland_view->view); xwayland_view_set_strut_partial(xwayland_view); xwayland_fixup_pointer_position(wlr_xwayland_surface->surface); } static void xwayland_view_handle_unmap(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, unmap); wl_list_remove(&xwayland_view->commit.link); wl_list_remove(&xwayland_view->set_strut_partial.link); wl_list_remove(&xwayland_view->request_move.link); wl_list_remove(&xwayland_view->request_resize.link); wl_list_remove(&xwayland_view->request_maximize.link); wl_list_remove(&xwayland_view->request_fullscreen.link); wl_list_remove(&xwayland_view->request_activate.link); wl_list_remove(&xwayland_view->request_sticky.link); wl_list_remove(&xwayland_view->request_skip_taskbar.link); wl_list_remove(&xwayland_view->request_above.link); wl_list_remove(&xwayland_view->request_below.link); wl_list_remove(&xwayland_view->request_demands_attention.link); wl_list_remove(&xwayland_view->set_title.link); wl_list_remove(&xwayland_view->set_class.link); wl_list_remove(&xwayland_view->set_parent.link); wl_list_remove(&xwayland_view->set_hints.link); wl_list_remove(&xwayland_view->set_decorations.link); wl_list_remove(&xwayland_view->ping_timeout.link); if (xwayland_view->xwayland->activated_surface == xwayland_view->wlr_xwayland_surface) { xwayland_view->xwayland->activated_surface = NULL; } /* surface_tree is destroyed by scene subsurface */ view_unmap(&xwayland_view->view); } static void xwayland_view_handle_surface_tree_destroy(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, surface_tree_destroy); wl_list_remove(&xwayland_view->surface_tree_destroy.link); xwayland_view->view.surface_tree = NULL; } static void xwayland_view_handle_client_commit(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, client_commit); if (xwayland_view->xwayland->scale == 1.0) { return; } struct wlr_surface_state *pending = &xwayland_view->wlr_xwayland_surface->surface->pending; pending->width = xwayland_unscale(pending->width); pending->height = xwayland_unscale(pending->height); float scale = 1.0 / xwayland_view->xwayland->scale; if (pending->committed & WLR_SURFACE_STATE_SURFACE_DAMAGE) { wlr_region_scale(&pending->surface_damage, &pending->surface_damage, scale); } if (pending->committed & WLR_SURFACE_STATE_OPAQUE_REGION) { wlr_region_scale(&pending->opaque, &pending->opaque, scale); } if (pending->committed & WLR_SURFACE_STATE_INPUT_REGION) { wlr_region_scale(&pending->input, &pending->input, scale); } if (pending->committed & WLR_SURFACE_STATE_OFFSET) { pending->dx = xwayland_unscale(pending->dx); pending->dy = xwayland_unscale(pending->dy); } } static void xwayland_view_handle_associate(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, associate); struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface; xwayland_view->view.surface = wlr_xwayland_surface->surface; wlr_xwayland_surface->surface->data = xwayland_view; xwayland_view->unmap.notify = xwayland_view_handle_unmap; wl_signal_add(&wlr_xwayland_surface->surface->events.unmap, &xwayland_view->unmap); /* create scene tree here as we get surface here */ xwayland_view->view.surface_tree = ky_scene_subsurface_tree_create(xwayland_view->view.tree, wlr_xwayland_surface->surface); /* event node will be destroyed when surface_node destroy */ input_event_node_create(&xwayland_view->view.surface_tree->node, &xwayland_view_event_node_impl, xwayland_view_get_root, xwayland_view_get_toplevel, xwayland_view); xwayland_surface_shape_select_input(wlr_xwayland_surface, true); xwayland_surface_apply_shape_region(wlr_xwayland_surface); // read all surface properties xwayland_read_wm_state(wlr_xwayland_surface->window_id); xwayland_read_wm_icon(wlr_xwayland_surface->window_id); xwayland_apply_wm_window_opacity(wlr_xwayland_surface->window_id, false); xwayland_apply_blur_region(wlr_xwayland_surface->window_id, false); xwayland_apply_opaque_region(wlr_xwayland_surface->window_id, false); xwayland_view->sync.counter = xwayland_get_sync_counter(wlr_xwayland_surface->window_id); xwayland_view->sync.alarm = xwayland_create_sync_alarm(wlr_xwayland_surface->window_id, xwayland_view->sync.counter); xwayland_view->client_commit.notify = xwayland_view_handle_client_commit; wl_signal_add(&wlr_xwayland_surface->surface->events.client_commit, &xwayland_view->client_commit); xwayland_view->map.notify = xwayland_view_handle_map; wl_signal_add(&wlr_xwayland_surface->surface->events.map, &xwayland_view->map); xwayland_view->surface_tree_destroy.notify = xwayland_view_handle_surface_tree_destroy; wl_signal_add(&xwayland_view->view.surface_tree->node.events.destroy, &xwayland_view->surface_tree_destroy); } static void xwayland_view_handle_dissociate(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, dissociate); xwayland_view->wlr_xwayland_surface->surface->data = NULL; xwayland_view->view.surface = NULL; if (xwayland_view->view.surface_tree) { ky_scene_node_destroy(&xwayland_view->view.surface_tree->node); } if (xwayland_view->sync.alarm) { xcb_sync_destroy_alarm(xwayland_view->xwayland->xcb_conn, xwayland_view->sync.alarm); xwayland_set_allow_commits(xwayland_view->wlr_xwayland_surface->window_id, true); xwayland_view->sync.blocked = false; } wl_list_remove(&xwayland_view->client_commit.link); wl_list_remove(&xwayland_view->map.link); wl_list_remove(&xwayland_view->unmap.link); } static void xwayland_view_icon_destroy(struct net_wm_icon *icon) { wl_list_remove(&icon->link); wlr_buffer_drop(icon->buffer); free(icon->data); free(icon); } static void xwayland_view_handle_destroy(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, destroy); wl_list_remove(&xwayland_view->link); wl_list_remove(&xwayland_view->destroy.link); wl_list_remove(&xwayland_view->associate.link); wl_list_remove(&xwayland_view->dissociate.link); wl_list_remove(&xwayland_view->request_configure.link); wl_list_remove(&xwayland_view->request_minimize.link); wl_list_remove(&xwayland_view->set_override_redirect.link); wl_list_remove(&xwayland_view->view_update_capabilities.link); xwayland_view_clear_wm_icon(xwayland_view->wlr_xwayland_surface); view_destroy(&xwayland_view->view); } static void xwayland_view_handle_set_override_redirect(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_override_redirect); struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface; struct xwayland_server *xwayland = xwayland_view->xwayland; if (wlr_xwayland_surface->surface) { if (wlr_xwayland_surface->surface->mapped) { xwayland_view_handle_unmap(&xwayland_view->unmap, NULL); } xwayland_view_handle_dissociate(&xwayland_view->dissociate, NULL); } xwayland_view_handle_destroy(&xwayland_view->destroy, NULL); wlr_xwayland_surface->data = NULL; xwayland_unmanaged_create(xwayland, wlr_xwayland_surface); } static void xwayland_view_handle_update_capabilities(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, view_update_capabilities); struct view_update_capabilities_event *event = data; if (event->mask & KYWC_VIEW_MINIMIZABLE) { if (!xwayland_view->minimizable) { event->state &= ~KYWC_VIEW_MINIMIZABLE; } } if (event->mask & KYWC_VIEW_MAXIMIZABLE) { if (!xwayland_view->maximizable) { event->state &= ~KYWC_VIEW_MAXIMIZABLE; } } if (event->mask & KYWC_VIEW_FOCUSABLE) { if (!xwayland_view->focusable) { event->state &= ~KYWC_VIEW_FOCUSABLE; } } if (event->mask & KYWC_VIEW_ACTIVATABLE) { if (!xwayland_view->activatable) { event->state &= ~KYWC_VIEW_ACTIVATABLE; } } if (event->mask & KYWC_VIEW_MOVABLE) { if (!xwayland_view->movable) { event->state &= ~KYWC_VIEW_MOVABLE; } } if (event->mask & KYWC_VIEW_RESIZABLE) { if (!xwayland_view->resizable) { event->state &= ~KYWC_VIEW_RESIZABLE; } } if (event->mask & KYWC_VIEW_MINIMIZE_BUTTON) { if (!xwayland_view->minimizable) { event->state &= ~KYWC_VIEW_MINIMIZE_BUTTON; } } if (event->mask & KYWC_VIEW_MAXIMIZE_BUTTON) { if (!xwayland_view->maximizable) { event->state &= ~KYWC_VIEW_MAXIMIZE_BUTTON; } } } static void xwayland_view_handle_request_configure(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_configure); struct wlr_xwayland_surface *wlr_xwayland_surface = xwayland_view->wlr_xwayland_surface; struct kywc_view *kywc_view = &xwayland_view->view.base; /* skip configure when moving or resizing by left or top edges */ if (kywc_view->mapped && (xwayland_view->view.interactive_moving || (xwayland_view->view.current_resize_edges & (KYWC_EDGE_LEFT | KYWC_EDGE_TOP)))) { wlr_xwayland_surface_configure(wlr_xwayland_surface, wlr_xwayland_surface->x, wlr_xwayland_surface->y, wlr_xwayland_surface->width, wlr_xwayland_surface->height); return; } struct wlr_xwayland_surface_configure_event *event = data; struct kywc_box geo = { xwayland_unscale(event->x), xwayland_unscale(event->y), xwayland_unscale(event->width), xwayland_unscale(event->height) }; if (!kywc_view->mapped) { wlr_xwayland_surface_configure(wlr_xwayland_surface, event->x, event->y, event->width, event->height); } else { xwayland_view_adjust_geometry(xwayland_view, &geo); } view_do_resize(&xwayland_view->view, &geo); } void xwayland_view_create(struct xwayland_server *xwayland, struct wlr_xwayland_surface *wlr_xwayland_surface) { struct xwayland_view *xwayland_view = calloc(1, sizeof(*xwayland_view)); if (!xwayland_view) { return; } xwayland_view->xwayland = xwayland; wl_list_insert(&xwayland->surfaces, &xwayland_view->link); view_init(&xwayland_view->view, &xwl_surface_impl, xwayland_view); xwayland_view->wlr_xwayland_surface = wlr_xwayland_surface; wlr_xwayland_surface->data = xwayland_view; xwayland_view->associate.notify = xwayland_view_handle_associate; wl_signal_add(&wlr_xwayland_surface->events.associate, &xwayland_view->associate); xwayland_view->dissociate.notify = xwayland_view_handle_dissociate; wl_signal_add(&wlr_xwayland_surface->events.dissociate, &xwayland_view->dissociate); xwayland_view->destroy.notify = xwayland_view_handle_destroy; wl_signal_add(&wlr_xwayland_surface->events.destroy, &xwayland_view->destroy); xwayland_view->request_configure.notify = xwayland_view_handle_request_configure; wl_signal_add(&wlr_xwayland_surface->events.request_configure, &xwayland_view->request_configure); xwayland_view->request_minimize.notify = xwayland_view_handle_request_minimize; wl_signal_add(&wlr_xwayland_surface->events.request_minimize, &xwayland_view->request_minimize); xwayland_view->set_override_redirect.notify = xwayland_view_handle_set_override_redirect; wl_signal_add(&wlr_xwayland_surface->events.set_override_redirect, &xwayland_view->set_override_redirect); wl_list_init(&xwayland_view->client_commit.link); wl_list_init(&xwayland_view->map.link); wl_list_init(&xwayland_view->unmap.link); wl_list_init(&xwayland_view->net_wm_icons); xwayland_view->minimizable = xwayland_view->maximizable = xwayland_view->focusable = xwayland_view->activatable = xwayland_view->movable = xwayland_view->resizable = true; xwayland_view->view_update_capabilities.notify = xwayland_view_handle_update_capabilities; view_add_update_capabilities_listener(&xwayland_view->view, &xwayland_view->view_update_capabilities); if (wlr_xwayland_surface->surface) { xwayland_view_handle_associate(&xwayland_view->associate, NULL); if (wlr_xwayland_surface->surface->mapped) { xwayland_view_handle_map(&xwayland_view->map, NULL); } } } struct wlr_xwayland_surface *xwayland_view_look_surface(struct xwayland_server *xwayland, xcb_window_t window_id) { struct xwayland_view *xwayland_view; wl_list_for_each(xwayland_view, &xwayland->surfaces, link) { if (xwayland_view->wlr_xwayland_surface->window_id == window_id) { return xwayland_view->wlr_xwayland_surface; } } return NULL; } void xwayland_view_clear_wm_icon(struct wlr_xwayland_surface *surface) { struct xwayland_view *xwayland_view = surface->data; if (!xwayland_view) { return; } struct net_wm_icon *icon, *tmp; wl_list_for_each_safe(icon, tmp, &xwayland_view->net_wm_icons, link) { xwayland_view_icon_destroy(icon); } } void xwayland_view_add_new_wm_icon(struct wlr_xwayland_surface *surface, uint32_t width, uint32_t height, uint32_t size, uint32_t *data) { struct xwayland_view *xwayland_view = surface->data; if (!xwayland_view) { return; } struct net_wm_icon *old_icon, *tmp; wl_list_for_each_safe(old_icon, tmp, &xwayland_view->net_wm_icons, link) { if (old_icon->width == width && old_icon->height == height) { xwayland_view_icon_destroy(old_icon); } } struct net_wm_icon *icon = calloc(1, sizeof(*icon)); if (!icon) { return; } wl_list_insert(&xwayland_view->net_wm_icons, &icon->link); icon->width = width; icon->height = height; icon->data = (unsigned char *)malloc(size); memcpy(icon->data, (unsigned char *)data, size); } void xwayland_view_update_icon(struct wlr_xwayland_surface *surface) { struct xwayland_view *xwayland_view = surface->data; if (!xwayland_view) { return; } view_set_icon(&xwayland_view->view, true); } bool xwayland_view_set_opacity(struct xwayland_server *xwayland, xcb_window_t window_id, float opacity) { struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, window_id); if (!surface) { return false; } struct xwayland_view *xwayland_view = surface->data; if (xwayland_view->view.surface) { struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(xwayland_view->view.surface); ky_scene_buffer_set_opacity(buffer, opacity); } return true; } bool xwayland_view_set_shape_region(struct xwayland_server *xwayland, xcb_window_t window_id, xcb_shape_sk_t kind, const pixman_region32_t *region) { struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, window_id); if (!surface) { return false; } struct xwayland_view *xwayland_view = surface->data; if (!xwayland_view->view.surface) { return true; } struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(xwayland_view->view.surface); if (kind == XCB_SHAPE_SK_BOUNDING || kind == XCB_SHAPE_SK_CLIP) { ky_scene_node_set_clip_region(&buffer->node, region); } if (kind == XCB_SHAPE_SK_BOUNDING || kind == XCB_SHAPE_SK_INPUT) { ky_scene_node_set_input_region(&buffer->node, region); /* empty input region means no input support */ bool need_bypassed = kind == XCB_SHAPE_SK_INPUT && !pixman_region32_not_empty(region); ky_scene_node_set_input_bypassed(&buffer->node, need_bypassed); } /* sync the decoration if clip region is changed */ xwayland_view_handle_set_decorations(&xwayland_view->set_decorations, NULL); return true; } bool xwayland_view_set_blur_region(struct xwayland_server *xwayland, xcb_window_t window_id, const pixman_region32_t *region) { struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, window_id); if (!surface) { return false; } struct xwayland_view *xwayland_view = surface->data; if (!xwayland_view->view.surface) { return true; } struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(xwayland_view->view.surface); if (!buffer) { return true; } ky_scene_node_set_blur_region(&buffer->node, region); return true; } bool xwayland_view_set_opaque_region(struct xwayland_server *xwayland, xcb_window_t window_id, const pixman_region32_t *region) { struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, window_id); if (!surface) { return false; } struct xwayland_view *xwayland_view = surface->data; if (!xwayland_view->view.surface) { return true; } struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(xwayland_view->view.surface); ky_scene_buffer_set_opaque_region(buffer, region); return true; } bool xwayland_view_set_no_title(struct xwayland_server *xwayland, xcb_window_t window_id, bool set) { struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, window_id); if (!surface) { return false; } struct xwayland_view *xwayland_view = surface->data; enum kywc_ssd ssd = xwayland_view->view.base.ssd; if (set) { ssd &= ~KYWC_SSD_TITLE; } else { ssd |= KYWC_SSD_TITLE; } view_set_decoration(&xwayland_view->view, ssd); return true; } void xwayland_view_move_resize(struct wlr_xwayland_surface *surface, struct kywc_box *box) { struct xwayland_view *xwayland_view = surface->data; struct kywc_box geo = { xwayland_unscale(box->x), xwayland_unscale(box->y), xwayland_unscale(box->width), xwayland_unscale(box->height) }; xwayland_view_adjust_geometry(xwayland_view, &geo); view_do_resize(&xwayland_view->view, &geo); } bool xwayland_check_view(struct view *view) { return view->impl == &xwl_surface_impl; } bool xwayland_view_reset_sync_counter(struct xwayland_server *xwayland, xcb_window_t window_id) { struct wlr_xwayland_surface *surface = xwayland_view_look_surface(xwayland, window_id); if (!surface) { return false; } struct xwayland_view *xwayland_view = surface->data; if (!xwayland_view->view.surface) { return true; } xwayland_view->sync.value = (xcb_sync_int64_t){ 0, 0 }; return true; } bool xwayland_view_ack_sync(struct xwayland_server *xwayland, xcb_sync_alarm_t alarm, xcb_sync_int64_t value) { struct xwayland_view *xwayland_view; wl_list_for_each(xwayland_view, &xwayland->surfaces, link) { if (xwayland_view->sync.alarm != alarm) { continue; } if (xwayland_view->sync.value.lo == value.lo && xwayland_view->sync.value.hi == value.hi && xwayland_view->sync.blocked) { xwayland_set_allow_commits(xwayland_view->wlr_xwayland_surface->window_id, true); xwayland_view->sync.blocked = false; } return true; } return false; } void xwayland_view_configure_all(struct xwayland_server *xwayland, struct kywc_output *output) { struct xwayland_view *xwayland_view; wl_list_for_each(xwayland_view, &xwayland->surfaces, link) { struct view *view = &xwayland_view->view; if (!view->base.mapped) { continue; } if (view->output == output) { continue; } int lx = view->base.geometry.x - view->base.margin.off_x; int ly = view->base.geometry.y - view->base.margin.off_y; if (!kywc_output_contains_point(view->output, lx, ly)) { continue; } view_move_to_output(view, NULL, NULL, view->output); } } kylin-wayland-compositor/src/xwayland/dnd.c0000664000175000017500000002774615160461067020034 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "xwayland_p.h" static void xwayland_data_source_send(struct wlr_data_source *wlr_source, const char *requested_mime_type, int32_t fd) { struct xwayland_data_source *xwayland_data_source = wl_container_of(wlr_source, xwayland_data_source, base); struct xwayland_drag_x11 *drag_x11 = xwayland_data_source->drag_x11; struct xwayland_server *xwayland = drag_x11->xwayland; xcb_atom_t *atoms = xwayland_data_source->mime_types_atoms.data; bool found = false; xcb_atom_t mime_type_atom; char **mime_type_ptr; size_t i = 0; wl_array_for_each(mime_type_ptr, &wlr_source->mime_types) { char *mime_type = *mime_type_ptr; if (strcmp(mime_type, requested_mime_type) == 0) { found = true; mime_type_atom = atoms[i]; break; } ++i; } if (!found) { kywc_log(KYWC_DEBUG, "Cannot send X11 selection to Wayland: " "unsupported MIME type"); close(fd); return; } fcntl(fd, F_SETFL, O_WRONLY | O_NONBLOCK); if (!xwayland_data_transfer_create(drag_x11, requested_mime_type, fd)) { close(fd); return; } xcb_convert_selection(xwayland->xcb_conn, xwayland->window_catcher, xwayland->atoms[DND_SELECTION], mime_type_atom, xwayland->atoms[WL_SELECTION], XCB_TIME_CURRENT_TIME); xcb_flush(xwayland->xcb_conn); } static void xwayland_data_source_accept(struct wlr_data_source *wlr_source, uint32_t serial, const char *mime_type) { struct xwayland_data_source *data_source = wl_container_of(wlr_source, data_source, base); enum wl_data_device_manager_dnd_action action = mime_type ? data_source->base.current_dnd_action : WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; struct xwayland_drag_x11 *drag_x11 = data_source->drag_x11; struct xwayland_server *xwayland = drag_x11->xwayland; xwayland_send_dnd_status(xwayland, xwayland->window_catcher, drag_x11->source_window, action); } static void xwayland_data_source_finish(struct wlr_data_source *wlr_source) { struct xwayland_data_source *data_source = wl_container_of(wlr_source, data_source, base); struct xwayland_drag_x11 *drag_x11 = data_source->drag_x11; struct xwayland_server *xwayland = drag_x11->xwayland; bool accepted = wlr_source->accepted; enum wl_data_device_manager_dnd_action action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; if (accepted) { action = data_device_manager_dnd_action_to_atom(wlr_source->actions); } xwayland_send_dnd_finish(xwayland, xwayland->window_catcher, drag_x11->source_window, accepted, action); } static void xwayland_data_source_destroy(struct wlr_data_source *wlr_source) { struct xwayland_data_source *data_source = wl_container_of(wlr_source, data_source, base); struct xwayland_server *xwayland = data_source->drag_x11->xwayland; wl_array_release(&data_source->mime_types_atoms); free(data_source); xwayland->drag_x11->data_source = NULL; xwayland_end_drag_x11(xwayland); } static const struct wlr_data_source_impl data_source_impl = { .send = xwayland_data_source_send, .accept = xwayland_data_source_accept, .dnd_finish = xwayland_data_source_finish, .destroy = xwayland_data_source_destroy, }; static bool xwayland_add_atom_to_mime_types(struct wl_array *mime_types, struct wl_array *mime_types_atoms, xcb_atom_t atom) { char *mime_type = xwayland_mime_type_from_atom(atom); if (mime_type == NULL) { return false; } char **mime_type_ptr = wl_array_add(mime_types, sizeof(*mime_type_ptr)); if (mime_type_ptr == NULL) { return false; } *mime_type_ptr = mime_type; xcb_atom_t *mime_type_atom_ptr = wl_array_add(mime_types_atoms, sizeof(*mime_type_atom_ptr)); if (mime_type_atom_ptr == NULL) { return false; } *mime_type_atom_ptr = atom; kywc_log(KYWC_DEBUG, "Mime_type %s", mime_type); return true; } static bool xwayland_dnd_get_mime_types(struct xwayland_server *xwayland, struct wl_array *mime_types, struct wl_array *mime_types_atoms, xcb_window_t source) { xcb_get_property_cookie_t cookie = xcb_get_property(xwayland->xcb_conn, 0, // delete source, xwayland->atoms[DND_TYPE_LIST], XCB_GET_PROPERTY_TYPE_ANY, 0, // offset 0x1fffffff // length ); xcb_get_property_reply_t *reply = xcb_get_property_reply(xwayland->xcb_conn, cookie, NULL); if (reply == NULL) { return false; } if (reply->type != XCB_ATOM_ATOM || reply->value_len == 0) { kywc_log(KYWC_ERROR, "Invalid XdndTypeList property"); goto error; } xcb_atom_t *atoms = xcb_get_property_value(reply); for (uint32_t i = 0; i < reply->value_len; ++i) { if (!xwayland_add_atom_to_mime_types(mime_types, mime_types_atoms, atoms[i])) { kywc_log(KYWC_ERROR, "Failed to add MIME type atom to list"); goto error; } } free(reply); return true; error: free(reply); return false; } static int xwayland_handle_dnd_enter(struct xwayland_server *xwayland, xcb_client_message_data_t *data) { if (!xwayland_is_dragging_x11(NULL)) { return 0; } // already has data source struct xwayland_drag_x11 *drag_x11 = xwayland->drag_x11; if (drag_x11_has_data_source(drag_x11)) { return 0; } xcb_window_t source_window = data->data32[0]; if (source_window != drag_x11->source_window) { kywc_log(KYWC_ERROR, "Ignoring XdndEnter client message because the source window " "hasn't set the drag-and-drop selection"); return 0; } // create data source drag_x11->data_source = calloc(1, sizeof(*drag_x11->data_source)); if (!drag_x11->data_source) { return 0; } /* init data source */ wlr_data_source_init(&drag_x11->data_source->base, &data_source_impl); wl_array_init(&drag_x11->data_source->mime_types_atoms); drag_x11->data_source->drag_x11 = drag_x11; struct xwayland_data_source *source = drag_x11->data_source; if ((data->data32[1] & 1) == 0) { // Less than 3 MIME types, those are in the message data for (size_t i = 0; i < 3; ++i) { xcb_atom_t atom = data->data32[2 + i]; if (atom == XCB_ATOM_NONE) { break; } if (!xwayland_add_atom_to_mime_types(&source->base.mime_types, &source->mime_types_atoms, atom)) { kywc_log(KYWC_ERROR, "Failed to add MIME type atom to list"); break; } } } else { if (!xwayland_dnd_get_mime_types(xwayland, &source->base.mime_types, &source->mime_types_atoms, source_window)) { kywc_log(KYWC_ERROR, "Failed to add MIME type atom to list"); } } // unmap the window catcher, mapping it when enter wayland window xwayland_map_selection_window(xwayland, xwayland->window_catcher, NULL, false); return 1; } static int xwayland_handle_dnd_position(struct xwayland_server *xwayland, xcb_client_message_data_t *data) { if (!xwayland_is_dragging_x11(NULL)) { kywc_log(KYWC_DEBUG, "Ignoring XdndPosition client message because " "no xwayland drag is being performed"); return 0; } xcb_window_t source_window = data->data32[0]; xcb_timestamp_t timestamp = data->data32[3]; xcb_atom_t action_atom = data->data32[4]; if (source_window != xwayland->drag_x11->source_window) { kywc_log(KYWC_DEBUG, "Ignoring XdndPosition client message because " "the source window hasn't set the drag-and-drop selection"); return 0; } enum wl_data_device_manager_dnd_action action = data_device_manager_dnd_action_from_atom(action_atom); xwayland->drag_x11->data_source->base.actions = action; kywc_log(KYWC_DEBUG, "DND_POSITION window=%d timestamp=%u action=%d", source_window, timestamp, action); return 1; } static int xwayland_handle_dnd_drop(struct xwayland_server *xwayland, xcb_client_message_data_t *data) { if (!xwayland_is_dragging_x11(NULL)) { return 0; } xcb_window_t source_window = data->data32[0]; if (source_window != xwayland->drag_x11->source_window) { kywc_log(KYWC_ERROR, "Ignoring XdndDrop client message because the source window " "hasn't set the drag-and-drop selection"); return 0; } struct xwayland_drag_x11 *drag_x11 = xwayland->drag_x11; if (drag_x11->hovered_client && drag_x11->data_source->base.current_dnd_action && drag_x11->data_source->base.accepted) { struct wl_resource *resource; wl_resource_for_each(resource, &drag_x11->hovered_client->data_devices) { wl_data_device_send_drop(resource); wl_data_device_send_leave(resource); } } else { xwayland_end_drag_x11(xwayland); } return 1; } void xwayland_send_dnd_status(struct xwayland_server *xwayland, xcb_window_t requestor, xcb_window_t window, uint32_t action) { uint32_t flags = 0; if (action != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE) { flags = 1 << 1; // Opt-in for XdndPosition messages flags |= 1 << 0; // We accept the drop } xcb_client_message_data_t data = { 0 }; data.data32[0] = requestor; data.data32[1] = flags; data.data32[4] = data_device_manager_dnd_action_to_atom(action); xcb_client_message_event_t event = { .response_type = XCB_CLIENT_MESSAGE, .format = 32, .sequence = 0, .window = window, .type = xwayland->atoms[DND_STATUS], .data = data, }; xcb_send_event(xwayland->xcb_conn, 0, window, XCB_EVENT_MASK_NO_EVENT, (char *)&event); xcb_flush(xwayland->xcb_conn); } void xwayland_send_dnd_finish(struct xwayland_server *xwayland, xcb_window_t requestor, xcb_window_t window, bool accept, uint32_t action) { xcb_client_message_data_t data = { 0 }; data.data32[0] = requestor; data.data32[1] = accept; if (accept) { data.data32[2] = data_device_manager_dnd_action_to_atom(action); } xcb_client_message_event_t event = { .response_type = XCB_CLIENT_MESSAGE, .format = 32, .sequence = 0, .window = window, .type = xwayland->atoms[DND_FINISHED], .data = data, }; xcb_send_event(xwayland->xcb_conn, 0, window, XCB_EVENT_MASK_NO_EVENT, (char *)&event); xcb_flush(xwayland->xcb_conn); } int xwayland_handle_dnd_message(struct xwayland_server *xwayland, xcb_client_message_event_t *client_message) { if (client_message->type == xwayland->atoms[DND_ENTER]) { kywc_log(KYWC_DEBUG, "DND enter"); xwayland_handle_dnd_enter(xwayland, &client_message->data); return 1; } else if (client_message->type == xwayland->atoms[DND_POSITION]) { kywc_log(KYWC_DEBUG, "DND position"); xwayland_handle_dnd_position(xwayland, &client_message->data); return 1; } else if (client_message->type == xwayland->atoms[DND_DROP]) { kywc_log(KYWC_DEBUG, "DND drop"); xwayland_handle_dnd_drop(xwayland, &client_message->data); return 1; } else if (client_message->type == xwayland->atoms[DND_LEAVE]) { kywc_log(KYWC_DEBUG, "DND leave"); return 1; } return 0; } kylin-wayland-compositor/src/xwayland/xwayland_p.h0000664000175000017500000002441115160461067021424 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _XWAYLAND_P_H_ #define _XWAYLAND_P_H_ #include #include #include #include #include #include #include "xwayland.h" struct kywc_box; struct kywc_output; /** * window type that for windows not OR * https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html */ enum atom_name { NET_WM_WINDOW_TYPE_DESKTOP, NET_WM_WINDOW_TYPE_DOCK, NET_WM_WINDOW_TYPE_TOOLBAR, NET_WM_WINDOW_TYPE_MENU, NET_WM_WINDOW_TYPE_UTILITY, NET_WM_WINDOW_TYPE_SPLASH, NET_WM_WINDOW_TYPE_DIALOG, NET_WM_WINDOW_TYPE_DROPDOWN_MENU, NET_WM_WINDOW_TYPE_POPUP_MENU, NET_WM_WINDOW_TYPE_COMBO, NET_WM_WINDOW_TYPE_TOOLTIP, NET_WM_WINDOW_TYPE_DND, NET_WM_WINDOW_TYPE_NOTIFICATION, NET_WM_WINDOW_TYPE_NORMAL, NET_MOVERESIZE_WINDOW, /* kde extensions */ KDE_NET_WM_WINDOW_TYPE_OVERRIDE, /* ukui atoms */ UKUI_NET_WM_WINDOW_TYPE_SYSTEMWINDOW, UKUI_NET_WM_WINDOW_TYPE_INPUTPANEL, UKUI_NET_WM_WINDOW_TYPE_LOGOUT, UKUI_NET_WM_WINDOW_TYPE_SCREENLOCK, UKUI_NET_WM_WINDOW_TYPE_SCREENLOCKNOTIFICATION, UKUI_NET_WM_WINDOW_TYPE_WATERMARK, KWIN_UKUI_DECORATION, NET_WM_STATE, // KDE-specific atom KDE_NET_WM_STATE_SKIP_SWITCHER, KDE_NET_WM_BLUR_BEHIND_REGION, NET_WM_ICON, NET_WM_WINDOW_OPACITY, NET_WM_OPAQUE_REGION, UTF8_STRING, NET_WM_NAME, NET_SUPPORTING_WM_CHECK, INCR, TEXT, WL_SELECTION, DND_SELECTION, DND_AWARE, DND_STATUS, DND_POSITION, DND_ENTER, DND_LEAVE, DND_DROP, DND_FINISHED, DND_PROXY, DND_TYPE_LIST, DND_ACTION_MOVE, DND_ACTION_COPY, DND_ACTION_ASK, DND_ACTION_PRIVATE, WM_PROTOCOLS, NET_WM_SYNC_REQUEST, NET_WM_SYNC_REQUEST_COUNTER, XWAYLAND_ALLOW_COMMITS, ATOM_LAST, }; struct xwayland_data_transfer { struct wl_list link; int wl_client_fd; // target fd char *mime_type; int property_start; xcb_get_property_reply_t *property_reply; struct wl_event_source *event_source; // for read loop }; struct xwayland_data_source { struct wlr_data_source base; struct wl_array mime_types_atoms; struct xwayland_drag_x11 *drag_x11; }; struct xwayland_drag_x11 { struct xwayland_server *xwayland; xcb_window_t source_window; struct xwayland_data_source *data_source; struct wlr_surface *hovered_surface; struct wl_listener surface_destroy; struct wlr_seat_client *hovered_client; struct wl_listener seat_client_destroy; struct wl_list transfers; // struct xwayland_data_transfer // TODO: listen touch, tablet struct wl_listener cursor_motion; struct wl_listener cursor_button; }; struct xwayland_server { struct server *server; struct wlr_xwayland *wlr_xwayland; struct wl_list surfaces; struct wl_list unmanaged_surfaces; struct wl_listener xwayland_ready; struct wl_listener new_xwayland_surface; struct wl_listener server_destroy; struct wl_listener max_scale; struct wl_listener seat_destroy; struct wlr_xwayland_shell_v1 *shell; struct wl_listener shell_destroy; xcb_atom_t atoms[ATOM_LAST]; xcb_connection_t *xcb_conn; xcb_screen_t *screen; const xcb_query_extension_reply_t *shape; const xcb_query_extension_reply_t *xfixes; const xcb_query_extension_reply_t *sync; struct wlr_surface *hoverd_surface; struct wl_listener surface_unmap; struct wl_listener view_minimized; struct wl_listener activate_view; struct wlr_xwayland_surface *activated_surface; // for drag x11 to wayland target xcb_window_t window_catcher; // TODO: multiple drags struct xwayland_drag_x11 *drag_x11; float scale; }; void xwayland_view_create(struct xwayland_server *xwayland, struct wlr_xwayland_surface *wlr_xwayland_surface); void xwayland_unmanaged_create(struct xwayland_server *xwayland, struct wlr_xwayland_surface *wlr_xwayland_surface); bool xwayland_surface_has_type(struct wlr_xwayland_surface *wlr_xwayland_surface, int type); enum { INPUT_MASK_POINTER = 1 << 0, INPUT_MASK_KEYBOARD = 1 << 1, }; bool xwayland_surface_has_input(struct wlr_xwayland_surface *wlr_xwayland_surface, uint32_t input); void xwayland_surface_shape_select_input(struct wlr_xwayland_surface *surface, bool enabled); void xwayland_surface_apply_shape_region(struct wlr_xwayland_surface *surface); bool xwayland_unmanaged_set_shape_region(struct xwayland_server *xwayland, xcb_window_t window_id, xcb_shape_sk_t kind, const pixman_region32_t *region); bool xwayland_unmanaged_set_opacity(struct xwayland_server *xwayland, xcb_window_t window_id, float opacity); bool xwayland_unmanaged_set_blur_region(struct xwayland_server *xwayland, xcb_window_t window_id, const pixman_region32_t *region); bool xwayland_unmanaged_set_opaque_region(struct xwayland_server *xwayland, xcb_window_t window_id, const pixman_region32_t *region); bool xwayland_unmanaged_set_grab_pointer(struct xwayland_server *xwayland, struct wlr_xwayland_surface *wlr_xwayland_surface, struct wlr_seat *wlr_seat, bool grab); bool xwayland_view_set_shape_region(struct xwayland_server *xwayland, xcb_window_t window_id, xcb_shape_sk_t kind, const pixman_region32_t *region); bool xwayland_view_set_blur_region(struct xwayland_server *xwayland, xcb_window_t window_id, const pixman_region32_t *region); bool xwayland_view_set_opaque_region(struct xwayland_server *xwayland, xcb_window_t window_id, const pixman_region32_t *region); bool xwayland_view_set_no_title(struct xwayland_server *xwayland, xcb_window_t window_id, bool set); int xwayland_apply_blur_region(xcb_window_t window_id, bool delete); int xwayland_apply_opaque_region(xcb_window_t window_id, bool delete); int xwayland_apply_ukui_decoration(xcb_window_t window_id, bool delete); void xwayland_surface_debug_type(struct wlr_xwayland_surface *wlr_xwayland_surface); void xwayland_view_set_skip_switcher(struct wlr_xwayland_surface *surface, bool skip_switcher); struct wlr_xwayland_surface *xwayland_view_look_surface(struct xwayland_server *xwayland, xcb_window_t window_id); void xwayland_view_clear_wm_icon(struct wlr_xwayland_surface *surface); void xwayland_view_add_new_wm_icon(struct wlr_xwayland_surface *surface, uint32_t width, uint32_t height, uint32_t size, uint32_t *data); void xwayland_view_update_icon(struct wlr_xwayland_surface *surface); bool xwayland_view_set_opacity(struct xwayland_server *xwayland, xcb_window_t window_id, float opacity); void xwayland_view_move_resize(struct wlr_xwayland_surface *surface, struct kywc_box *box); void xwayland_update_seat(struct seat *seat); void xwayland_update_hovered_surface(struct wlr_surface *surface); void xwayland_fixup_pointer_position(struct wlr_surface *surface); int xwayland_read_wm_state(xcb_window_t window_id); int xwayland_read_wm_icon(xcb_window_t window_id); int xwayland_apply_wm_window_opacity(xcb_window_t window_id, bool delete); char *xwayland_mime_type_from_atom(xcb_atom_t atom); enum wl_data_device_manager_dnd_action data_device_manager_dnd_action_from_atom(enum atom_name atom); xcb_atom_t data_device_manager_dnd_action_to_atom(enum wl_data_device_manager_dnd_action action); // selection int xwayland_handle_selection_event(struct xwayland_server *xwayland, xcb_generic_event_t *event); void xwayland_create_seletion_window(struct xwayland_server *xwayland, xcb_window_t *window, int16_t x, int16_t y, uint16_t width, uint16_t height); void xwayland_map_selection_window(struct xwayland_server *xwayland, xcb_window_t window, struct kywc_box *box, bool map); struct xwayland_data_transfer *xwayland_data_transfer_create(struct xwayland_drag_x11 *drag_x11, const char *mime_type, int fd); void xwayland_data_transfer_destroy(struct xwayland_data_transfer *transfer); struct xwayland_data_transfer * xwayland_data_transfer_find_by_type(struct xwayland_drag_x11 *drag_x11, const char *mime_type); // drag_x11 bool drag_x11_has_data_source(struct xwayland_drag_x11 *drag_x11); bool xwayland_start_drag_x11(struct xwayland_server *xwayland, xcb_window_t source_window); void xwayland_end_drag_x11(struct xwayland_server *xwayland); void drag_set_focus(struct xwayland_drag_x11 *drag, struct wlr_surface *surface, double sx, double sy); // dnd protocol int xwayland_handle_dnd_message(struct xwayland_server *xwayland, xcb_client_message_event_t *client_message); void xwayland_send_dnd_status(struct xwayland_server *xwayland, xcb_window_t requestor, xcb_window_t window, uint32_t action); void xwayland_send_dnd_finish(struct xwayland_server *xwayland, xcb_window_t requestor, xcb_window_t window, bool accept, uint32_t action); xcb_sync_counter_t xwayland_get_sync_counter(xcb_window_t window_id); xcb_sync_alarm_t xwayland_create_sync_alarm(xcb_window_t window_id, xcb_sync_counter_t counter); void xwayland_send_sync_request(xcb_window_t window_id, xcb_sync_int64_t *value); void xwayland_set_allow_commits(xcb_window_t window_id, bool allow); bool xwayland_view_reset_sync_counter(struct xwayland_server *xwayland, xcb_window_t window_id); bool xwayland_view_ack_sync(struct xwayland_server *xwayland, xcb_sync_alarm_t alarm, xcb_sync_int64_t value); void xwayland_view_configure_all(struct xwayland_server *xwayland, struct kywc_output *output); void xwayland_surface_set_grab_pointer(struct wlr_xwayland_surface *wlr_xwayland_surface, struct wlr_seat *wlr_seat, bool grab); #endif /* _XWAYLAND_P_H_ */ kylin-wayland-compositor/src/xwayland/selection.c0000664000175000017500000001710615160461067021241 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include "kywc/boxes.h" #include "xwayland_p.h" #define XDND_VERSION 5 static int write_selection_property_to_wl_client(int fd, uint32_t mask, void *data) { struct xwayland_data_transfer *transfer = data; char *property = xcb_get_property_value(transfer->property_reply); int remainder = xcb_get_property_value_length(transfer->property_reply) - transfer->property_start; ssize_t len = write(fd, property + transfer->property_start, remainder); if (len == -1) { kywc_log(KYWC_ERROR, "Write error to target fd %d", fd); xwayland_data_transfer_destroy(transfer); return 0; } kywc_log(KYWC_DEBUG, "Wrote %zd (total %zd, remaining %d) of %d bytes to fd %d", len, transfer->property_start + len, remainder, xcb_get_property_value_length(transfer->property_reply), fd); if (len < remainder) { transfer->property_start += len; return 1; } xwayland_data_transfer_destroy(transfer); return 0; } static int xwayland_handle_selection_notify(struct xwayland_server *xwayland, xcb_selection_notify_event_t *event) { if (!xwayland_is_dragging_x11(NULL)) { return 0; } if (event->requestor != xwayland->window_catcher) { return 0; } if (event->property == XCB_ATOM_NONE) { kywc_log(KYWC_ERROR, "Convert selection failed"); return 0; } char *mime_type = xwayland_mime_type_from_atom(event->target); struct xwayland_data_transfer *transfer = xwayland_data_transfer_find_by_type(xwayland->drag_x11, mime_type); free(mime_type); if (!transfer) { kywc_log(KYWC_DEBUG, "Cannot find mime_type transfer"); return 0; } xcb_window_t window = xwayland->window_catcher; xcb_get_property_cookie_t cookie = xcb_get_property( xwayland->xcb_conn, true, window, xwayland->atoms[WL_SELECTION], XCB_GET_PROPERTY_TYPE_ANY, 0, // offset 0x1fffffff // length ); transfer->property_start = 0; transfer->property_reply = xcb_get_property_reply(xwayland->xcb_conn, cookie, NULL); if (!transfer->property_reply) { kywc_log(KYWC_ERROR, "Cannot get selection property"); return 0; } if (transfer->property_reply->type == xwayland->atoms[INCR]) { kywc_log(KYWC_INFO, "Incr start !"); // TODO: handle incr xwayland_data_transfer_destroy(transfer); return 0; } /* write selection preoperty to client */ int need_continue = write_selection_property_to_wl_client(transfer->wl_client_fd, WL_EVENT_WRITABLE, transfer); if (need_continue) { kywc_log(KYWC_DEBUG, "Continue reading in loop"); // continue reading in loop struct wl_event_loop *loop = wl_display_get_event_loop(xwayland->wlr_xwayland->wl_display); transfer->event_source = wl_event_loop_add_fd(loop, transfer->wl_client_fd, WL_EVENT_WRITABLE, write_selection_property_to_wl_client, transfer); } return 1; } static int xwayland_handle_xfixes_selection_notify(struct xwayland_server *xwayland, xcb_xfixes_selection_notify_event_t *event) { if (event->selection == xwayland->atoms[DND_SELECTION]) { if (event->owner != XCB_ATOM_NONE) { return xwayland_start_drag_x11(xwayland, event->owner); } else { xwayland_end_drag_x11(xwayland); } return 1; } return 0; } void xwayland_create_seletion_window(struct xwayland_server *xwayland, xcb_window_t *window, int16_t x, int16_t y, uint16_t width, uint16_t height) { *window = xcb_generate_id(xwayland->xcb_conn); xcb_create_window( xwayland->xcb_conn, XCB_COPY_FROM_PARENT, *window, xwayland->screen->root, x, y, width, height, 0, XCB_WINDOW_CLASS_INPUT_ONLY, xwayland->screen->root_visual, XCB_CW_EVENT_MASK, (uint32_t[]){ XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE }); xcb_change_property(xwayland->xcb_conn, XCB_PROP_MODE_REPLACE, *window, xwayland->atoms[DND_AWARE], XCB_ATOM_ATOM, 32, // format 1, &(uint32_t){ XDND_VERSION }); xcb_unmap_window(xwayland->xcb_conn, *window); uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(xwayland->xcb_conn, *window, XCB_CONFIG_WINDOW_STACK_MODE, values); xcb_flush(xwayland->xcb_conn); } int xwayland_handle_selection_event(struct xwayland_server *xwayland, xcb_generic_event_t *event) { if (xwayland->wlr_xwayland->seat == NULL) { kywc_log(KYWC_ERROR, "Not handling selection events: no seat assigned to xwayland"); return 0; } const uint8_t response_type = event->response_type & 0x7f; if (response_type == XCB_SELECTION_NOTIFY) { return xwayland_handle_selection_notify(xwayland, (xcb_selection_notify_event_t *)event); } else if (response_type == XCB_PROPERTY_NOTIFY) { // XCB_PROPERTY_NOTIFY } else if (xwayland->xfixes && response_type == xwayland->xfixes->first_event + XCB_XFIXES_SELECTION_NOTIFY) { // xfixes return xwayland_handle_xfixes_selection_notify( xwayland, (xcb_xfixes_selection_notify_event_t *)event); } return 0; } void xwayland_map_selection_window(struct xwayland_server *xwayland, xcb_window_t window, struct kywc_box *box, bool map) { if (map) { xcb_map_window(xwayland->xcb_conn, window); uint16_t value_mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; uint32_t value_list[] = { box->x, box->y, box->width, box->height }; xcb_configure_window(xwayland->xcb_conn, window, value_mask, value_list); uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(xwayland->xcb_conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values); } else { xcb_unmap_window(xwayland->xcb_conn, window); } xcb_flush(xwayland->xcb_conn); } struct xwayland_data_transfer *xwayland_data_transfer_create(struct xwayland_drag_x11 *drag_x11, const char *mime_type, int fd) { struct xwayland_data_transfer *transfer = calloc(1, sizeof(*transfer)); if (!transfer) { return NULL; } transfer->wl_client_fd = fd; transfer->mime_type = strdup(mime_type); if (!transfer->mime_type) { free(transfer); return NULL; } wl_list_insert(&drag_x11->transfers, &transfer->link); return transfer; } void xwayland_data_transfer_destroy(struct xwayland_data_transfer *transfer) { if (transfer->event_source) { wl_event_source_remove(transfer->event_source); } wl_list_remove(&transfer->link); close(transfer->wl_client_fd); free(transfer->property_reply); free(transfer->mime_type); free(transfer); } struct xwayland_data_transfer * xwayland_data_transfer_find_by_type(struct xwayland_drag_x11 *drag_x11, const char *mime_type) { struct xwayland_data_transfer *transfer; wl_list_for_each(transfer, &drag_x11->transfers, link) { if (strcmp(mime_type, transfer->mime_type) == 0) { return transfer; } } return NULL; } kylin-wayland-compositor/src/backend/0000775000175000017500000000000015160461067016643 5ustar fengfengkylin-wayland-compositor/src/backend/meson.build0000664000175000017500000000015115160461067021002 0ustar fengfengwlcom_sources += files( 'backend.c', ) subdir('drm') subdir('eis') subdir('fbdev') subdir('libinput') kylin-wayland-compositor/src/backend/eis/0000775000175000017500000000000015160461067017423 5ustar fengfengkylin-wayland-compositor/src/backend/eis/meson.build0000664000175000017500000000027515160461067021571 0ustar fengfengeis = dependency('libeis-1.0') if not eis.found() subdir_done() endif wlcom_deps += eis wlcom_sources += files( 'backend.c', 'client.c', 'context.c', 'event.c', 'filter.c', ) kylin-wayland-compositor/src/backend/eis/client.c0000664000175000017500000002750615160461067021057 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "backend/eis.h" #include "eis_p.h" #include "input/keyboard.h" #include "input/seat.h" static const struct wlr_pointer_impl eis_pointer_impl = { .name = "eis-pointer", }; static void eis_context_client_create_pointer(struct eis_context_client *client) { if (client->pointer) { return; } char device_name[256]; snprintf(device_name, sizeof(device_name), "%s %s%s", eis_client_get_name(client->client), client->sender ? "" : "capture", "eis pointer"); client->pointer = eis_seat_new_device(client->seat); eis_device_configure_name(client->pointer, device_name); eis_device_configure_capability(client->pointer, EIS_DEVICE_CAP_POINTER); eis_device_configure_capability(client->pointer, EIS_DEVICE_CAP_SCROLL); eis_device_configure_capability(client->pointer, EIS_DEVICE_CAP_BUTTON); if (client->sender) { wlr_pointer_init(&client->wlr_pointer, &eis_pointer_impl, device_name); wl_signal_emit_mutable(&client->context->backend->backend.events.new_input, &client->wlr_pointer.base); } eis_device_add(client->pointer); eis_device_resume(client->pointer); } static void eis_context_client_destroy_pointer(struct eis_context_client *client) { if (!client->pointer) { return; } if (client->wlr_pointer.impl) { wlr_pointer_finish(&client->wlr_pointer); } eis_device_pause(client->pointer); eis_device_remove(client->pointer); eis_device_unref(client->pointer); client->pointer = NULL; } static const struct wlr_pointer_impl eis_abs_pointer_impl = { .name = "eis-abs-pointer", }; static void eis_context_client_create_absolute(struct eis_context_client *client) { if (client->absolute) { return; } char device_name[256]; snprintf(device_name, sizeof(device_name), "%s %s%s", eis_client_get_name(client->client), client->sender ? "" : "capture", "eis abs pointer"); client->absolute = eis_seat_new_device(client->seat); eis_device_configure_name(client->absolute, device_name); eis_device_configure_capability(client->absolute, EIS_DEVICE_CAP_POINTER_ABSOLUTE); eis_device_configure_capability(client->absolute, EIS_DEVICE_CAP_SCROLL); eis_device_configure_capability(client->absolute, EIS_DEVICE_CAP_BUTTON); struct wlr_output_layout *layout = client->context->backend->layout; struct wlr_output_layout_output *output_layout; wl_list_for_each(output_layout, &layout->outputs, link) { struct eis_region *region = eis_device_new_region(client->absolute); int width, height; wlr_output_effective_resolution(output_layout->output, &width, &height); eis_region_set_offset(region, output_layout->x, output_layout->y); eis_region_set_size(region, width, height); eis_region_set_physical_scale(region, output_layout->output->scale); eis_region_add(region); eis_region_unref(region); } wl_signal_add(&layout->events.change, &client->layout_update); if (client->sender) { wlr_pointer_init(&client->wlr_absolute, &eis_abs_pointer_impl, device_name); wl_signal_emit_mutable(&client->context->backend->backend.events.new_input, &client->wlr_absolute.base); } eis_device_add(client->absolute); eis_device_resume(client->absolute); } static void eis_context_client_destroy_absolute(struct eis_context_client *client) { if (!client->absolute) { return; } if (client->wlr_absolute.impl) { wlr_pointer_finish(&client->wlr_absolute); } wl_list_remove(&client->layout_update.link); wl_list_init(&client->layout_update.link); eis_device_pause(client->absolute); eis_device_remove(client->absolute); eis_device_unref(client->absolute); client->absolute = NULL; } static const struct wlr_keyboard_impl eis_keyboard_impl = { .name = "eis-keyboard", }; static void eis_context_client_create_keyboard(struct eis_context_client *client) { if (client->keyboard) { return; } char device_name[256]; snprintf(device_name, 256, "%s %s%s", eis_client_get_name(client->client), client->sender ? "" : "capture ", "eis keyboard"); client->keyboard = eis_seat_new_device(client->seat); eis_device_configure_name(client->keyboard, device_name); eis_device_configure_capability(client->keyboard, EIS_DEVICE_CAP_KEYBOARD); struct wlr_keyboard *wlr_keyboard = NULL; if (client->sender) { wlr_keyboard_init(&client->wlr_keyboard, &eis_keyboard_impl, device_name); wl_signal_emit_mutable(&client->context->backend->backend.events.new_input, &client->wlr_keyboard.base); wlr_keyboard = &client->wlr_keyboard; } else { wlr_keyboard = input_manager_get_default_seat()->keyboard->wlr_keyboard; } if (wlr_keyboard->keymap) { struct eis_keymap *keymap = eis_device_new_keymap(client->keyboard, EIS_KEYMAP_TYPE_XKB, wlr_keyboard->keymap_fd, wlr_keyboard->keymap_size); eis_keymap_add(keymap); eis_keymap_unref(keymap); } wl_signal_add(&wlr_keyboard->events.keymap, &client->keymap_update); wl_signal_add(&wlr_keyboard->events.modifiers, &client->modifiers_update); eis_device_add(client->keyboard); eis_device_resume(client->keyboard); } static void eis_context_client_destroy_keyboard(struct eis_context_client *client) { if (!client->keyboard) { return; } if (client->wlr_keyboard.impl) { wlr_keyboard_finish(&client->wlr_keyboard); } wl_list_remove(&client->keymap_update.link); wl_list_init(&client->keymap_update.link); wl_list_remove(&client->modifiers_update.link); wl_list_init(&client->modifiers_update.link); eis_device_pause(client->keyboard); eis_device_remove(client->keyboard); eis_device_unref(client->keyboard); client->keyboard = NULL; } static void client_handle_layout_change(struct wl_listener *listener, void *data) { struct eis_context_client *client = wl_container_of(listener, client, layout_update); if (!client->absolute) { return; } eis_context_client_destroy_absolute(client); eis_context_client_create_absolute(client); } static void client_handle_keymap_change(struct wl_listener *listener, void *data) { struct eis_context_client *client = wl_container_of(listener, client, keymap_update); if (!client->keyboard) { return; } eis_context_client_destroy_keyboard(client); eis_context_client_create_keyboard(client); } static void client_handle_modifiers_change(struct wl_listener *listener, void *data) { struct eis_context_client *client = wl_container_of(listener, client, modifiers_update); if (!client->keyboard) { return; } // TODO: check enabled if input capture context struct wlr_keyboard_modifiers *modifiers = &client->wlr_keyboard.modifiers; eis_device_keyboard_send_xkb_modifiers(client->keyboard, modifiers->depressed, modifiers->latched, modifiers->locked, modifiers->group); } struct eis_context_client *eis_context_add_client(struct eis_context *context, struct eis_client *eis_client) { struct eis_context_client *client = calloc(1, sizeof(*client)); if (!client) { return NULL; } client->context = context; wl_list_insert(&context->clients, &client->link); client->client = eis_client_ref(eis_client); eis_client_set_user_data(eis_client, client); eis_client_connect(eis_client); struct eis_seat *eis_seat = eis_client_new_seat(eis_client, "eis-0"); if (context->capabilities & EIS_DEVICE_CAP_POINTER) { eis_seat_configure_capability(eis_seat, EIS_DEVICE_CAP_POINTER); } if (context->capabilities & EIS_DEVICE_CAP_POINTER_ABSOLUTE) { eis_seat_configure_capability(eis_seat, EIS_DEVICE_CAP_POINTER_ABSOLUTE); } if (context->capabilities & EIS_DEVICE_CAP_KEYBOARD) { eis_seat_configure_capability(eis_seat, EIS_DEVICE_CAP_KEYBOARD); } if (context->capabilities & EIS_DEVICE_CAP_TOUCH) { eis_seat_configure_capability(eis_seat, EIS_DEVICE_CAP_TOUCH); } if (context->capabilities & EIS_DEVICE_CAP_SCROLL) { eis_seat_configure_capability(eis_seat, EIS_DEVICE_CAP_SCROLL); } if (context->capabilities & EIS_DEVICE_CAP_BUTTON) { eis_seat_configure_capability(eis_seat, EIS_DEVICE_CAP_BUTTON); } eis_seat_add(eis_seat); client->seat = eis_seat; // layout changed for abs pointer client->layout_update.notify = client_handle_layout_change; wl_list_init(&client->layout_update.link); // keymap and modifiers changed for keyboard client->keymap_update.notify = client_handle_keymap_change; wl_list_init(&client->keymap_update.link); client->modifiers_update.notify = client_handle_modifiers_change; wl_list_init(&client->modifiers_update.link); client->sender = eis_client_is_sender(eis_client); return client; } struct eis_context_client *eis_context_get_client(struct eis_context *context, struct eis_client *eis_client) { if (!eis_client) { return NULL; } return eis_client_get_user_data(eis_client); } void eis_context_client_process_event(struct eis_context_client *client, struct eis_event *event) { enum eis_event_type type = eis_event_get_type(event); if (type == EIS_EVENT_SEAT_BIND) { if (eis_event_seat_has_capability(event, EIS_DEVICE_CAP_POINTER)) { eis_context_client_create_pointer(client); } else { eis_context_client_destroy_pointer(client); } if (eis_event_seat_has_capability(event, EIS_DEVICE_CAP_POINTER_ABSOLUTE)) { eis_context_client_create_absolute(client); } else { eis_context_client_destroy_absolute(client); } if (eis_event_seat_has_capability(event, EIS_DEVICE_CAP_KEYBOARD)) { eis_context_client_create_keyboard(client); } else { eis_context_client_destroy_keyboard(client); } } else if (type == EIS_EVENT_DEVICE_CLOSED) { struct eis_device *device = eis_event_get_device(event); if (device == client->pointer) { eis_context_client_destroy_pointer(client); } else if (device == client->absolute) { eis_context_client_destroy_absolute(client); } else if (device == client->keyboard) { eis_context_client_destroy_keyboard(client); } } else if (!client->sender) { kywc_log(KYWC_DEBUG, "Skip EIS event type %d from receiver client", type); } else { eis_context_client_feed_event(client, event); } } void eis_context_remove_client(struct eis_context *context, struct eis_client *eis_client) { struct eis_context_client *client = eis_context_get_client(context, eis_client); if (!client) { return; } eis_context_client_destroy_pointer(client); eis_context_client_destroy_absolute(client); eis_context_client_destroy_keyboard(client); eis_seat_unref(client->seat); eis_client_disconnect(eis_client); eis_client_unref(eis_client); wl_list_remove(&client->link); free(client); } bool wlr_input_device_is_eis(struct wlr_input_device *wlr_dev) { switch (wlr_dev->type) { case WLR_INPUT_DEVICE_KEYBOARD: return wlr_keyboard_from_input_device(wlr_dev)->impl == &eis_keyboard_impl; case WLR_INPUT_DEVICE_POINTER:; struct wlr_pointer *pointer = wlr_pointer_from_input_device(wlr_dev); return pointer->impl == &eis_pointer_impl || pointer->impl == &eis_abs_pointer_impl; default: return false; } } kylin-wayland-compositor/src/backend/eis/eis_p.h0000664000175000017500000000543615160461067020703 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _EIS_P_H_ #define _EIS_P_H_ #include #include #include #include #include #include enum eis_context_type { EIS_CONTEXT_TYPE_NORMAL = 0, EIS_CONTEXT_TYPE_REMOTE_DESKTOP, EIS_CONTEXT_TYPE_INPUT_CAPTURE, }; struct eis_backend { struct wlr_backend backend; struct wl_event_loop *event_loop; struct wlr_output_layout *layout; struct wl_list contexts; struct dbus_object *capture_manager; uint32_t capabilities; struct eis_context *active_capture; int filter_locks; }; enum { VERTICAL = 0, HORIZONTAL = 1, }; struct barrier { int position, start, end; int orientation; int hit; }; struct eis_context { struct eis_backend *backend; struct wl_list link; struct eis *eis; struct wl_event_source *event; struct wl_list clients; struct dbus_object *dbus; const char *dbus_path; // input_capture struct sd_bus_slot *watcher; enum eis_context_type type; uint32_t capabilities; struct wl_array barriers; uint32_t activation_id; }; struct eis_context_client { struct eis_context *context; struct wl_list link; struct eis_client *client; struct eis_seat *seat; struct eis_device *pointer; struct wlr_pointer wlr_pointer; struct eis_device *absolute; struct wlr_pointer wlr_absolute; struct wl_listener layout_update; struct eis_device *keyboard; struct wlr_keyboard wlr_keyboard; struct wl_listener keymap_update; struct wl_listener modifiers_update; bool sender; // sender or receiver }; struct eis_context *eis_context_create(struct eis_backend *backend, enum eis_context_type type, uint32_t capabilities, const void *data); void eis_context_destroy(struct eis_context *context); struct eis_context_client *eis_context_add_client(struct eis_context *context, struct eis_client *eis_client); void eis_context_remove_client(struct eis_context *context, struct eis_client *eis_client); void eis_context_set_activated(struct eis_context *context, bool activated, double lx, double ly); struct eis_context_client *eis_context_get_client(struct eis_context *context, struct eis_client *eis_client); void eis_context_client_process_event(struct eis_context_client *client, struct eis_event *event); void eis_context_client_feed_event(struct eis_context_client *client, struct eis_event *event); void eis_backend_lock_filter(struct eis_backend *backend, bool lock); #endif /* _EIS_P_H_ */ kylin-wayland-compositor/src/backend/eis/event.c0000664000175000017500000002225015160461067020711 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include "eis_p.h" #include "util/time.h" #include static void handle_frame(struct eis_context_client *client, struct eis_device *device, struct eis_event *event) { if (device == client->pointer) { wl_signal_emit_mutable(&client->wlr_pointer.events.frame, &client->wlr_pointer); } else if (device == client->absolute) { wl_signal_emit_mutable(&client->wlr_absolute.events.frame, &client->wlr_absolute); } else { kywc_log(KYWC_DEBUG, "Unhandled EIS frame event"); } } static void handle_pointer_motion(struct eis_context_client *client, struct eis_device *device, struct eis_event *event) { if (device != client->pointer) { kywc_log(KYWC_DEBUG, "Unhandled EIS pointer motion event"); return; } struct wlr_pointer_motion_event wlr_event = { .pointer = &client->wlr_pointer, .time_msec = current_time_msec(), .delta_x = eis_event_pointer_get_dx(event), .delta_y = eis_event_pointer_get_dy(event), .unaccel_dx = eis_event_pointer_get_dx(event), .unaccel_dy = eis_event_pointer_get_dy(event), }; wl_signal_emit_mutable(&client->wlr_pointer.events.motion, &wlr_event); } static bool eis_device_transform_coord(struct eis_device *device, double *x, double *y) { struct eis_region *region = eis_device_get_region_at(device, *x, *y); if (!region) { return false; } // TODO: check when multi-output struct wlr_box box = { eis_region_get_x(region), eis_region_get_y(region), eis_region_get_width(region), eis_region_get_height(region), }; *x = (*x - box.x) / box.width; *y = (*y - box.y) / box.height; return true; } static void handle_pointer_motion_absolute(struct eis_context_client *client, struct eis_device *device, struct eis_event *event) { if (device != client->absolute) { kywc_log(KYWC_DEBUG, "Unhandled EIS pointer motion absolute event"); return; } double x = eis_event_pointer_get_absolute_x(event); double y = eis_event_pointer_get_absolute_y(event); // transform to 0..1 if (!eis_device_transform_coord(device, &x, &y)) { return; } struct wlr_pointer_motion_absolute_event wlr_event = { .pointer = &client->wlr_absolute, .time_msec = current_time_msec(), .x = x, .y = y, }; wl_signal_emit_mutable(&client->wlr_absolute.events.motion_absolute, &wlr_event); } static void handle_pointer_button(struct eis_context_client *client, struct eis_device *device, struct eis_event *event) { struct wlr_pointer *pointer = NULL; if (device == client->pointer) { pointer = &client->wlr_pointer; } else if (device == client->absolute) { pointer = &client->wlr_absolute; } else { kywc_log(KYWC_DEBUG, "Unhandled EIS pointer button event"); return; } struct wlr_pointer_button_event wlr_event = { .pointer = pointer, .time_msec = current_time_msec(), .button = eis_event_button_get_button(event), .state = eis_event_button_get_is_press(event) ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED, }; wl_signal_emit_mutable(&pointer->events.button, &wlr_event); } static void handle_scroll_delta(struct eis_context_client *client, struct eis_device *device, struct eis_event *event) { struct wlr_pointer *pointer = NULL; if (device == client->pointer) { pointer = &client->wlr_pointer; } else if (device == client->absolute) { pointer = &client->wlr_absolute; } else { kywc_log(KYWC_DEBUG, "Unhandled EIS scroll delta event"); return; } struct wlr_pointer_axis_event wlr_event = { .pointer = pointer, .time_msec = current_time_msec(), .source = WL_POINTER_AXIS_SOURCE_WHEEL, }; double x = eis_event_scroll_get_dx(event); if (x != 0) { wlr_event.orientation = WL_POINTER_AXIS_HORIZONTAL_SCROLL; wlr_event.delta = x; wl_signal_emit_mutable(&pointer->events.axis, &wlr_event); } double y = eis_event_scroll_get_dy(event); if (y != 0) { wlr_event.orientation = WL_POINTER_AXIS_VERTICAL_SCROLL; wlr_event.delta = y; wl_signal_emit_mutable(&pointer->events.axis, &wlr_event); } } static void handle_scroll_stop_or_cancel(struct eis_context_client *client, struct eis_device *device, struct eis_event *event) { struct wlr_pointer *pointer = NULL; if (device == client->pointer) { pointer = &client->wlr_pointer; } else if (device == client->absolute) { pointer = &client->wlr_absolute; } else { kywc_log(KYWC_DEBUG, "Unhandled EIS scroll stop or cancel event"); return; } struct wlr_pointer_axis_event wlr_event = { .pointer = pointer, .time_msec = current_time_msec(), .source = WL_POINTER_AXIS_SOURCE_WHEEL, }; double x = eis_event_scroll_get_stop_x(event); if (x != 0) { wlr_event.orientation = WL_POINTER_AXIS_HORIZONTAL_SCROLL; wl_signal_emit_mutable(&pointer->events.axis, &wlr_event); } double y = eis_event_scroll_get_stop_y(event); if (y != 0) { wlr_event.orientation = WL_POINTER_AXIS_VERTICAL_SCROLL; wl_signal_emit_mutable(&pointer->events.axis, &wlr_event); } } static void handle_scroll_discrete(struct eis_context_client *client, struct eis_device *device, struct eis_event *event) { struct wlr_pointer *pointer = NULL; if (device == client->pointer) { pointer = &client->wlr_pointer; } else if (device == client->absolute) { pointer = &client->wlr_absolute; } else { kywc_log(KYWC_DEBUG, "Unhandled EIS scroll discrete event"); return; } struct wlr_pointer_axis_event wlr_event = { .pointer = pointer, .time_msec = current_time_msec(), .source = WL_POINTER_AXIS_SOURCE_WHEEL, }; double x = eis_event_scroll_get_discrete_dx(event); if (x != 0) { wlr_event.orientation = WL_POINTER_AXIS_HORIZONTAL_SCROLL; wlr_event.delta = x * 15 / 120.0; wlr_event.delta_discrete = x; wl_signal_emit_mutable(&pointer->events.axis, &wlr_event); } double y = eis_event_scroll_get_discrete_dy(event); if (y != 0) { wlr_event.orientation = WL_POINTER_AXIS_VERTICAL_SCROLL; wlr_event.delta = y * 15 / 120.0; wlr_event.delta_discrete = y; wl_signal_emit_mutable(&pointer->events.axis, &wlr_event); } } static void handle_keyboard_key(struct eis_context_client *client, struct eis_device *device, struct eis_event *event) { if (device != client->keyboard) { kywc_log(KYWC_DEBUG, "Unhandled EIS keyboard key event"); return; } struct wlr_keyboard_key_event wlr_event = { .time_msec = current_time_msec(), .keycode = eis_event_keyboard_get_key(event), .state = eis_event_keyboard_get_key_is_press(event) ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED, .update_state = true, }; wlr_keyboard_notify_key(&client->wlr_keyboard, &wlr_event); } void eis_context_client_feed_event(struct eis_context_client *client, struct eis_event *event) { enum eis_event_type type = eis_event_get_type(event); struct eis_device *device = eis_event_get_device(event); switch (type) { case EIS_EVENT_FRAME: handle_frame(client, device, event); break; case EIS_EVENT_DEVICE_START_EMULATING:; kywc_log(KYWC_DEBUG, "Device %s is ready to send events", eis_device_get_name(device)); break; case EIS_EVENT_DEVICE_STOP_EMULATING: kywc_log(KYWC_DEBUG, "Device %s will no longer send events", eis_device_get_name(device)); break; case EIS_EVENT_POINTER_MOTION: handle_pointer_motion(client, device, event); break; case EIS_EVENT_POINTER_MOTION_ABSOLUTE: handle_pointer_motion_absolute(client, device, event); break; case EIS_EVENT_BUTTON_BUTTON: handle_pointer_button(client, device, event); break; case EIS_EVENT_SCROLL_DELTA: handle_scroll_delta(client, device, event); break; case EIS_EVENT_SCROLL_STOP: case EIS_EVENT_SCROLL_CANCEL: handle_scroll_stop_or_cancel(client, device, event); break; case EIS_EVENT_SCROLL_DISCRETE: handle_scroll_discrete(client, device, event); break; case EIS_EVENT_KEYBOARD_KEY: handle_keyboard_key(client, device, event); break; case EIS_EVENT_TOUCH_DOWN: case EIS_EVENT_TOUCH_UP: case EIS_EVENT_TOUCH_MOTION: kywc_log(KYWC_DEBUG, "Unsupport EIS event type %d", type); break; default: kywc_log(KYWC_DEBUG, "Unhandled EIS event type %d", type); } } kylin-wayland-compositor/src/backend/eis/filter.c0000664000175000017500000001237615160461067021065 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "eis_p.h" #include "input/cursor.h" #include "input/seat.h" static inline bool hit_test(struct barrier *barrier, int lx, int ly) { if (barrier->orientation == VERTICAL) { return lx == barrier->position && barrier->start <= ly && ly <= barrier->end; } else { return ly == barrier->position && barrier->start <= lx && lx <= barrier->end; } } static bool barrier_hit_test(struct barrier *barrier, int lx, int ly, int dx, int dy) { if (dx == 0 && dy == 0) { return false; } if (!hit_test(barrier, lx, ly)) { barrier->hit = 0; return false; } if (++barrier->hit <= 1) { return false; } bool hit = (barrier->orientation == VERTICAL) ? (dx != 0) : (dy != 0); if (hit) { barrier->hit = 0; } return hit; } static void barrier_hint(struct eis_backend *backend, int lx, int ly, int dx, int dy) { struct eis_context *context; wl_list_for_each(context, &backend->contexts, link) { if (context->type != EIS_CONTEXT_TYPE_INPUT_CAPTURE) { continue; } struct barrier *barrier; wl_array_for_each(barrier, &context->barriers) { if (barrier_hit_test(barrier, lx, ly, dx, dy)) { eis_context_set_activated(context, true, lx + dx, ly + dy); return; } } } } static void handle_motion(struct eis_context_client *client, struct wlr_pointer_motion_event *event) { if (!client->pointer) { return; } eis_device_pointer_motion(client->pointer, event->delta_x, event->delta_y); } static void handle_button(struct eis_context_client *client, struct wlr_pointer_button_event *event) { if (!client->pointer) { return; } eis_device_button_button(client->pointer, event->button, event->state == WL_POINTER_BUTTON_STATE_PRESSED); } static void handle_axis(struct eis_context_client *client, struct wlr_pointer_axis_event *event) { if (!client->pointer) { return; } if (event->delta) { if (event->delta_discrete) { if (event->orientation == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { eis_device_scroll_discrete(client->pointer, event->delta_discrete, 0); } else { eis_device_scroll_discrete(client->pointer, 0, event->delta_discrete); } } else { if (event->orientation == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { eis_device_scroll_delta(client->pointer, event->delta, 0); } else { eis_device_scroll_delta(client->pointer, 0, event->delta); } } } else { if (event->orientation == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { eis_device_scroll_stop(client->pointer, true, false); } else { eis_device_scroll_stop(client->pointer, false, true); } } } static void handle_frame(struct eis_context_client *client) { if (!client->pointer) { return; } eis_device_frame(client->pointer, eis_now(client->context->eis)); } static void handle_key(struct eis_context_client *client, struct wlr_keyboard_key_event *event) { if (!client->keyboard) { return; } eis_device_keyboard_key(client->keyboard, event->keycode, event->state != WL_KEYBOARD_KEY_STATE_RELEASED); eis_device_frame(client->keyboard, eis_now(client->context->eis)); } static bool backend_handle_event(struct input_event *event, void *data) { struct eis_backend *backend = data; if (!backend->active_capture && event->type == INPUT_EVENT_POINTER_MOTION) { struct cursor *cursor = event->input->seat->cursor; struct wlr_pointer_motion_event *motion = event->event; barrier_hint(backend, cursor->lx, cursor->ly, motion->delta_x, motion->delta_y); } struct eis_context *context = backend->active_capture; if (!context || wl_list_empty(&context->clients)) { return false; } struct eis_context_client *client = wl_container_of(context->clients.next, client, link); switch (event->type) { case INPUT_EVENT_KEYBOARD_KEY: handle_key(client, event->event); break; case INPUT_EVENT_POINTER_MOTION: handle_motion(client, event->event); break; case INPUT_EVENT_POINTER_BUTTON: handle_button(client, event->event); break; case INPUT_EVENT_POINTER_AXIS: handle_axis(client, event->event); break; case INPUT_EVENT_POINTER_FRAME: handle_frame(client); break; default: break; } return true; } void eis_backend_lock_filter(struct eis_backend *backend, bool lock) { if (lock) { backend->filter_locks++; } else { assert(backend->filter_locks > 0); --backend->filter_locks; } kywc_log(KYWC_INFO, "Eis backend input filter %s (locks: %d)", lock ? "Enabling" : "Disabling", backend->filter_locks); if (backend->filter_locks > 0) { input_manager_set_event_filter(backend_handle_event, backend); } else { input_manager_set_event_filter(NULL, NULL); } } kylin-wayland-compositor/src/backend/eis/backend.c0000664000175000017500000001163415160461067021163 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "backend/eis.h" #include "eis_p.h" #include "util/dbus.h" static int add_input_capture(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { uint32_t capabilities = 0; CK(sd_bus_message_read(msg, "u", &capabilities)); uint32_t eis_capabilities = 0; if (capabilities & 1) { eis_capabilities |= EIS_DEVICE_CAP_KEYBOARD; } if (capabilities & 2) { eis_capabilities |= EIS_DEVICE_CAP_POINTER; eis_capabilities |= EIS_DEVICE_CAP_POINTER_ABSOLUTE; eis_capabilities |= EIS_DEVICE_CAP_BUTTON; eis_capabilities |= EIS_DEVICE_CAP_SCROLL; } if (capabilities & 4) { eis_capabilities |= EIS_DEVICE_CAP_TOUCH; } struct eis_backend *backend = userdata; eis_capabilities &= backend->capabilities; const char *sender = sd_bus_message_get_sender(msg); struct eis_context *context = eis_context_create(backend, EIS_CONTEXT_TYPE_INPUT_CAPTURE, eis_capabilities, sender); if (!context) { return -ENOMEM; } return sd_bus_reply_method_return(msg, "o", context->dbus_path); } static int remove_input_capture(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { const char *path = NULL; CK(sd_bus_message_read(msg, "o", &path)); struct eis_backend *backend = userdata; struct eis_context *context, *tmp; wl_list_for_each_safe(context, tmp, &backend->contexts, link) { if (context->type != EIS_CONTEXT_TYPE_INPUT_CAPTURE) { continue; } if (context->dbus_path && strcmp(context->dbus_path, path) == 0) { eis_context_destroy(context); break; } } return sd_bus_reply_method_return(msg, NULL); } static const sd_bus_vtable capture_manager_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("AddInputCapture", "u", "o", add_input_capture, 0), SD_BUS_METHOD("RemoveInputCapture", "o", "", remove_input_capture, 0), SD_BUS_VTABLE_END, }; static struct eis_backend *get_eis_backend_from_backend(struct wlr_backend *wlr_backend) { assert(wlr_backend_is_eis(wlr_backend)); struct eis_backend *backend = wl_container_of(wlr_backend, backend, backend); return backend; } static bool backend_start(struct wlr_backend *wlr_backend) { struct eis_backend *backend = get_eis_backend_from_backend(wlr_backend); kywc_log(KYWC_DEBUG, "Starting eis backend"); /* input capture manager dbus */ backend->capture_manager = dbus_register_object(NULL, "/com/kylin/Wlcom/EIS/InputCapture", "com.kylin.Wlcom.EIS.InputCaptureManager", capture_manager_vtable, backend); if (!backend->capture_manager) { kywc_log(KYWC_ERROR, "Failed to register capture manager, skip eis backend"); return true; } /* create socket context for debug */ const char *name = getenv("KYWC_EIS_SOCKET"); if (name) { kywc_log(KYWC_INFO, "Create eis context with socket %s", name); eis_context_create(backend, EIS_CONTEXT_TYPE_NORMAL, backend->capabilities, name); } kywc_log(KYWC_INFO, "Create eis context for remote desktop"); eis_context_create(backend, EIS_CONTEXT_TYPE_REMOTE_DESKTOP, backend->capabilities, NULL); kywc_log(KYWC_DEBUG, "Eis successfully initialized"); return true; } static void backend_destroy(struct wlr_backend *wlr_backend) { if (!wlr_backend) { return; } struct eis_backend *backend = get_eis_backend_from_backend(wlr_backend); dbus_unregister_object(backend->capture_manager); // destroy all contexts if compsitor is destroyed struct eis_context *context, *tmp; wl_list_for_each_safe(context, tmp, &backend->contexts, link) { eis_context_destroy(context); } wlr_backend_finish(wlr_backend); free(backend); } static const struct wlr_backend_impl backend_impl = { .start = backend_start, .destroy = backend_destroy, }; bool wlr_backend_is_eis(struct wlr_backend *backend) { return backend->impl == &backend_impl; } struct wlr_backend *eis_backend_create(struct wl_display *display, struct wlr_output_layout *layout) { struct eis_backend *backend = calloc(1, sizeof(*backend)); if (!backend) { kywc_log(KYWC_ERROR, "Failed to allocate eis_backend"); return NULL; } // TODO: pause the process when session inactive wlr_backend_init(&backend->backend, &backend_impl); wl_list_init(&backend->contexts); backend->event_loop = wl_display_get_event_loop(display); backend->layout = layout; // touch is not support current backend->capabilities = (EIS_DEVICE_CAP_POINTER | EIS_DEVICE_CAP_POINTER_ABSOLUTE | EIS_DEVICE_CAP_KEYBOARD | EIS_DEVICE_CAP_SCROLL | EIS_DEVICE_CAP_BUTTON); return &backend->backend; } kylin-wayland-compositor/src/backend/eis/context.c0000664000175000017500000002544015160461067021260 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "eis_p.h" #include "util/dbus.h" #include "util/string.h" static void log_handler(struct eis *eis, enum eis_log_priority priority, const char *message, struct eis_log_context *ctx) { enum kywc_log_level level = KYWC_INFO; switch (priority) { case EIS_LOG_PRIORITY_DEBUG: level = KYWC_DEBUG; break; case EIS_LOG_PRIORITY_WARNING: level = KYWC_WARN; break; case EIS_LOG_PRIORITY_ERROR: level = KYWC_ERROR; break; case EIS_LOG_PRIORITY_INFO: default: break; } kywc_log(level, "[EIS]: %s", message); } static void handle_eis_event(struct eis_context *context, struct eis_event *event) { enum eis_event_type type = eis_event_get_type(event); struct eis_client *eis_client = eis_event_get_client(event); const char *name = eis_client_get_name(eis_client); switch (type) { case EIS_EVENT_CLIENT_CONNECT:; bool sender = eis_client_is_sender(eis_client); // reject client if (sender == (context->type == EIS_CONTEXT_TYPE_INPUT_CAPTURE)) { kywc_log(KYWC_WARN, "Reject mismatch mode client %s", name); eis_client_disconnect(eis_client); break; } // only one client in receiver mode per context if (!sender && !wl_list_empty(&context->clients)) { kywc_log(KYWC_WARN, "Reject unexpected client %s", name); eis_client_disconnect(eis_client); break; } kywc_log(KYWC_DEBUG, "New %s client: %s", sender ? "sender" : "receiver", name); eis_context_add_client(context, eis_client); break; case EIS_EVENT_CLIENT_DISCONNECT: kywc_log(KYWC_DEBUG, "client: %s disconnect", name); eis_context_remove_client(context, eis_client); break; default:; struct eis_context_client *client = eis_context_get_client(context, eis_client); if (!client) { kywc_log(KYWC_WARN, "Event for unknown EIS client: %s", name); return; } eis_context_client_process_event(client, event); break; } } static int handle_eis_readable(int fd, uint32_t mask, void *_context) { struct eis_context *context = _context; eis_dispatch(context->eis); struct eis_event *event; while ((event = eis_get_event(context->eis))) { handle_eis_event(context, event); eis_event_unref(event); } return 0; } static int connect_to_eis(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { struct eis_context *context = userdata; int fd = eis_backend_fd_add_client(context->eis); return sd_bus_reply_method_return(msg, "h", fd); } static const sd_bus_vtable remote_desktop_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("ConnectToEIS", "", "h", connect_to_eis, 0), SD_BUS_VTABLE_END, }; static int enable(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { struct eis_context *context = userdata; sd_bus_message_enter_container(msg, 'a', "(iiii)"); while (sd_bus_message_enter_container(msg, 'r', "iiii") > 0) { int x1, y1, x2, y2; sd_bus_message_read(msg, "iiii", &x1, &y1, &x2, &y2); struct barrier *barrier = wl_array_add(&context->barriers, sizeof(*barrier)); if (x1 == x2) { *barrier = (struct barrier){ x1, y1, y2, VERTICAL, 0 }; } else { *barrier = (struct barrier){ y1, x1, x2, HORIZONTAL, 0 }; } sd_bus_message_exit_container(msg); } sd_bus_message_exit_container(msg); return sd_bus_reply_method_return(msg, NULL); } void eis_context_set_activated(struct eis_context *context, bool activated, double lx, double ly) { struct eis_backend *backend = context->backend; kywc_log(KYWC_DEBUG, "Eis backend %s %s", activated ? "Activate" : "Deactivate", context->dbus_path); // signal activated or deactivated if (activated) { backend->active_capture = context; dbus_emit_signal(context->dbus_path, "com.kylin.Wlcom.EIS.InputCapture", "Activated", "u(dd)", ++context->activation_id, lx, ly); } else { backend->active_capture = NULL; dbus_emit_signal(context->dbus_path, "com.kylin.Wlcom.EIS.InputCapture", "Deactivated", "u", context->activation_id); } if (wl_list_empty(&context->clients)) { return; } struct eis_context_client *client = wl_container_of(context->clients.next, client, link); if (client->pointer) { activated ? eis_device_start_emulating(client->pointer, context->activation_id) : eis_device_stop_emulating(client->pointer); } if (client->keyboard) { activated ? eis_device_start_emulating(client->keyboard, context->activation_id) : eis_device_stop_emulating(client->keyboard); } if (client->absolute) { activated ? eis_device_start_emulating(client->absolute, context->activation_id) : eis_device_stop_emulating(client->absolute); } } static int disable(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { struct eis_context *context = userdata; if (context->backend->active_capture == context) { eis_context_set_activated(context, false, 0, 0); } // clear the barriers context->barriers.size = 0; dbus_emit_signal(context->dbus_path, "com.kylin.Wlcom.EIS.InputCapture", "Disabled", NULL); return sd_bus_reply_method_return(msg, NULL); } static int release(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { struct eis_context *context = userdata; if (context->backend->active_capture != context) { return sd_bus_reply_method_return(msg, NULL); } double cursor_x, cursor_y; uint32_t apply; sd_bus_message_read(msg, "(dd)b", &cursor_x, &cursor_y, &apply); if (apply) { // TODO: apply position to default seat cursor } eis_context_set_activated(context, false, 0, 0); return sd_bus_reply_method_return(msg, NULL); } static const sd_bus_vtable input_capture_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("ConnectToEIS", "", "h", connect_to_eis, 0), SD_BUS_METHOD("Enable", "a(iiii)", "", enable, 0), SD_BUS_METHOD("Disable", "", "", disable, 0), SD_BUS_METHOD("Release", "(dd)b", "", release, 0), SD_BUS_SIGNAL("Activated", "u(dd)", 0), SD_BUS_SIGNAL("Deactivated", "u", 0), SD_BUS_SIGNAL("Disabled", "", 0), SD_BUS_VTABLE_END, }; static int handle_name_lost(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { struct eis_context *context = userdata; kywc_log(KYWC_INFO, "%s lost name, closing context", context->dbus_path); eis_context_destroy(context); return 0; } struct eis_context *eis_context_create(struct eis_backend *backend, enum eis_context_type type, uint32_t capabilities, const void *data) { struct eis_context *context = calloc(1, sizeof(*context)); if (!context) { return NULL; } context->eis = eis_new(context); if (!context->eis) { kywc_log(KYWC_ERROR, "Failed to create eis context"); goto failed; } eis_log_set_priority(context->eis, EIS_LOG_PRIORITY_WARNING); eis_log_set_handler(context->eis, log_handler); if (type == EIS_CONTEXT_TYPE_NORMAL) { if (eis_setup_backend_socket(context->eis, data) != 0) { goto failed; } } else if (type == EIS_CONTEXT_TYPE_REMOTE_DESKTOP) { if (eis_setup_backend_fd(context->eis) != 0) { goto failed; } context->dbus = dbus_register_object(NULL, "/com/kylin/Wlcom/EIS/RemoteDesktop", "com.kylin.Wlcom.EIS.RemoteDesktop", remote_desktop_vtable, context); if (!context->dbus) { goto failed; } } else if (type == EIS_CONTEXT_TYPE_INPUT_CAPTURE) { static uint32_t counter = 0; if (eis_setup_backend_fd(context->eis) != 0) { goto failed; } context->dbus_path = string_create("/com/kylin/Wlcom/EIS/InputCapture/%d", ++counter); if (!context->dbus_path) { goto failed; } context->dbus = dbus_register_object(NULL, context->dbus_path, "com.kylin.Wlcom.EIS.InputCapture", input_capture_vtable, context); if (!context->dbus) { goto failed; } } int eis_fd = eis_get_fd(context->eis); context->event = wl_event_loop_add_fd(backend->event_loop, eis_fd, WL_EVENT_READABLE, handle_eis_readable, context); if (!context->event) { kywc_log(KYWC_ERROR, "Failed to create event on event loop"); goto failed; } context->type = type; context->capabilities = capabilities; wl_list_init(&context->clients); wl_array_init(&context->barriers); context->backend = backend; wl_list_insert(&backend->contexts, &context->link); if (type == EIS_CONTEXT_TYPE_INPUT_CAPTURE) { static char match_rule[1024]; snprintf(match_rule, sizeof(match_rule), "sender='org.freedesktop.DBus'," "type='signal'," "interface='org.freedesktop.DBus'," "member='NameOwnerChanged'," "path='/org/freedesktop/DBus'," "arg0='%s'", (const char *)data); sd_bus_add_match(dbus_context_get_bus(false), &context->watcher, match_rule, handle_name_lost, context); eis_backend_lock_filter(backend, true); } return context; failed: eis_unref(context->eis); dbus_unregister_object(context->dbus); free((void *)context->dbus_path); free(context); return NULL; } void eis_context_destroy(struct eis_context *context) { if (!context) { return; } struct eis_context_client *client, *tmp; wl_list_for_each_safe(client, tmp, &context->clients, link) { eis_context_remove_client(context, client->client); } wl_list_remove(&context->link); wl_event_source_remove(context->event); eis_unref(context->eis); if (context->watcher) { sd_bus_slot_unref(context->watcher); } dbus_unregister_object(context->dbus); if (context->type == EIS_CONTEXT_TYPE_INPUT_CAPTURE) { struct eis_backend *backend = context->backend; eis_backend_lock_filter(backend, false); if (backend->active_capture == context) { eis_context_set_activated(context, false, 0, 0); } } wl_array_release(&context->barriers); free((void *)context->dbus_path); free(context); } kylin-wayland-compositor/src/backend/fbdev/0000775000175000017500000000000015160461067017731 5ustar fengfengkylin-wayland-compositor/src/backend/fbdev/meson.build0000664000175000017500000000006515160460057022072 0ustar fengfengwlcom_sources += files( 'backend.c', 'fbdev.c' ) kylin-wayland-compositor/src/backend/fbdev/fbdev.c0000664000175000017500000005423015160461067021167 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include "fbdev_p.h" enum dpms_mode { DPMS_MODE_ON = 0, DPMS_MODE_OFF, }; struct fbdev_state { const struct wlr_output_state *base; struct fb_var_screeninfo mode_info; }; static const uint32_t COMMIT_OUTPUT_STATE = WLR_OUTPUT_STATE_BUFFER | WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_ENABLED; static const uint32_t SUPPORTED_OUTPUT_STATE = WLR_OUTPUT_STATE_BACKEND_OPTIONAL | COMMIT_OUTPUT_STATE; struct fbdev_output *fbdev_output_from_output(struct wlr_output *wlr_output) { assert(wlr_output_is_fbdev(wlr_output)); struct fbdev_output *output = wl_container_of(wlr_output, output, wlr_output); return output; } static uint32_t calculate_drm_format(struct fb_var_screeninfo *vinfo, struct fb_fix_screeninfo *finfo) { kywc_log(KYWC_DEBUG, "Calculating drm format from: \n" " type: %i, aux: %i, visual: %i, bpp: %i, grayscale: %i\n" " red(offset: %i, length: %i, MSB: %i)\n" " green(offset: %i, length: %i, MSB: %i)\n" " blue(offset: %i, length: %i, MSB: %i)\n" " transp(offset: %i, length: %i, MSB: %i)", finfo->type, finfo->type_aux, finfo->visual, vinfo->bits_per_pixel, vinfo->grayscale, vinfo->red.offset, vinfo->red.length, vinfo->red.msb_right, vinfo->green.offset, vinfo->green.length, vinfo->green.msb_right, vinfo->blue.offset, vinfo->blue.length, vinfo->blue.msb_right, vinfo->transp.offset, vinfo->transp.length, vinfo->transp.msb_right); /* We only handle packed formats at the moment */ if (finfo->type != FB_TYPE_PACKED_PIXELS) { return DRM_FORMAT_INVALID; } /* We only handle true-colour frame buffers at the moment */ switch (finfo->visual) { case FB_VISUAL_TRUECOLOR: case FB_VISUAL_DIRECTCOLOR: if (vinfo->grayscale != 0) { return DRM_FORMAT_INVALID; } break; default: return DRM_FORMAT_INVALID; } /* We only support formats with MSBs on the left */ if (vinfo->red.msb_right != 0 || vinfo->green.msb_right != 0 || vinfo->blue.msb_right != 0) { return DRM_FORMAT_INVALID; } /** * Work out the format type from the offsets. We only support RGBA,ARGB * and ABGR at the moment */ if (vinfo->bits_per_pixel == 16) { return DRM_FORMAT_RGB565; } else if (vinfo->bits_per_pixel == 24) { return DRM_FORMAT_RGB888; } else if ((vinfo->transp.offset >= vinfo->red.offset || vinfo->transp.length == 0) && vinfo->red.offset >= vinfo->green.offset && vinfo->green.offset >= vinfo->blue.offset) { return DRM_FORMAT_ARGB8888; } else if (vinfo->red.offset >= vinfo->green.offset && vinfo->green.offset >= vinfo->blue.offset && vinfo->blue.offset >= vinfo->transp.offset) { return DRM_FORMAT_RGBA8888; } else if (vinfo->transp.offset >= vinfo->blue.offset && vinfo->blue.offset >= vinfo->green.offset && vinfo->green.offset >= vinfo->red.offset) { return DRM_FORMAT_ABGR8888; } return DRM_FORMAT_INVALID; } static bool fbdev_wakeup_screen(int fd, struct fbdev_screeninfo *info) { struct fb_var_screeninfo varinfo; /* Grab the current screen information */ if (ioctl(fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { return false; } /* force the framebuffer to wake up */ varinfo.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; /* Set the device's screen information */ if (ioctl(fd, FBIOPUT_VSCREENINFO, &varinfo) < 0) { return false; } return true; } static uint32_t calculate_refresh_rate(struct fb_var_screeninfo *vinfo) { uint64_t quot; /* Calculate monitor refresh rate. Default is 60 Hz. Units are mHz */ quot = (vinfo->upper_margin + vinfo->lower_margin + vinfo->yres); quot *= (vinfo->left_margin + vinfo->right_margin + vinfo->xres); quot *= vinfo->pixclock; if (quot > 0) { uint64_t refresh_rate; refresh_rate = 1000000000000000LLU / quot; if (refresh_rate > 200000) { refresh_rate = 200000; /* cap at 200 Hz */ } if (refresh_rate >= 1000) { /* at least 1 Hz */ return refresh_rate; } } return 60 * 1000; /* default to 60 Hz */ } static bool fbdev_query_screen_info(int fd, struct fbdev_screeninfo *info) { struct fb_var_screeninfo varinfo; struct fb_fix_screeninfo fixinfo; /* Probe the device for screen information */ if (ioctl(fd, FBIOGET_FSCREENINFO, &fixinfo) < 0 || ioctl(fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { return false; } /* Store the pertinent data */ info->current = varinfo; info->x_resolution = varinfo.xres; info->y_resolution = varinfo.yres; info->width_mm = varinfo.width == 0xFFFFFFFF ? 0 : varinfo.width; info->height_mm = varinfo.height == 0xFFFFFFFF ? 0 : varinfo.height; info->bits_per_pixel = varinfo.bits_per_pixel; info->buffer_length = fixinfo.smem_len; info->line_length = fixinfo.line_length; snprintf(info->desc, sizeof(info->desc), "%s", fixinfo.id); info->refresh_rate = calculate_refresh_rate(&varinfo); info->pixel_format = calculate_drm_format(&varinfo, &fixinfo); if (info->pixel_format == DRM_FORMAT_INVALID) { kywc_log(KYWC_WARN, "Frame buffer uses an unsupported format"); return false; } return true; } static bool fbdev_dpms_set(struct fbdev_output *output, enum dpms_mode mode) { if (!output->dpms_mode_support) { return false; } kywc_log(KYWC_DEBUG, "Fbdev output dpms set"); unsigned long fbmode = mode == DPMS_MODE_ON ? 0 : 4; RETRY: if (ioctl(output->fd, FBIOBLANK, (void *)fbmode) == -1) { kywc_log_errno(KYWC_ERROR, "FBIOBLANK"); if (errno == EAGAIN) { return false; } if (errno == ERESTART || errno == EINTR) { goto RETRY; } output->dpms_mode_support = false; return false; } return true; } static int fbdev_frame_buffer_open(const char *fb_dev, struct fbdev_screeninfo *screen_info) { int fd = -1; fd = open(fb_dev, O_RDWR | O_CLOEXEC); if (fd < 0) { kywc_log_errno(KYWC_ERROR, "Failed to open frame buffer device %s", fb_dev); return -1; } /* Grab the screen info */ if (!fbdev_query_screen_info(fd, screen_info)) { kywc_log_errno(KYWC_ERROR, "Failed to get frame buffer info"); close(fd); return -1; } if (!fbdev_wakeup_screen(fd, screen_info)) { kywc_log(KYWC_ERROR, "Failed to activate framebuffer display. " "Attempting to open output anyway"); } return fd; } /* Closes the FD on success or failure */ static bool fbdev_frame_buffer_map(struct fbdev_output *output) { kywc_log(KYWC_INFO, "Mapping fbdev frame buffer"); /* Map the frame buffer. Write mode */ output->fb = mmap(NULL, output->screen_info.buffer_length, PROT_WRITE, MAP_SHARED, output->fd, 0); if (output->fb == MAP_FAILED) { kywc_log_errno(KYWC_ERROR, "Failed to mmap frame buffer"); output->fb = NULL; return false; } return true; } static void fbdev_frame_buffer_unmap(struct fbdev_output *output) { if (!output->fb) { return; } kywc_log(KYWC_INFO, "Unmapping fbdev frame buffer"); if (munmap(output->fb, output->screen_info.buffer_length) < 0) { kywc_log_errno(KYWC_ERROR, "Failed to munmap frame buffer"); } output->fb = NULL; } static bool fbdev_set_screen_info(int fd, struct fb_var_screeninfo *varinfo, bool test_only) { varinfo->activate = test_only ? FB_ACTIVATE_TEST : FB_ACTIVATE_FORCE; /* Set the device's screen information */ if (ioctl(fd, FBIOPUT_VSCREENINFO, varinfo) < 0) { kywc_log_errno(KYWC_ERROR, "Failed to FBIOPUT VSCREENINFO"); return false; } return true; } static bool fbdev_fix_screen_info(int fd, struct fbdev_screeninfo *info) { struct fb_var_screeninfo varinfo; /* Grab the current screen information */ if (ioctl(fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { kywc_log_errno(KYWC_ERROR, "Failed to FBIOGET VSCREENINFO"); return false; } /* Update the information. */ varinfo.xres = info->x_resolution; varinfo.yres = info->y_resolution; varinfo.width = info->width_mm; varinfo.height = info->height_mm; varinfo.bits_per_pixel = info->bits_per_pixel; /* Try to set up an ARGB (x8r8g8b8) pixel format */ varinfo.grayscale = 0; varinfo.transp.offset = 24; varinfo.transp.length = 0; varinfo.transp.msb_right = 0; varinfo.red.offset = 16; varinfo.red.length = 8; varinfo.red.msb_right = 0; varinfo.green.offset = 8; varinfo.green.length = 8; varinfo.green.msb_right = 0; varinfo.blue.offset = 0; varinfo.blue.length = 8; varinfo.blue.msb_right = 0; return fbdev_set_screen_info(fd, &varinfo, false); } static bool fbdev_output_disable(struct fbdev_output *output, bool clear_screen) { if (!output->fb || !output->frame_timer) { return true; } /* dpms off output */ if (clear_screen && !fbdev_dpms_set(output, DPMS_MODE_OFF)) { /* clear buffer */ memset(output->fb, 0x00, output->screen_info.buffer_length); } /* Unmap frame_buffer*/ fbdev_frame_buffer_unmap(output); wl_event_source_remove(output->frame_timer); output->frame_timer = NULL; kywc_log(KYWC_INFO, "Fbdev output disabled"); return true; } bool fbdev_output_offscreen(struct fbdev_output *output) { if (output->backend->session->active) { return false; } fbdev_output_disable(output, false); close(output->fd); return true; } static void output_destroy(struct wlr_output *wlr_output) { struct fbdev_output *output = fbdev_output_from_output(wlr_output); wl_list_remove(&output->link); fbdev_output_disable(output, false); close(output->fd); free((void *)output->device); free(output); } static int signal_frame_handler(void *data) { struct fbdev_output *output = data; wlr_output_send_frame(&output->wlr_output); return 0; } static bool fbdev_output_enable(struct fbdev_output *output) { /* dpms on output */ fbdev_dpms_set(output, DPMS_MODE_ON); /* Map frame_buffer*/ fbdev_frame_buffer_map(output); output->frame_timer = wl_event_loop_add_timer(output->wlr_output.event_loop, signal_frame_handler, output); kywc_log(KYWC_INFO, "Fbdev output enabled"); return true; } struct deferred_present_event { struct wlr_output *output; struct wl_event_source *idle_source; struct wlr_output_event_present event; struct wl_listener output_destroy; }; static bool output_pending_enabled(struct wlr_output *output, const struct wlr_output_state *state) { if (state->committed & WLR_OUTPUT_STATE_ENABLED) { return state->enabled; } return output->enabled; } static void deferred_present_event_destroy(struct deferred_present_event *deferred) { wl_list_remove(&deferred->output_destroy.link); free(deferred); } static void deferred_present_event_handle_idle(void *data) { struct deferred_present_event *deferred = data; wlr_output_send_present(deferred->output, &deferred->event); deferred_present_event_destroy(deferred); } static void deferred_present_event_handle_output_destroy(struct wl_listener *listener, void *data) { struct deferred_present_event *deferred = wl_container_of(listener, deferred, output_destroy); wl_event_source_remove(deferred->idle_source); deferred_present_event_destroy(deferred); } static void output_defer_present(struct wlr_output *output, struct wlr_output_event_present event) { struct deferred_present_event *deferred = calloc(1, sizeof(*deferred)); if (!deferred) { return; } *deferred = (struct deferred_present_event){ .output = output, .event = event, }; deferred->output_destroy.notify = deferred_present_event_handle_output_destroy; wl_signal_add(&output->events.destroy, &deferred->output_destroy); deferred->idle_source = wl_event_loop_add_idle(output->event_loop, deferred_present_event_handle_idle, deferred); } static bool fbdev_output_state_update_fb(struct fbdev_output *output, const struct wlr_output_state *state) { if (!output->fb) { return false; } void *data; uint32_t format; size_t stride; if (!wlr_buffer_begin_data_ptr_access(state->buffer, WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { return false; } assert(output->screen_info.x_resolution * output->screen_info.bits_per_pixel / 8 == stride); pixman_region32_t clipped; pixman_region32_init_rect(&clipped, 0, 0, output->screen_info.x_resolution, output->screen_info.y_resolution); if (state->committed & WLR_OUTPUT_STATE_DAMAGE) { pixman_region32_intersect_rect(&clipped, &state->damage, 0, 0, state->buffer->width, state->buffer->height); } /* Copy shadow fb */ uint32_t padding = output->screen_info.line_length - stride; uint32_t pixel_bytes = output->screen_info.bits_per_pixel / 8; uint32_t offset, size, offset_src, offset_dst; uint8_t *src, *dst; int rects_len; const pixman_box32_t *rects = pixman_region32_rectangles(&clipped, &rects_len); for (int i = 0; i < rects_len; ++i) { if (rects[i].x2 - rects[i].x1 == state->buffer->width && padding == 0) { offset = stride * rects[i].y1; src = (uint8_t *)data + offset; dst = (uint8_t *)output->fb + offset; size = (rects[i].y2 - rects[i].y1) * stride; memcpy(dst, src, size); continue; } for (int32_t y = rects[i].y1; y < rects[i].y2; ++y) { offset_src = y * stride + rects[i].x1 * pixel_bytes; src = (uint8_t *)data + offset_src; offset_dst = y * (stride + padding) + rects[i].x1 * pixel_bytes; dst = (uint8_t *)output->fb + offset_dst; size = (rects[i].x2 - rects[i].x1) * pixel_bytes; memcpy(dst, src, size); } } pixman_region32_fini(&clipped); wlr_buffer_end_data_ptr_access(state->buffer); return true; } static void fbdev_output_update_refresh(struct fbdev_output *output, int32_t refresh) { if (refresh <= 0) { refresh = 60 * 1000; // 60 HZ } output->frame_delay = 1000000 / refresh; } static bool fbdev_modes_equal(struct fb_var_screeninfo *set, struct fb_var_screeninfo *req) { return (set->xres_virtual >= req->xres_virtual && set->yres_virtual >= req->yres_virtual && set->bits_per_pixel == req->bits_per_pixel && set->red.length == req->red.length && set->green.length == req->green.length && set->blue.length == req->blue.length && set->xres == req->xres && set->yres == req->yres && set->right_margin == req->right_margin && set->hsync_len == req->hsync_len && set->left_margin == req->left_margin && set->lower_margin == req->lower_margin && set->vsync_len == req->vsync_len && set->upper_margin == req->upper_margin && set->sync == req->sync && set->vmode == req->vmode); } static bool fbdev_output_test(struct wlr_output *wlr_output, struct fbdev_state *state) { struct fbdev_output *output = fbdev_output_from_output(wlr_output); if (!output->backend->session->active) { return false; } uint32_t unsupported = state->base->committed & ~SUPPORTED_OUTPUT_STATE; if (unsupported != 0) { kywc_log(KYWC_DEBUG, "Unsupported output state fields: 0x%" PRIx32, unsupported); return false; } if ((state->base->committed & COMMIT_OUTPUT_STATE) == 0) { // This commit doesn't change the fbdev state return true; }; if (state->base->committed & WLR_OUTPUT_STATE_MODE) { struct fbdev_mode *mode = wl_container_of(state->base->mode, mode, wlr_mode); state->mode_info = mode->mode_info; return state->base->mode_type != WLR_OUTPUT_STATE_MODE_CUSTOM; } return true; } static bool fbdev_output_commit(struct wlr_output *wlr_output, const struct wlr_output_state *state) { struct fbdev_output *output = fbdev_output_from_output(wlr_output); if (!output->backend->session->active) { return false; } struct fbdev_state pending = { .base = state }; if (!fbdev_output_test(wlr_output, &pending)) { kywc_log(KYWC_ERROR, "Fbdev output test failed"); return false; } if (pending.base->committed & WLR_OUTPUT_STATE_MODE) { if (!fbdev_modes_equal(&pending.mode_info, &output->screen_info.current) && !fbdev_set_screen_info(output->fd, &pending.mode_info, false)) { return false; } fbdev_query_screen_info(output->fd, &output->screen_info); fbdev_output_update_refresh(output, pending.base->mode->refresh); wlr_output->current_mode = pending.base->mode; } if (pending.base->committed & WLR_OUTPUT_STATE_ENABLED) { if (pending.base->enabled && !wlr_output->enabled) { fbdev_output_enable(output); } else if (!pending.base->enabled && wlr_output->enabled) { return fbdev_output_disable(output, true); } } if (pending.base->committed & WLR_OUTPUT_STATE_BUFFER && output->wlr_output.enabled) { fbdev_output_state_update_fb(output, pending.base); } if (output_pending_enabled(wlr_output, pending.base)) { struct wlr_output_event_present present_event = { .commit_seq = wlr_output->commit_seq + 1, .presented = true, }; output_defer_present(wlr_output, present_event); wl_event_source_timer_update(output->frame_timer, output->frame_delay); } return true; } static const struct wlr_output_impl output_impl = { .destroy = output_destroy, .commit = fbdev_output_commit, }; struct wlr_output *fbdev_output_create(struct wlr_backend *wlr_backend, const char *device, int index) { struct fbdev_output *output = calloc(1, sizeof(*output)); /* Create the frame buffer */ int fd = fbdev_frame_buffer_open(device, &output->screen_info); if (fd < 0) { kywc_log(KYWC_ERROR, "Creating frame buffer output failed"); free(output); return NULL; } output->fd = fd; output->dpms_mode_support = true; struct wlr_output_state state; wlr_output_state_init(&state); struct fbdev_backend *backend = get_fbdev_backend_from_backend(wlr_backend); wlr_output_init(&output->wlr_output, wlr_backend, &output_impl, backend->event_loop, &state); wlr_output_state_finish(&state); char name[8]; snprintf(name, 8, "FB-%d", index); wlr_output_set_name(&output->wlr_output, name); wlr_output_set_description(&output->wlr_output, output->screen_info.desc); kywc_log(KYWC_INFO, "Fbdev desc: %s", output->screen_info.desc); kywc_log(KYWC_INFO, "Fbdev output render format: 0x%" PRIX32, output->screen_info.pixel_format); wlr_output_lock_software_cursors(&output->wlr_output, true); output->wlr_output.phys_width = output->screen_info.width_mm; output->wlr_output.phys_height = output->screen_info.height_mm; kywc_log(KYWC_DEBUG, "Screen physic_size: %" PRId32 "x%" PRId32 " (mm)", output->screen_info.width_mm, output->screen_info.height_mm); /* only one static builtin mode in list */ struct fbdev_mode *mode = &output->mode; mode->wlr_mode.width = output->screen_info.x_resolution; mode->wlr_mode.height = output->screen_info.y_resolution; mode->wlr_mode.refresh = output->screen_info.refresh_rate; mode->mode_info = output->screen_info.current; wl_list_insert(&output->wlr_output.modes, &mode->wlr_mode.link); kywc_log(KYWC_INFO, "Detected modes:"); kywc_log(KYWC_INFO, " %" PRId32 "x%" PRId32 " @ %.3f Hz", mode->wlr_mode.width, mode->wlr_mode.height, (float)mode->wlr_mode.refresh / 1000); output->device = strdup(device); output->backend = backend; wl_list_insert(&backend->outputs, &output->link); return &output->wlr_output; } bool wlr_output_is_fbdev(struct wlr_output *wlr_output) { return wlr_output->impl == &output_impl; } bool fbdev_output_reenable(struct fbdev_output *output) { kywc_log(KYWC_INFO, "Re-enabling fbdev output"); struct fbdev_screeninfo new_screen_info; int fd = fbdev_frame_buffer_open(output->device, &new_screen_info); if (fd < 0) { kywc_log(KYWC_ERROR, "Creating frame buffer failed"); return false; } output->fd = fd; output->dpms_mode_support = true; /* Check whether the frame buffer details have changed since we were disable */ if (output->screen_info.x_resolution != new_screen_info.x_resolution || output->screen_info.y_resolution != new_screen_info.y_resolution || output->screen_info.width_mm != new_screen_info.width_mm || output->screen_info.height_mm != new_screen_info.height_mm || output->screen_info.bits_per_pixel != new_screen_info.bits_per_pixel || output->screen_info.pixel_format != new_screen_info.pixel_format || output->screen_info.refresh_rate != new_screen_info.refresh_rate) { /* Perform a mode-set to restore the old mode */ if (!fbdev_fix_screen_info(output->fd, &output->screen_info)) { kywc_log(KYWC_ERROR, "Failed to restore mode settings. " "Attempting to re-open output anyway"); } } fbdev_output_enable(output); /* refresh the frame immediately */ signal_frame_handler(output); return true; } kylin-wayland-compositor/src/backend/fbdev/fbdev_p.h0000664000175000017500000000373515160460057021515 0ustar fengfeng// SPDX-FileCopyrightText: 2024 The wlroots contributors // SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _FBDEV_H_ #define _FBDEV_H_ #include #include #include struct fbdev_backend { struct wlr_backend backend; struct wl_event_loop *event_loop; struct wlr_session *session; struct wl_listener session_destroy; struct wl_listener session_active; struct wl_list outputs; }; struct fbdev_mode { struct wlr_output_mode wlr_mode; struct fb_var_screeninfo mode_info; }; struct fbdev_screeninfo { uint32_t x_resolution; /* pixels, visible area */ uint32_t y_resolution; /* pixels, visible area */ uint32_t width_mm; /* visible screen width in mm */ uint32_t height_mm; /* visible screen height in mm */ uint32_t bits_per_pixel; uint32_t pixel_format; /* frame buffer pixel format */ uint32_t refresh_rate; /* Hertz */ size_t buffer_length; /* length of frame buffer memory in bytes */ size_t line_length; /* length of a line in bytes */ struct fb_var_screeninfo current; char desc[16]; /* screen identifier */ }; struct fbdev_output { struct wlr_output wlr_output; struct fbdev_backend *backend; struct wl_list link; struct fbdev_mode mode; // builtin mode struct wl_event_source *frame_timer; int frame_delay; // ms struct fbdev_screeninfo screen_info; const char *device; int fd; bool dpms_mode_support; void *fb; // map }; struct wlr_output *fbdev_output_create(struct wlr_backend *wlr_backend, const char *device, int index); struct fbdev_output *fbdev_output_from_output(struct wlr_output *wlr_output); struct fbdev_backend *get_fbdev_backend_from_backend(struct wlr_backend *wlr_backend); bool fbdev_output_reenable(struct fbdev_output *output); bool fbdev_output_offscreen(struct fbdev_output *output); #endif kylin-wayland-compositor/src/backend/fbdev/backend.c0000664000175000017500000000732315160461067021471 0ustar fengfeng// SPDX-FileCopyrightText: 2024 The wlroots contributors // SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include "fbdev_p.h" struct fbdev_backend *get_fbdev_backend_from_backend(struct wlr_backend *wlr_backend) { assert(wlr_backend_is_fbdev(wlr_backend)); struct fbdev_backend *backend = wl_container_of(wlr_backend, backend, backend); return backend; } static bool backend_start(struct wlr_backend *wlr_backend) { struct fbdev_backend *backend = get_fbdev_backend_from_backend(wlr_backend); struct fbdev_output *output; wl_list_for_each(output, &backend->outputs, link) { wl_signal_emit_mutable(&backend->backend.events.new_output, &output->wlr_output); } return true; } static void backend_destroy(struct wlr_backend *wlr_backend) { if (!wlr_backend) { return; } struct fbdev_backend *backend = get_fbdev_backend_from_backend(wlr_backend); if (!backend) { return; } wlr_backend_finish(wlr_backend); struct fbdev_output *output, *output_tmp; wl_list_for_each_safe(output, output_tmp, &backend->outputs, link) { wlr_output_destroy(&output->wlr_output); } wl_list_remove(&backend->session_destroy.link); wl_list_remove(&backend->session_active.link); free(backend); } static const struct wlr_backend_impl backend_impl = { .start = backend_start, .destroy = backend_destroy, }; static void fbdev_output_damage_whole(struct fbdev_output *output) { int width, height; struct wlr_output *wlr_output = &output->wlr_output; wlr_output_transformed_resolution(wlr_output, &width, &height); pixman_region32_t damage; pixman_region32_init_rect(&damage, 0, 0, width, height); struct wlr_output_event_damage event = { .output = wlr_output, .damage = &damage, }; wl_signal_emit_mutable(&wlr_output->events.damage, &event); pixman_region32_fini(&damage); } static void handle_session_active(struct wl_listener *listener, void *data) { struct fbdev_backend *backend = wl_container_of(listener, backend, session_active); struct wlr_session *session = backend->session; struct fbdev_output *output; wl_list_for_each(output, &backend->outputs, link) { if (session->active) { fbdev_output_reenable(output); fbdev_output_damage_whole(output); } else { fbdev_output_offscreen(output); } } } static void handle_session_destroy(struct wl_listener *listener, void *data) { struct fbdev_backend *backend = wl_container_of(listener, backend, session_destroy); backend_destroy(&backend->backend); } bool wlr_backend_is_fbdev(struct wlr_backend *backend) { return backend->impl == &backend_impl; } struct wlr_backend *fbdev_backend_create(struct wlr_session *session, const char **devices, int devices_len) { struct fbdev_backend *backend = calloc(1, sizeof(*backend)); if (!backend) { kywc_log(KYWC_ERROR, "Failed to allocate fbdev_backend"); return NULL; } wlr_backend_init(&backend->backend, &backend_impl); backend->session = session; wl_list_init(&backend->outputs); for (int i = 0; i < devices_len; i++) { fbdev_output_create(&backend->backend, devices[i], i); } backend->session_active.notify = handle_session_active; wl_signal_add(&session->events.active, &backend->session_active); backend->session_destroy.notify = handle_session_destroy; wl_signal_add(&session->events.destroy, &backend->session_destroy); return &backend->backend; } kylin-wayland-compositor/src/backend/libinput/0000775000175000017500000000000015160461067020471 5ustar fengfengkylin-wayland-compositor/src/backend/libinput/meson.build0000664000175000017500000000022615160461067022633 0ustar fengfengwlcom_sources += files( 'backend.c', 'events.c', 'keyboard.c', 'pointer.c', 'switch.c', 'tablet_pad.c', 'tablet_tool.c', 'touch.c', ) kylin-wayland-compositor/src/backend/libinput/tablet_pad.c0000664000175000017500000001640015160461067022735 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include "libinput_p.h" #include "util/time.h" const struct wlr_tablet_pad_impl libinput_tablet_pad_impl = { .name = "libinput-tablet-pad", }; static void group_destroy(struct wlr_tablet_pad_group *group) { free(group->buttons); free(group->strips); free(group->rings); free(group); } static void add_pad_group_from_libinput(struct wlr_tablet_pad *pad, struct libinput_device *device, unsigned int index) { struct libinput_tablet_pad_mode_group *li_group = libinput_device_tablet_pad_get_mode_group(device, index); struct wlr_tablet_pad_group *group = calloc(1, sizeof(*group)); if (!group) { kywc_log_errno(KYWC_ERROR, "failed to allocate wlr_tablet_pad_group"); return; } for (size_t i = 0; i < pad->ring_count; ++i) { if (libinput_tablet_pad_mode_group_has_ring(li_group, i)) { ++group->ring_count; } } group->rings = calloc(group->ring_count, sizeof(unsigned int)); if (!group->rings) { goto group_fail; } size_t ring = 0; for (size_t i = 0; i < pad->ring_count; ++i) { if (libinput_tablet_pad_mode_group_has_ring(li_group, i)) { group->rings[ring++] = i; } } for (size_t i = 0; i < pad->strip_count; ++i) { if (libinput_tablet_pad_mode_group_has_strip(li_group, i)) { ++group->strip_count; } } group->strips = calloc(group->strip_count, sizeof(unsigned int)); if (!group->strips) { goto group_fail; } size_t strip = 0; for (size_t i = 0; i < pad->strip_count; ++i) { if (libinput_tablet_pad_mode_group_has_strip(li_group, i)) { group->strips[strip++] = i; } } for (size_t i = 0; i < pad->button_count; ++i) { if (libinput_tablet_pad_mode_group_has_button(li_group, i)) { ++group->button_count; } } group->buttons = calloc(group->button_count, sizeof(unsigned int)); if (!group->buttons) { goto group_fail; } size_t button = 0; for (size_t i = 0; i < pad->button_count; ++i) { if (libinput_tablet_pad_mode_group_has_button(li_group, i)) { group->buttons[button++] = i; } } group->mode_count = libinput_tablet_pad_mode_group_get_num_modes(li_group); libinput_tablet_pad_mode_group_ref(li_group); wl_list_insert(&pad->groups, &group->link); return; group_fail: kywc_log(KYWC_ERROR, "failed to configure wlr_tablet_pad_group"); group_destroy(group); } void init_device_tablet_pad(struct libinput_input_device *dev) { struct libinput_device *handle = dev->handle; const char *name = libinput_device_get_name(handle); struct wlr_tablet_pad *wlr_tablet_pad = &dev->tablet_pad; wlr_tablet_pad_init(wlr_tablet_pad, &libinput_tablet_pad_impl, name); wlr_tablet_pad->button_count = libinput_device_tablet_pad_get_num_buttons(handle); wlr_tablet_pad->ring_count = libinput_device_tablet_pad_get_num_rings(handle); wlr_tablet_pad->strip_count = libinput_device_tablet_pad_get_num_strips(handle); struct udev_device *udev = libinput_device_get_udev_device(handle); char **dst = wl_array_add(&wlr_tablet_pad->paths, sizeof(char *)); *dst = strdup(udev_device_get_syspath(udev)); udev_device_unref(udev); int groups = libinput_device_tablet_pad_get_num_mode_groups(handle); for (int i = 0; i < groups; ++i) { add_pad_group_from_libinput(wlr_tablet_pad, handle, i); } } void finish_device_tablet_pad(struct libinput_input_device *dev) { struct wlr_tablet_pad_group *group, *tmp; wl_list_for_each_safe(group, tmp, &dev->tablet_pad.groups, link) { group_destroy(group); } wlr_tablet_pad_finish(&dev->tablet_pad); int groups = libinput_device_tablet_pad_get_num_mode_groups(dev->handle); for (int i = 0; i < groups; ++i) { struct libinput_tablet_pad_mode_group *li_group = libinput_device_tablet_pad_get_mode_group(dev->handle, i); libinput_tablet_pad_mode_group_unref(li_group); } } struct libinput_input_device *device_from_tablet_pad(struct wlr_tablet_pad *wlr_tablet_pad) { assert(wlr_tablet_pad->impl == &libinput_tablet_pad_impl); struct libinput_input_device *dev = wl_container_of(wlr_tablet_pad, dev, tablet_pad); return dev; } void handle_tablet_pad_button(struct libinput_event *event, struct wlr_tablet_pad *tablet_pad) { struct libinput_event_tablet_pad *pevent = libinput_event_get_tablet_pad_event(event); struct wlr_tablet_pad_button_event wlr_event = { .time_msec = usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)), .button = libinput_event_tablet_pad_get_button_number(pevent), .mode = libinput_event_tablet_pad_get_mode(pevent), .group = libinput_tablet_pad_mode_group_get_index( libinput_event_tablet_pad_get_mode_group(pevent)), }; switch (libinput_event_tablet_pad_get_button_state(pevent)) { case LIBINPUT_BUTTON_STATE_PRESSED: wlr_event.state = WLR_BUTTON_PRESSED; break; case LIBINPUT_BUTTON_STATE_RELEASED: wlr_event.state = WLR_BUTTON_RELEASED; break; } wl_signal_emit_mutable(&tablet_pad->events.button, &wlr_event); } void handle_tablet_pad_ring(struct libinput_event *event, struct wlr_tablet_pad *tablet_pad) { struct libinput_event_tablet_pad *pevent = libinput_event_get_tablet_pad_event(event); struct wlr_tablet_pad_ring_event wlr_event = { .time_msec = usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)), .ring = libinput_event_tablet_pad_get_ring_number(pevent), .position = libinput_event_tablet_pad_get_ring_position(pevent), .mode = libinput_event_tablet_pad_get_mode(pevent), }; switch (libinput_event_tablet_pad_get_ring_source(pevent)) { case LIBINPUT_TABLET_PAD_RING_SOURCE_UNKNOWN: wlr_event.source = WLR_TABLET_PAD_RING_SOURCE_UNKNOWN; break; case LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER: wlr_event.source = WLR_TABLET_PAD_RING_SOURCE_FINGER; break; } wl_signal_emit_mutable(&tablet_pad->events.ring, &wlr_event); } void handle_tablet_pad_strip(struct libinput_event *event, struct wlr_tablet_pad *tablet_pad) { struct libinput_event_tablet_pad *pevent = libinput_event_get_tablet_pad_event(event); struct wlr_tablet_pad_strip_event wlr_event = { .time_msec = usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)), .strip = libinput_event_tablet_pad_get_strip_number(pevent), .position = libinput_event_tablet_pad_get_strip_position(pevent), .mode = libinput_event_tablet_pad_get_mode(pevent), }; switch (libinput_event_tablet_pad_get_strip_source(pevent)) { case LIBINPUT_TABLET_PAD_STRIP_SOURCE_UNKNOWN: wlr_event.source = WLR_TABLET_PAD_STRIP_SOURCE_UNKNOWN; break; case LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER: wlr_event.source = WLR_TABLET_PAD_STRIP_SOURCE_FINGER; break; } wl_signal_emit_mutable(&tablet_pad->events.strip, &wlr_event); } kylin-wayland-compositor/src/backend/libinput/switch.c0000664000175000017500000000327515160461067022145 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "libinput_p.h" #include "util/time.h" const struct wlr_switch_impl libinput_switch_impl = { .name = "libinput-switch", }; void init_device_switch(struct libinput_input_device *dev) { const char *name = libinput_device_get_name(dev->handle); struct wlr_switch *wlr_switch = &dev->switch_device; wlr_switch_init(wlr_switch, &libinput_switch_impl, name); } struct libinput_input_device *device_from_switch(struct wlr_switch *wlr_switch) { assert(wlr_switch->impl == &libinput_switch_impl); struct libinput_input_device *dev = wl_container_of(wlr_switch, dev, switch_device); return dev; } void handle_switch_toggle(struct libinput_event *event, struct wlr_switch *wlr_switch) { struct libinput_event_switch *sevent = libinput_event_get_switch_event(event); struct wlr_switch_toggle_event wlr_event = { .time_msec = usec_to_msec(libinput_event_switch_get_time_usec(sevent)), }; switch (libinput_event_switch_get_switch(sevent)) { case LIBINPUT_SWITCH_LID: wlr_event.switch_type = WLR_SWITCH_TYPE_LID; break; case LIBINPUT_SWITCH_TABLET_MODE: wlr_event.switch_type = WLR_SWITCH_TYPE_TABLET_MODE; break; } switch (libinput_event_switch_get_switch_state(sevent)) { case LIBINPUT_SWITCH_STATE_OFF: wlr_event.switch_state = WLR_SWITCH_STATE_OFF; break; case LIBINPUT_SWITCH_STATE_ON: wlr_event.switch_state = WLR_SWITCH_STATE_ON; break; } wl_signal_emit_mutable(&wlr_switch->events.toggle, &wlr_event); } kylin-wayland-compositor/src/backend/libinput/tablet_tool.c0000664000175000017500000002465215160461067023156 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include "libinput_p.h" #include "util/time.h" struct tablet_tool { struct wlr_tablet_tool wlr_tool; struct libinput_tablet_tool *handle; struct wl_list link; // libinput_input_device.tablet_tools }; const struct wlr_tablet_impl libinput_tablet_impl = { .name = "libinput-tablet-tool", }; void init_device_tablet(struct libinput_input_device *dev) { const char *name = libinput_device_get_name(dev->handle); struct wlr_tablet *wlr_tablet = &dev->tablet; wlr_tablet_init(wlr_tablet, &libinput_tablet_impl, name); libinput_device_get_size(dev->handle, &wlr_tablet->width_mm, &wlr_tablet->height_mm); struct udev_device *udev = libinput_device_get_udev_device(dev->handle); char **dst = wl_array_add(&wlr_tablet->paths, sizeof(char *)); *dst = strdup(udev_device_get_syspath(udev)); udev_device_unref(udev); wl_list_init(&dev->tablet_tools); } static void tool_destroy(struct tablet_tool *tool) { wl_signal_emit_mutable(&tool->wlr_tool.events.destroy, &tool->wlr_tool); libinput_tablet_tool_unref(tool->handle); libinput_tablet_tool_set_user_data(tool->handle, NULL); wl_list_remove(&tool->link); free(tool); } void finish_device_tablet(struct libinput_input_device *dev) { struct tablet_tool *tool, *tmp; wl_list_for_each_safe(tool, tmp, &dev->tablet_tools, link) { tool_destroy(tool); } wlr_tablet_finish(&dev->tablet); } struct libinput_input_device *device_from_tablet(struct wlr_tablet *wlr_tablet) { assert(wlr_tablet->impl == &libinput_tablet_impl); struct libinput_input_device *dev = wl_container_of(wlr_tablet, dev, tablet); return dev; } static enum wlr_tablet_tool_type wlr_type_from_libinput_type(enum libinput_tablet_tool_type value) { switch (value) { case LIBINPUT_TABLET_TOOL_TYPE_PEN: return WLR_TABLET_TOOL_TYPE_PEN; case LIBINPUT_TABLET_TOOL_TYPE_ERASER: return WLR_TABLET_TOOL_TYPE_ERASER; case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: return WLR_TABLET_TOOL_TYPE_BRUSH; case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: return WLR_TABLET_TOOL_TYPE_PENCIL; case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: return WLR_TABLET_TOOL_TYPE_AIRBRUSH; case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: return WLR_TABLET_TOOL_TYPE_MOUSE; case LIBINPUT_TABLET_TOOL_TYPE_LENS: return WLR_TABLET_TOOL_TYPE_LENS; case LIBINPUT_TABLET_TOOL_TYPE_TOTEM: return WLR_TABLET_TOOL_TYPE_TOTEM; } abort(); // unreachable } static struct tablet_tool *get_tablet_tool(struct libinput_input_device *dev, struct libinput_tablet_tool *libinput_tool) { struct tablet_tool *tool = libinput_tablet_tool_get_user_data(libinput_tool); if (tool) { return tool; } tool = calloc(1, sizeof(*tool)); if (!tool) { kywc_log_errno(KYWC_ERROR, "failed to allocate libinput_tablet_tool"); return NULL; } tool->wlr_tool.type = wlr_type_from_libinput_type(libinput_tablet_tool_get_type(libinput_tool)); tool->wlr_tool.hardware_serial = libinput_tablet_tool_get_serial(libinput_tool); tool->wlr_tool.hardware_wacom = libinput_tablet_tool_get_tool_id(libinput_tool); tool->wlr_tool.pressure = libinput_tablet_tool_has_pressure(libinput_tool); tool->wlr_tool.distance = libinput_tablet_tool_has_distance(libinput_tool); tool->wlr_tool.tilt = libinput_tablet_tool_has_tilt(libinput_tool); tool->wlr_tool.rotation = libinput_tablet_tool_has_rotation(libinput_tool); tool->wlr_tool.slider = libinput_tablet_tool_has_slider(libinput_tool); tool->wlr_tool.wheel = libinput_tablet_tool_has_wheel(libinput_tool); wl_signal_init(&tool->wlr_tool.events.destroy); tool->handle = libinput_tablet_tool_ref(libinput_tool); libinput_tablet_tool_set_user_data(libinput_tool, tool); wl_list_insert(&dev->tablet_tools, &tool->link); return tool; } void handle_tablet_tool_axis(struct libinput_event *event, struct wlr_tablet *wlr_tablet) { struct libinput_event_tablet_tool *tevent = libinput_event_get_tablet_tool_event(event); struct libinput_input_device *dev = device_from_tablet(wlr_tablet); struct tablet_tool *tool = get_tablet_tool(dev, libinput_event_tablet_tool_get_tool(tevent)); struct wlr_tablet_tool_axis_event wlr_event = { .tablet = wlr_tablet, .tool = &tool->wlr_tool, .time_msec = usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)), }; if (libinput_event_tablet_tool_x_has_changed(tevent)) { wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_X; wlr_event.x = libinput_event_tablet_tool_get_x_transformed(tevent, 1); wlr_event.dx = libinput_event_tablet_tool_get_dx(tevent); } if (libinput_event_tablet_tool_y_has_changed(tevent)) { wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_Y; wlr_event.y = libinput_event_tablet_tool_get_y_transformed(tevent, 1); wlr_event.dy = libinput_event_tablet_tool_get_dy(tevent); } if (libinput_event_tablet_tool_pressure_has_changed(tevent)) { wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_PRESSURE; wlr_event.pressure = libinput_event_tablet_tool_get_pressure(tevent); } if (libinput_event_tablet_tool_distance_has_changed(tevent)) { wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_DISTANCE; wlr_event.distance = libinput_event_tablet_tool_get_distance(tevent); } if (libinput_event_tablet_tool_tilt_x_has_changed(tevent)) { wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_TILT_X; wlr_event.tilt_x = libinput_event_tablet_tool_get_tilt_x(tevent); } if (libinput_event_tablet_tool_tilt_y_has_changed(tevent)) { wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_TILT_Y; wlr_event.tilt_y = libinput_event_tablet_tool_get_tilt_y(tevent); } if (libinput_event_tablet_tool_rotation_has_changed(tevent)) { wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_ROTATION; wlr_event.rotation = libinput_event_tablet_tool_get_rotation(tevent); } if (libinput_event_tablet_tool_slider_has_changed(tevent)) { wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_SLIDER; wlr_event.slider = libinput_event_tablet_tool_get_slider_position(tevent); } if (libinput_event_tablet_tool_wheel_has_changed(tevent)) { wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_WHEEL; wlr_event.wheel_delta = libinput_event_tablet_tool_get_wheel_delta(tevent); } wl_signal_emit_mutable(&wlr_tablet->events.axis, &wlr_event); } void handle_tablet_tool_proximity(struct libinput_event *event, struct wlr_tablet *wlr_tablet) { struct libinput_event_tablet_tool *tevent = libinput_event_get_tablet_tool_event(event); struct libinput_input_device *dev = device_from_tablet(wlr_tablet); struct tablet_tool *tool = get_tablet_tool(dev, libinput_event_tablet_tool_get_tool(tevent)); struct wlr_tablet_tool_proximity_event wlr_event = { .tablet = wlr_tablet, .tool = &tool->wlr_tool, .time_msec = usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)), .x = libinput_event_tablet_tool_get_x_transformed(tevent, 1), .y = libinput_event_tablet_tool_get_y_transformed(tevent, 1), }; switch (libinput_event_tablet_tool_get_proximity_state(tevent)) { case LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT: wlr_event.state = WLR_TABLET_TOOL_PROXIMITY_OUT; break; case LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN: wlr_event.state = WLR_TABLET_TOOL_PROXIMITY_IN; break; } wl_signal_emit_mutable(&wlr_tablet->events.proximity, &wlr_event); if (libinput_event_tablet_tool_get_proximity_state(tevent) == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN) { handle_tablet_tool_axis(event, wlr_tablet); } // If the tool is not unique, libinput will not find it again after the // proximity out, so we should destroy it if (!libinput_tablet_tool_is_unique(tool->handle) && libinput_event_tablet_tool_get_proximity_state(tevent) == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT) { // The tool isn't unique, it can't be on multiple tablets tool_destroy(tool); } } void handle_tablet_tool_tip(struct libinput_event *event, struct wlr_tablet *wlr_tablet) { handle_tablet_tool_axis(event, wlr_tablet); struct libinput_event_tablet_tool *tevent = libinput_event_get_tablet_tool_event(event); struct libinput_input_device *dev = device_from_tablet(wlr_tablet); struct tablet_tool *tool = get_tablet_tool(dev, libinput_event_tablet_tool_get_tool(tevent)); struct wlr_tablet_tool_tip_event wlr_event = { .tablet = wlr_tablet, .tool = &tool->wlr_tool, .time_msec = usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)), .x = libinput_event_tablet_tool_get_x_transformed(tevent, 1), .y = libinput_event_tablet_tool_get_y_transformed(tevent, 1), }; switch (libinput_event_tablet_tool_get_tip_state(tevent)) { case LIBINPUT_TABLET_TOOL_TIP_UP: wlr_event.state = WLR_TABLET_TOOL_TIP_UP; break; case LIBINPUT_TABLET_TOOL_TIP_DOWN: wlr_event.state = WLR_TABLET_TOOL_TIP_DOWN; break; } wl_signal_emit_mutable(&wlr_tablet->events.tip, &wlr_event); } void handle_tablet_tool_button(struct libinput_event *event, struct wlr_tablet *wlr_tablet) { handle_tablet_tool_axis(event, wlr_tablet); struct libinput_event_tablet_tool *tevent = libinput_event_get_tablet_tool_event(event); struct libinput_input_device *dev = device_from_tablet(wlr_tablet); struct tablet_tool *tool = get_tablet_tool(dev, libinput_event_tablet_tool_get_tool(tevent)); struct wlr_tablet_tool_button_event wlr_event = { .tablet = wlr_tablet, .tool = &tool->wlr_tool, .time_msec = usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)), .button = libinput_event_tablet_tool_get_button(tevent), }; switch (libinput_event_tablet_tool_get_button_state(tevent)) { case LIBINPUT_BUTTON_STATE_RELEASED: wlr_event.state = WLR_BUTTON_RELEASED; break; case LIBINPUT_BUTTON_STATE_PRESSED: wlr_event.state = WLR_BUTTON_PRESSED; break; } wl_signal_emit_mutable(&wlr_tablet->events.button, &wlr_event); } kylin-wayland-compositor/src/backend/libinput/touch.c0000664000175000017500000000576115160461067021770 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "libinput_p.h" #include "util/time.h" const struct wlr_touch_impl libinput_touch_impl = { .name = "libinput-touch", }; void init_device_touch(struct libinput_input_device *dev) { const char *name = libinput_device_get_name(dev->handle); struct wlr_touch *wlr_touch = &dev->touch; wlr_touch_init(wlr_touch, &libinput_touch_impl, name); libinput_device_get_size(dev->handle, &wlr_touch->width_mm, &wlr_touch->height_mm); } struct libinput_input_device *device_from_touch(struct wlr_touch *wlr_touch) { assert(wlr_touch->impl == &libinput_touch_impl); struct libinput_input_device *dev = wl_container_of(wlr_touch, dev, touch); return dev; } void handle_touch_down(struct libinput_event *event, struct wlr_touch *touch) { struct libinput_event_touch *tevent = libinput_event_get_touch_event(event); struct wlr_touch_down_event wlr_event = { 0 }; wlr_event.touch = touch; wlr_event.time_msec = usec_to_msec(libinput_event_touch_get_time_usec(tevent)); wlr_event.touch_id = libinput_event_touch_get_seat_slot(tevent); wlr_event.x = libinput_event_touch_get_x_transformed(tevent, 1); wlr_event.y = libinput_event_touch_get_y_transformed(tevent, 1); wl_signal_emit_mutable(&touch->events.down, &wlr_event); } void handle_touch_up(struct libinput_event *event, struct wlr_touch *touch) { struct libinput_event_touch *tevent = libinput_event_get_touch_event(event); struct wlr_touch_up_event wlr_event = { .touch = touch, .time_msec = usec_to_msec(libinput_event_touch_get_time_usec(tevent)), .touch_id = libinput_event_touch_get_seat_slot(tevent), }; wl_signal_emit_mutable(&touch->events.up, &wlr_event); } void handle_touch_motion(struct libinput_event *event, struct wlr_touch *touch) { struct libinput_event_touch *tevent = libinput_event_get_touch_event(event); struct wlr_touch_motion_event wlr_event = { .touch = touch, .time_msec = usec_to_msec(libinput_event_touch_get_time_usec(tevent)), .touch_id = libinput_event_touch_get_seat_slot(tevent), .x = libinput_event_touch_get_x_transformed(tevent, 1), .y = libinput_event_touch_get_y_transformed(tevent, 1), }; wl_signal_emit_mutable(&touch->events.motion, &wlr_event); } void handle_touch_cancel(struct libinput_event *event, struct wlr_touch *touch) { struct libinput_event_touch *tevent = libinput_event_get_touch_event(event); struct wlr_touch_cancel_event wlr_event = { .touch = touch, .time_msec = usec_to_msec(libinput_event_touch_get_time_usec(tevent)), .touch_id = libinput_event_touch_get_seat_slot(tevent), }; wl_signal_emit_mutable(&touch->events.cancel, &wlr_event); } void handle_touch_frame(struct libinput_event *event, struct wlr_touch *touch) { wl_signal_emit_mutable(&touch->events.frame, NULL); } kylin-wayland-compositor/src/backend/libinput/events.c0000664000175000017500000002013315160461067022140 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "backend/libinput.h" #include "libinput_p.h" #include "util/time.h" void destroy_libinput_input_device(struct libinput_input_device *dev) { if (dev->keyboard.impl) { wlr_keyboard_finish(&dev->keyboard); } if (dev->pointer.impl) { wlr_pointer_finish(&dev->pointer); } if (dev->switch_device.impl) { wlr_switch_finish(&dev->switch_device); } if (dev->touch.impl) { wlr_touch_finish(&dev->touch); } if (dev->tablet.impl) { finish_device_tablet(dev); } if (dev->tablet_pad.impl) { finish_device_tablet_pad(dev); } libinput_device_unref(dev->handle); wl_list_remove(&dev->link); free(dev); } static void handle_device_added(struct libinput_backend *backend, struct libinput_device *libinput_dev) { int vendor = libinput_device_get_id_vendor(libinput_dev); int product = libinput_device_get_id_product(libinput_dev); const char *name = libinput_device_get_name(libinput_dev); kywc_log(KYWC_DEBUG, "Adding %s [%d:%d]", name, vendor, product); struct libinput_input_device *dev = calloc(1, sizeof(*dev)); if (!dev) { kywc_log_errno(KYWC_ERROR, "failed to allocate libinput_input_device"); return; } dev->handle = libinput_dev; libinput_device_ref(libinput_dev); libinput_device_set_user_data(libinput_dev, dev); wl_list_insert(&backend->devices, &dev->link); wl_signal_init(&dev->events.raw_tap); if (libinput_device_has_capability(libinput_dev, LIBINPUT_DEVICE_CAP_KEYBOARD)) { init_device_keyboard(dev); wl_signal_emit_mutable(&backend->backend.events.new_input, &dev->keyboard.base); } if (libinput_device_has_capability(libinput_dev, LIBINPUT_DEVICE_CAP_POINTER)) { init_device_pointer(dev); wl_signal_emit_mutable(&backend->backend.events.new_input, &dev->pointer.base); } if (libinput_device_has_capability(libinput_dev, LIBINPUT_DEVICE_CAP_SWITCH)) { init_device_switch(dev); wl_signal_emit_mutable(&backend->backend.events.new_input, &dev->switch_device.base); } if (libinput_device_has_capability(libinput_dev, LIBINPUT_DEVICE_CAP_TOUCH)) { init_device_touch(dev); wl_signal_emit_mutable(&backend->backend.events.new_input, &dev->touch.base); } if (libinput_device_has_capability(libinput_dev, LIBINPUT_DEVICE_CAP_TABLET_TOOL)) { init_device_tablet(dev); wl_signal_emit_mutable(&backend->backend.events.new_input, &dev->tablet.base); } if (libinput_device_has_capability(libinput_dev, LIBINPUT_DEVICE_CAP_TABLET_PAD)) { init_device_tablet_pad(dev); wl_signal_emit_mutable(&backend->backend.events.new_input, &dev->tablet_pad.base); } } static void handle_device_removed(struct libinput_backend *backend, struct libinput_input_device *dev) { int vendor = libinput_device_get_id_vendor(dev->handle); int product = libinput_device_get_id_product(dev->handle); const char *name = libinput_device_get_name(dev->handle); kywc_log(KYWC_DEBUG, "Removing %s [%d:%d]", name, vendor, product); destroy_libinput_input_device(dev); } #if HAVE_LIBINPUT_RAW_TAP static void handle_pointer_raw_tap(struct libinput_event *event, struct libinput_input_device *input_device) { struct libinput_event_raw_tap *gevent = libinput_event_get_raw_tap_event(event); struct pointer_raw_tap_event wlr_event = { .time_msec = usec_to_msec(libinput_event_raw_tap_get_time(gevent)), .fingers = libinput_event_raw_tap_get_finger_count(gevent) }; wl_signal_emit_mutable(&input_device->events.raw_tap, &wlr_event); } #endif void handle_libinput_event(struct libinput_backend *backend, struct libinput_event *event) { struct libinput_device *libinput_dev = libinput_event_get_device(event); struct libinput_input_device *dev = libinput_device_get_user_data(libinput_dev); enum libinput_event_type event_type = libinput_event_get_type(event); if (!dev && event_type != LIBINPUT_EVENT_DEVICE_ADDED) { kywc_log(KYWC_ERROR, "libinput_device has no libinput_input_device"); return; } switch (event_type) { case LIBINPUT_EVENT_DEVICE_ADDED: handle_device_added(backend, libinput_dev); break; case LIBINPUT_EVENT_DEVICE_REMOVED: handle_device_removed(backend, dev); break; case LIBINPUT_EVENT_KEYBOARD_KEY: handle_keyboard_key(event, &dev->keyboard); break; case LIBINPUT_EVENT_POINTER_MOTION: handle_pointer_motion(event, &dev->pointer); break; case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: handle_pointer_motion_abs(event, &dev->pointer); break; case LIBINPUT_EVENT_POINTER_BUTTON: handle_pointer_button(event, &dev->pointer); break; case LIBINPUT_EVENT_POINTER_AXIS: /* This event must be ignored in favour of the SCROLL_* events */ break; case LIBINPUT_EVENT_POINTER_SCROLL_WHEEL: handle_pointer_axis_value120(event, &dev->pointer, WL_POINTER_AXIS_SOURCE_WHEEL); break; case LIBINPUT_EVENT_POINTER_SCROLL_FINGER: handle_pointer_axis_value120(event, &dev->pointer, WL_POINTER_AXIS_SOURCE_FINGER); break; case LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS: handle_pointer_axis_value120(event, &dev->pointer, WL_POINTER_AXIS_SOURCE_CONTINUOUS); break; case LIBINPUT_EVENT_TOUCH_DOWN: handle_touch_down(event, &dev->touch); break; case LIBINPUT_EVENT_TOUCH_UP: handle_touch_up(event, &dev->touch); break; case LIBINPUT_EVENT_TOUCH_MOTION: handle_touch_motion(event, &dev->touch); break; case LIBINPUT_EVENT_TOUCH_CANCEL: handle_touch_cancel(event, &dev->touch); break; case LIBINPUT_EVENT_TOUCH_FRAME: handle_touch_frame(event, &dev->touch); break; case LIBINPUT_EVENT_TABLET_TOOL_AXIS: handle_tablet_tool_axis(event, &dev->tablet); break; case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: handle_tablet_tool_proximity(event, &dev->tablet); break; case LIBINPUT_EVENT_TABLET_TOOL_TIP: handle_tablet_tool_tip(event, &dev->tablet); break; case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: handle_tablet_tool_button(event, &dev->tablet); break; case LIBINPUT_EVENT_TABLET_PAD_BUTTON: handle_tablet_pad_button(event, &dev->tablet_pad); break; case LIBINPUT_EVENT_TABLET_PAD_RING: handle_tablet_pad_ring(event, &dev->tablet_pad); break; case LIBINPUT_EVENT_TABLET_PAD_STRIP: handle_tablet_pad_strip(event, &dev->tablet_pad); break; case LIBINPUT_EVENT_SWITCH_TOGGLE: handle_switch_toggle(event, &dev->switch_device); break; case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN: handle_pointer_swipe_begin(event, &dev->pointer); break; case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE: handle_pointer_swipe_update(event, &dev->pointer); break; case LIBINPUT_EVENT_GESTURE_SWIPE_END: handle_pointer_swipe_end(event, &dev->pointer); break; case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: handle_pointer_pinch_begin(event, &dev->pointer); break; case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: handle_pointer_pinch_update(event, &dev->pointer); break; case LIBINPUT_EVENT_GESTURE_PINCH_END: handle_pointer_pinch_end(event, &dev->pointer); break; case LIBINPUT_EVENT_GESTURE_HOLD_BEGIN: handle_pointer_hold_begin(event, &dev->pointer); break; case LIBINPUT_EVENT_GESTURE_HOLD_END: handle_pointer_hold_end(event, &dev->pointer); break; #if HAVE_LIBINPUT_RAW_TAP case LIBINPUT_EVENT_RAW_TAP: handle_pointer_raw_tap(event, dev); break; #endif default: kywc_log(KYWC_DEBUG, "Unknown libinput event %d", event_type); break; } } kylin-wayland-compositor/src/backend/libinput/libinput_p.h0000664000175000017500000001251415160461067023012 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _LIBINPUT_P_H_ #define _LIBINPUT_P_H_ #include #include #include #include #include #include #include #include struct libinput_backend { struct wlr_backend backend; struct wl_event_loop *event_loop; struct wlr_session *session; struct wl_listener session_active; struct wl_listener session_destroy; struct libinput *libinput_context; struct wl_event_source *input_event; struct wl_list devices; }; struct libinput_input_device { struct libinput_device *handle; struct wl_list link; struct wlr_keyboard keyboard; struct wlr_pointer pointer; struct wlr_switch switch_device; struct wlr_touch touch; struct wlr_tablet tablet; struct wl_list tablet_tools; // see backend/libinput/tablet_tool.c struct wlr_tablet_pad tablet_pad; struct { struct wl_signal raw_tap; // struct pointer_raw_tap_event } events; }; /* events.c */ void handle_libinput_event(struct libinput_backend *backend, struct libinput_event *event); void destroy_libinput_input_device(struct libinput_input_device *dev); /* keyboard.c */ extern const struct wlr_keyboard_impl libinput_keyboard_impl; void init_device_keyboard(struct libinput_input_device *dev); struct libinput_input_device *device_from_keyboard(struct wlr_keyboard *kb); void handle_keyboard_key(struct libinput_event *event, struct wlr_keyboard *kb); /* pointer.c */ extern const struct wlr_pointer_impl libinput_pointer_impl; void init_device_pointer(struct libinput_input_device *dev); struct libinput_input_device *device_from_pointer(struct wlr_pointer *wlr_pointer); void handle_pointer_motion(struct libinput_event *event, struct wlr_pointer *pointer); void handle_pointer_motion_abs(struct libinput_event *event, struct wlr_pointer *pointer); void handle_pointer_button(struct libinput_event *event, struct wlr_pointer *pointer); void handle_pointer_axis(struct libinput_event *event, struct wlr_pointer *pointer); void handle_pointer_axis_value120(struct libinput_event *event, struct wlr_pointer *pointer, enum wl_pointer_axis_source source); void handle_pointer_swipe_begin(struct libinput_event *event, struct wlr_pointer *pointer); void handle_pointer_swipe_update(struct libinput_event *event, struct wlr_pointer *pointer); void handle_pointer_swipe_end(struct libinput_event *event, struct wlr_pointer *pointer); void handle_pointer_pinch_begin(struct libinput_event *event, struct wlr_pointer *pointer); void handle_pointer_pinch_update(struct libinput_event *event, struct wlr_pointer *pointer); void handle_pointer_pinch_end(struct libinput_event *event, struct wlr_pointer *pointer); void handle_pointer_hold_begin(struct libinput_event *event, struct wlr_pointer *pointer); void handle_pointer_hold_end(struct libinput_event *event, struct wlr_pointer *pointer); /* switch.c */ extern const struct wlr_switch_impl libinput_switch_impl; void init_device_switch(struct libinput_input_device *dev); struct libinput_input_device *device_from_switch(struct wlr_switch *wlr_switch); void handle_switch_toggle(struct libinput_event *event, struct wlr_switch *wlr_switch); /* tablet_pad.c */ extern const struct wlr_tablet_pad_impl libinput_tablet_pad_impl; void init_device_tablet_pad(struct libinput_input_device *dev); void finish_device_tablet_pad(struct libinput_input_device *dev); struct libinput_input_device *device_from_tablet_pad(struct wlr_tablet_pad *wlr_tablet_pad); void handle_tablet_pad_button(struct libinput_event *event, struct wlr_tablet_pad *tablet_pad); void handle_tablet_pad_ring(struct libinput_event *event, struct wlr_tablet_pad *tablet_pad); void handle_tablet_pad_strip(struct libinput_event *event, struct wlr_tablet_pad *tablet_pad); /* tablet_tool.c */ extern const struct wlr_tablet_impl libinput_tablet_impl; void init_device_tablet(struct libinput_input_device *dev); void finish_device_tablet(struct libinput_input_device *dev); struct libinput_input_device *device_from_tablet(struct wlr_tablet *wlr_tablet); void handle_tablet_tool_axis(struct libinput_event *event, struct wlr_tablet *wlr_tablet); void handle_tablet_tool_proximity(struct libinput_event *event, struct wlr_tablet *wlr_tablet); void handle_tablet_tool_tip(struct libinput_event *event, struct wlr_tablet *wlr_tablet); void handle_tablet_tool_button(struct libinput_event *event, struct wlr_tablet *wlr_tablet); /* touch.c */ extern const struct wlr_touch_impl libinput_touch_impl; void init_device_touch(struct libinput_input_device *dev); struct libinput_input_device *device_from_touch(struct wlr_touch *wlr_touch); void handle_touch_down(struct libinput_event *event, struct wlr_touch *touch); void handle_touch_up(struct libinput_event *event, struct wlr_touch *touch); void handle_touch_motion(struct libinput_event *event, struct wlr_touch *touch); void handle_touch_cancel(struct libinput_event *event, struct wlr_touch *touch); void handle_touch_frame(struct libinput_event *event, struct wlr_touch *touch); #endif /* _LIBINPUT_P_H_ */ kylin-wayland-compositor/src/backend/libinput/pointer.c0000664000175000017500000002474515160461067022331 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "libinput_p.h" #include "util/time.h" const struct wlr_pointer_impl libinput_pointer_impl = { .name = "libinput-pointer", }; void init_device_pointer(struct libinput_input_device *dev) { const char *name = libinput_device_get_name(dev->handle); struct wlr_pointer *wlr_pointer = &dev->pointer; wlr_pointer_init(wlr_pointer, &libinput_pointer_impl, name); } struct libinput_input_device *device_from_pointer(struct wlr_pointer *wlr_pointer) { assert(wlr_pointer->impl == &libinput_pointer_impl); struct libinput_input_device *dev = wl_container_of(wlr_pointer, dev, pointer); return dev; } void handle_pointer_motion(struct libinput_event *event, struct wlr_pointer *pointer) { struct libinput_event_pointer *pevent = libinput_event_get_pointer_event(event); struct wlr_pointer_motion_event wlr_event = { .pointer = pointer, .time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)), .delta_x = libinput_event_pointer_get_dx(pevent), .delta_y = libinput_event_pointer_get_dy(pevent), .unaccel_dx = libinput_event_pointer_get_dx_unaccelerated(pevent), .unaccel_dy = libinput_event_pointer_get_dy_unaccelerated(pevent), }; wl_signal_emit_mutable(&pointer->events.motion, &wlr_event); wl_signal_emit_mutable(&pointer->events.frame, pointer); } void handle_pointer_motion_abs(struct libinput_event *event, struct wlr_pointer *pointer) { struct libinput_event_pointer *pevent = libinput_event_get_pointer_event(event); struct wlr_pointer_motion_absolute_event wlr_event = { .pointer = pointer, .time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)), .x = libinput_event_pointer_get_absolute_x_transformed(pevent, 1), .y = libinput_event_pointer_get_absolute_y_transformed(pevent, 1), }; wl_signal_emit_mutable(&pointer->events.motion_absolute, &wlr_event); wl_signal_emit_mutable(&pointer->events.frame, pointer); } void handle_pointer_button(struct libinput_event *event, struct wlr_pointer *pointer) { struct libinput_event_pointer *pevent = libinput_event_get_pointer_event(event); struct wlr_pointer_button_event wlr_event = { .pointer = pointer, .time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)), .button = libinput_event_pointer_get_button(pevent), }; switch (libinput_event_pointer_get_button_state(pevent)) { case LIBINPUT_BUTTON_STATE_PRESSED: wlr_event.state = WL_POINTER_BUTTON_STATE_PRESSED; break; case LIBINPUT_BUTTON_STATE_RELEASED: wlr_event.state = WL_POINTER_BUTTON_STATE_RELEASED; break; } wl_signal_emit_mutable(&pointer->events.button, &wlr_event); wl_signal_emit_mutable(&pointer->events.frame, pointer); } void handle_pointer_axis(struct libinput_event *event, struct wlr_pointer *pointer) { struct libinput_event_pointer *pevent = libinput_event_get_pointer_event(event); struct wlr_pointer_axis_event wlr_event = { .pointer = pointer, .time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)), }; switch (libinput_event_pointer_get_axis_source(pevent)) { case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL: wlr_event.source = WL_POINTER_AXIS_SOURCE_WHEEL; break; case LIBINPUT_POINTER_AXIS_SOURCE_FINGER: wlr_event.source = WL_POINTER_AXIS_SOURCE_FINGER; break; case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: wlr_event.source = WL_POINTER_AXIS_SOURCE_CONTINUOUS; break; case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT: wlr_event.source = WL_POINTER_AXIS_SOURCE_WHEEL_TILT; break; } const enum libinput_pointer_axis axes[] = { LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, }; for (size_t i = 0; i < sizeof(axes) / sizeof(axes[0]); ++i) { if (!libinput_event_pointer_has_axis(pevent, axes[i])) { continue; } switch (axes[i]) { case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL: wlr_event.orientation = WL_POINTER_AXIS_VERTICAL_SCROLL; break; case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL: wlr_event.orientation = WL_POINTER_AXIS_HORIZONTAL_SCROLL; break; } wlr_event.delta = libinput_event_pointer_get_axis_value(pevent, axes[i]); wlr_event.delta_discrete = libinput_event_pointer_get_axis_value_discrete(pevent, axes[i]); wlr_event.delta_discrete *= WLR_POINTER_AXIS_DISCRETE_STEP; wl_signal_emit_mutable(&pointer->events.axis, &wlr_event); } wl_signal_emit_mutable(&pointer->events.frame, pointer); } void handle_pointer_axis_value120(struct libinput_event *event, struct wlr_pointer *pointer, enum wl_pointer_axis_source source) { struct libinput_event_pointer *pevent = libinput_event_get_pointer_event(event); struct wlr_pointer_axis_event wlr_event = { .pointer = pointer, .time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)), .source = source, }; const enum libinput_pointer_axis axes[] = { LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, }; for (size_t i = 0; i < sizeof(axes) / sizeof(axes[0]); ++i) { if (!libinput_event_pointer_has_axis(pevent, axes[i])) { continue; } switch (axes[i]) { case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL: wlr_event.orientation = WL_POINTER_AXIS_VERTICAL_SCROLL; break; case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL: wlr_event.orientation = WL_POINTER_AXIS_HORIZONTAL_SCROLL; break; } wlr_event.delta = libinput_event_pointer_get_scroll_value(pevent, axes[i]); if (source == WL_POINTER_AXIS_SOURCE_WHEEL) { wlr_event.delta_discrete = libinput_event_pointer_get_scroll_value_v120(pevent, axes[i]); } wl_signal_emit_mutable(&pointer->events.axis, &wlr_event); } wl_signal_emit_mutable(&pointer->events.frame, pointer); } void handle_pointer_swipe_begin(struct libinput_event *event, struct wlr_pointer *pointer) { struct libinput_event_gesture *gevent = libinput_event_get_gesture_event(event); struct wlr_pointer_swipe_begin_event wlr_event = { .pointer = pointer, .time_msec = usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), .fingers = libinput_event_gesture_get_finger_count(gevent), }; wl_signal_emit_mutable(&pointer->events.swipe_begin, &wlr_event); } void handle_pointer_swipe_update(struct libinput_event *event, struct wlr_pointer *pointer) { struct libinput_event_gesture *gevent = libinput_event_get_gesture_event(event); struct wlr_pointer_swipe_update_event wlr_event = { .pointer = pointer, .time_msec = usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), .fingers = libinput_event_gesture_get_finger_count(gevent), .dx = libinput_event_gesture_get_dx_unaccelerated(gevent), .dy = libinput_event_gesture_get_dy_unaccelerated(gevent), }; wl_signal_emit_mutable(&pointer->events.swipe_update, &wlr_event); } void handle_pointer_swipe_end(struct libinput_event *event, struct wlr_pointer *pointer) { struct libinput_event_gesture *gevent = libinput_event_get_gesture_event(event); struct wlr_pointer_swipe_end_event wlr_event = { .pointer = pointer, .time_msec = usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), .cancelled = libinput_event_gesture_get_cancelled(gevent), }; wl_signal_emit_mutable(&pointer->events.swipe_end, &wlr_event); } void handle_pointer_pinch_begin(struct libinput_event *event, struct wlr_pointer *pointer) { struct libinput_event_gesture *gevent = libinput_event_get_gesture_event(event); struct wlr_pointer_pinch_begin_event wlr_event = { .pointer = pointer, .time_msec = usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), .fingers = libinput_event_gesture_get_finger_count(gevent), }; wl_signal_emit_mutable(&pointer->events.pinch_begin, &wlr_event); } void handle_pointer_pinch_update(struct libinput_event *event, struct wlr_pointer *pointer) { struct libinput_event_gesture *gevent = libinput_event_get_gesture_event(event); struct wlr_pointer_pinch_update_event wlr_event = { .pointer = pointer, .time_msec = usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), .fingers = libinput_event_gesture_get_finger_count(gevent), .dx = libinput_event_gesture_get_dx_unaccelerated(gevent), .dy = libinput_event_gesture_get_dy_unaccelerated(gevent), .scale = libinput_event_gesture_get_scale(gevent), .rotation = libinput_event_gesture_get_angle_delta(gevent), }; wl_signal_emit_mutable(&pointer->events.pinch_update, &wlr_event); } void handle_pointer_pinch_end(struct libinput_event *event, struct wlr_pointer *pointer) { struct libinput_event_gesture *gevent = libinput_event_get_gesture_event(event); struct wlr_pointer_pinch_end_event wlr_event = { .pointer = pointer, .time_msec = usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), .cancelled = libinput_event_gesture_get_cancelled(gevent), }; wl_signal_emit_mutable(&pointer->events.pinch_end, &wlr_event); } void handle_pointer_hold_begin(struct libinput_event *event, struct wlr_pointer *pointer) { struct libinput_event_gesture *gevent = libinput_event_get_gesture_event(event); struct wlr_pointer_hold_begin_event wlr_event = { .pointer = pointer, .time_msec = usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), .fingers = libinput_event_gesture_get_finger_count(gevent), }; wl_signal_emit_mutable(&pointer->events.hold_begin, &wlr_event); } void handle_pointer_hold_end(struct libinput_event *event, struct wlr_pointer *pointer) { struct libinput_event_gesture *gevent = libinput_event_get_gesture_event(event); struct wlr_pointer_hold_end_event wlr_event = { .pointer = pointer, .time_msec = usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), .cancelled = libinput_event_gesture_get_cancelled(gevent), }; wl_signal_emit_mutable(&pointer->events.hold_end, &wlr_event); } kylin-wayland-compositor/src/backend/libinput/keyboard.c0000664000175000017500000000340115160461067022433 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "libinput_p.h" #include "util/time.h" struct libinput_input_device *device_from_keyboard(struct wlr_keyboard *kb) { assert(kb->impl == &libinput_keyboard_impl); struct libinput_input_device *dev = wl_container_of(kb, dev, keyboard); return dev; } static void keyboard_set_leds(struct wlr_keyboard *wlr_kb, uint32_t leds) { struct libinput_input_device *dev = device_from_keyboard(wlr_kb); libinput_device_led_update(dev->handle, leds); } const struct wlr_keyboard_impl libinput_keyboard_impl = { .name = "libinput-keyboard", .led_update = keyboard_set_leds, }; void init_device_keyboard(struct libinput_input_device *dev) { const char *name = libinput_device_get_name(dev->handle); struct wlr_keyboard *wlr_kb = &dev->keyboard; wlr_keyboard_init(wlr_kb, &libinput_keyboard_impl, name); libinput_device_led_update(dev->handle, 0); } void handle_keyboard_key(struct libinput_event *event, struct wlr_keyboard *kb) { struct libinput_event_keyboard *kbevent = libinput_event_get_keyboard_event(event); struct wlr_keyboard_key_event wlr_event = { .time_msec = usec_to_msec(libinput_event_keyboard_get_time_usec(kbevent)), .keycode = libinput_event_keyboard_get_key(kbevent), .update_state = true, }; switch (libinput_event_keyboard_get_key_state(kbevent)) { case LIBINPUT_KEY_STATE_RELEASED: wlr_event.state = WL_KEYBOARD_KEY_STATE_RELEASED; break; case LIBINPUT_KEY_STATE_PRESSED: wlr_event.state = WL_KEYBOARD_KEY_STATE_PRESSED; break; } wlr_keyboard_notify_key(kb, &wlr_event); } kylin-wayland-compositor/src/backend/libinput/backend.c0000664000175000017500000002106015160461067022223 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include "backend/libinput.h" #include "libinput_p.h" static struct libinput_backend *get_libinput_backend_from_backend(struct wlr_backend *wlr_backend) { assert(wlr_backend_is_libinput(wlr_backend)); struct libinput_backend *backend = wl_container_of(wlr_backend, backend, backend); return backend; } static int libinput_open_restricted(const char *path, int flags, void *_backend) { struct libinput_backend *backend = _backend; struct wlr_device *dev = wlr_session_open_file(backend->session, path); return dev ? dev->fd : -1; } static void libinput_close_restricted(int fd, void *_backend) { struct libinput_backend *backend = _backend; bool found = false; struct wlr_device *dev; wl_list_for_each(dev, &backend->session->devices, link) { if (dev->fd == fd) { found = true; break; } } if (found) { wlr_session_close_file(backend->session, dev); } } static const struct libinput_interface libinput_impl = { .open_restricted = libinput_open_restricted, .close_restricted = libinput_close_restricted, }; static enum kywc_log_level libinput_log_priority_to_kywc(enum libinput_log_priority priority) { switch (priority) { case LIBINPUT_LOG_PRIORITY_ERROR: return KYWC_ERROR; case LIBINPUT_LOG_PRIORITY_INFO: return KYWC_INFO; default: return KYWC_DEBUG; } } static void log_libinput(struct libinput *libinput_context, enum libinput_log_priority priority, const char *fmt, va_list args) { enum kywc_log_level level = libinput_log_priority_to_kywc(priority); static char kywc_fmt[1024]; snprintf(kywc_fmt, sizeof(kywc_fmt), "[libinput] %s", fmt); kywc_vlog(level, kywc_fmt, args); } static int handle_libinput_readable(int fd, uint32_t mask, void *_backend) { struct libinput_backend *backend = _backend; int ret = libinput_dispatch(backend->libinput_context); if (ret != 0) { kywc_log(KYWC_ERROR, "Failed to dispatch libinput: %s", strerror(-ret)); wlr_backend_destroy(&backend->backend); return 0; } struct libinput_event *event; while ((event = libinput_get_event(backend->libinput_context))) { handle_libinput_event(backend, event); libinput_event_destroy(event); } return 0; } static bool backend_start(struct wlr_backend *wlr_backend) { struct libinput_backend *backend = get_libinput_backend_from_backend(wlr_backend); kywc_log(KYWC_DEBUG, "Starting libinput backend"); backend->libinput_context = libinput_udev_create_context(&libinput_impl, backend, backend->session->udev); if (!backend->libinput_context) { kywc_log(KYWC_ERROR, "Failed to create libinput context"); return false; } if (libinput_udev_assign_seat(backend->libinput_context, backend->session->seat) != 0) { kywc_log(KYWC_ERROR, "Failed to assign libinput seat"); return false; } libinput_log_set_handler(backend->libinput_context, log_libinput); libinput_log_set_priority(backend->libinput_context, LIBINPUT_LOG_PRIORITY_ERROR); int libinput_fd = libinput_get_fd(backend->libinput_context); handle_libinput_readable(libinput_fd, WL_EVENT_READABLE, backend); if (wl_list_empty(&backend->devices)) { kywc_log(KYWC_WARN, "No input devices during libinput backend initialization"); } if (backend->input_event) { wl_event_source_remove(backend->input_event); } backend->input_event = wl_event_loop_add_fd(backend->event_loop, libinput_fd, WL_EVENT_READABLE, handle_libinput_readable, backend); if (!backend->input_event) { kywc_log(KYWC_ERROR, "Failed to create input event on event loop"); return false; } kywc_log(KYWC_DEBUG, "libinput successfully initialized"); return true; } static void backend_destroy(struct wlr_backend *wlr_backend) { if (!wlr_backend) { return; } struct libinput_backend *backend = get_libinput_backend_from_backend(wlr_backend); struct libinput_input_device *dev, *tmp; wl_list_for_each_safe(dev, tmp, &backend->devices, link) { destroy_libinput_input_device(dev); } wlr_backend_finish(wlr_backend); wl_list_remove(&backend->session_destroy.link); wl_list_remove(&backend->session_active.link); if (backend->input_event) { wl_event_source_remove(backend->input_event); } libinput_unref(backend->libinput_context); free(backend); } static const struct wlr_backend_impl backend_impl = { .start = backend_start, .destroy = backend_destroy, }; bool wlr_backend_is_libinput(struct wlr_backend *backend) { return backend->impl == &backend_impl; } static void handle_session_active(struct wl_listener *listener, void *data) { struct libinput_backend *backend = wl_container_of(listener, backend, session_active); struct wlr_session *session = backend->session; if (!backend->libinput_context) { return; } if (session->active) { libinput_resume(backend->libinput_context); } else { libinput_suspend(backend->libinput_context); } int libinput_fd = libinput_get_fd(backend->libinput_context); handle_libinput_readable(libinput_fd, WL_EVENT_READABLE, backend); } static void handle_session_destroy(struct wl_listener *listener, void *data) { struct libinput_backend *backend = wl_container_of(listener, backend, session_destroy); backend_destroy(&backend->backend); } struct wlr_backend *libinput_backend_create(struct wl_event_loop *loop, struct wlr_session *session) { struct libinput_backend *backend = calloc(1, sizeof(*backend)); if (!backend) { kywc_log(KYWC_ERROR, "Allocation failed: %s", strerror(errno)); return NULL; } wlr_backend_init(&backend->backend, &backend_impl); backend->event_loop = loop; wl_list_init(&backend->devices); backend->session = session; backend->session_active.notify = handle_session_active; wl_signal_add(&session->events.active, &backend->session_active); backend->session_destroy.notify = handle_session_destroy; wl_signal_add(&session->events.destroy, &backend->session_destroy); return &backend->backend; } bool wlr_input_device_is_libinput(struct wlr_input_device *wlr_dev) { switch (wlr_dev->type) { case WLR_INPUT_DEVICE_KEYBOARD: return wlr_keyboard_from_input_device(wlr_dev)->impl == &libinput_keyboard_impl; case WLR_INPUT_DEVICE_POINTER: return wlr_pointer_from_input_device(wlr_dev)->impl == &libinput_pointer_impl; case WLR_INPUT_DEVICE_TOUCH: return wlr_touch_from_input_device(wlr_dev)->impl == &libinput_touch_impl; case WLR_INPUT_DEVICE_TABLET: return wlr_tablet_from_input_device(wlr_dev)->impl == &libinput_tablet_impl; case WLR_INPUT_DEVICE_TABLET_PAD: return wlr_tablet_pad_from_input_device(wlr_dev)->impl == &libinput_tablet_pad_impl; case WLR_INPUT_DEVICE_SWITCH: return wlr_switch_from_input_device(wlr_dev)->impl == &libinput_switch_impl; default: return false; } } struct libinput_device *wlr_libinput_get_device_handle(struct wlr_input_device *wlr_dev) { struct libinput_input_device *dev = NULL; switch (wlr_dev->type) { case WLR_INPUT_DEVICE_KEYBOARD: dev = device_from_keyboard(wlr_keyboard_from_input_device(wlr_dev)); break; case WLR_INPUT_DEVICE_POINTER: dev = device_from_pointer(wlr_pointer_from_input_device(wlr_dev)); break; case WLR_INPUT_DEVICE_SWITCH: dev = device_from_switch(wlr_switch_from_input_device(wlr_dev)); break; case WLR_INPUT_DEVICE_TOUCH: dev = device_from_touch(wlr_touch_from_input_device(wlr_dev)); break; case WLR_INPUT_DEVICE_TABLET: dev = device_from_tablet(wlr_tablet_from_input_device(wlr_dev)); break; case WLR_INPUT_DEVICE_TABLET_PAD: dev = device_from_tablet_pad(wlr_tablet_pad_from_input_device(wlr_dev)); break; } assert(dev); return dev->handle; } void wlr_libinput_add_raw_tap_listener(struct wlr_input_device *wlr_dev, struct wl_listener *listener) { struct libinput_input_device *dev = device_from_pointer(wlr_pointer_from_input_device(wlr_dev)); wl_signal_add(&dev->events.raw_tap, listener); } kylin-wayland-compositor/src/backend/drm/0000775000175000017500000000000015160461067017425 5ustar fengfengkylin-wayland-compositor/src/backend/drm/gen_pnpids.sh0000775000175000017500000000101415160460057022104 0ustar fengfeng#!/bin/sh -eu # # usage: gen_pnpids.sh < pnp.ids > pnpids.c gen_pnps() { while read -r id vendor; do [ "${#id}" = 3 ] || exit 1 printf "\tcase PNP_ID('%c', '%c', '%c'): return \"%s\";\n" \ "$id" "${id#?}" "${id#??}" "$vendor" done } cat < #include #include #include #include #include #include "drm_p.h" int32_t drm_util_calculate_refresh_rate(const drmModeModeInfo *mode) { int32_t refresh = (mode->clock * 1000000LL / mode->htotal + mode->vtotal / 2) / mode->vtotal; if (mode->flags & DRM_MODE_FLAG_INTERLACE) { refresh *= 2; } if (mode->flags & DRM_MODE_FLAG_DBLSCAN) { refresh /= 2; } if (mode->vscan > 1) { refresh /= mode->vscan; } return refresh; } enum wlr_output_mode_aspect_ratio drm_util_get_picture_aspect_ratio(const drmModeModeInfo *mode) { switch (mode->flags & DRM_MODE_FLAG_PIC_AR_MASK) { case DRM_MODE_FLAG_PIC_AR_NONE: return WLR_OUTPUT_MODE_ASPECT_RATIO_NONE; case DRM_MODE_FLAG_PIC_AR_4_3: return WLR_OUTPUT_MODE_ASPECT_RATIO_4_3; case DRM_MODE_FLAG_PIC_AR_16_9: return WLR_OUTPUT_MODE_ASPECT_RATIO_16_9; case DRM_MODE_FLAG_PIC_AR_64_27: return WLR_OUTPUT_MODE_ASPECT_RATIO_64_27; case DRM_MODE_FLAG_PIC_AR_256_135: return WLR_OUTPUT_MODE_ASPECT_RATIO_256_135; default: kywc_log(KYWC_ERROR, "Unknown mode picture aspect ratio: %u", mode->flags & DRM_MODE_FLAG_PIC_AR_MASK); return WLR_OUTPUT_MODE_ASPECT_RATIO_NONE; } } void drm_util_parse_edid(struct drm_connector *conn, const uint8_t *data, size_t len) { struct wlr_output *output = &conn->output; free(output->make); free(output->model); free(output->serial); output->make = NULL; output->model = NULL; output->serial = NULL; struct di_info *info = di_info_parse_edid(data, len); if (info == NULL) { kywc_log(KYWC_ERROR, "Failed to parse EDID"); return; } const struct di_edid *edid = di_info_get_edid(info); const struct di_edid_vendor_product *vendor_product = di_edid_get_vendor_product(edid); char pnp_id[] = { vendor_product->manufacturer[0], vendor_product->manufacturer[1], vendor_product->manufacturer[2], '\0', }; const char *manu = pnp_get_manufacturer(vendor_product->manufacturer); if (!manu) { manu = pnp_id; } output->make = strdup(manu); output->model = di_info_get_model(info); output->serial = di_info_get_serial(info); di_info_destroy(info); } const char *drm_util_get_connector_status_str(drmModeConnection status) { switch (status) { case DRM_MODE_CONNECTED: return "connected"; case DRM_MODE_DISCONNECTED: return "disconnected"; case DRM_MODE_UNKNOWNCONNECTION: return "unknown"; } return ""; } /* * Store all of the non-recursive state in a struct, so we aren't literally * passing 12 arguments to a function. */ struct match_state { const size_t num_objs; const uint32_t *restrict objs; const size_t num_res; size_t score; size_t replaced; uint32_t *restrict res; uint32_t *restrict best; const uint32_t *restrict orig; bool exit_early; }; static bool is_taken(size_t n, const uint32_t arr[static n], uint32_t key) { for (size_t i = 0; i < n; ++i) { if (arr[i] == key) { return true; } } return false; } /* * skips: The number of SKIP elements encountered so far. * score: The number of resources we've matched so far. * replaced: The number of changes from the original solution. * i: The index of the current element. * * This tries to match a solution as close to st->orig as it can. * * Returns whether we've set a new best element with this solution. */ static bool match_obj_(struct match_state *st, size_t skips, size_t score, size_t replaced, size_t i) { // Finished if (i >= st->num_res) { if (score > st->score || (score == st->score && replaced < st->replaced)) { st->score = score; st->replaced = replaced; memcpy(st->best, st->res, sizeof(st->best[0]) * st->num_res); st->exit_early = (st->score == st->num_res - skips || st->score == st->num_objs) && st->replaced == 0; return true; } else { return false; } } if (st->orig[i] == SKIP) { st->res[i] = SKIP; return match_obj_(st, skips + 1, score, replaced, i + 1); } bool has_best = false; /* * Attempt to use the current solution first, to try and avoid * recalculating everything */ if (st->orig[i] != UNMATCHED && !is_taken(i, st->res, st->orig[i])) { st->res[i] = st->orig[i]; size_t obj_score = st->objs[st->res[i]] != 0 ? 1 : 0; if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) { has_best = true; } } if (st->orig[i] == UNMATCHED) { st->res[i] = UNMATCHED; if (match_obj_(st, skips, score, replaced, i + 1)) { has_best = true; } } if (st->exit_early) { return true; } if (st->orig[i] != UNMATCHED) { ++replaced; } for (size_t candidate = 0; candidate < st->num_objs; ++candidate) { // We tried this earlier if (candidate == st->orig[i]) { continue; } // Not compatible if (!(st->objs[candidate] & (1 << i))) { continue; } // Already taken if (is_taken(i, st->res, candidate)) { continue; } st->res[i] = candidate; size_t obj_score = st->objs[candidate] != 0 ? 1 : 0; if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) { has_best = true; } if (st->exit_early) { return true; } } if (has_best) { return true; } // Maybe this resource can't be matched st->res[i] = UNMATCHED; return match_obj_(st, skips, score, replaced, i + 1); } size_t drm_util_match_obj(size_t num_objs, const uint32_t objs[static restrict num_objs], size_t num_res, const uint32_t res[static restrict num_res], uint32_t out[static restrict num_res]) { uint32_t solution[num_res]; for (size_t i = 0; i < num_res; ++i) { solution[i] = UNMATCHED; } struct match_state st = { .num_objs = num_objs, .num_res = num_res, .score = 0, .replaced = SIZE_MAX, .objs = objs, .res = solution, .best = out, .orig = res, .exit_early = false, }; match_obj_(&st, 0, 0, 0, 0); return st.score; } void drm_util_generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay, float vrefresh) { // TODO: depending on capabilities advertised in the EDID, use reduced // blanking if possible (and update sync polarity) struct di_cvt_options options = { .red_blank_ver = DI_CVT_REDUCED_BLANKING_NONE, .h_pixels = hdisplay, .v_lines = vdisplay, .ip_freq_rqd = vrefresh ? vrefresh : 60, }; struct di_cvt_timing timing; di_cvt_compute(&timing, &options); uint16_t hsync_start = hdisplay + timing.h_front_porch; uint16_t vsync_start = timing.v_lines_rnd + timing.v_front_porch; uint16_t hsync_end = hsync_start + timing.h_sync; uint16_t vsync_end = vsync_start + timing.v_sync; *mode = (drmModeModeInfo){ .clock = roundf(timing.act_pixel_freq * 1000), .hdisplay = hdisplay, .vdisplay = timing.v_lines_rnd, .hsync_start = hsync_start, .vsync_start = vsync_start, .hsync_end = hsync_end, .vsync_end = vsync_end, .htotal = hsync_end + timing.h_back_porch, .vtotal = vsync_end + timing.v_back_porch, .vrefresh = roundf(timing.act_frame_rate), .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC, .type = DRM_MODE_TYPE_USERDEF, }; snprintf(mode->name, sizeof(mode->name), "%dx%d", hdisplay, vdisplay); } kylin-wayland-compositor/src/backend/drm/fb.c0000664000175000017500000002040515160461067020161 0ustar fengfeng// SPDX-FileCopyrightText: 2025 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "drm_p.h" #include "render/pixel_format.h" static void drm_fb_handle_destroy(struct wlr_addon *addon) { struct drm_fb *fb = wl_container_of(addon, fb, addon); drm_fb_destroy_fb(fb); } static const struct wlr_addon_interface fb_addon_impl = { .name = "drm_fb", .destroy = drm_fb_handle_destroy, }; static uint32_t get_fb_for_bo(struct drm_device *drm, struct wlr_dmabuf_attributes *dmabuf, uint32_t handles[static 4], bool use_yuyv) { uint64_t modifiers[4] = { 0 }; for (int i = 0; i < dmabuf->n_planes; i++) { // KMS requires all BO planes to have the same modifier modifiers[i] = dmabuf->modifier; } uint32_t id = 0; if (drm->addfb2_modifiers && dmabuf->modifier != DRM_FORMAT_MOD_INVALID) { if (drmModeAddFB2WithModifiers(drm->fd, use_yuyv ? dmabuf->width * 2 : dmabuf->width, dmabuf->height, use_yuyv ? DRM_FORMAT_YUYV : dmabuf->format, handles, dmabuf->stride, dmabuf->offset, modifiers, &id, DRM_MODE_FB_MODIFIERS) != 0) { kywc_log_errno(KYWC_DEBUG, "Failed with drmModeAddFB2WithModifiers"); } } else { if (dmabuf->modifier != DRM_FORMAT_MOD_INVALID && dmabuf->modifier != DRM_FORMAT_MOD_LINEAR) { kywc_log(KYWC_ERROR, "Cannot import DRM framebuffer with explicit modifier 0x%" PRIX64, dmabuf->modifier); return 0; } int ret = drmModeAddFB2(drm->fd, use_yuyv ? dmabuf->width * 2 : dmabuf->width, dmabuf->height, use_yuyv ? DRM_FORMAT_YUYV : dmabuf->format, handles, dmabuf->stride, dmabuf->offset, &id, 0); if (ret != 0 && dmabuf->format == DRM_FORMAT_ARGB8888 && dmabuf->n_planes == 1 && dmabuf->offset[0] == 0) { // Some big-endian machines don't support drmModeAddFB2. Try a // last-resort fallback for ARGB8888 buffers, like Xorg's // modesetting driver does. kywc_log(KYWC_DEBUG, "Failed with drmModeAddFB2(%s), falling back to legacy drmModeAddFB", strerror(-ret)); uint32_t depth = 32; uint32_t bpp = 32; ret = drmModeAddFB(drm->fd, use_yuyv ? dmabuf->width * 2 : dmabuf->width, dmabuf->height, depth, bpp, dmabuf->stride[0], handles[0], &id); if (ret != 0) { kywc_log_errno(KYWC_DEBUG, "Failed with drmModeAddFB"); } } else if (ret != 0) { kywc_log_errno(KYWC_DEBUG, "Failed with drmModeAddFB2"); } } return id; } static void close_all_bo_handles(struct drm_device *drm, uint32_t handles[static 4]) { for (int i = 0; i < 4; ++i) { if (handles[i] == 0) { continue; } // If multiple planes share the same BO handle, avoid double-closing it bool already_closed = false; for (int j = 0; j < i; ++j) { if (handles[i] == handles[j]) { already_closed = true; break; } } if (already_closed) { continue; } if (drmCloseBufferHandle(drm->fd, handles[i]) != 0) { kywc_log_errno(KYWC_ERROR, "Failed with drmCloseBufferHandle"); } } } static void drm_poisoned_fb_handle_destroy(struct wlr_addon *addon) { wlr_addon_finish(addon); free(addon); } static const struct wlr_addon_interface poisoned_fb_addon_impl = { .name = "drm_poisoned_fb", .destroy = drm_poisoned_fb_handle_destroy, }; static bool buffer_is_poisoned(struct drm_device *drm, struct wlr_buffer *buf) { return wlr_addon_find(&buf->addons, drm, &poisoned_fb_addon_impl) != NULL; } /** * Mark the buffer as "poisoned", ie. it cannot be imported into KMS. This * allows us to avoid repeatedly trying to import it when it's not * scanout-capable. */ static void poison_buffer(struct drm_device *drm, struct wlr_buffer *buf) { struct wlr_addon *addon = calloc(1, sizeof(*addon)); if (addon == NULL) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); return; } kywc_log(KYWC_DEBUG, "Poisoning buffer"); wlr_addon_init(addon, &buf->addons, drm, &poisoned_fb_addon_impl); } static struct drm_fb *drm_fb_create(struct drm_device *drm, struct wlr_buffer *buf, const struct wlr_drm_format_set *formats, bool use_yuyv) { struct wlr_dmabuf_attributes attribs; if (!wlr_buffer_get_dmabuf(buf, &attribs)) { kywc_log(KYWC_DEBUG, "Failed to get DMA-BUF from buffer"); return NULL; } if (buffer_is_poisoned(drm, buf)) { kywc_log(KYWC_DEBUG, "Buffer is poisoned"); return NULL; } struct drm_fb *fb = calloc(1, sizeof(*fb)); if (!fb) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); return NULL; } if (formats && !wlr_drm_format_set_has(formats, attribs.format, attribs.modifier)) { // The format isn't supported by the plane. Try stripping the alpha // channel, if any. const struct ky_pixel_format *info = ky_pixel_format_from_drm(attribs.format); if (info != NULL && info->drm_format != DRM_FORMAT_INVALID && wlr_drm_format_set_has(formats, info->drm_format, attribs.modifier)) { attribs.format = info->drm_format; } else { kywc_log(KYWC_DEBUG, "Buffer format 0x%" PRIX32 " with modifier 0x%" PRIX64 " cannot be scanned out", attribs.format, attribs.modifier); goto error_fb; } } uint32_t handles[4] = { 0 }; for (int i = 0; i < attribs.n_planes; ++i) { int ret = drmPrimeFDToHandle(drm->fd, attribs.fd[i], &handles[i]); if (ret != 0) { kywc_log_errno(KYWC_DEBUG, "Failed with drmPrimeFDToHandle"); goto error_bo_handle; } } fb->id = get_fb_for_bo(drm, &attribs, handles, use_yuyv); if (!fb->id) { kywc_log(KYWC_DEBUG, "Failed to import BO in KMS"); poison_buffer(drm, buf); goto error_bo_handle; } close_all_bo_handles(drm, handles); fb->drm = drm; fb->wlr_buf = buf; wlr_addon_init(&fb->addon, &buf->addons, drm, &fb_addon_impl); wl_list_insert(&drm->fbs, &fb->link); return fb; error_bo_handle: close_all_bo_handles(drm, handles); error_fb: free(fb); return NULL; } void drm_fb_destroy_fb(struct drm_fb *fb) { struct drm_device *drm = fb->drm; wl_list_remove(&fb->link); wlr_addon_finish(&fb->addon); int ret = drmModeCloseFB(drm->fd, fb->id); if (ret == -EINVAL) { ret = drmModeRmFB(drm->fd, fb->id); } if (ret != 0) { kywc_log(KYWC_ERROR, "Failed to close FB: %s", strerror(-ret)); } free(fb); } void drm_fb_clear_fb(struct drm_fb **fb_ptr) { if (*fb_ptr == NULL) { return; } struct drm_fb *fb = *fb_ptr; wlr_buffer_unlock(fb->wlr_buf); // may destroy the buffer *fb_ptr = NULL; } struct drm_fb *drm_fb_lock_fb(struct drm_fb *fb) { wlr_buffer_lock(fb->wlr_buf); return fb; } void drm_fb_move_fb(struct drm_fb **new, struct drm_fb **old) { drm_fb_clear_fb(new); *new = *old; *old = NULL; } void drm_fb_copy_fb(struct drm_fb **new, struct drm_fb *old) { drm_fb_clear_fb(new); if (old != NULL) { *new = drm_fb_lock_fb(old); } } /* drm_fb_import_from_wlr_buffer*/ bool drm_fb_import_fb(struct drm_fb **fb_ptr, struct drm_device *drm, struct wlr_buffer *buf, const struct wlr_drm_format_set *formats, bool use_yuyv) { struct drm_fb *fb; struct wlr_addon *addon = wlr_addon_find(&buf->addons, drm, &fb_addon_impl); if (addon) { fb = wl_container_of(addon, fb, addon); } else { fb = drm_fb_create(drm, buf, formats, use_yuyv); if (!fb) { return false; } } wlr_buffer_lock(buf); drm_fb_move_fb(fb_ptr, &fb); return true; } kylin-wayland-compositor/src/backend/drm/output.c0000664000175000017500000006035215160461067021137 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "drm_p.h" #include "output.h" #include "util/quirks.h" /** * From Redshift project: * https://github.com/jonls/redshift/blob/master/src/colorramp.c * * Copyright (c) 2013-2014 Jon Lund Steffensen * Copyright (c) 2013 Ingo Thies */ // clang-format off static const float blackbody_color[] = { 1.00000000, 0.18172716, 0.00000000, /* 1000K */ 1.00000000, 0.25503671, 0.00000000, /* 1100K */ 1.00000000, 0.30942099, 0.00000000, /* 1200K */ 1.00000000, 0.35357379, 0.00000000, /* ... */ 1.00000000, 0.39091524, 0.00000000, 1.00000000, 0.42322816, 0.00000000, 1.00000000, 0.45159884, 0.00000000, 1.00000000, 0.47675916, 0.00000000, 1.00000000, 0.49923747, 0.00000000, 1.00000000, 0.51943421, 0.00000000, 1.00000000, 0.54360078, 0.08679949, 1.00000000, 0.56618736, 0.14065513, 1.00000000, 0.58734976, 0.18362641, 1.00000000, 0.60724493, 0.22137978, 1.00000000, 0.62600248, 0.25591950, 1.00000000, 0.64373109, 0.28819679, 1.00000000, 0.66052319, 0.31873863, 1.00000000, 0.67645822, 0.34786758, 1.00000000, 0.69160518, 0.37579588, 1.00000000, 0.70602449, 0.40267128, 1.00000000, 0.71976951, 0.42860152, 1.00000000, 0.73288760, 0.45366838, 1.00000000, 0.74542112, 0.47793608, 1.00000000, 0.75740814, 0.50145662, 1.00000000, 0.76888303, 0.52427322, 1.00000000, 0.77987699, 0.54642268, 1.00000000, 0.79041843, 0.56793692, 1.00000000, 0.80053332, 0.58884417, 1.00000000, 0.81024551, 0.60916971, 1.00000000, 0.81957693, 0.62893653, 1.00000000, 0.82854786, 0.64816570, 1.00000000, 0.83717703, 0.66687674, 1.00000000, 0.84548188, 0.68508786, 1.00000000, 0.85347859, 0.70281616, 1.00000000, 0.86118227, 0.72007777, 1.00000000, 0.86860704, 0.73688797, 1.00000000, 0.87576611, 0.75326132, 1.00000000, 0.88267187, 0.76921169, 1.00000000, 0.88933596, 0.78475236, 1.00000000, 0.89576933, 0.79989606, 1.00000000, 0.90198230, 0.81465502, 1.00000000, 0.90963069, 0.82838210, 1.00000000, 0.91710889, 0.84190889, 1.00000000, 0.92441842, 0.85523742, 1.00000000, 0.93156127, 0.86836903, 1.00000000, 0.93853986, 0.88130458, 1.00000000, 0.94535695, 0.89404470, 1.00000000, 0.95201559, 0.90658983, 1.00000000, 0.95851906, 0.91894041, 1.00000000, 0.96487079, 0.93109690, 1.00000000, 0.97107439, 0.94305985, 1.00000000, 0.97713351, 0.95482993, 1.00000000, 0.98305189, 0.96640795, 1.00000000, 0.98883326, 0.97779486, 1.00000000, 0.99448139, 0.98899179, 1.00000000, 1.00000000, 1.00000000, /* 6500K */ 0.98947904, 0.99348723, 1.00000000, 0.97940448, 0.98722715, 1.00000000, 0.96975025, 0.98120637, 1.00000000, 0.96049223, 0.97541240, 1.00000000, 0.95160805, 0.96983355, 1.00000000, 0.94303638, 0.96443333, 1.00000000, 0.93480451, 0.95923080, 1.00000000, 0.92689056, 0.95421394, 1.00000000, 0.91927697, 0.94937330, 1.00000000, 0.91194747, 0.94470005, 1.00000000, 0.90488690, 0.94018594, 1.00000000, 0.89808115, 0.93582323, 1.00000000, 0.89151710, 0.93160469, 1.00000000, 0.88518247, 0.92752354, 1.00000000, 0.87906581, 0.92357340, 1.00000000, 0.87315640, 0.91974827, 1.00000000, 0.86744421, 0.91604254, 1.00000000, 0.86191983, 0.91245088, 1.00000000, 0.85657444, 0.90896831, 1.00000000, 0.85139976, 0.90559011, 1.00000000, 0.84638799, 0.90231183, 1.00000000, 0.84153180, 0.89912926, 1.00000000, 0.83682430, 0.89603843, 1.00000000, 0.83225897, 0.89303558, 1.00000000, 0.82782969, 0.89011714, 1.00000000, 0.82353066, 0.88727974, 1.00000000, 0.81935641, 0.88452017, 1.00000000, 0.81530175, 0.88183541, 1.00000000, 0.81136180, 0.87922257, 1.00000000, 0.80753191, 0.87667891, 1.00000000, 0.80380769, 0.87420182, 1.00000000, 0.80018497, 0.87178882, 1.00000000, 0.79665980, 0.86943756, 1.00000000, 0.79322843, 0.86714579, 1.00000000, 0.78988728, 0.86491137, 1.00000000, /* 10000K */ 0.78663296, 0.86273225, 1.00000000, 0.78346225, 0.86060650, 1.00000000, 0.78037207, 0.85853224, 1.00000000, 0.77735950, 0.85650771, 1.00000000, 0.77442176, 0.85453121, 1.00000000, 0.77155617, 0.85260112, 1.00000000, 0.76876022, 0.85071588, 1.00000000, 0.76603147, 0.84887402, 1.00000000, 0.76336762, 0.84707411, 1.00000000, 0.76076645, 0.84531479, 1.00000000, 0.75822586, 0.84359476, 1.00000000, 0.75574383, 0.84191277, 1.00000000, 0.75331843, 0.84026762, 1.00000000, 0.75094780, 0.83865816, 1.00000000, 0.74863017, 0.83708329, 1.00000000, 0.74636386, 0.83554194, 1.00000000, 0.74414722, 0.83403311, 1.00000000, 0.74197871, 0.83255582, 1.00000000, 0.73985682, 0.83110912, 1.00000000, 0.73778012, 0.82969211, 1.00000000, 0.73574723, 0.82830393, 1.00000000, 0.73375683, 0.82694373, 1.00000000, 0.73180765, 0.82561071, 1.00000000, 0.72989845, 0.82430410, 1.00000000, 0.72802807, 0.82302316, 1.00000000, 0.72619537, 0.82176715, 1.00000000, 0.72439927, 0.82053539, 1.00000000, 0.72263872, 0.81932722, 1.00000000, 0.72091270, 0.81814197, 1.00000000, 0.71922025, 0.81697905, 1.00000000, 0.71756043, 0.81583783, 1.00000000, 0.71593234, 0.81471775, 1.00000000, 0.71433510, 0.81361825, 1.00000000, 0.71276788, 0.81253878, 1.00000000, 0.71122987, 0.81147883, 1.00000000, 0.70972029, 0.81043789, 1.00000000, 0.70823838, 0.80941546, 1.00000000, 0.70678342, 0.80841109, 1.00000000, 0.70535469, 0.80742432, 1.00000000, 0.70395153, 0.80645469, 1.00000000, 0.70257327, 0.80550180, 1.00000000, 0.70121928, 0.80456522, 1.00000000, 0.69988894, 0.80364455, 1.00000000, 0.69858167, 0.80273941, 1.00000000, 0.69729688, 0.80184943, 1.00000000, 0.69603402, 0.80097423, 1.00000000, 0.69479255, 0.80011347, 1.00000000, 0.69357196, 0.79926681, 1.00000000, 0.69237173, 0.79843391, 1.00000000, 0.69119138, 0.79761446, 1.00000000, /* 15000K */ 0.69003044, 0.79680814, 1.00000000, 0.68888844, 0.79601466, 1.00000000, 0.68776494, 0.79523371, 1.00000000, 0.68665951, 0.79446502, 1.00000000, 0.68557173, 0.79370830, 1.00000000, 0.68450119, 0.79296330, 1.00000000, 0.68344751, 0.79222975, 1.00000000, 0.68241029, 0.79150740, 1.00000000, 0.68138918, 0.79079600, 1.00000000, 0.68038380, 0.79009531, 1.00000000, 0.67939381, 0.78940511, 1.00000000, 0.67841888, 0.78872517, 1.00000000, 0.67745866, 0.78805526, 1.00000000, 0.67651284, 0.78739518, 1.00000000, 0.67558112, 0.78674472, 1.00000000, 0.67466317, 0.78610368, 1.00000000, 0.67375872, 0.78547186, 1.00000000, 0.67286748, 0.78484907, 1.00000000, 0.67198916, 0.78423512, 1.00000000, 0.67112350, 0.78362984, 1.00000000, 0.67027024, 0.78303305, 1.00000000, 0.66942911, 0.78244457, 1.00000000, 0.66859988, 0.78186425, 1.00000000, 0.66778228, 0.78129191, 1.00000000, 0.66697610, 0.78072740, 1.00000000, 0.66618110, 0.78017057, 1.00000000, 0.66539706, 0.77962127, 1.00000000, 0.66462376, 0.77907934, 1.00000000, 0.66386098, 0.77854465, 1.00000000, 0.66310852, 0.77801705, 1.00000000, 0.66236618, 0.77749642, 1.00000000, 0.66163375, 0.77698261, 1.00000000, 0.66091106, 0.77647551, 1.00000000, 0.66019791, 0.77597498, 1.00000000, 0.65949412, 0.77548090, 1.00000000, 0.65879952, 0.77499315, 1.00000000, 0.65811392, 0.77451161, 1.00000000, 0.65743716, 0.77403618, 1.00000000, 0.65676908, 0.77356673, 1.00000000, 0.65610952, 0.77310316, 1.00000000, 0.65545831, 0.77264537, 1.00000000, 0.65481530, 0.77219324, 1.00000000, 0.65418036, 0.77174669, 1.00000000, 0.65355332, 0.77130560, 1.00000000, 0.65293404, 0.77086988, 1.00000000, 0.65232240, 0.77043944, 1.00000000, 0.65171824, 0.77001419, 1.00000000, 0.65112144, 0.76959404, 1.00000000, 0.65053187, 0.76917889, 1.00000000, 0.64994941, 0.76876866, 1.00000000, /* 20000K */ 0.64937392, 0.76836326, 1.00000000, 0.64880528, 0.76796263, 1.00000000, 0.64824339, 0.76756666, 1.00000000, 0.64768812, 0.76717529, 1.00000000, 0.64713935, 0.76678844, 1.00000000, 0.64659699, 0.76640603, 1.00000000, 0.64606092, 0.76602798, 1.00000000, 0.64553103, 0.76565424, 1.00000000, 0.64500722, 0.76528472, 1.00000000, 0.64448939, 0.76491935, 1.00000000, 0.64397745, 0.76455808, 1.00000000, 0.64347129, 0.76420082, 1.00000000, 0.64297081, 0.76384753, 1.00000000, 0.64247594, 0.76349813, 1.00000000, 0.64198657, 0.76315256, 1.00000000, 0.64150261, 0.76281076, 1.00000000, 0.64102399, 0.76247267, 1.00000000, 0.64055061, 0.76213824, 1.00000000, 0.64008239, 0.76180740, 1.00000000, 0.63961926, 0.76148010, 1.00000000, 0.63916112, 0.76115628, 1.00000000, 0.63870790, 0.76083590, 1.00000000, 0.63825953, 0.76051890, 1.00000000, 0.63781592, 0.76020522, 1.00000000, 0.63737701, 0.75989482, 1.00000000, 0.63694273, 0.75958764, 1.00000000, 0.63651299, 0.75928365, 1.00000000, 0.63608774, 0.75898278, 1.00000000, 0.63566691, 0.75868499, 1.00000000, 0.63525042, 0.75839025, 1.00000000, 0.63483822, 0.75809849, 1.00000000, 0.63443023, 0.75780969, 1.00000000, 0.63402641, 0.75752379, 1.00000000, 0.63362667, 0.75724075, 1.00000000, 0.63323097, 0.75696053, 1.00000000, 0.63283925, 0.75668310, 1.00000000, 0.63245144, 0.75640840, 1.00000000, 0.63206749, 0.75613641, 1.00000000, 0.63168735, 0.75586707, 1.00000000, 0.63131096, 0.75560036, 1.00000000, 0.63093826, 0.75533624, 1.00000000, 0.63056920, 0.75507467, 1.00000000, 0.63020374, 0.75481562, 1.00000000, 0.62984181, 0.75455904, 1.00000000, 0.62948337, 0.75430491, 1.00000000, 0.62912838, 0.75405319, 1.00000000, 0.62877678, 0.75380385, 1.00000000, 0.62842852, 0.75355685, 1.00000000, 0.62808356, 0.75331217, 1.00000000, 0.62774186, 0.75306977, 1.00000000, /* 25000K */ 0.62740336, 0.75282962, 1.00000000 /* 25100K */ }; static float default_mat[] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; static float grayscale_mat[] = { 0.299f, 0.299f, 0.299f, 0.0f, 0.587f, 0.587f, 0.587f, 0.0f, 0.114f, 0.114f, 0.114f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; static float invert_mat[] = { -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f }; static float protanopia_mat[] = { 1.0f, 0.5836f, 0.6384f, 0.0f, 0.0f, 0.4146f, -0.6384f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; static float deuteranopia_mat[] = { 1.4027f, 0.1740f, -0.2818f, 0.0f, -0.8266f, 0.6429f, 0.5784f, 0.0f, 0.4240f, 0.1832f, 0.7032f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; static float tritanopia_mat[] = { 1.0f, 0.0f, 0.0f, 0.0f, -0.805712f, 0.378838f, 0.104823f, 0.0f, 0.805712f, 0.621162f, 0.895177f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; // clang-format on /* Helper macro used in the fill functions */ #define F(Y, C) pow((Y) * _brightness * whitepoint[C], 1.0) static void interpolate_clolor(float a, const float *c1, const float *c2, float *c) { c[0] = (1.0 - a) * c1[0] + a * c2[0]; c[1] = (1.0 - a) * c1[1] + a * c2[1]; c[2] = (1.0 - a) * c1[2] + a * c2[2]; } void drm_color_temp_to_rgb(uint32_t color_temp, float *rgb) { float alpha = (color_temp % 100) / 100.; int bbc_index = ((color_temp - 1000) / 100) * 3; interpolate_clolor(alpha, &blackbody_color[bbc_index], &blackbody_color[bbc_index + 3], rgb); } static void fill_gamma_ramp_with_color_temp(struct drm_color_lut *gamma_lut, uint32_t gamma_size, uint32_t color_temp, uint32_t brightness, bool invert) { /* linear default state or invert state */ for (uint32_t i = 0; i < gamma_size; i++) { uint16_t value = (double)i / gamma_size * (UINT16_MAX + 1); value = invert ? UINT16_MAX - value : value; gamma_lut[i].red = value; gamma_lut[i].green = value; gamma_lut[i].blue = value; } /* approximate white point */ float whitepoint[3]; drm_color_temp_to_rgb(color_temp, whitepoint); float _brightness = (float)brightness / 100; /* Helper macro used in the fill functions */ for (uint32_t i = 0; i < gamma_size; i++) { gamma_lut[i].red = F((double)gamma_lut[i].red / (UINT16_MAX + 1), 0) * (UINT16_MAX + 1); gamma_lut[i].green = F((double)gamma_lut[i].green / (UINT16_MAX + 1), 1) * (UINT16_MAX + 1); gamma_lut[i].blue = F((double)gamma_lut[i].blue / (UINT16_MAX + 1), 2) * (UINT16_MAX + 1); } } static bool output_state_set_gamma_lut(struct drm_output_state *state, struct drm_color_lut *gamma_lut, size_t gamma_size) { free(state->lut); state->lut = gamma_lut; state->lut_size = gamma_size; state->committed |= DRM_OUTPUT_STATE_GAMMA_LUT; return true; } static void output_set_brightness_state(struct output *output) { struct drm_connector *conn = drm_connector_from_output(output->wlr_output); if (!conn || !conn->props[DRM_CONNECTOR_PROP_BRIGHTNESS].id) { return; } conn->state.brightness = output->base.state.brightness; conn->state.committed |= DRM_OUTPUT_STATE_BRIGHTNESS; } static void output_set_gamma_lut_state(struct output *output) { if (output->quirks & QUIRKS_MASK_DISABLE_GAMMA) { return; } struct drm_connector *conn = drm_connector_from_output(output->wlr_output); if (!conn || !conn->crtc) { return; } size_t gamma_size = drm_get_crtc_gamma_lut_size(conn->crtc); if (gamma_size <= 1) { return; } uint32_t color_temp = output->base.state.color_temp; uint32_t brightness = conn->props[DRM_CONNECTOR_PROP_BRIGHTNESS].id ? 100 : output->base.state.brightness; struct drm_color_lut *gamma_lut = malloc(gamma_size * sizeof(struct drm_color_lut)); fill_gamma_ramp_with_color_temp(gamma_lut, gamma_size, color_temp, brightness, false); output_state_set_gamma_lut(&conn->state, gamma_lut, gamma_size); } static void output_set_color_ctm_state(struct output *output) { if (output->quirks & QUIRKS_MASK_DISABLE_CTM) { return; } struct drm_connector *conn = drm_connector_from_output(output->wlr_output); if (!conn || !conn->crtc || conn->crtc->props[DRM_CRTC_PROP_CTM].id == 0) { return; } float color_ctm[9] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 }; if (output->base.state.color_filter != KYWC_OUTPUT_COLOR_FILTER_INVERT) { const float *mat = drm_output_get_color_filter_matrix(output->base.state.color_filter); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { color_ctm[i * 3 + j] = mat[j * 4 + i]; } } } for (int i = 0; i < 9; i++) { // ctm values are in S31.32 sign-magnitude format uint64_t value = fabs(color_ctm[i]) * (1ull << 32); if (color_ctm[i] < 0) { value |= 1ull << 63; } conn->state.rgb_ctm.matrix[i] = value; } conn->state.committed |= DRM_OUTPUT_STATE_RGB_CTM; } static bool drm_output_is_force_soft_color(void) { char *env = getenv("KYWC_SOFTWARE_COLOR"); if (env && strcmp(env, "1") == 0) { return true; } return false; } void drm_output_set_state(struct output *output) { struct drm_connector *conn = drm_connector_from_output(output->wlr_output); if (!conn->state.color_changed) { return; } conn->state.color_changed = false; if (drm_output_is_force_soft_color()) { return; } output_set_brightness_state(output); output_set_gamma_lut_state(output); output_set_color_ctm_state(output); } void wlr_output_set_buffer_damage(struct wlr_output *wlr_output, const pixman_region32_t *damage) { if (!wlr_output_is_drm(wlr_output)) { return; } struct drm_connector *conn = drm_connector_from_output(wlr_output); if (!conn) { return; } pixman_region32_copy(&conn->state.damage, damage); conn->state.committed |= DRM_OUTPUT_STATE_DAMAGE; return; } void wlr_output_set_color_changed(struct wlr_output *wlr_output, bool changed) { if (!wlr_output_is_drm(wlr_output)) { return; } struct drm_connector *conn = drm_connector_from_output(wlr_output); if (!conn) { return; } conn->state.color_changed = changed; } void wlr_output_set_rgb_range(struct wlr_output *wlr_output, uint32_t rgb_range) { if (!wlr_output_is_drm(wlr_output)) { return; } struct drm_connector *conn = drm_connector_from_output(wlr_output); if (!conn) { return; } if (!conn->props[DRM_CONNECTOR_PROP_RGB_RANGE].id) { return; } if (rgb_range > KYWC_OUTPUT_RGB_RANGE_LIMITED) { return; } conn->state.committed |= DRM_OUTPUT_STATE_RGB_RANGE; conn->state.rgb_range = rgb_range; } void wlr_output_set_overscan(struct wlr_output *wlr_output, uint32_t overscan) { if (!wlr_output_is_drm(wlr_output)) { return; } struct drm_connector *conn = drm_connector_from_output(wlr_output); if (!conn) { return; } if (!conn->props[DRM_CONNECTOR_PROP_OVERSCAN].id) { return; } conn->state.committed |= DRM_OUTPUT_STATE_OVERSCAN; conn->state.overscan = overscan; } void wlr_output_set_scaling_mode(struct wlr_output *wlr_output, uint32_t scaling_mode) { if (!wlr_output_is_drm(wlr_output)) { return; } struct drm_connector *conn = drm_connector_from_output(wlr_output); if (!conn) { return; } if (!conn->props[DRM_CONNECTOR_PROP_SCALING_MODE].id) { return; } if ((scaling_mode > KYWC_OUTPUT_SCALING_MODE_FULL_ASPECT) || (!conn->has_none_mode && scaling_mode == KYWC_OUTPUT_SCALING_MODE_NONE)) { return; } conn->state.committed |= DRM_OUTPUT_STATE_SCALING_MODE; conn->state.scaling_mode = scaling_mode; } void drm_output_state_clear(struct drm_output_state *state) { free(state->lut); state->lut = NULL; state->committed = 0; } const void *wlr_output_get_edid(struct wlr_output *wlr_output, size_t *length) { if (!wlr_output_is_drm(wlr_output)) { return NULL; } struct drm_connector *conn = drm_connector_from_output(wlr_output); if (!conn) { return NULL; } *length = conn->edid_len; return conn->edid; } bool wlr_output_get_vrr_capable(struct wlr_output *wlr_output) { if (!wlr_output_is_drm(wlr_output)) { return false; } struct drm_connector *conn = drm_connector_from_output(wlr_output); return conn && conn->adaptive_sync_supported; } bool wlr_output_get_rgb_range(struct wlr_output *wlr_output, uint32_t *rgb_range) { if (!wlr_output_is_drm(wlr_output)) { return false; } struct drm_connector *conn = drm_connector_from_output(wlr_output); if (!conn) { return false; } struct drm_prop_info *prop = &conn->props[DRM_CONNECTOR_PROP_RGB_RANGE]; if (!prop->id) { return false; } *rgb_range = conn->rgb_range; return true; } bool wlr_output_get_overscan(struct wlr_output *wlr_output, uint32_t *overscan) { if (!wlr_output_is_drm(wlr_output)) { return false; } struct drm_connector *conn = drm_connector_from_output(wlr_output); if (!conn) { return false; } struct drm_prop_info *prop = &conn->props[DRM_CONNECTOR_PROP_OVERSCAN]; if (!prop->id) { return false; } *overscan = conn->overscan; return false; } bool wlr_output_get_scaling_mode(struct wlr_output *wlr_output, uint32_t *scaling_mode) { if (!wlr_output_is_drm(wlr_output)) { return false; } struct drm_connector *conn = drm_connector_from_output(wlr_output); if (!conn) { return false; } struct drm_prop_info *prop = &conn->props[DRM_CONNECTOR_PROP_SCALING_MODE]; if (!prop->id) { return false; } *scaling_mode = conn->scaling_mode; return true; } const float *drm_output_get_color_filter_matrix(enum kywc_output_color_filter color_filter) { switch (color_filter) { case KYWC_OUTPUT_COLOR_FILTER_PROTANOPIA: return protanopia_mat; case KYWC_OUTPUT_COLOR_FILTER_DEUTERANOPIA: return deuteranopia_mat; case KYWC_OUTPUT_COLOR_FILTER_TRITANOPIA: return tritanopia_mat; case KYWC_OUTPUT_COLOR_FILTER_GRAYSCALE: return grayscale_mat; case KYWC_OUTPUT_COLOR_FILTER_INVERT: return invert_mat; default: return default_mat; } } bool drm_output_use_hw_brightness(struct wlr_output *wlr_output, const struct kywc_output_state *state) { if (drm_output_is_force_soft_color()) { return state->brightness == 100; } struct drm_connector *conn = drm_connector_from_output(wlr_output); if (!conn || !conn->crtc) { return false; } if (state->color_feature == KYWC_OUTPUT_COLOR_FEATURE_DISABLE) { return true; } if (state->color_feature == KYWC_OUTPUT_COLOR_FEATURE_SOFTCOLOR) { return false; } if (state->color_feature == KYWC_OUTPUT_COLOR_FEATURE_SOFTGAMMA) { return conn->props[DRM_CONNECTOR_PROP_BRIGHTNESS].id; } struct output *output = output_from_wlr_output(wlr_output); if (output->quirks & QUIRKS_MASK_DISABLE_GAMMA) { return conn->props[DRM_CONNECTOR_PROP_BRIGHTNESS].id; } uint32_t gamma_size = drm_get_crtc_gamma_lut_size(conn->crtc); return state->brightness == 100 || conn->props[DRM_CONNECTOR_PROP_BRIGHTNESS].id || gamma_size > 1; } bool drm_output_use_hw_color_temp(struct wlr_output *wlr_output, const struct kywc_output_state *state) { if (drm_output_is_force_soft_color()) { return state->color_temp == 0 || state->color_temp == 6500; } struct drm_connector *conn = drm_connector_from_output(wlr_output); if (!conn || !conn->crtc) { return false; } if (state->color_feature == KYWC_OUTPUT_COLOR_FEATURE_DISABLE) { return true; } if (state->color_feature == KYWC_OUTPUT_COLOR_FEATURE_SOFTCOLOR || state->color_feature == KYWC_OUTPUT_COLOR_FEATURE_SOFTGAMMA) { return false; } struct output *output = output_from_wlr_output(wlr_output); if (output->quirks & QUIRKS_MASK_DISABLE_GAMMA) { return false; } uint32_t gamma_size = drm_get_crtc_gamma_lut_size(conn->crtc); return state->color_temp == 0 || state->color_temp == 6500 || gamma_size > 1; } bool drm_output_use_hw_color_filter(struct wlr_output *wlr_output, const struct kywc_output_state *state) { if (drm_output_is_force_soft_color()) { return !state->color_filter; } struct drm_connector *conn = drm_connector_from_output(wlr_output); if (!conn || !conn->crtc) { return false; } if (state->color_feature == KYWC_OUTPUT_COLOR_FEATURE_DISABLE) { return true; } if (state->color_feature == KYWC_OUTPUT_COLOR_FEATURE_SOFTCOLOR || state->color_feature == KYWC_OUTPUT_COLOR_FEATURE_SOFTCTM) { return false; } struct output *output = output_from_wlr_output(wlr_output); if (output->quirks & QUIRKS_MASK_DISABLE_CTM) { return false; } return !state->color_filter || (state->color_filter != KYWC_OUTPUT_COLOR_FILTER_INVERT && conn->crtc->props[DRM_CRTC_PROP_CTM].id); } bool wlr_output_is_hardware_color_allowed(struct wlr_output *wlr_output, const struct kywc_output_state *state) { if (!wlr_output_is_drm(wlr_output)) { return false; } return drm_output_use_hw_brightness(wlr_output, state) && drm_output_use_hw_color_temp(wlr_output, state) && drm_output_use_hw_color_filter(wlr_output, state); } bool drm_output_yuyv_enabled(struct wlr_output *wlr_output, const struct kywc_output_state *state) { struct output *output = output_from_wlr_output(wlr_output); if (output->quirks & QUIRKS_MASK_YUYV_MODE) { return true; } return false; } kylin-wayland-compositor/src/backend/drm/monitor.c0000664000175000017500000001003315160460057021253 0ustar fengfeng// SPDX-FileCopyrightText: 2025 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "drm_p.h" static void drm_backend_monitor_destroy(struct drm_backend_monitor *monitor) { wl_list_remove(&monitor->session_add_drm_card.link); wl_list_remove(&monitor->session_destroy.link); wl_list_remove(&monitor->primary_drm_destroy.link); wl_list_remove(&monitor->multi_destroy.link); free(monitor); } static struct wlr_device *session_open_if_kms(struct wlr_session *restrict session, const char *restrict path) { if (!path) { return NULL; } struct wlr_device *dev = wlr_session_open_file(session, path); if (!dev) { return NULL; } if (!drmIsKMS(dev->fd)) { kywc_log(KYWC_DEBUG, "Ignoring '%s': not a KMS device", path); wlr_session_close_file(session, dev); return NULL; } return dev; } static void handle_add_drm_card(struct wl_listener *listener, void *data) { struct wlr_session_add_event *event = data; struct drm_backend_monitor *backend_monitor = wl_container_of(listener, backend_monitor, session_add_drm_card); struct wlr_device *dev = session_open_if_kms(backend_monitor->session, event->path); if (!dev) { kywc_log(KYWC_ERROR, "Unable to open %s as DRM device", event->path); return; } kywc_log(KYWC_DEBUG, "Creating DRM backend for %s after hotplug", event->path); struct wlr_backend *child_drm = drm_backend_create(backend_monitor->session, dev, backend_monitor->primary_drm); if (!child_drm) { kywc_log(KYWC_ERROR, "Failed to create DRM backend after hotplug"); return; } if (!wlr_multi_backend_add(backend_monitor->multi, child_drm)) { kywc_log(KYWC_ERROR, "Failed to add new drm backend to multi backend"); wlr_backend_destroy(child_drm); return; } if (!wlr_backend_start(child_drm)) { kywc_log(KYWC_ERROR, "Failed to start new child DRM backend"); wlr_backend_destroy(child_drm); } } static void handle_session_destroy(struct wl_listener *listener, void *data) { struct drm_backend_monitor *backend_monitor = wl_container_of(listener, backend_monitor, session_destroy); drm_backend_monitor_destroy(backend_monitor); } static void handle_primary_drm_destroy(struct wl_listener *listener, void *data) { struct drm_backend_monitor *backend_monitor = wl_container_of(listener, backend_monitor, primary_drm_destroy); drm_backend_monitor_destroy(backend_monitor); } static void handle_multi_destroy(struct wl_listener *listener, void *data) { struct drm_backend_monitor *backend_monitor = wl_container_of(listener, backend_monitor, multi_destroy); drm_backend_monitor_destroy(backend_monitor); } struct drm_backend_monitor *drm_backend_monitor_create(struct wlr_backend *multi, struct wlr_backend *primary_drm, struct wlr_session *session) { struct drm_backend_monitor *monitor = calloc(1, sizeof(*monitor)); if (!monitor) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); return NULL; } monitor->multi = multi; monitor->primary_drm = primary_drm; monitor->session = session; monitor->session_add_drm_card.notify = handle_add_drm_card; wl_signal_add(&session->events.add_drm_card, &monitor->session_add_drm_card); monitor->session_destroy.notify = handle_session_destroy; wl_signal_add(&session->events.destroy, &monitor->session_destroy); monitor->primary_drm_destroy.notify = handle_primary_drm_destroy; wl_signal_add(&primary_drm->events.destroy, &monitor->primary_drm_destroy); monitor->multi_destroy.notify = handle_multi_destroy; wl_signal_add(&multi->events.destroy, &monitor->multi_destroy); return monitor; } kylin-wayland-compositor/src/backend/drm/drm.c0000664000175000017500000021545215160461067020364 0ustar fengfeng// SPDX-FileCopyrightText: 2025 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include "drm_p.h" #include "output.h" // Output state which needs a KMS commit to be applied static const uint32_t COMMIT_OUTPUT_STATE = WLR_OUTPUT_STATE_BUFFER | WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_ENABLED | WLR_OUTPUT_STATE_GAMMA_LUT | WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED; static const uint32_t SUPPORTED_OUTPUT_STATE = WLR_OUTPUT_STATE_BACKEND_OPTIONAL | COMMIT_OUTPUT_STATE; static const int32_t subpixel_map[] = { [DRM_MODE_SUBPIXEL_UNKNOWN] = WL_OUTPUT_SUBPIXEL_UNKNOWN, [DRM_MODE_SUBPIXEL_HORIZONTAL_RGB] = WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB, [DRM_MODE_SUBPIXEL_HORIZONTAL_BGR] = WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR, [DRM_MODE_SUBPIXEL_VERTICAL_RGB] = WL_OUTPUT_SUBPIXEL_VERTICAL_RGB, [DRM_MODE_SUBPIXEL_VERTICAL_BGR] = WL_OUTPUT_SUBPIXEL_VERTICAL_BGR, [DRM_MODE_SUBPIXEL_NONE] = WL_OUTPUT_SUBPIXEL_NONE, }; static struct drm_device *drm_device_get_parent(struct drm_device *drm) { struct drm_device *drm_parent = NULL; if (wlr_backend_is_drm(drm->wlr_backend)) { struct drm_backend *backend = drm_backend_from_wlr_backend(drm->wlr_backend)->parent; drm_parent = backend ? backend->drm : NULL; } return drm_parent; } static bool drm_check_features(struct drm_device *drm) { if (drmGetCap(drm->fd, DRM_CAP_CURSOR_WIDTH, &drm->cursor_width)) { drm->cursor_width = 64; } if (drmGetCap(drm->fd, DRM_CAP_CURSOR_HEIGHT, &drm->cursor_height)) { drm->cursor_height = 64; } if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { kywc_log(KYWC_ERROR, "DRM universal planes unsupported"); return false; } uint64_t cap; if (drmGetCap(drm->fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap) || !cap) { kywc_log(KYWC_ERROR, "DRM_CRTC_IN_VBLANK_EVENT unsupported"); return false; } if (drmGetCap(drm->fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap) || !cap) { kywc_log(KYWC_ERROR, "DRM_CAP_TIMESTAMP_MONOTONIC unsupported"); return false; } char *env = getenv("KYWC_DRM_NO_ATOMIC"); if (env && strcmp(env, "1") == 0) { drm->mode = DRM_KMS_MODE_LEGACY; kywc_log(KYWC_DEBUG, "KYWC_DRM_NO_ATOMIC set, forcing legacy DRM interface"); } else if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_ATOMIC, 1)) { drm->mode = DRM_KMS_MODE_LEGACY; kywc_log(KYWC_DEBUG, "Atomic modesetting unsupported, using legacy DRM interface"); } else { drm->mode = DRM_KMS_MODE_ATOMIC; kywc_log(KYWC_DEBUG, "Using atomic DRM interface"); } #ifdef DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT if (drm->mode == DRM_KMS_MODE_ATOMIC && drmSetClientCap(drm->fd, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT, 1) == 0) { kywc_log(KYWC_INFO, "DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT supported"); } #endif if (drm->mode == DRM_KMS_MODE_LEGACY) { drm->supports_tearing_page_flips = drmGetCap(drm->fd, DRM_CAP_ASYNC_PAGE_FLIP, &cap) == 0 && cap == 1; } else { drm->supports_tearing_page_flips = drmGetCap(drm->fd, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, &cap) == 0 && cap == 1; } env = getenv("KYWC_DRM_NO_MODIFIERS"); if (env && strcmp(env, "1") == 0) { kywc_log(KYWC_DEBUG, "KYWC_DRM_NO_MODIFIERS set, disabling modifiers"); } else { int ret = drmGetCap(drm->fd, DRM_CAP_ADDFB2_MODIFIERS, &cap); drm->addfb2_modifiers = ret == 0 && cap == 1; kywc_log(KYWC_DEBUG, "ADDFB2 modifiers %s", drm->addfb2_modifiers ? "supported" : "unsupported"); } return true; } static bool init_plane_cursor_sizes(struct drm_plane *plane, const struct drm_plane_size_hint *hints, size_t hints_len) { assert(hints_len > 0); plane->cursor_sizes = calloc(hints_len, sizeof(plane->cursor_sizes[0])); if (plane->cursor_sizes == NULL) { return false; } plane->cursor_sizes_len = hints_len; for (size_t i = 0; i < hints_len; i++) { const struct drm_plane_size_hint hint = hints[i]; plane->cursor_sizes[i] = (struct wlr_output_cursor_size){ .width = hint.width, .height = hint.height, }; } return true; } static bool init_plane(struct drm_device *drm, struct drm_plane *plane, const drmModePlane *drm_plane) { plane->id = drm_plane->plane_id; if (!drm_get_plane_props(drm->fd, plane->id, plane->props)) { return false; } uint64_t type; if (!drm_get_property_value(drm->fd, DRM_MODE_OBJECT_PLANE, plane->id, &plane->props[DRM_PLANE_PROP_TYPE], &type)) { return false; } plane->type = type; plane->crtc_id = drm_plane->crtc_id; for (size_t i = 0; i < drm_plane->count_formats; ++i) { // Force a LINEAR layout for the cursor if the driver doesn't support modifiers wlr_drm_format_set_add(&plane->formats, drm_plane->formats[i], DRM_FORMAT_MOD_LINEAR); if (plane->type != DRM_PLANE_TYPE_CURSOR) { wlr_drm_format_set_add(&plane->formats, drm_plane->formats[i], DRM_FORMAT_MOD_INVALID); } } if (plane->props[DRM_PLANE_PROP_IN_FORMATS].id && drm->addfb2_modifiers) { uint64_t blob_id; if (!drm_get_property_value(drm->fd, DRM_MODE_OBJECT_PLANE, plane->id, &plane->props[DRM_PLANE_PROP_IN_FORMATS], &blob_id)) { kywc_log(KYWC_ERROR, "Failed to read IN_FORMATS property"); return false; } drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(drm->fd, blob_id); if (!blob) { kywc_log(KYWC_ERROR, "Failed to read IN_FORMATS blob"); return false; } drmModeFormatModifierIterator iter = { 0 }; while (drmModeFormatModifierBlobIterNext(blob, &iter)) { wlr_drm_format_set_add(&plane->formats, iter.fmt, iter.mod); } drmModeFreePropertyBlob(blob); } uint64_t size_hints_blob_id = 0; if (plane->props[DRM_PLANE_PROP_SIZE_HINTS].id) { if (!drm_get_property_value(drm->fd, DRM_MODE_OBJECT_PLANE, plane->id, &plane->props[DRM_PLANE_PROP_SIZE_HINTS], &size_hints_blob_id)) { kywc_log(KYWC_ERROR, "Failed to read SIZE_HINTS property"); return false; } } if (size_hints_blob_id != 0) { drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(drm->fd, size_hints_blob_id); if (!blob) { kywc_log(KYWC_ERROR, "Failed to read SIZE_HINTS blob"); return false; } const struct drm_plane_size_hint *size_hints = blob->data; size_t size_hints_len = blob->length / sizeof(size_hints[0]); if (!init_plane_cursor_sizes(plane, size_hints, size_hints_len)) { return false; } drmModeFreePropertyBlob(blob); } else { const struct drm_plane_size_hint size_hint = { .width = drm->cursor_width, .height = drm->cursor_height, }; if (!init_plane_cursor_sizes(plane, &size_hint, 1)) { return false; } } assert(drm->num_crtcs <= 32); for (size_t j = 0; j < drm->num_crtcs; j++) { uint32_t crtc_bit = 1 << j; if ((drm_plane->possible_crtcs & crtc_bit) == 0) { continue; } struct drm_crtc *crtc = &drm->crtcs[j]; if (plane->type == DRM_PLANE_TYPE_PRIMARY && !crtc->primary) { crtc->primary = plane; break; } if (type == DRM_PLANE_TYPE_CURSOR && !crtc->cursor) { crtc->cursor = plane; break; } } return true; } static void add_cursor_plane(struct drm_device *drm, drmModePlaneRes *plane_res, uint32_t *crtcs) { if (drm->mode != DRM_KMS_MODE_LEGACY) { return; } for (uint32_t i = 0; i < drm->num_planes; ++i) { uint32_t id = plane_res->planes[i]; struct drm_prop_info props[DRM_PLANE_PROP_COUNT]; if (!drm_get_plane_props(drm->fd, id, props)) { return; } uint64_t type; if (!drm_get_property_value(drm->fd, DRM_MODE_OBJECT_PLANE, id, &props[DRM_PLANE_PROP_TYPE], &type)) { return; } if (type == DRM_PLANE_TYPE_CURSOR) { // if cursor plane exits, do not add any return; } } // check each crtc whether hardware cursor is supported for (size_t i = 0; i < drm->num_crtcs; ++i) { if (!drmModeSetCursor(drm->fd, drm->crtcs[i].id, 0, 0, 0)) { kywc_log(KYWC_DEBUG, "Going to add cursor plane for crtc %d", drm->crtcs[i].id); ++drm->num_planes; *crtcs |= 1 << i; } } } static bool init_planes(struct drm_device *drm) { drmModePlaneRes *plane_res = drmModeGetPlaneResources(drm->fd); if (!plane_res) { kywc_log_errno(KYWC_ERROR, "Failed to get DRM plane resources"); return false; } kywc_log(KYWC_INFO, "Found %" PRIu32 " DRM planes", plane_res->count_planes); drm->num_planes = plane_res->count_planes; /** * when drm works on legacy instead of atomic, * hardware cursor do not work without cursor plane, * add cursor plane for crtcs support hardware cursor. * * drm->num_planes may increase here. */ uint32_t crtcs = 0; add_cursor_plane(drm, plane_res, &crtcs); drm->planes = calloc(drm->num_planes, sizeof(*drm->planes)); if (drm->planes == NULL) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); goto error; } for (uint32_t i = 0; i < plane_res->count_planes; ++i) { uint32_t id = plane_res->planes[i]; drmModePlane *drm_plane = drmModeGetPlane(drm->fd, id); if (!drm_plane) { kywc_log_errno(KYWC_ERROR, "Failed to get DRM plane"); goto error; } struct drm_plane *plane = &drm->planes[i]; if (!init_plane(drm, plane, drm_plane)) { goto error; } drmModeFreePlane(drm_plane); } for (uint32_t i = 0, j = plane_res->count_planes; i < drm->num_crtcs; ++i) { if (~crtcs & 1 << i) { continue; } struct drm_plane *cursor_plane = &drm->planes[j++]; // add format ARGB8888 for cursor plane wlr_drm_format_set_add(&cursor_plane->formats, DRM_FORMAT_ARGB8888, DRM_FORMAT_MOD_LINEAR); cursor_plane->type = DRM_PLANE_TYPE_CURSOR; drm->crtcs[i].cursor = cursor_plane; } drmModeFreePlaneResources(plane_res); return true; error: free(drm->planes); drmModeFreePlaneResources(plane_res); return false; } static bool drm_init_resources(struct drm_device *drm) { drmModeRes *res = drmModeGetResources(drm->fd); if (!res) { kywc_log_errno(KYWC_ERROR, "Failed to get DRM resources"); return false; } kywc_log(KYWC_INFO, "Found %d DRM CRTCs", res->count_crtcs); drm->num_crtcs = res->count_crtcs; if (drm->num_crtcs == 0) { drmModeFreeResources(res); return true; } drm->crtcs = calloc(drm->num_crtcs, sizeof(drm->crtcs[0])); if (!drm->crtcs) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); goto error_res; } for (size_t i = 0; i < drm->num_crtcs; ++i) { struct drm_crtc *crtc = &drm->crtcs[i]; crtc->drm = drm; crtc->id = res->crtcs[i]; drmModeCrtc *drm_crtc = drmModeGetCrtc(drm->fd, crtc->id); if (drm_crtc == NULL) { kywc_log_errno(KYWC_ERROR, "Failed with drmModeGetCrtc"); goto error_crtcs; } crtc->legacy_gamma_size = drm_crtc->gamma_size; drmModeFreeCrtc(drm_crtc); if (!drm_get_crtc_props(drm->fd, crtc->id, crtc->props)) { goto error_crtcs; } } if (!init_planes(drm)) { goto error_crtcs; } drmModeFreeResources(res); return true; error_crtcs: free(drm->crtcs); error_res: drmModeFreeResources(res); return false; } static void drm_destroy_page_flip(struct drm_page_flip *page_flip) { if (!page_flip) { return; } wl_list_remove(&page_flip->link); free(page_flip); } static int mhz_to_nsec(int mhz) { return 1000000000000LL / mhz; } static void drm_plane_fb_finish(struct drm_plane *plane) { if (!plane) { return; } drm_fb_clear_fb(&plane->queued_fb); drm_fb_clear_fb(&plane->current_fb); drm_surface_finish(&plane->multi_surf); } /* drm connector */ static void deallocate_crtc(struct drm_connector *conn); static struct drm_crtc *get_drm_connector_current_crtc(struct drm_connector *conn, const drmModeConnector *drm_conn) { uint32_t crtc_id = 0; struct drm_device *drm = conn->drm; uint32_t crtc_prop = conn->props[DRM_CONNECTOR_PROP_CRTC_ID].id; if (crtc_prop) { uint64_t value; if (!drm_get_property_value(conn->drm->fd, DRM_MODE_OBJECT_CONNECTOR, conn->id, &conn->props[DRM_CONNECTOR_PROP_CRTC_ID], &value)) { kywc_log(KYWC_ERROR, "Failed to get CRTC_ID connector property"); return NULL; } crtc_id = (uint32_t)value; } else if (drm_conn->encoder_id != 0) { // Fallback to the legacy API drmModeEncoder *enc = drmModeGetEncoder(drm->fd, drm_conn->encoder_id); if (enc == NULL) { kywc_log_errno(KYWC_ERROR, "Failed with drmModeGetEncoder"); return NULL; } crtc_id = enc->crtc_id; drmModeFreeEncoder(enc); } if (!crtc_id) { return NULL; } for (size_t i = 0; i < drm->num_crtcs; ++i) { if (drm->crtcs[i].id == crtc_id) { return &drm->crtcs[i]; } } kywc_log(KYWC_ERROR, "Failed to find current CRTC ID %" PRIu32, crtc_id); return NULL; } static drmModeModeInfo *get_connector_current_mode(struct drm_connector *conn) { if (!conn->crtc) { return NULL; } struct drm_device *drm = conn->drm; uint32_t mode_id = conn->crtc->props[DRM_CRTC_PROP_MODE_ID].id; if (mode_id) { size_t size = 0; drmModeModeInfo *mode = drm_get_property_blob(drm->fd, DRM_MODE_OBJECT_CRTC, conn->crtc->id, &conn->crtc->props[DRM_CRTC_PROP_MODE_ID], &size); return mode; } else { // Fallback to the legacy API drmModeCrtc *drm_crtc = drmModeGetCrtc(drm->fd, conn->crtc->id); if (drm_crtc == NULL) { kywc_log_errno(KYWC_ERROR, "Failed with drmModeGetCrtc"); return NULL; } if (!drm_crtc->mode_valid) { drmModeFreeCrtc(drm_crtc); return NULL; } drmModeModeInfo *mode = malloc(sizeof(*mode)); if (mode == NULL) { kywc_log_errno(KYWC_ERROR, "Allocation drmModeModeInfo failed"); drmModeFreeCrtc(drm_crtc); return NULL; } *mode = drm_crtc->mode; drmModeFreeCrtc(drm_crtc); return mode; } } static struct drm_mode *drm_mode_create(const drmModeModeInfo *modeinfo) { struct drm_mode *mode = calloc(1, sizeof(*mode)); if (!mode) { return NULL; } mode->drm_mode = *modeinfo; mode->wlr_mode.width = mode->drm_mode.hdisplay; mode->wlr_mode.height = mode->drm_mode.vdisplay; mode->wlr_mode.refresh = drm_util_calculate_refresh_rate(modeinfo); mode->wlr_mode.picture_aspect_ratio = drm_util_get_picture_aspect_ratio(modeinfo); mode->wlr_mode.preferred = modeinfo->type & DRM_MODE_TYPE_PREFERRED; return mode; } struct drm_connector *drm_connector_from_output(struct wlr_output *wlr_output) { struct drm_connector *conn = wl_container_of(wlr_output, conn, output); return conn; } static bool drm_connector_update_cursor(struct drm_connector *conn, struct wlr_buffer *buffer) { if (!buffer) { return false; } struct wlr_buffer *local_buf = NULL; struct drm_device *drm = conn->drm; struct output *output = output_from_wlr_output(&conn->output); struct kywc_output *kywc_output = &output->base; if (conn->cursor_buffer != buffer) { wlr_buffer_unlock(conn->cursor_buffer); conn->cursor_buffer = wlr_buffer_lock(buffer); } bool default_brightness = kywc_output->state.brightness == 100; bool default_color_temp = (kywc_output->state.color_temp == 0 || kywc_output->state.color_temp == 6500); bool default_color_filter = !kywc_output->state.color_filter; bool default_color = kywc_output->state.color_feature == KYWC_OUTPUT_COLOR_FEATURE_DISABLE || (default_color_filter && default_color_temp && default_brightness); bool main_gpu = !drm_device_get_parent(drm); bool buffer_is_shm = conn->output.allocator->buffer_caps & WLR_BUFFER_CAP_SHM; if (!main_gpu || !default_color || buffer_is_shm) { uint32_t format = DRM_FORMAT_ARGB8888; struct drm_surface *surface = &conn->crtc->cursor->multi_surf; if (!drm_plane_configure_surface_swapchain( conn->crtc->cursor, conn, main_gpu ? NULL : &drm->mgpu_renderer, buffer->width, buffer->height, format, NULL, false)) { return false; } struct drm_render_target target = { .source = buffer, .brightness = kywc_output->state.brightness, .color_temp = default_color_temp ? 6500 : kywc_output->state.color_temp, .color_mat = default_color_filter ? drm_output_get_color_filter_matrix(KYWC_OUTPUT_COLOR_FILTER_NONE) : drm_output_get_color_filter_matrix(kywc_output->state.color_filter), .wlr_rend = surface->wlr_rend, .damage = NULL, .rgb_clear = true, .use_yuyv = false, .output = output, }; local_buf = drm_surface_blit(surface, &target); if (!local_buf) { return false; } } else { local_buf = wlr_buffer_lock(buffer); } bool ok = drm_fb_import_fb(&conn->cursor_pending_fb, conn->drm, local_buf, &conn->crtc->cursor->formats, false); wlr_buffer_unlock(local_buf); if (!ok) { return false; } return true; } static bool drm_connector_set_cursor(struct wlr_output *output, struct wlr_buffer *buffer, int hotspot_x, int hotspot_y) { struct drm_connector *conn = drm_connector_from_output(output); if (!conn->crtc) { return false; } if (!conn->crtc->cursor) { return false; } if (conn->cursor_hotspot_x != hotspot_x || conn->cursor_hotspot_y != hotspot_y) { conn->cursor_x -= hotspot_x - conn->cursor_hotspot_x; conn->cursor_y -= hotspot_y - conn->cursor_hotspot_y; conn->cursor_hotspot_x = hotspot_x; conn->cursor_hotspot_y = hotspot_y; } conn->cursor_enabled = false; drm_fb_clear_fb(&conn->cursor_pending_fb); if (!buffer) { wlr_buffer_unlock(conn->cursor_buffer); conn->cursor_buffer = NULL; wlr_output_update_needs_frame(output); return true; } bool found = false; struct drm_plane *plane = conn->crtc->cursor; for (size_t i = 0; i < plane->cursor_sizes_len; i++) { struct wlr_output_cursor_size size = plane->cursor_sizes[i]; if (size.width == buffer->width && size.height == buffer->height) { found = true; break; } } if (!found) { kywc_log(KYWC_DEBUG, "Cursor buffer size mismatch"); return false; } if (!drm_connector_update_cursor(conn, buffer)) { return false; } conn->cursor_enabled = true; conn->cursor_width = buffer->width; conn->cursor_height = buffer->height; wlr_output_update_needs_frame(output); return true; } static bool drm_connector_move_cursor(struct wlr_output *output, int x, int y) { struct drm_connector *conn = drm_connector_from_output(output); if (!conn->crtc) { return false; } if (!conn->crtc->cursor) { return false; } int width, height; wlr_output_transformed_resolution(output, &width, &height); struct wlr_box box = { .x = x, .y = y }; enum wl_output_transform transform = wlr_output_transform_invert(output->transform); wlr_box_transform(&box, &box, transform, width, height); box.x -= conn->cursor_hotspot_x; box.y -= conn->cursor_hotspot_y; conn->cursor_x = box.x; conn->cursor_y = box.y; wlr_output_update_needs_frame(output); return true; } static void drm_connector_set_pending_page_flip(struct drm_connector *conn, struct drm_page_flip *page_flip) { if (conn->pending_page_flip) { conn->pending_page_flip->connector = NULL; } conn->pending_page_flip = page_flip; } static void drm_connector_destroy_output(struct wlr_output *output) { struct drm_connector *conn = drm_connector_from_output(output); deallocate_crtc(conn); conn->status = DRM_MODE_DISCONNECTED; drm_connector_set_pending_page_flip(conn, NULL); struct drm_mode *mode, *mode_tmp; wl_list_for_each_safe(mode, mode_tmp, &conn->output.modes, wlr_mode.link) { wl_list_remove(&mode->wlr_mode.link); free(mode); } free(conn->edid); conn->edid = NULL; conn->output = (struct wlr_output){ 0 }; wlr_buffer_unlock(conn->cursor_buffer); conn->state.committed = 0; pixman_region32_fini(&conn->state.damage); } static bool output_pending_enable(struct wlr_output *output, const struct wlr_output_state *state) { if (state->committed & WLR_OUTPUT_STATE_ENABLED) { return state->enabled; } return output->enabled; } static void reallocate_crtcs(struct drm_connector *want_conn) { struct drm_device *drm = want_conn->drm; assert(drm->num_crtcs > 0); size_t num_connectors = wl_list_length(&drm->connectors); if (num_connectors == 0) { return; } kywc_log(KYWC_DEBUG, "Reallocating CRTCs"); struct drm_connector *connectors[num_connectors]; uint32_t connector_constraints[num_connectors]; uint32_t previous_match[drm->num_crtcs]; uint32_t new_match[drm->num_crtcs]; for (size_t i = 0; i < drm->num_crtcs; ++i) { previous_match[i] = UNMATCHED; } kywc_log(KYWC_DEBUG, "State before reallocation:"); size_t i = 0; struct drm_connector *conn; wl_list_for_each(conn, &drm->connectors, link) { connectors[i] = conn; int index = conn->crtc - drm->crtcs; if (conn->crtc) { previous_match[index] = i; } // Only request a CRTC if the connected is currently enabled or it's the // connector the user wants to enable bool want_crtc = conn == want_conn || conn->output.enabled; kywc_log(KYWC_DEBUG, " '%s': crtc=%d status=%s want_crtc=%d", conn->name, conn->crtc ? index : -1, drm_util_get_connector_status_str(conn->status), want_crtc); if (conn->status == DRM_MODE_CONNECTED && want_crtc) { connector_constraints[i] = conn->possible_crtcs; } else { // Will always fail to match anything connector_constraints[i] = 0; } ++i; } drm_util_match_obj(num_connectors, connector_constraints, drm->num_crtcs, previous_match, new_match); // Converts our crtc=>connector result into a connector=>crtc one. ssize_t connector_match[num_connectors]; for (size_t i = 0; i < num_connectors; ++i) { connector_match[i] = -1; } for (size_t i = 0; i < drm->num_crtcs; ++i) { if (new_match[i] != UNMATCHED) { connector_match[new_match[i]] = i; } } // Refuse to remove a CRTC from an enabled connector, and refuse to // change the CRTC of an enabled connector. for (size_t i = 0; i < num_connectors; ++i) { struct drm_connector *conn = connectors[i]; if (conn->status != DRM_MODE_CONNECTED || !conn->output.enabled) { continue; } if (connector_match[i] == -1) { kywc_log(KYWC_DEBUG, "Could not match a CRTC for previously connected output; " "keeping old configuration"); return; } assert(conn->crtc != NULL); if (connector_match[i] != conn->crtc - drm->crtcs) { kywc_log(KYWC_DEBUG, "Cannot switch CRTC for enabled output; " "keeping old configuration"); return; } } // Apply new configuration kywc_log(KYWC_DEBUG, "State after reallocation:"); for (size_t i = 0; i < num_connectors; ++i) { struct drm_connector *conn = connectors[i]; kywc_log(KYWC_DEBUG, " '%s': crtc=%zd", conn->name, connector_match[i]); if (conn->crtc != NULL && connector_match[i] == conn->crtc - drm->crtcs) { // We don't need to change anything continue; } deallocate_crtc(conn); if (connector_match[i] >= 0) { conn->crtc = &drm->crtcs[connector_match[i]]; } } } static bool drm_connector_allocate_crtc(struct drm_connector *conn) { if (!conn->crtc) { reallocate_crtcs(conn); } bool ok = conn->crtc != NULL; if (!ok) { kywc_log(KYWC_WARN, "Failed to find free CRTC"); } return ok; } void drm_output_pending_resolution(struct wlr_output *output, const struct wlr_output_state *state, int *width, int *height) { if (state->committed & WLR_OUTPUT_STATE_MODE) { switch (state->mode_type) { case WLR_OUTPUT_STATE_MODE_FIXED: *width = state->mode->width; *height = state->mode->height; return; case WLR_OUTPUT_STATE_MODE_CUSTOM: *width = state->custom_mode.width; *height = state->custom_mode.height; return; } abort(); } else { *width = output->width; *height = output->height; } } static void drm_connector_state_init(struct drm_connector_state *state, struct drm_connector *conn, const struct wlr_output_state *base) { *state = (struct drm_connector_state){ .connector = conn, .base = base, .active = output_pending_enable(&conn->output, base), .output_state = &conn->state, }; struct wlr_output_mode *mode = conn->output.current_mode; int32_t width = conn->output.width; int32_t height = conn->output.height; int32_t refresh = conn->output.refresh; if (base->committed & WLR_OUTPUT_STATE_MODE) { switch (base->mode_type) { case WLR_OUTPUT_STATE_MODE_FIXED:; mode = base->mode; break; case WLR_OUTPUT_STATE_MODE_CUSTOM: mode = NULL; width = base->custom_mode.width; height = base->custom_mode.height; refresh = base->custom_mode.refresh; break; } } if (mode) { struct drm_mode *drm_mode = wl_container_of(mode, drm_mode, wlr_mode); state->mode = drm_mode->drm_mode; } else { drm_util_generate_cvt_mode(&state->mode, width, height, (float)refresh / 1000); } if (output_pending_enable(&conn->output, base)) { // The crtc must be set up before this function is called assert(conn->crtc != NULL); struct drm_plane *primary = conn->crtc->primary; if (primary->queued_fb != NULL) { state->primary_fb = drm_fb_lock_fb(primary->queued_fb); } else if (primary->current_fb != NULL) { state->primary_fb = drm_fb_lock_fb(primary->current_fb); } if (conn->cursor_enabled) { struct drm_plane *cursor = conn->crtc->cursor; assert(cursor != NULL); if (conn->cursor_pending_fb) { state->cursor_fb = drm_fb_lock_fb(conn->cursor_pending_fb); } else if (cursor->queued_fb) { state->cursor_fb = drm_fb_lock_fb(cursor->queued_fb); } else if (cursor->current_fb) { state->cursor_fb = drm_fb_lock_fb(cursor->current_fb); } } } } static bool drm_connector_state_update_primary_fb(struct drm_connector *conn, struct drm_connector_state *state, bool test_only) { assert(state->base->committed & WLR_OUTPUT_STATE_BUFFER); struct drm_crtc *crtc = conn->crtc; assert(crtc != NULL); struct wlr_buffer *local_buf = NULL; struct wlr_buffer *source = state->base->buffer; struct drm_device *drm = conn->drm; struct output *output = output_from_wlr_output(&conn->output); bool hw_brightness = drm_output_use_hw_brightness(&conn->output, &output->base.state); bool hw_color_temp = drm_output_use_hw_color_temp(&conn->output, &output->base.state); bool hw_color_filter = drm_output_use_hw_color_filter(&conn->output, &output->base.state); bool main_gpu = !drm_device_get_parent(drm); bool hw_support = hw_brightness && hw_color_temp && hw_color_filter; bool buffer_is_shm = conn->output.allocator->buffer_caps & WLR_BUFFER_CAP_SHM; bool use_yuyv = drm_output_yuyv_enabled(&conn->output, &output->base.state) && !buffer_is_shm; if (!test_only && (!main_gpu || !hw_support || use_yuyv || buffer_is_shm)) { uint32_t format = main_gpu && !use_yuyv ? conn->output.render_format : DRM_FORMAT_ARGB8888; if (state->base->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { format = state->base->render_format; } int width = 0, height = 0; struct drm_plane *primary = crtc->primary; struct drm_surface *surface = &primary->multi_surf; drm_output_pending_resolution(&conn->output, state->base, &width, &height); if (!drm_plane_configure_surface_swapchain( primary, conn, main_gpu ? NULL : &drm->mgpu_renderer, use_yuyv ? width / 2 : width, height, format, state->base, use_yuyv)) { return false; } use_yuyv = use_yuyv && !wlr_renderer_is_pixman(surface->wlr_rend); struct kywc_output *kywc_output = &output->base; struct drm_render_target target = { .source = source, .brightness = hw_brightness ? 100 : kywc_output->state.brightness, .color_temp = hw_color_temp ? 6500 : kywc_output->state.color_temp, .color_mat = hw_color_filter ? drm_output_get_color_filter_matrix(KYWC_OUTPUT_COLOR_FILTER_NONE) : drm_output_get_color_filter_matrix(kywc_output->state.color_filter), .wlr_rend = surface->wlr_rend, .damage = conn->state.committed & DRM_OUTPUT_STATE_DAMAGE ? &conn->state.damage : NULL, .output = output, .rgb_clear = !use_yuyv ? !hw_support : false, .use_yuyv = use_yuyv, }; local_buf = drm_surface_blit(surface, &target); if (!local_buf) { return false; } } else { local_buf = wlr_buffer_lock(source); } drm_output_set_state(output); use_yuyv = use_yuyv && !test_only; crtc->primary->enable_yuyv = use_yuyv; bool ok = drm_fb_import_fb(&state->primary_fb, conn->drm, local_buf, &crtc->primary->formats, use_yuyv); wlr_buffer_unlock(local_buf); if (!ok) { kywc_log(KYWC_DEBUG, "Failed to import buffer for scan-out"); return false; } return true; } static bool drm_connector_prepare(struct drm_connector_state *conn_state, bool test_only) { const struct wlr_output_state *state = conn_state->base; struct drm_connector *conn = conn_state->connector; struct wlr_output *output = &conn->output; uint32_t unsupported = state->committed & ~SUPPORTED_OUTPUT_STATE; if (unsupported) { kywc_log(KYWC_DEBUG, "Unsupported output state fields = 0x%" PRIx32, unsupported); return false; } if (state->committed & WLR_OUTPUT_STATE_ENABLED && state->enabled) { if (output->current_mode == NULL && !(state->committed & WLR_OUTPUT_STATE_MODE)) { kywc_log(KYWC_DEBUG, "Can't enable an output without a mode"); return false; } } if ((state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) && state->adaptive_sync_enabled && !conn->adaptive_sync_supported) { return false; } if ((state->committed & WLR_OUTPUT_STATE_BUFFER) && (drm_device_get_parent(conn->drm) && !(conn->output.allocator->buffer_caps & WLR_BUFFER_CAP_SHM))) { struct wlr_dmabuf_attributes dmabuf; if (!wlr_buffer_get_dmabuf(state->buffer, &dmabuf)) { kywc_log(KYWC_DEBUG, "Buffer is not a DMA-BUF"); return false; } if (!wlr_drm_format_set_has(&conn->drm->mgpu_renderer.formats, dmabuf.format, dmabuf.modifier)) { kywc_log(KYWC_DEBUG, "Buffer format 0x%" PRIX32 " with modifier 0x%" PRIX64 " cannot be " "imported into multi-GPU renderer", dmabuf.format, dmabuf.modifier); } } if (test_only && (drm_device_get_parent(conn->drm) || (conn->output.allocator->buffer_caps & WLR_BUFFER_CAP_SHM))) { // if we are running as a secondary GPU, we can't peform an atomic // commit without blitting a buffer return true; } if (state->committed & WLR_OUTPUT_STATE_BUFFER) { if (!drm_connector_state_update_primary_fb(conn, conn_state, test_only)) { return false; } } if (conn_state->base->tearing_page_flip && !conn->drm->supports_tearing_page_flips) { kywc_log(KYWC_ERROR, "Attempted to submit a tearing page flip to an unsupportd drm"); return false; } if (conn_state->active && !conn_state->primary_fb) { kywc_log(KYWC_ERROR, "No Primary frame buffer available for this connector"); } return true; } static struct drm_page_flip *drm_create_page_flip(struct drm_device *drm, const struct drm_device_state *state) { struct drm_page_flip *page_flip = calloc(1, sizeof(*page_flip)); if (!page_flip) { return NULL; } page_flip->connector = state->conn_state->connector; wl_list_insert(&drm->page_flips, &page_flip->link); return page_flip; } static void drm_connector_state_finish(struct drm_connector_state *state, bool test_only) { drm_fb_clear_fb(&state->primary_fb); drm_fb_clear_fb(&state->cursor_fb); if (!test_only) { drm_output_state_clear(&state->connector->state); } } static void drm_connector_apply_commit(const struct drm_connector_state *state, struct drm_page_flip *page_flip) { struct drm_connector *conn = state->connector; struct drm_crtc *crtc = conn->crtc; drm_fb_copy_fb(&crtc->primary->queued_fb, state->primary_fb); if (crtc->cursor) { drm_fb_copy_fb(&crtc->cursor->queued_fb, state->cursor_fb); } drm_fb_clear_fb(&conn->cursor_pending_fb); drm_connector_set_pending_page_flip(conn, page_flip); if (state->base->committed & WLR_OUTPUT_STATE_MODE) { conn->refresh = drm_util_calculate_refresh_rate(&state->mode); } if (!state->active) { drm_plane_fb_finish(crtc->primary); drm_plane_fb_finish(crtc->cursor); drm_fb_clear_fb(&conn->cursor_pending_fb); conn->cursor_enabled = false; conn->crtc = NULL; } } static bool drm_commit(struct drm_device *drm, const struct drm_device_state *state, uint32_t flags, bool test_only) { // Disallow atomic-only flags assert((flags & ~DRM_MODE_PAGE_FLIP_FLAGS) == 0); struct drm_page_flip *page_flip = NULL; if (flags & DRM_MODE_PAGE_FLIP_EVENT) { page_flip = drm_create_page_flip(drm, state); if (page_flip == NULL) { return false; } page_flip->async = flags & DRM_MODE_PAGE_FLIP_ASYNC; } bool ok = drm_kms_commit(drm, state, page_flip, flags, test_only); if (ok && !test_only) { drm_connector_apply_commit(state->conn_state, page_flip); } else { drm_destroy_page_flip(page_flip); // The set_cursor() hook is a bit special: it's not really synchronized // to commit() or test(). Once set_cursor() returns true, the new // cursor is effectively committed. So don't roll it back here, or we // risk ending up in a state where we don't have a cursor FB but // wlr_drm_connector.cursor_enabled is true. // TODO: fix our output interface to avoid this issue. } return ok; } static bool drm_commit_connector_state(struct drm_connector *conn, const struct wlr_output_state *state, bool test_only) { struct drm_device *drm = conn->drm; if (!drm->session_active) { return false; } if (test_only && !(state->committed & COMMIT_OUTPUT_STATE)) { return true; } if (output_pending_enable(&conn->output, state) && !drm_connector_allocate_crtc(conn)) { kywc_log(KYWC_ERROR, "No CRTC available for this connector"); return false; } if (conn->state.color_changed && drm_connector_update_cursor(conn, conn->cursor_buffer)) { conn->cursor_enabled = true; } bool ok = false; struct drm_connector_state pending = { 0 }; drm_connector_state_init(&pending, conn, state); struct drm_device_state pending_dev = { .modeset = state->allow_reconfiguration, // The wlr_output API requires non-modeset commits with a new buffer to // wait for the frame event. However compositors often perform // non-modesets commits without a new buffer without waiting for the // frame event. In that case we need to make the KMS commit blocking, // otherwise the kernel will error out with EBUSY. .nonblock = !state->allow_reconfiguration && (state->committed & WLR_OUTPUT_STATE_BUFFER), .conn_state = &pending, }; if (!drm_connector_prepare(&pending, test_only)) { goto out; } if (test_only && (drm_device_get_parent(conn->drm) || (conn->output.allocator->buffer_caps & WLR_BUFFER_CAP_SHM))) { // if we are running as a secondary GPU, we can not perform an atomic commit without // blitting a buffer ok = true; goto out; } if (!pending.active && !conn->crtc) { // Disabling an already-diabled connector ok = true; goto out; } if (!test_only && pending_dev.modeset) { if (pending.active) { kywc_log(KYWC_INFO, "Modesetting with %dx%d @ %.3f Hz", pending.mode.hdisplay, pending.mode.vdisplay, (float)drm_util_calculate_refresh_rate(&pending.mode) / 1000); } else { kywc_log(KYWC_INFO, "%s Turning off", conn->name); } } // kms_commit will perform either a non-blocking // page-flip, eigher a blocking modeset. when performing a blocking modeset // we will wati for all queued page-flips to complete, so we don't need this safeguard if (!test_only && pending_dev.nonblock && conn->pending_page_flip) { kywc_log_ratelimited(KYWC_ERROR, "Failed to page-flip output: a page-flip is already pending"); goto out; } uint32_t flags = 0; if (!test_only && pending.active) { flags |= DRM_MODE_PAGE_FLIP_EVENT; } if (pending.base->tearing_page_flip) { flags |= DRM_MODE_PAGE_FLIP_ASYNC; } ok = drm_commit(conn->drm, &pending_dev, flags, test_only); out: drm_connector_state_finish(&pending, test_only); return ok; } static void deallocate_crtc(struct drm_connector *conn) { if (conn->crtc == NULL) { return; } kywc_log(KYWC_DEBUG, "De-allocating CRTC %" PRIu32, conn->crtc->id); struct wlr_output_state state; wlr_output_state_init(&state); wlr_output_state_set_enabled(&state, false); if (!drm_commit_connector_state(conn, &state, false)) { // On GPU unplug, disabling the CRTC can fail with EPERM kywc_log(KYWC_ERROR, "Failed to disable [%s]CRTC %" PRIu32, conn->name, conn->crtc->id); drm_plane_fb_finish(conn->crtc->primary); drm_plane_fb_finish(conn->crtc->cursor); drm_fb_clear_fb(&conn->cursor_pending_fb); conn->cursor_enabled = false; conn->crtc = NULL; } wlr_output_state_finish(&state); } static bool drm_connector_test(struct wlr_output *output, const struct wlr_output_state *state) { struct drm_connector *conn = drm_connector_from_output(output); return drm_commit_connector_state(conn, state, true); } static bool drm_connector_commit(struct wlr_output *output, const struct wlr_output_state *state) { struct drm_connector *conn = drm_connector_from_output(output); return drm_commit_connector_state(conn, state, false); } static size_t drm_connector_get_gamma_size(struct wlr_output *output) { struct drm_connector *conn = drm_connector_from_output(output); struct drm_crtc *crtc = conn->crtc; if (!crtc) { return 0; } return drm_get_crtc_gamma_lut_size(crtc); } static const struct wlr_drm_format_set *drm_connector_get_cursor_formats(struct wlr_output *output, uint32_t buffer_caps) { if (!(buffer_caps & (WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_DATA_PTR))) { return NULL; } struct drm_connector *conn = drm_connector_from_output(output); if (!drm_connector_allocate_crtc(conn)) { return NULL; } if (!conn->crtc->cursor) { return NULL; } if (drm_device_get_parent(conn->drm)) { return &conn->drm->mgpu_renderer.formats; } return &conn->crtc->cursor->formats; } static const struct wlr_output_cursor_size * drm_connector_get_cursor_sizes(struct wlr_output *output, size_t *len) { struct drm_connector *conn = drm_connector_from_output(output); if (!drm_connector_allocate_crtc(conn)) { return NULL; } struct drm_plane *plane = conn->crtc->cursor; if (!plane) { return NULL; } *len = plane->cursor_sizes_len; return plane->cursor_sizes; } static const struct wlr_drm_format_set *drm_connector_get_primary_formats(struct wlr_output *output, uint32_t buffer_caps) { if (!(buffer_caps & (WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_DATA_PTR))) { return NULL; } struct drm_connector *conn = drm_connector_from_output(output); if (!drm_connector_allocate_crtc(conn)) { return NULL; } kywc_log(KYWC_DEBUG, "Output: %s current CRTC_ID: %d get primary_formats", output->name, conn->crtc->id); if (drm_device_get_parent(conn->drm)) { return &conn->drm->mgpu_renderer.formats; } kywc_log(KYWC_DEBUG, "Primary_plane: %d, formats_len: %ld", conn->crtc->primary->id, conn->crtc->primary->formats.len); return &conn->crtc->primary->formats; } static const struct wlr_output_impl output_impl = { .set_cursor = drm_connector_set_cursor, .move_cursor = drm_connector_move_cursor, .destroy = drm_connector_destroy_output, .test = drm_connector_test, .commit = drm_connector_commit, .get_gamma_size = drm_connector_get_gamma_size, .get_cursor_formats = drm_connector_get_cursor_formats, .get_cursor_sizes = drm_connector_get_cursor_sizes, .get_primary_formats = drm_connector_get_primary_formats, }; bool wlr_output_is_drm(struct wlr_output *wlr_output) { return wlr_output->impl == &output_impl; } static bool drm_connector_connect(struct drm_connector *conn, const drmModeConnector *drm_conn) { kywc_log(KYWC_DEBUG, "Current CRTC: %d", conn->crtc ? (int)conn->crtc->id : -1); if (!conn->crtc) { conn->possible_crtcs = drmModeConnectorGetPossibleCrtcs(conn->drm->fd, drm_conn); if (!conn->possible_crtcs) { kywc_log(KYWC_WARN, "No CRTC possible"); } conn->crtc = get_drm_connector_current_crtc(conn, drm_conn); } // keep track of all the modes ourselves first. We must only fill out // the modes list after wlr_output_init() struct wl_list modes; wl_list_init(&modes); struct wlr_output_state state; wlr_output_state_init(&state); wlr_output_state_set_enabled(&state, false); struct drm_device *drm = conn->drm; drmModeModeInfo *current_modeinfo = get_connector_current_mode(conn); kywc_log(KYWC_INFO, " Detected modes:"); for (int i = 0; i < drm_conn->count_modes; ++i) { if (drm_conn->modes[i].flags & DRM_MODE_FLAG_INTERLACE) { continue; } struct drm_mode *mode = drm_mode_create(&drm_conn->modes[i]); if (!mode) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); wlr_output_state_finish(&state); return false; } // If this is the current mode set on the conn's crtc, // then set it as the conn's output current mode. if (current_modeinfo != NULL && memcmp(&mode->drm_mode, current_modeinfo, sizeof(*current_modeinfo)) == 0) { wlr_output_state_set_mode(&state, &mode->wlr_mode); uint64_t mode_id = 0; if (!drm_get_property_value(drm->fd, DRM_MODE_OBJECT_CRTC, conn->crtc->id, &conn->crtc->props[DRM_CRTC_PROP_MODE_ID], &mode_id)) { kywc_log(KYWC_DEBUG, "Failed to get MODE ID"); } conn->crtc->mode_id = mode_id; conn->refresh = drm_util_calculate_refresh_rate(current_modeinfo); } kywc_log(KYWC_INFO, " %" PRId32 "x%" PRId32 " @ %.3f Hz %s", mode->wlr_mode.width, mode->wlr_mode.height, (float)mode->wlr_mode.refresh / 1000, mode->wlr_mode.preferred ? "(preferred)" : ""); wl_list_insert(modes.prev, &mode->wlr_mode.link); } free(current_modeinfo); conn->state = (struct drm_output_state){ 0 }; pixman_region32_init(&conn->state.damage); struct wlr_output *output = &conn->output; wlr_output_init(output, drm->wlr_backend, &output_impl, drm->event_loop, &state); wlr_output_state_finish(&state); // fill out the modes wl_list_insert_list(&output->modes, &modes); wlr_output_set_name(output, conn->name); free(conn->edid); conn->edid_len = 0; conn->edid = drm_get_property_blob(drm->fd, DRM_MODE_OBJECT_CONNECTOR, conn->id, &conn->props[DRM_CONNECTOR_PROP_EDID], &conn->edid_len); if (conn->edid_len > 0) { drm_util_parse_edid(conn, conn->edid, conn->edid_len); } else { kywc_log(KYWC_WARN, "Connector has no EDID"); } output->phys_width = drm_conn->mmWidth; output->phys_height = drm_conn->mmHeight; kywc_log(KYWC_INFO, " Make: %s, Model: %s, Serial: %s, Physical size: %d x %d mm", output->make ? output->make : "(null)", output->model ? output->model : "(null)", output->serial ? output->serial : "(null)", output->phys_width, output->phys_height); if (drm_conn->subpixel < sizeof(subpixel_map) / sizeof(subpixel_map[0])) { output->subpixel = subpixel_map[drm_conn->subpixel]; } else { kywc_log(KYWC_ERROR, "Unknown subpixel value: %d", (int)drm_conn->subpixel); } uint64_t non_desktop; if (drm_get_property_value(drm->fd, DRM_MODE_OBJECT_CONNECTOR, conn->id, &conn->props[DRM_CONNECTOR_PROP_NON_DESKTOP], &non_desktop)) { if (non_desktop == 1) { kywc_log(KYWC_INFO, "Non-desktop connector"); } output->non_desktop = non_desktop; } memset(conn->max_bpc_bounds, 0, sizeof(conn->max_bpc_bounds)); uint32_t maxbpc = conn->props[DRM_CONNECTOR_PROP_MAX_BPC].id; if (maxbpc != 0) { if (!drm_get_property_range(drm->fd, maxbpc, &conn->max_bpc_bounds[0], &conn->max_bpc_bounds[1])) { kywc_log(KYWC_ERROR, "Failed to introspect 'max bpc' property"); } } uint64_t vrr_capable = 0; struct drm_prop_info *vrr_capable_prop = &conn->props[DRM_CONNECTOR_PROP_VRR_CAPABLE]; if (vrr_capable_prop->id) { drm_get_property_value(conn->drm->fd, DRM_MODE_OBJECT_CONNECTOR, conn->id, vrr_capable_prop, &vrr_capable); } conn->adaptive_sync_supported = vrr_capable; struct drm_prop_info *rgb_range_prop = &conn->props[DRM_CONNECTOR_PROP_RGB_RANGE]; if (rgb_range_prop->id) { uint64_t rgb_range = 0; if (drm_get_property_value(conn->drm->fd, DRM_MODE_OBJECT_CONNECTOR, conn->id, rgb_range_prop, &rgb_range)) { kywc_log(KYWC_DEBUG, "Connector support rgb range"); } conn->rgb_range = rgb_range; } struct drm_prop_info *overscan_prop = &conn->props[DRM_CONNECTOR_PROP_OVERSCAN]; if (overscan_prop->id) { uint64_t overscan = 0; if (drm_get_property_value(conn->drm->fd, DRM_MODE_OBJECT_CONNECTOR, conn->id, overscan_prop, &overscan)) { kywc_log(KYWC_DEBUG, "Connector support overscan"); } conn->overscan = overscan; } struct drm_prop_info *scaling_mode_prop = &conn->props[DRM_CONNECTOR_PROP_SCALING_MODE]; if (scaling_mode_prop->id) { uint64_t scaling_mode = 0; if (drm_get_property_value(conn->drm->fd, DRM_MODE_OBJECT_CONNECTOR, conn->id, scaling_mode_prop, &scaling_mode)) { kywc_log(KYWC_DEBUG, "Connector support scaling mode, current mode: %ld", scaling_mode); } conn->scaling_mode = scaling_mode; char *enum_list = drm_get_property_enum_list(drm->fd, DRM_MODE_OBJECT_CONNECTOR, conn->id, scaling_mode_prop); if (enum_list) { if (strncmp(enum_list, "None", 4) == 0) { conn->has_none_mode = true; } kywc_log(KYWC_INFO, " Scaling mode: %s", enum_list); free(enum_list); } } char *subconnector = NULL; uint32_t sub = conn->props[DRM_CONNECTOR_PROP_SUBCONNECTOR].id; if (sub) { subconnector = drm_get_property_enum(drm->fd, DRM_MODE_OBJECT_CONNECTOR, conn->id, &conn->props[DRM_CONNECTOR_PROP_SUBCONNECTOR]); } if (subconnector && strcmp(subconnector, "Native") == 0) { free(subconnector); subconnector = NULL; } char description[128]; snprintf(description, sizeof(description), "%s %s%s%s (%s%s%s)", output->make, output->model, output->serial ? " " : "", output->serial ? output->serial : "", output->name, subconnector ? " via " : "", subconnector ? subconnector : ""); wlr_output_set_description(output, description); free(subconnector); conn->status = DRM_MODE_CONNECTED; return true; } size_t drm_get_crtc_gamma_lut_size(struct drm_crtc *crtc) { struct drm_device *drm = crtc->drm; uint32_t gamma_lut_size_prop = crtc->props[DRM_CRTC_PROP_GAMMA_LUT_SIZE].id; if (gamma_lut_size_prop == 0 || drm->mode == DRM_KMS_MODE_LEGACY) { return (size_t)crtc->legacy_gamma_size; } uint64_t gamma_lut_size; if (!drm_get_property_value(drm->fd, DRM_MODE_OBJECT_CRTC, crtc->id, &crtc->props[DRM_CRTC_PROP_GAMMA_LUT_SIZE], &gamma_lut_size)) { kywc_log(KYWC_ERROR, "Unable to get gamma lut size"); return 0; } return gamma_lut_size; } static struct drm_connector *drm_connector_create(struct drm_device *drm, drmModeConnector *drm_conn) { struct drm_connector *conn = calloc(1, sizeof(*conn)); if (!conn) { kywc_log_errno(KYWC_ERROR, "Allocation drm_connector failed"); return NULL; } conn->id = drm_conn->connector_id; conn->drm = drm; conn->status = DRM_MODE_DISCONNECTED; const char *conn_name = drmModeGetConnectorTypeName(drm_conn->connector_type); if (conn_name == NULL) { conn_name = "Unknown"; } snprintf(conn->name, sizeof(conn->name), "%s-%" PRIu32, conn_name, drm_conn->connector_type_id); kywc_log(KYWC_INFO, "Find connector: %s", conn->name); if (!drm_get_connector_props(drm->fd, conn->id, conn->props)) { free(conn); return NULL; } conn->possible_crtcs = drmModeConnectorGetPossibleCrtcs(drm->fd, drm_conn); if (!conn->possible_crtcs) { kywc_log(KYWC_WARN, "No CRTC possible"); } conn->crtc = get_drm_connector_current_crtc(conn, drm_conn); wl_list_insert(drm->connectors.prev, &conn->link); return conn; } static void handle_page_flip(int fd, unsigned seq, unsigned tv_sec, unsigned tv_usec, unsigned crtc_id, void *data) { struct drm_page_flip *page_flip = data; struct drm_connector *conn = page_flip->connector; if (conn) { conn->pending_page_flip = NULL; } drm_destroy_page_flip(page_flip); if (!conn) { return; } if (conn->status != DRM_MODE_CONNECTED || conn->crtc == NULL) { kywc_log(KYWC_WARN, "Ignoring page-flip event for disabled connector"); return; } if (conn->crtc->primary->queued_fb) { drm_fb_move_fb(&conn->crtc->primary->current_fb, &conn->crtc->primary->queued_fb); } if (conn->crtc->cursor && conn->crtc->cursor->queued_fb) { drm_fb_move_fb(&conn->crtc->cursor->current_fb, &conn->crtc->cursor->queued_fb); } uint32_t present_flags = WLR_OUTPUT_PRESENT_VSYNC | WLR_OUTPUT_PRESENT_HW_CLOCK | WLR_OUTPUT_PRESENT_HW_COMPLETION; /* Don't report ZERO_COPY in multi-gpu situations, because we had to copy * data between the GPUs, even if we were using the direct scanout * interface. */ struct drm_device *drm = conn->drm; if (!drm_device_get_parent(drm)) { present_flags |= WLR_OUTPUT_PRESENT_ZERO_COPY; } struct timespec present_time = { .tv_sec = tv_sec, .tv_nsec = tv_usec * 1000, }; struct wlr_output_event_present present_event = { /* The DRM backend guarantees that the presentation event will be for * the last submitted frame. */ .commit_seq = conn->output.commit_seq, .presented = drm->session_active, .when = present_time, .seq = seq, .refresh = mhz_to_nsec(conn->refresh), .flags = present_flags, }; wlr_output_send_present(&conn->output, &present_event); if (drm->session_active) { wlr_output_send_frame(&conn->output); } return; } static int drm_handle_event(int fd, uint32_t mask, void *data) { struct drm_device *drm = data; drmEventContext event = { .version = 3, .page_flip_handler2 = handle_page_flip, }; if (drmHandleEvent(fd, &event) != 0) { kywc_log(KYWC_ERROR, "Failed with drmHandleEvent failed"); wlr_backend_destroy(drm->wlr_backend); } return 1; } static void drm_connector_disconnect(struct drm_connector *conn) { if (conn->status == DRM_MODE_DISCONNECTED) { return; } wlr_output_destroy(&conn->output); } static void drm_connector_destroy(struct drm_connector *conn) { drm_connector_disconnect(conn); wl_list_remove(&conn->link); free(conn); } void drm_update_connector(struct drm_device *drm, struct wlr_device_hotplug_event *event) { if (event == NULL || event->connector_id == 0) { return; } struct drm_connector *conn; wl_list_for_each(conn, &drm->connectors, link) { if (event->connector_id != conn->id) { continue; } drmModeConnector *drm_conn = drmModeGetConnector(drm->fd, conn->id); if (!drm_conn) { kywc_log_errno(KYWC_ERROR, "Failed to get DRM connector"); continue; } if (conn->status == DRM_MODE_CONNECTED && drm_conn->connection == DRM_MODE_DISCONNECTED) { kywc_log(KYWC_INFO, "Monitor DRM connector %" PRIu32 " on %s Changed", event->connector_id, drm->name); // the connector disconnection is detected update_drm_connectors // disconnect it so that the client will modeset and // rerender when the session is activated again. wlr_output_destroy(&conn->output); if (!conn->crtc) { continue; } // After changing the session, the CRTC ID might have changed, // which caused the destroy output commit to fail. drm_plane_fb_finish(conn->crtc->primary); drm_plane_fb_finish(conn->crtc->cursor); drm_fb_clear_fb(&conn->cursor_pending_fb); conn->crtc = NULL; conn->cursor_enabled = false; break; } drmModeFreeConnector(drm_conn); } } void drm_restore_connectors(struct drm_device *drm) { drm_scan_connectors(drm, NULL); struct drm_connector *conn; wl_list_for_each(conn, &drm->connectors, link) { struct wlr_output_state state; wlr_output_state_init(&state); bool enabled = conn->status != DRM_MODE_DISCONNECTED && conn->output.enabled; wlr_output_state_set_enabled(&state, enabled); if (enabled) { if (conn->output.current_mode != NULL) { wlr_output_state_set_mode(&state, conn->output.current_mode); } else { wlr_output_state_set_custom_mode(&state, conn->output.width, conn->output.height, conn->output.refresh); } } if (!drm_commit_connector_state(conn, &state, false)) { kywc_log(KYWC_ERROR, "Failed to restore state after VT switch"); } wlr_output_state_finish(&state); } } void drm_scan_connectors(struct drm_device *drm, struct wlr_device_hotplug_event *event) { if (event != NULL && event->connector_id != 0) { kywc_log(KYWC_INFO, "Scanning DRM connector %" PRIu32 " on %s", event->connector_id, drm->name); } else { kywc_log(KYWC_INFO, "Scanning DRM connectors on %s", drm->name); } drmModeRes *res = drmModeGetResources(drm->fd); if (!res) { kywc_log_errno(KYWC_ERROR, "Failed to get DRM resources"); return; } size_t seen_len = wl_list_length(&drm->connectors); // +1 so length can never be 0, which is undefined behaviour. // Last element isn't used. bool seen[seen_len + 1]; memset(seen, false, sizeof(seen)); struct drm_connector *new_outputs[res->count_connectors + 1]; size_t new_outputs_len = 0; for (int i = 0; i < res->count_connectors; i++) { uint32_t conn_id = res->connectors[i]; ssize_t index = -1; struct drm_connector *c, *conn = NULL; wl_list_for_each(c, &drm->connectors, link) { index++; if (c->id == conn_id) { conn = c; break; } } if (conn && conn->lease) { continue; } // If the hotplug event contains a connector ID, ignore any other // connector. if (event != NULL && event->connector_id != 0 && event->connector_id != conn_id) { if (conn) { seen[index] = true; } continue; } drmModeConnector *drm_conn = drmModeGetConnector(drm->fd, conn_id); if (!drm_conn) { kywc_log_errno(KYWC_ERROR, "Failed to get DRM connector"); continue; } /* can not find in device::connector_list */ if (!conn) { conn = drm_connector_create(drm, drm_conn); if (!conn) { drmModeFreeConnector(drm_conn); continue; } } else { seen[index] = true; } // This can only happen *after* hotplug, since we haven't read the // connector properties yet uint32_t linksta_prop = conn->props[DRM_CONNECTOR_PROP_LINK_STATUS].id; if (linksta_prop) { uint64_t link_status; if (!drm_get_property_value(drm->fd, DRM_MODE_OBJECT_CONNECTOR, conn->id, &conn->props[DRM_CONNECTOR_PROP_LINK_STATUS], &link_status)) { kywc_log(KYWC_ERROR, "Failed to get link status prop"); drmModeFreeConnector(drm_conn); continue; } if (link_status == DRM_MODE_LINK_STATUS_BAD) { kywc_log(KYWC_INFO, "Bad link detected"); // We need to reload our list of modes and force a modeset drm_connector_disconnect(conn); } } if (conn->status == DRM_MODE_DISCONNECTED && drm_conn->connection == DRM_MODE_CONNECTED) { if (!drm_connector_connect(conn, drm_conn)) { kywc_log(KYWC_ERROR, "Failed to connect DRM connector"); drmModeFreeConnector(drm_conn); continue; } new_outputs[new_outputs_len++] = conn; } else if (conn->status == DRM_MODE_CONNECTED && drm_conn->connection != DRM_MODE_CONNECTED) { kywc_log(KYWC_INFO, "'%s' disconnected", conn->name); drm_connector_disconnect(conn); } else if (conn->status == DRM_MODE_CONNECTED && drm_conn->connection == DRM_MODE_CONNECTED) { struct drm_crtc *current_crtc = get_drm_connector_current_crtc(conn, drm_conn); if (conn->crtc != current_crtc) { kywc_log(KYWC_WARN, "Be carefull crtc changed when session active!"); if (current_crtc && drmModeSetCrtc(drm->fd, current_crtc->id, 0, 0, 0, NULL, 0, NULL) != 0) { kywc_log(KYWC_ERROR, "Failed to close Current Crtc Failed!"); } } } drmModeFreeConnector(drm_conn); } drmModeFreeResources(res); // Iterate in reverse order because we'll remove items from the list and // still want indices to remain correct. struct drm_connector *conn, *tmp_conn; size_t index = wl_list_length(&drm->connectors); wl_list_for_each_reverse_safe(conn, tmp_conn, &drm->connectors, link) { index--; if (index >= seen_len || seen[index]) { continue; } kywc_log(KYWC_INFO, "'%s' disappeared", conn->name); drm_connector_destroy(conn); } for (size_t i = 0; i < new_outputs_len; ++i) { struct drm_connector *conn = new_outputs[i]; kywc_log(KYWC_DEBUG, "Requesting modeset, emit new_output"); wl_signal_emit_mutable(&drm->wlr_backend->events.new_output, &conn->output); } } void drm_device_destroy(struct drm_device *drm) { if (!drm) { return; } if (drm_device_get_parent(drm)) { drm_mgpu_renderer_finish(&drm->mgpu_renderer); } // Disconnect any active connectors so that the client will modeset and // rerender when the session is activated again. struct drm_connector *conn, *next; wl_list_for_each_safe(conn, next, &drm->connectors, link) { conn->crtc = NULL; drm_connector_destroy(conn); } struct drm_page_flip *pageflip, *pageflip_tmp; wl_list_for_each_safe(pageflip, pageflip_tmp, &drm->page_flips, link) { drm_destroy_page_flip(pageflip); } for (size_t i = 0; i < drm->num_crtcs; ++i) { struct drm_crtc *crtc = &drm->crtcs[i]; if (crtc->mode_id && crtc->own_mode_id) { drmModeDestroyPropertyBlob(drm->fd, crtc->mode_id); } if (crtc->gamma_lut) { drmModeDestroyPropertyBlob(drm->fd, crtc->gamma_lut); } if (crtc->ctm) { drmModeDestroyPropertyBlob(drm->fd, crtc->ctm); } } free(drm->crtcs); for (size_t i = 0; i < drm->num_planes; ++i) { struct drm_plane *plane = &drm->planes[i]; drm_plane_fb_finish(plane); wlr_drm_format_set_finish(&plane->formats); } free(drm->planes); struct drm_fb *fb, *fb_tmp; wl_list_for_each_safe(fb, fb_tmp, &drm->fbs, link) { drm_fb_destroy_fb(fb); } wl_event_source_remove(drm->drm_event); free(drm->name); free(drm); } struct drm_device *drm_device_create(int fd, struct wlr_backend *backend, struct wl_event_loop *loop) { struct drm_device *drm = calloc(1, sizeof(*drm)); if (!drm) { return NULL; } char *name = drmGetDeviceNameFromFd2(fd); drmVersion *version = drmGetVersion(fd); if (!version) { kywc_log_errno(KYWC_ERROR, "Failed with drmGetVersion"); goto error; } kywc_log(KYWC_INFO, "Initializing DRM backend for %s (%s)", name, version->name); drmFreeVersion(version); drm->fd = fd; drm->name = name; drm->event_loop = loop; drm->wlr_backend = backend; drm->session_active = true; drm->drm_event = wl_event_loop_add_fd(loop, drm->fd, WL_EVENT_READABLE, drm_handle_event, drm); if (!drm->drm_event) { kywc_log(KYWC_ERROR, "Failed to create DRM event source"); goto error; } if (!drm_check_features(drm)) { goto error_event; } if (!drm_init_resources(drm)) { goto error_event; } wl_list_init(&drm->connectors); wl_list_init(&drm->fbs); wl_list_init(&drm->page_flips); return drm; error_event: wl_event_source_remove(drm->drm_event); error: free(name); free(drm); return NULL; } struct wlr_output_mode *wlr_output_add_mode(struct wlr_output *wlr_output, int32_t width, int32_t height, int32_t refresh) { struct drm_connector *conn = drm_connector_from_output(wlr_output); struct wlr_output_mode *wlr_mode; wl_list_for_each(wlr_mode, &conn->output.modes, link) { if (wlr_mode->width == width && wlr_mode->height == height && (refresh == 0 || wlr_mode->refresh == refresh)) { return wlr_mode; } } drmModeModeInfo drm_mode; drm_util_generate_cvt_mode(&drm_mode, width, height, refresh * 0.001); struct drm_mode *mode = drm_mode_create(&drm_mode); if (!mode) { return NULL; } wl_list_insert(&conn->output.modes, &mode->wlr_mode.link); kywc_log(KYWC_INFO, "%s: add custom mode %" PRId32 "x%" PRId32 "@%" PRId32, wlr_output->name, mode->wlr_mode.width, mode->wlr_mode.height, mode->wlr_mode.refresh); return &mode->wlr_mode; } struct drm_lease *drm_create_lease(struct wlr_output **outputs, size_t n_outputs, int *lease_fd_ptr) { assert(outputs); if (n_outputs == 0) { kywc_log(KYWC_ERROR, "Can't lease 0 outputs"); return NULL; } struct drm_backend *backend = drm_backend_from_wlr_backend(outputs[0]->backend); int n_objects = 0; uint32_t objects[4 * n_outputs + 1]; for (size_t i = 0; i < n_outputs; ++i) { struct drm_connector *conn = drm_connector_from_output(outputs[i]); assert(conn->lease == NULL); if (conn->drm != backend->drm) { kywc_log(KYWC_ERROR, "Can't lease output from different backend"); return NULL; } objects[n_objects++] = conn->id; kywc_log(KYWC_DEBUG, "Connector %d", conn->id); if (!drm_connector_allocate_crtc(conn)) { kywc_log(KYWC_ERROR, "Failled to allocate connector CRTC"); return NULL; } objects[n_objects++] = conn->crtc->id; kywc_log(KYWC_DEBUG, "CRTC %d", conn->crtc->id); objects[n_objects++] = conn->crtc->primary->id; kywc_log(KYWC_DEBUG, "Primary plane %d", conn->crtc->primary->id); if (conn->crtc->cursor) { kywc_log(KYWC_DEBUG, "Cursor plane %d", conn->crtc->cursor->id); objects[n_objects++] = conn->crtc->cursor->id; } } assert(n_objects != 0); struct drm_lease *lease = calloc(1, sizeof(*lease)); if (lease == NULL) { return NULL; } lease->backend = backend; wl_signal_init(&lease->events.destroy); kywc_log(KYWC_DEBUG, "Issuing DRM lease with %d objects", n_objects); int lease_fd = drmModeCreateLease(backend->drm->fd, objects, n_objects, O_CLOEXEC, &lease->lessee_id); if (lease_fd < 0) { free(lease); return NULL; } *lease_fd_ptr = lease_fd; kywc_log(KYWC_DEBUG, "Issued DRM lease %" PRIu32, lease->lessee_id); for (size_t i = 0; i < n_outputs; ++i) { struct drm_connector *conn = drm_connector_from_output(outputs[i]); conn->lease = lease; conn->crtc->lease = lease; drm_connector_disconnect(conn); } return lease; } static void drm_lease_destroy(struct drm_lease *lease) { struct drm_backend *backend = lease->backend; struct drm_device *drm = backend->drm; wl_signal_emit_mutable(&lease->events.destroy, NULL); assert(wl_list_empty(&lease->events.destroy.listener_list)); struct drm_connector *conn; wl_list_for_each(conn, &drm->connectors, link) { if (conn->lease == lease) { conn->lease = NULL; } } for (size_t i = 0; i < drm->num_crtcs; ++i) { if (drm->crtcs[i].lease == lease) { drm->crtcs[i].lease = NULL; } } free(lease); drm_scan_connectors(drm, NULL); } void drm_lease_terminate(struct drm_lease *lease) { kywc_log(KYWC_INFO, "Terminating DRM lease %d", lease->lessee_id); struct drm_backend *backend = lease->backend; int ret = drmModeRevokeLease(backend->drm->fd, lease->lessee_id); if (ret < 0) { kywc_log_errno(KYWC_ERROR, "Failed to terminate lease"); } drm_lease_destroy(lease); } void drm_scan_leases(struct drm_device *drm) { drmModeLesseeListRes *list = drmModeListLessees(drm->fd); if (!list) { kywc_log(KYWC_ERROR, "DrmModeListLessees failed"); return; } struct drm_connector *conn; wl_list_for_each(conn, &drm->connectors, link) { if (!conn->lease) { continue; } bool found = false; for (size_t i = 0; i < list->count; i++) { if (list->lessees[i] == conn->lease->lessee_id) { found = true; break; } } if (!found) { kywc_log(KYWC_DEBUG, "DRM lease %" PRIu32 " has been terminated", conn->lease->lessee_id); drm_lease_destroy(conn->lease); } } drmFree(list); } kylin-wayland-compositor/src/backend/drm/drm_p.h0000664000175000017500000003256615160461067020713 0ustar fengfeng// SPDX-FileCopyrightText: 2025 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _BACKEND_DRM_P_H_ #define _BACKEND_DRM_P_H_ #include #include #include #include #include #include "backend/drm.h" struct drm_device; struct wlr_output; struct wlr_buffer; struct output; struct drm_output_state; struct wlr_output_state; struct kywc_output_state; enum kywc_output_color_filter; enum drm_kms_mode { DRM_KMS_MODE_ATOMIC = 0, DRM_KMS_MODE_LEGACY, }; enum drm_output_state_field { DRM_OUTPUT_STATE_BRIGHTNESS = 1 << 1, DRM_OUTPUT_STATE_GAMMA_LUT = 1 << 2, DRM_OUTPUT_STATE_RGB_CTM = 1 << 3, DRM_OUTPUT_STATE_DAMAGE = 1 << 4, DRM_OUTPUT_STATE_RGB_RANGE = 1 << 5, DRM_OUTPUT_STATE_OVERSCAN = 1 << 6, DRM_OUTPUT_STATE_SCALING_MODE = 1 << 7, }; enum drm_crtc_prop { DRM_CRTC_PROP_ACTIVE = 0, DRM_CRTC_PROP_MODE_ID, DRM_CRTC_PROP_OUT_FENCE_PTR, DRM_CRTC_PROP_VRR_ENABLE, DRM_CRTC_PROP_CTM, DRM_CRTC_PROP_GAMMA_LUT, DRM_CRTC_PROP_GAMMA_LUT_SIZE, DRM_CRTC_PROP_COUNT, }; enum drm_plane_prop { DRM_PLANE_PROP_TYPE = 0, DRM_PLANE_PROP_FB_ID, DRM_PLANE_PROP_CRTC_ID, DRM_PLANE_PROP_CRTC_X, DRM_PLANE_PROP_CRTC_Y, DRM_PLANE_PROP_CRTC_W, DRM_PLANE_PROP_CRTC_H, DRM_PLANE_PROP_SRC_X, DRM_PLANE_PROP_SRC_Y, DRM_PLANE_PROP_SRC_W, DRM_PLANE_PROP_SRC_H, DRM_PLANE_PROP_IN_FORMATS, DRM_PLANE_PROP_FB_DAMAGE_CLIPS, DRM_PLANE_PROP_ROTATION, DRM_PLANE_PROP_HOTSPOT_X, DRM_PLANE_PROP_HOTSPOT_Y, DRM_PLANE_PROP_COLOR_ENCODING, DRM_PLANE_PROP_COLOR_RANGE, DRM_PLANE_PROP_SIZE_HINTS, DRM_PLANE_PROP_COUNT, }; enum drm_connector_prop { DRM_CONNECTOR_PROP_EDID = 0, DRM_CONNECTOR_PROP_DPMS, DRM_CONNECTOR_PROP_LINK_STATUS, DRM_CONNECTOR_PROP_NON_DESKTOP, DRM_CONNECTOR_PROP_CRTC_ID, DRM_CONNECTOR_PROP_CONTENT_TYPE, DRM_CONNECTOR_PROP_MAX_BPC, DRM_CONNECTOR_PROP_PANEL_ORIENTATION, DRM_CONNECTOR_PROP_SUBCONNECTOR, DRM_CONNECTOR_PROP_VRR_CAPABLE, DRM_CONNECTOR_PROP_BRIGHTNESS, DRM_CONNECTOR_PROP_RGB_RANGE, DRM_CONNECTOR_PROP_OVERSCAN, DRM_CONNECTOR_PROP_SCALING_MODE, DRM_CONNECTOR_PROP_COUNT, }; enum drm_color_encoding { DRM_COLOR_ENCODING_BT601 = 0, DRM_COLOR_ENCODDING_BT709, }; enum drm_color_range { DRM_COLOR_RANGE_LIMITTED = 0, DRM_COLOR_RANGE_FULL, }; // Part of match_obj enum { UNMATCHED = (uint32_t)-1, SKIP = (uint32_t)-2, }; struct drm_prop_info { const char *name; /* name as string (static, not freed) */ uint32_t id; }; struct drm_fb { struct wlr_buffer *wlr_buf; struct wlr_addon addon; struct drm_device *drm; struct wl_list link; uint32_t id; // fb-id }; struct drm_lease { int fd; uint32_t lessee_id; struct drm_backend *backend; struct { struct wl_signal destroy; } events; void *data; }; struct drm_backend { struct wlr_backend wlr_backend; struct drm_backend *parent; struct wlr_device *dev; struct drm_device *drm; struct wlr_session *session; struct wl_listener display_destroy; struct wl_listener session_destroy; struct wl_listener session_active; struct wl_listener parent_destroy; struct wl_listener device_change; struct wl_listener device_remove; }; struct drm_renderer { struct wlr_renderer *wlr_rend; struct wlr_allocator *allocator; struct wlr_drm_format_set formats; }; struct swapchain_option { int width; int height; struct wlr_output *output; struct wlr_drm_format *drm_format; const struct wlr_output_state *state; }; struct surf_swapchain { int width, height; struct swapchain *swapchain; // TODO: check buffer matched }; struct drm_surface { struct wlr_renderer *wlr_rend; struct surf_swapchain *swapchain; struct surf_swapchain *dumb_swapchain; }; struct drm_plane { struct wl_list link; /* drm_device::plane_list */ uint32_t type; uint32_t id; /* initialized required for multi-GPU setups, or single-GPU backend buffers */ struct drm_surface multi_surf; /* Buffer submitted to the kernel, will be presented on next vblank */ struct drm_fb *queued_fb; /* Buffer currently displayed on screen */ struct drm_fb *current_fb; struct wlr_drm_format_set formats; struct drm_prop_info props[DRM_PLANE_PROP_COUNT]; uint32_t crtc_id; uint32_t possible_crtcs; // mask struct wlr_output_cursor_size *cursor_sizes; size_t cursor_sizes_len; bool enable_yuyv; }; struct drm_crtc { struct drm_device *drm; struct drm_lease *lease; uint32_t id; uint32_t index; /* index of CRTC in resource arry */ // Atomic modesetting only bool own_mode_id; uint32_t mode_id; uint32_t gamma_lut; uint32_t ctm; // Legacy only int legacy_gamma_size; struct drm_plane *primary; struct drm_plane *cursor; struct drm_prop_info props[DRM_CRTC_PROP_COUNT]; }; struct drm_page_flip { struct wl_list link; // drm_connector.pageflip struct drm_connector *connector; // True if DRM_MODE_PAGE_FLIP_ASYNC was set bool async; }; struct drm_mode { struct wlr_output_mode wlr_mode; drmModeModeInfo drm_mode; }; struct drm_output_state { uint32_t committed; uint32_t brightness; uint32_t overscan; uint32_t rgb_range; uint32_t scaling_mode; bool color_changed; /* render damage */ pixman_region32_t damage; struct drm_color_lut *lut; size_t lut_size; struct drm_color_ctm rgb_ctm; }; struct drm_render_target { struct wlr_renderer *wlr_rend; struct wlr_buffer *source; uint32_t brightness; uint32_t color_temp; const float *color_mat; const pixman_region32_t *damage; struct output *output; bool rgb_clear; bool use_yuyv; }; struct drm_connector { struct wl_list link; /* drm_device::connector_list */ struct wlr_output output; // only valid if status != DISCONNECTED struct drm_device *drm; struct drm_lease *lease; char name[24]; drmModeConnection status; uint32_t id; uint64_t max_bpc_bounds[2]; uint8_t *edid; size_t edid_len; bool adaptive_sync_supported; uint32_t brightness; uint32_t rgb_range; uint32_t overscan; uint32_t scaling_mode; bool has_none_mode; struct drm_crtc *crtc; uint32_t possible_crtcs; /* Holds the properties for the connector */ struct drm_prop_info props[DRM_CONNECTOR_PROP_COUNT]; bool cursor_enabled; int cursor_x, cursor_y; int cursor_width, cursor_height; int cursor_hotspot_x, cursor_hotspot_y; /* Buffer to be submitted to the kernel on the next page-flip */ struct drm_fb *cursor_pending_fb; struct wlr_buffer *cursor_buffer; // Last committed page-flip struct drm_page_flip *pending_page_flip; int32_t refresh; // output state struct drm_output_state state; }; struct drm_connector_state { struct drm_connector *connector; const struct wlr_output_state *base; const struct drm_output_state *output_state; bool active; drmModeModeInfo mode; struct drm_fb *primary_fb; struct drm_fb *cursor_fb; // used by atomic uint32_t brightness; uint32_t overscan; uint32_t rgb_range; uint32_t scaling_mode; uint32_t mode_id; uint32_t gamma_lut; uint32_t rgb_ctm; uint32_t fb_damage_clips; bool vrr_enabled; }; struct drm_device_state { bool modeset; bool nonblock; struct drm_connector_state *conn_state; }; struct drm_device { struct wl_event_loop *event_loop; struct wlr_backend *wlr_backend; const struct drm_impl *impl; int fd; char *name; bool addfb2_modifiers; bool supports_tearing_page_flips; uint64_t cursor_width, cursor_height; enum drm_kms_mode mode; struct wl_event_source *drm_event; /* Only initialized on multi-GPU setups */ struct drm_renderer mgpu_renderer; size_t num_crtcs; struct drm_crtc *crtcs; size_t num_planes; struct drm_plane *planes; /* drm_connector::link */ struct wl_list connectors; /* drm_pageflip::link */ struct wl_list page_flips; /* drm_fb::link */ struct wl_list fbs; bool session_active; /* wlr_session::active */ }; /** * Helper to create new DRM sub-backends on GPU hotplog */ struct drm_backend_monitor { struct wlr_backend *multi; struct wlr_backend *primary_drm; struct wlr_session *session; struct wl_listener multi_destroy; struct wl_listener primary_drm_destroy; struct wl_listener session_destroy; struct wl_listener session_add_drm_card; }; const char *pnp_get_manufacturer(const char code[static 3]); struct drm_device *drm_device_create(int fd, struct wlr_backend *backend, struct wl_event_loop *loop); void drm_device_destroy(struct drm_device *drm); void drm_restore_connectors(struct drm_device *drm); void drm_scan_connectors(struct drm_device *drm, struct wlr_device_hotplug_event *event); void drm_update_connector(struct drm_device *drm, struct wlr_device_hotplug_event *event); size_t drm_get_crtc_gamma_lut_size(struct drm_crtc *crtc); /* property */ bool drm_get_property_value(int fd, uint32_t type, uint32_t obj, struct drm_prop_info *prop, uint64_t *ret); void *drm_get_property_blob(int fd, uint32_t type, uint32_t obj, struct drm_prop_info *prop, size_t *ret_len); char *drm_get_property_enum_list(int fd, uint32_t type, uint32_t obj, struct drm_prop_info *prop); char *drm_get_property_enum(int fd, uint32_t type, uint32_t obj, struct drm_prop_info *prop); bool drm_get_property_range(int fd, uint32_t prop_id, uint64_t *min, uint64_t *max); bool drm_get_connector_props(int fd, uint32_t obj, struct drm_prop_info *out); bool drm_get_plane_props(int fd, uint32_t obj, struct drm_prop_info *out); bool drm_get_crtc_props(int fd, uint32_t obj, struct drm_prop_info *out); /* drm-fb */ struct drm_fb *drm_fb_lock_fb(struct drm_fb *fb); void drm_fb_clear_fb(struct drm_fb **fb_ptr); void drm_fb_destroy_fb(struct drm_fb *fb); void drm_fb_copy_fb(struct drm_fb **new, struct drm_fb *old); void drm_fb_move_fb(struct drm_fb **new, struct drm_fb **old); bool drm_fb_import_fb(struct drm_fb **fb_ptr, struct drm_device *drm, struct wlr_buffer *buf, const struct wlr_drm_format_set *formats, bool use_yuyv); /* util */ int32_t drm_util_calculate_refresh_rate(const drmModeModeInfo *mode); enum wlr_output_mode_aspect_ratio drm_util_get_picture_aspect_ratio(const drmModeModeInfo *mode); void drm_util_parse_edid(struct drm_connector *conn, const uint8_t *data, size_t len); const char *drm_util_get_connector_status_str(drmModeConnection status); size_t drm_util_match_obj(size_t num_objs, const uint32_t objs[static restrict num_objs], size_t num_res, const uint32_t res[static restrict num_res], uint32_t out[static restrict num_res]); void drm_util_generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay, float vrefresh); /* renderer */ bool drm_mgpu_renderer_init(struct drm_device *drm, struct drm_renderer *renderer); void drm_mgpu_renderer_finish(struct drm_renderer *renderer); bool drm_plane_configure_surface_swapchain(struct drm_plane *plane, struct drm_connector *conn, struct drm_renderer *mgpu_renderer, int width, int height, uint32_t format, const struct wlr_output_state *state, bool use_yuyv); struct wlr_buffer *drm_surface_blit(struct drm_surface *surface, struct drm_render_target *target); void drm_surface_finish(struct drm_surface *surf); /* kms */ bool drm_kms_commit(struct drm_device *drm, const struct drm_device_state *state, struct drm_page_flip *page_flip, uint32_t flags, bool test_only); /* drm lease */ void drm_scan_leases(struct drm_device *drm); struct drm_lease *drm_create_lease(struct wlr_output **outputs, size_t n_outputs, int *lease_fd_ptr); void drm_lease_terminate(struct drm_lease *lease); struct drm_backend *drm_backend_from_wlr_backend(struct wlr_backend *wlr_backend); struct drm_connector *drm_connector_from_output(struct wlr_output *wlr_output); void drm_output_pending_resolution(struct wlr_output *output, const struct wlr_output_state *state, int *width, int *height); bool drm_output_use_hw_brightness(struct wlr_output *output, const struct kywc_output_state *state); bool drm_output_use_hw_color_temp(struct wlr_output *output, const struct kywc_output_state *state); bool drm_output_use_hw_color_filter(struct wlr_output *output, const struct kywc_output_state *state); void drm_output_set_state(struct output *output); void drm_output_state_clear(struct drm_output_state *state); void drm_color_temp_to_rgb(uint32_t color_temp, float *rgb); const float *drm_output_get_color_filter_matrix(enum kywc_output_color_filter color_filter); #if HAVE_DRM_LEASE_DEVICE bool drm_lease_device_v1_create(struct drm_backend *backend); #else static __attribute__((unused)) inline bool drm_lease_device_v1_create(struct drm_backend *backend) { return false; } #endif #endif /* _BACKEND_DRM_P_H_ */ kylin-wayland-compositor/src/backend/drm/kms.c0000664000175000017500000007223215160461067020371 0ustar fengfeng// SPDX-FileCopyrightText: 2025 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include "drm_p.h" static bool state_create_mode_blob(const struct drm_connector_state *state, uint32_t *blob_id) { if (!state->active) { *blob_id = 0; return true; } struct drm_device *drm = state->connector->drm; if (drmModeCreatePropertyBlob(drm->fd, &state->mode, sizeof(drmModeModeInfo), blob_id)) { kywc_log_errno(KYWC_ERROR, "Unable to create mode property blob"); return false; } return true; } static bool state_create_rgb_ctm_blob(const struct drm_connector_state *state, uint32_t *blob_id) { const struct drm_color_ctm *ctm = &state->output_state->rgb_ctm; if (ctm->matrix[0] == ctm->matrix[4] && ctm->matrix[0] == ctm->matrix[8] && ctm->matrix[0] == (uint64_t)1 << 32) { *blob_id = 0; kywc_log(KYWC_DEBUG, "Default ctm"); return true; } struct drm_device *drm = state->connector->drm; if (drmModeCreatePropertyBlob(drm->fd, ctm, sizeof(*ctm), blob_id) != 0) { kywc_log_errno(KYWC_ERROR, "Unable to creat clolor CTM property blob"); return false; } return true; } static bool state_create_gamma_lut_blob(const struct drm_connector_state *state, uint32_t *blob_id) { size_t lut_size = state->output_state->lut_size; if (lut_size == 0) { *blob_id = 0; return true; } const struct drm_color_lut *lut = state->output_state->lut; struct drm_device *drm = state->connector->drm; if (drmModeCreatePropertyBlob(drm->fd, lut, lut_size * sizeof(*lut), blob_id) != 0) { kywc_log_errno(KYWC_ERROR, "Unable to create gamma LUT property blob"); return false; } return true; } static bool state_create_fb_damage_clips_blob(const struct drm_connector_state *state, uint32_t *blob_id) { const pixman_region32_t *damage = &state->base->damage; if (!pixman_region32_not_empty(damage)) { *blob_id = 0; return true; } pixman_region32_t clipped; pixman_region32_init(&clipped); uint32_t width = state->primary_fb->wlr_buf->width; uint32_t height = state->primary_fb->wlr_buf->height; pixman_region32_intersect_rect(&clipped, damage, 0, 0, width, height); int rects_len; const pixman_box32_t *rects = pixman_region32_rectangles(&clipped, &rects_len); struct drm_device *drm = state->connector->drm; int ret = drmModeCreatePropertyBlob(drm->fd, rects, sizeof(*rects) * rects_len, blob_id); pixman_region32_fini(&clipped); if (ret != 0) { kywc_log_errno(KYWC_ERROR, "Failed to create FB_DAMAGE_CLIPS property blob"); return false; } return true; } static bool connector_cursor_is_visible(struct drm_connector *conn) { return conn->cursor_enabled && conn->cursor_x < conn->output.width && conn->cursor_y < conn->output.height && conn->cursor_x + conn->cursor_width >= 0 && conn->cursor_y + conn->cursor_height >= 0; } static uint64_t max_bpc_for_format(uint32_t format) { switch (format) { case DRM_FORMAT_XRGB2101010: case DRM_FORMAT_ARGB2101010: case DRM_FORMAT_XBGR2101010: case DRM_FORMAT_ABGR2101010: return 10; case DRM_FORMAT_XBGR16161616F: case DRM_FORMAT_ABGR16161616F: case DRM_FORMAT_XBGR16161616: case DRM_FORMAT_ABGR16161616: return 16; default: return 8; } } static uint64_t pick_max_bpc(struct drm_connector *conn, struct drm_fb *fb) { uint32_t format = DRM_FORMAT_INVALID; struct wlr_dmabuf_attributes attribs = { 0 }; if (wlr_buffer_get_dmabuf(fb->wlr_buf, &attribs)) { format = attribs.format; } uint64_t target_bpc = max_bpc_for_format(format); if (target_bpc < conn->max_bpc_bounds[0]) { target_bpc = conn->max_bpc_bounds[0]; } if (target_bpc > conn->max_bpc_bounds[1]) { target_bpc = conn->max_bpc_bounds[1]; } return target_bpc; } static void destroy_blob(struct drm_device *drm, uint32_t id) { if (id == 0) { return; } if (drmModeDestroyPropertyBlob(drm->fd, id)) { kywc_log_errno(KYWC_ERROR, "Failed to destroy blob"); } } static void commit_blob(struct drm_device *drm, uint32_t *current, uint32_t next) { if (*current == next) { return; } if (*current != 0) { drmModeDestroyPropertyBlob(drm->fd, *current); } *current = next; } static void rollback_blob(struct drm_device *drm, uint32_t *current, uint32_t next) { if (*current == next) { return; } if (next != 0) { drmModeDestroyPropertyBlob(drm->fd, next); } } /* legacy */ static bool legacy_fb_info_match(struct drm_fb *fb1, struct drm_fb *fb2) { struct wlr_dmabuf_attributes dmabuf1 = { 0 }, dmabuf2 = { 0 }; if (!wlr_buffer_get_dmabuf(fb1->wlr_buf, &dmabuf1) || !wlr_buffer_get_dmabuf(fb2->wlr_buf, &dmabuf2)) { return false; } if (dmabuf1.width != dmabuf2.width || dmabuf1.height != dmabuf2.height || dmabuf1.format != dmabuf2.format || dmabuf1.modifier != dmabuf2.modifier || dmabuf1.n_planes != dmabuf2.n_planes) { return false; } for (int i = 0; i < dmabuf1.n_planes; i++) { if (dmabuf1.stride[i] != dmabuf2.stride[i] || dmabuf1.offset[i] != dmabuf2.offset[i]) { return false; } } return true; } static void fill_empty_gamma_table(size_t size, uint16_t *r, uint16_t *g, uint16_t *b) { assert(0xFFFF < UINT64_MAX / (size - 1)); for (uint32_t i = 0; i < size; ++i) { uint16_t val = (uint64_t)0xFFFF * i / (size - 1); r[i] = g[i] = b[i] = val; } } static bool legacy_crtc_set_gamma(struct drm_crtc *crtc, const struct drm_color_lut *lut, size_t size) { uint16_t *linear_lut = NULL; if (size == 0) { // The legacy interface doesn't offer a way to reset the gamma LUT size = drm_get_crtc_gamma_lut_size(crtc); if (size == 0) { return false; } } linear_lut = malloc(3 * size * sizeof(uint16_t)); if (linear_lut == NULL) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); return false; } uint16_t *r = linear_lut; uint16_t *g = linear_lut + size; uint16_t *b = linear_lut + 2 * size; if (lut) { for (uint32_t i = 0; i < size; ++i) { r[i] = lut[i].red; g[i] = lut[i].green; b[i] = lut[i].blue; } } else { // reset the gamma LUT fill_empty_gamma_table(size, r, g, b); } if (drmModeCrtcSetGamma(crtc->drm->fd, crtc->id, size, r, g, b) != 0) { kywc_log_errno(KYWC_ERROR, "Failed to set gamma LUT on CRTC %" PRIu32, crtc->id); free(linear_lut); return false; } free(linear_lut); return true; } static bool legacy_crtc_test(const struct drm_connector_state *state, bool modeset) { struct drm_crtc *crtc = state->connector->crtc; if (state->base->committed & WLR_OUTPUT_STATE_BUFFER) { int pending_width, pending_height; drm_output_pending_resolution(&state->connector->output, state->base, &pending_width, &pending_height); if (state->base->buffer->width != pending_width || state->base->buffer->height != pending_height) { kywc_log(KYWC_DEBUG, "Primary buffer size mismatch"); return false; } if (!modeset) { struct drm_fb *prev_fb = crtc->primary->queued_fb; if (!prev_fb) { prev_fb = crtc->primary->current_fb; } /* Legacy is only guaranteed to be able to display a FB if it's been * allocated the same way as the previous one. */ struct drm_fb *pending_fb = state->primary_fb; if (prev_fb != NULL && !legacy_fb_info_match(prev_fb, pending_fb)) { kywc_log(KYWC_DEBUG, "Cannot change scan-out buffer parameters with legacy KMS API"); return false; } } } return true; } static bool legacy_crtc_commit(const struct drm_connector_state *state, struct drm_page_flip *page_flip, uint32_t flags, bool modeset) { struct drm_connector *conn = state->connector; struct drm_crtc *crtc = conn->crtc; struct drm_device *drm = conn->drm; uint32_t fb_id = 0; if (state->active) { if (state->primary_fb == NULL) { kywc_log(KYWC_ERROR, "%s: failed to acquire primary FB", conn->output.name); return false; } fb_id = state->primary_fb->id; } if (modeset) { if (drmModeConnectorSetProperty(drm->fd, conn->id, conn->props[DRM_CONNECTOR_PROP_DPMS].id, DRM_MODE_DPMS_OFF)) { kywc_log_errno(KYWC_ERROR, "Failed to set DPMS property"); return false; } uint32_t *conns = NULL; size_t conns_len = 0; drmModeModeInfo *mode = NULL; if (state->active) { conns = &conn->id; conns_len = 1; mode = (drmModeModeInfo *)&state->mode; } if (drmModeSetCrtc(drm->fd, crtc->id, fb_id, 0, 0, conns, conns_len, mode)) { kywc_log_errno(KYWC_ERROR, "Failed to set CRTC"); return false; } if (state->active && drmModeConnectorSetProperty(drm->fd, conn->id, conn->props[DRM_CONNECTOR_PROP_DPMS].id, DRM_MODE_DPMS_ON)) { kywc_log_errno(KYWC_ERROR, "Failed to set DPMS property"); return false; } } if (state->output_state->committed & DRM_OUTPUT_STATE_BRIGHTNESS && conn->props[DRM_CONNECTOR_PROP_BRIGHTNESS].id) { if (drmModeConnectorSetProperty(drm->fd, conn->id, conn->props[DRM_CONNECTOR_PROP_BRIGHTNESS].id, state->output_state->brightness)) { kywc_log_errno(KYWC_ERROR, "Failed to set Brightness property"); return false; } conn->brightness = state->output_state->brightness; } if (state->output_state->committed & DRM_OUTPUT_STATE_RGB_RANGE && conn->props[DRM_CONNECTOR_PROP_RGB_RANGE].id) { if (drmModeConnectorSetProperty(drm->fd, conn->id, conn->props[DRM_CONNECTOR_PROP_RGB_RANGE].id, state->output_state->rgb_range)) { kywc_log_errno(KYWC_ERROR, "Failed to set Broadcast RGB property"); return false; } conn->rgb_range = state->output_state->rgb_range; } if (state->output_state->committed & DRM_OUTPUT_STATE_OVERSCAN && conn->props[DRM_CONNECTOR_PROP_OVERSCAN].id) { if (drmModeConnectorSetProperty(drm->fd, conn->id, conn->props[DRM_CONNECTOR_PROP_OVERSCAN].id, state->output_state->overscan)) { kywc_log_errno(KYWC_ERROR, "Failed to set Overscan property"); return false; } conn->overscan = state->output_state->overscan; } if (state->output_state->committed & DRM_OUTPUT_STATE_SCALING_MODE && conn->props[DRM_CONNECTOR_PROP_SCALING_MODE].id) { if (drmModeConnectorSetProperty(drm->fd, conn->id, conn->props[DRM_CONNECTOR_PROP_SCALING_MODE].id, state->output_state->scaling_mode)) { kywc_log_errno(KYWC_ERROR, "Failed to set Scaling mode property %d", state->output_state->scaling_mode); return false; } conn->scaling_mode = state->output_state->scaling_mode; } if (state->output_state->committed & DRM_OUTPUT_STATE_GAMMA_LUT) { if (!legacy_crtc_set_gamma(crtc, state->output_state->lut, state->output_state->lut_size)) { return false; } } if (state->base->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) { if (!conn->adaptive_sync_supported && state->base->adaptive_sync_enabled) { return false; } if (crtc->props[DRM_CRTC_PROP_VRR_ENABLE].id && drmModeObjectSetProperty(drm->fd, crtc->id, DRM_MODE_OBJECT_CRTC, crtc->props[DRM_CRTC_PROP_VRR_ENABLE].id, state->base->adaptive_sync_enabled) != 0) { kywc_log_errno(KYWC_ERROR, "Failed with drmModeObjectSetProperty(VRR_ENABLED)"); return false; } conn->output.adaptive_sync_status = state->base->adaptive_sync_enabled ? WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED : WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED; kywc_log(KYWC_DEBUG, "VRR %s", state->base->adaptive_sync_enabled ? "enabled" : "disabled"); } if (crtc->cursor && state->active && connector_cursor_is_visible(conn)) { struct drm_fb *cursor_fb = state->cursor_fb; if (cursor_fb == NULL) { kywc_log(KYWC_DEBUG, "Failed to acquire cursor FB"); return false; } drmModeFB *drm_fb = drmModeGetFB(drm->fd, cursor_fb->id); if (drm_fb == NULL) { kywc_log_errno(KYWC_DEBUG, "Failed to get cursor BO handle: drmModeGetFB failed"); return false; } uint32_t cursor_handle = drm_fb->handle; uint32_t cursor_width = drm_fb->width; uint32_t cursor_height = drm_fb->height; drmModeFreeFB(drm_fb); int ret = drmModeSetCursor(drm->fd, crtc->id, cursor_handle, cursor_width, cursor_height); int set_errno = errno; if (drmCloseBufferHandle(drm->fd, cursor_handle) != 0) { kywc_log_errno(KYWC_ERROR, "Failed with drmCloseBufferHandle"); } if (ret != 0) { kywc_log(KYWC_DEBUG, "Failed with drmModeSetCursor failed: %s", strerror(set_errno)); return false; } if (drmModeMoveCursor(drm->fd, crtc->id, conn->cursor_x, conn->cursor_y) != 0) { kywc_log_errno(KYWC_ERROR, "Failed with drmModeMoveCursor"); return false; } } else { if (drmModeSetCursor(drm->fd, crtc->id, 0, 0, 0)) { kywc_log_errno_ratelimited(KYWC_WARN, "Failed with drmModeSetCursor"); return false; } } if (flags & DRM_MODE_PAGE_FLIP_EVENT) { if (drmModePageFlip(drm->fd, crtc->id, fb_id, flags, page_flip)) { kywc_log_errno_ratelimited(KYWC_ERROR, "Failed with drmModePageFlip(crtc_id: %d)", crtc->id); return false; } } return true; } static bool legacy_kms(struct drm_device *drm, const struct drm_device_state *state, struct drm_page_flip *page_flip, uint32_t flags, bool test_only) { if (!legacy_crtc_test(state->conn_state, state->modeset)) { return false; } if (test_only) { return true; } if (!legacy_crtc_commit(state->conn_state, page_flip, flags, state->modeset)) { return false; } return true; } /* atomic */ struct atomic { drmModeAtomicReq *req; bool failed; }; static char *atomic_commit_flags_str(uint32_t flags) { const char *const l[] = { (flags & DRM_MODE_PAGE_FLIP_EVENT) ? "PAGE_FLIP_EVENT" : NULL, (flags & DRM_MODE_PAGE_FLIP_ASYNC) ? "PAGE_FLIP_ASYNC" : NULL, (flags & DRM_MODE_ATOMIC_TEST_ONLY) ? "ATOMIC_TEST_ONLY" : NULL, (flags & DRM_MODE_ATOMIC_NONBLOCK) ? "ATOMIC_NONBLOCK" : NULL, (flags & DRM_MODE_ATOMIC_ALLOW_MODESET) ? "ATOMIC_ALLOW_MODESET" : NULL, }; char *buf = NULL; size_t size = 0; FILE *f = open_memstream(&buf, &size); if (f == NULL) { return NULL; } for (size_t i = 0; i < sizeof(l) / sizeof(l[0]); i++) { if (l[i] == NULL) { continue; } if (ftell(f) > 0) { fprintf(f, " | "); } fprintf(f, "%s", l[i]); } if (ftell(f) == 0) { fprintf(f, "none"); } fclose(f); return buf; } static void atomic_begin(struct atomic *atom) { *atom = (struct atomic){ 0 }; atom->req = drmModeAtomicAlloc(); if (!atom->req) { kywc_log_errno(KYWC_ERROR, "Allocation failed"); atom->failed = true; } } static void atomic_add(struct atomic *atom, uint32_t id, uint32_t prop, uint64_t val) { if (!atom->failed && drmModeAtomicAddProperty(atom->req, id, prop, val) < 0) { kywc_log_errno(KYWC_ERROR, "Failed to add atomic DRM property"); atom->failed = true; } } static bool atomic_commit(struct atomic *atom, struct drm_device *drm, struct drm_page_flip *page_flip, uint32_t flags) { if (atom->failed) { return false; } int ret = drmModeAtomicCommit(drm->fd, atom->req, flags, page_flip); if (ret != 0) { kywc_log_errno_ratelimited(flags & DRM_MODE_ATOMIC_TEST_ONLY ? KYWC_WARN : KYWC_ERROR, "Atomic commit failed"); char *flags_str = atomic_commit_flags_str(flags); kywc_log_ratelimited(KYWC_DEBUG, "(Atomic commit flags: %s)", flags_str ? flags_str : ""); free(flags_str); return false; } return true; } static void atomic_finish(struct atomic *atom) { drmModeAtomicFree(atom->req); } static void drm_kms_apply_atomic_commit(struct drm_connector_state *state) { struct drm_connector *conn = state->connector; struct drm_crtc *crtc = conn->crtc; if (!crtc->own_mode_id) { crtc->mode_id = 0; // don't try to delete previous master's blobs } crtc->own_mode_id = true; commit_blob(conn->drm, &crtc->mode_id, state->mode_id); commit_blob(conn->drm, &crtc->gamma_lut, state->gamma_lut); commit_blob(conn->drm, &crtc->ctm, state->rgb_ctm); conn->output.adaptive_sync_status = state->vrr_enabled ? WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED : WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED; conn->brightness = state->brightness; conn->rgb_range = state->rgb_range; conn->overscan = state->overscan; conn->scaling_mode = state->scaling_mode; destroy_blob(conn->drm, state->fb_damage_clips); } static void drm_kms_rollback_atomic_commit(struct drm_connector_state *state) { struct drm_connector *conn = state->connector; struct drm_crtc *crtc = conn->crtc; rollback_blob(conn->drm, &crtc->mode_id, state->mode_id); rollback_blob(conn->drm, &crtc->gamma_lut, state->gamma_lut); rollback_blob(conn->drm, &crtc->ctm, state->rgb_ctm); destroy_blob(conn->drm, state->fb_damage_clips); } static bool atomic_connector_prepare(struct drm_connector_state *state, bool modeset) { struct drm_connector *conn = state->connector; struct drm_crtc *crtc = conn->crtc; uint32_t mode_id = crtc->mode_id; if (modeset) { if (!state_create_mode_blob(state, &mode_id)) { return false; } } uint32_t gamma_lut = crtc->gamma_lut; if (state->output_state->committed & DRM_OUTPUT_STATE_GAMMA_LUT) { // Fallback to legacy gamma interface when gamma properties are not // available (can happen on older Intel GPUs that support gamma but not // degamma). if (crtc->props[DRM_CRTC_PROP_GAMMA_LUT].id == 0) { if (!legacy_crtc_set_gamma(crtc, state->output_state->lut, state->output_state->lut_size)) { return false; } } else { if (!state_create_gamma_lut_blob(state, &gamma_lut)) { return false; } } } uint32_t rgb_ctm = crtc->ctm; if (state->output_state->committed & DRM_OUTPUT_STATE_RGB_CTM && crtc->props[DRM_CRTC_PROP_CTM].id) { if (!state_create_rgb_ctm_blob(state, &rgb_ctm)) { return false; } } uint32_t fb_damage_clips = 0; if ((state->base->committed & WLR_OUTPUT_STATE_DAMAGE) && crtc->primary->props[DRM_PLANE_PROP_FB_DAMAGE_CLIPS].id != 0) { state_create_fb_damage_clips_blob(state, &fb_damage_clips); } bool prev_vrr_enabled = conn->output.adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED; bool vrr_enabled = prev_vrr_enabled; if ((state->base->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED)) { if (!conn->adaptive_sync_supported) { return false; } vrr_enabled = state->base->adaptive_sync_enabled; } uint32_t brightness = conn->brightness; if (state->output_state->committed & DRM_OUTPUT_STATE_BRIGHTNESS && conn->props[DRM_CONNECTOR_PROP_BRIGHTNESS].id) { brightness = state->output_state->brightness; } uint32_t rgb_range = conn->rgb_range; if (state->output_state->committed & DRM_OUTPUT_STATE_RGB_RANGE && conn->props[DRM_CONNECTOR_PROP_RGB_RANGE].id) { rgb_range = state->output_state->rgb_range; } uint32_t overscan = conn->overscan; if (state->output_state->committed & DRM_OUTPUT_STATE_OVERSCAN && conn->props[DRM_CONNECTOR_PROP_OVERSCAN].id) { overscan = state->output_state->overscan; } uint32_t scaling_mode = conn->scaling_mode; if (state->output_state->committed & DRM_OUTPUT_STATE_SCALING_MODE && conn->props[DRM_CONNECTOR_PROP_SCALING_MODE].id) { scaling_mode = state->output_state->scaling_mode; } state->mode_id = mode_id; state->gamma_lut = gamma_lut; state->fb_damage_clips = fb_damage_clips; state->vrr_enabled = vrr_enabled; state->rgb_ctm = rgb_ctm; state->brightness = brightness; state->rgb_range = rgb_range; state->overscan = overscan; state->scaling_mode = scaling_mode; return true; } static bool cursor_plane_support_hotspots(const struct drm_plane *plane) { return plane->props[DRM_PLANE_PROP_HOTSPOT_X].id && plane->props[DRM_PLANE_PROP_HOTSPOT_Y].id; } static void plane_disable(struct atomic *atom, struct drm_plane *plane) { atomic_add(atom, plane->id, plane->props[DRM_PLANE_PROP_FB_ID].id, 0); atomic_add(atom, plane->id, plane->props[DRM_PLANE_PROP_CRTC_ID].id, 0); } static void plane_set_properties(struct atomic *atom, struct drm_plane *plane, struct drm_fb *fb, uint32_t crtc_id, int32_t x, int32_t y) { if (fb == NULL) { kywc_log(KYWC_ERROR, "Failed to acquire FB for plane %" PRIu32, plane->id); atom->failed = true; return; } uint32_t width = fb->wlr_buf->width; uint32_t height = fb->wlr_buf->height; width = plane->enable_yuyv ? width * 2 : width; // The src_* properties are in 16.16 fixed point atomic_add(atom, plane->id, plane->props[DRM_PLANE_PROP_SRC_X].id, 0); atomic_add(atom, plane->id, plane->props[DRM_PLANE_PROP_SRC_Y].id, 0); atomic_add(atom, plane->id, plane->props[DRM_PLANE_PROP_SRC_W].id, (uint64_t)width << 16); atomic_add(atom, plane->id, plane->props[DRM_PLANE_PROP_SRC_H].id, (uint64_t)height << 16); atomic_add(atom, plane->id, plane->props[DRM_PLANE_PROP_CRTC_W].id, width); atomic_add(atom, plane->id, plane->props[DRM_PLANE_PROP_CRTC_H].id, height); atomic_add(atom, plane->id, plane->props[DRM_PLANE_PROP_FB_ID].id, fb->id); atomic_add(atom, plane->id, plane->props[DRM_PLANE_PROP_CRTC_ID].id, crtc_id); atomic_add(atom, plane->id, plane->props[DRM_PLANE_PROP_CRTC_X].id, (uint64_t)x); atomic_add(atom, plane->id, plane->props[DRM_PLANE_PROP_CRTC_Y].id, (uint64_t)y); } static void plane_set_color_properties(struct atomic *atom, struct drm_plane *plane, uint32_t color_encode, uint32_t color_range) { if (plane->type != DRM_PLANE_TYPE_PRIMARY || !plane->enable_yuyv) { return; } uint32_t prop_encode = plane->props[DRM_PLANE_PROP_COLOR_ENCODING].id; if (prop_encode) { atomic_add(atom, plane->id, plane->props[DRM_PLANE_PROP_COLOR_ENCODING].id, color_encode); } uint32_t prop_range = plane->props[DRM_PLANE_PROP_COLOR_RANGE].id; if (prop_range) { atomic_add(atom, plane->id, plane->props[DRM_PLANE_PROP_COLOR_RANGE].id, color_range); } } static void atomic_connector_add(struct atomic *atom, struct drm_connector_state *state, bool modeset) { struct drm_connector *conn = state->connector; struct drm_crtc *crtc = conn->crtc; bool active = state->active; atomic_add(atom, conn->id, conn->props[DRM_CONNECTOR_PROP_CRTC_ID].id, active ? crtc->id : 0); uint32_t link_state = conn->props[DRM_CONNECTOR_PROP_LINK_STATUS].id; if (modeset && active && link_state) { atomic_add(atom, conn->id, link_state, DRM_MODE_LINK_STATUS_GOOD); } uint32_t content_type = conn->props[DRM_CONNECTOR_PROP_CONTENT_TYPE].id; if (active && content_type) { atomic_add(atom, conn->id, content_type, DRM_MODE_CONTENT_TYPE_GRAPHICS); } uint32_t max_bpc = conn->props[DRM_CONNECTOR_PROP_MAX_BPC].id; if (modeset && active && max_bpc && conn->max_bpc_bounds[1]) { atomic_add(atom, conn->id, max_bpc, pick_max_bpc(conn, state->primary_fb)); } atomic_add(atom, crtc->id, crtc->props[DRM_CRTC_PROP_MODE_ID].id, state->mode_id); atomic_add(atom, crtc->id, crtc->props[DRM_CRTC_PROP_ACTIVE].id, active); if (!active) { plane_disable(atom, crtc->primary); if (crtc->cursor) { plane_disable(atom, crtc->cursor); } return; } uint32_t brightness = conn->props[DRM_CONNECTOR_PROP_BRIGHTNESS].id; if (brightness) { atomic_add(atom, conn->id, brightness, state->brightness); } uint32_t rgb_range = conn->props[DRM_CONNECTOR_PROP_RGB_RANGE].id; if (rgb_range) { atomic_add(atom, conn->id, rgb_range, state->rgb_range); } uint32_t overscan = conn->props[DRM_CONNECTOR_PROP_OVERSCAN].id; if (overscan) { atomic_add(atom, conn->id, overscan, state->overscan); } uint32_t scaling_mode = conn->props[DRM_CONNECTOR_PROP_SCALING_MODE].id; if (scaling_mode) { atomic_add(atom, conn->id, scaling_mode, state->scaling_mode); } uint32_t gamma_lut = crtc->props[DRM_CRTC_PROP_GAMMA_LUT].id; if (gamma_lut) { atomic_add(atom, crtc->id, gamma_lut, state->gamma_lut); } uint32_t rgb_ctm = crtc->props[DRM_CRTC_PROP_CTM].id; if (rgb_ctm) { atomic_add(atom, crtc->id, rgb_ctm, state->rgb_ctm); } uint32_t vrr_enabled = crtc->props[DRM_CRTC_PROP_VRR_ENABLE].id; if (vrr_enabled) { atomic_add(atom, crtc->id, vrr_enabled, state->vrr_enabled); } plane_set_color_properties(atom, crtc->primary, DRM_COLOR_ENCODING_BT601, DRM_COLOR_RANGE_LIMITTED); plane_set_properties(atom, crtc->primary, state->primary_fb, crtc->id, 0, 0); uint32_t fb_damage_clips = crtc->primary->props[DRM_PLANE_PROP_FB_DAMAGE_CLIPS].id; if (fb_damage_clips) { atomic_add(atom, crtc->primary->id, fb_damage_clips, state->fb_damage_clips); } if (!crtc->cursor) { return; } if (connector_cursor_is_visible(conn)) { plane_set_properties(atom, crtc->cursor, state->cursor_fb, crtc->id, conn->cursor_x, conn->cursor_y); if (cursor_plane_support_hotspots(crtc->cursor)) { uint32_t hotspot_x = crtc->cursor->props[DRM_PLANE_PROP_HOTSPOT_X].id; uint32_t hotspot_y = crtc->cursor->props[DRM_PLANE_PROP_HOTSPOT_Y].id; atomic_add(atom, crtc->cursor->id, hotspot_x, conn->cursor_hotspot_x); atomic_add(atom, crtc->cursor->id, hotspot_y, conn->cursor_hotspot_y); } } else { plane_disable(atom, crtc->cursor); } } static bool atomic_kms(struct drm_device *drm, const struct drm_device_state *state, struct drm_page_flip *page_flip, uint32_t flags, bool test_only) { bool ok = false; if (!atomic_connector_prepare(state->conn_state, state->modeset)) { goto out; } struct atomic atom; atomic_begin(&atom); atomic_connector_add(&atom, state->conn_state, state->modeset); if (test_only) { flags |= DRM_MODE_ATOMIC_TEST_ONLY; } if (state->modeset) { flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; } if (!test_only && state->nonblock) { flags |= DRM_MODE_ATOMIC_NONBLOCK; } ok = atomic_commit(&atom, drm, page_flip, flags); atomic_finish(&atom); out: if (ok && !test_only) { drm_kms_apply_atomic_commit(state->conn_state); } else { drm_kms_rollback_atomic_commit(state->conn_state); } return ok; } bool drm_kms_commit(struct drm_device *drm, const struct drm_device_state *state, struct drm_page_flip *page_flip, uint32_t flags, bool test_only) { switch (drm->mode) { case DRM_KMS_MODE_ATOMIC: return atomic_kms(drm, state, page_flip, flags, test_only); break; case DRM_KMS_MODE_LEGACY: return legacy_kms(drm, state, page_flip, flags, test_only); break; default: break; } return true; } kylin-wayland-compositor/src/backend/drm/properties.c0000664000175000017500000002003615160461067021766 0ustar fengfeng// SPDX-FileCopyrightText: 2025 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include "drm_p.h" const struct drm_prop_info crtc_infos[DRM_CRTC_PROP_COUNT] = { [DRM_CRTC_PROP_ACTIVE] = { .name = "ACTIVE", }, [DRM_CRTC_PROP_MODE_ID] = {.name = "MODE_ID",}, [DRM_CRTC_PROP_OUT_FENCE_PTR] = { .name = "OUTPUT_FENCE_PTR", }, [DRM_CRTC_PROP_VRR_ENABLE] = { .name = "VRR_ENABLED", }, [DRM_CRTC_PROP_CTM] = {.name= "CTM",}, [DRM_CRTC_PROP_GAMMA_LUT] = { .name = "GAMMA_LUT", }, [DRM_CRTC_PROP_GAMMA_LUT_SIZE] = { .name = "GAMMA_LUT_SIZE", }, }; const struct drm_prop_info plane_infos[DRM_PLANE_PROP_COUNT] = { [DRM_PLANE_PROP_TYPE] = { .name = "type",}, [DRM_PLANE_PROP_FB_ID] = { .name = "FB_ID",}, [DRM_PLANE_PROP_CRTC_ID] = { .name = "CRTC_ID",}, [DRM_PLANE_PROP_CRTC_X] = { .name = "CRTC_X", }, [DRM_PLANE_PROP_CRTC_Y] = { .name = "CRTC_Y", }, [DRM_PLANE_PROP_CRTC_W] = { .name = "CRTC_W", }, [DRM_PLANE_PROP_CRTC_H] = { .name = "CRTC_H", }, [DRM_PLANE_PROP_SRC_X] = { .name = "SRC_X", }, [DRM_PLANE_PROP_SRC_Y] = { .name = "SRC_Y",}, [DRM_PLANE_PROP_SRC_W] = { .name = "SRC_W", }, [DRM_PLANE_PROP_SRC_H] = { .name = "SRC_H", }, [DRM_PLANE_PROP_IN_FORMATS] = { .name = "IN_FORMATS",}, [DRM_PLANE_PROP_FB_DAMAGE_CLIPS] = { .name = "FB_DAMAGE_CLIPS",}, [DRM_PLANE_PROP_ROTATION] = { .name = "ROTATION",}, [DRM_PLANE_PROP_HOTSPOT_X] = { .name = "HOTSPOT_X",}, [DRM_PLANE_PROP_HOTSPOT_Y] = { .name = "HOTSPOT_Y",}, [DRM_PLANE_PROP_COLOR_ENCODING] = { .name = "COLOR_ENCODING",}, [DRM_PLANE_PROP_COLOR_RANGE] = { .name = "COLOR_RANGE",}, [DRM_PLANE_PROP_SIZE_HINTS] = { .name = "SIZE_HINTS",}, }; const struct drm_prop_info connector_infos[DRM_CONNECTOR_PROP_COUNT] = { [DRM_CONNECTOR_PROP_EDID] = { .name = "EDID",}, [DRM_CONNECTOR_PROP_DPMS] = { .name = "DPMS",}, [DRM_CONNECTOR_PROP_LINK_STATUS] = { .name = "link-status",}, [DRM_CONNECTOR_PROP_NON_DESKTOP] = { .name = "non-desktop",}, [DRM_CONNECTOR_PROP_CRTC_ID] = { .name = "CRTC_ID",}, [DRM_CONNECTOR_PROP_CONTENT_TYPE] = { .name = "content type",}, [DRM_CONNECTOR_PROP_MAX_BPC] = { .name = "max bpc",}, [DRM_CONNECTOR_PROP_PANEL_ORIENTATION] = { .name = "panel orientation",}, [DRM_CONNECTOR_PROP_SUBCONNECTOR] = { .name = "subconnector",}, [DRM_CONNECTOR_PROP_VRR_CAPABLE] = { .name = "vrr_capable",}, [DRM_CONNECTOR_PROP_BRIGHTNESS] = { .name = "brightness",}, [DRM_CONNECTOR_PROP_RGB_RANGE] = { .name = "Broadcast RGB",}, [DRM_CONNECTOR_PROP_OVERSCAN] = { .name = "overscan",}, [DRM_CONNECTOR_PROP_SCALING_MODE] = { .name = "scaling mode",}, }; char *drm_get_property_enum_list(int fd, uint32_t type, uint32_t obj, struct drm_prop_info *prop) { uint64_t value; if (!drm_get_property_value(fd, type, obj, prop, &value)) { return NULL; } drmModePropertyRes *drm_prop = drmModeGetProperty(fd, prop->id); if (!drm_prop) { return NULL; } char *str = NULL; size_t total_len = 0; for (int i = 0; i < drm_prop->count_enums; i++) { total_len += strlen(drm_prop->enums[i].name) + (i > 0 ? 2 : 0); } if (total_len > 0) { str = malloc(total_len + 1); // +1 for null terminator if (str) { char *ptr = str; for (int i = 0; i < drm_prop->count_enums; i++) { if (i > 0) { *ptr++ = ','; *ptr++ = ' '; } const char *name = drm_prop->enums[i].name; size_t len = strlen(name); memcpy(ptr, name, len); ptr += len; } *ptr = '\0'; } } drmModeFreeProperty(drm_prop); return str; } char *drm_get_property_enum(int fd, uint32_t type, uint32_t obj, struct drm_prop_info *prop) { uint64_t value; if (!drm_get_property_value(fd, type, obj, prop, &value)) { return NULL; } drmModePropertyRes *drm_prop = drmModeGetProperty(fd, prop->id); if (!drm_prop) { return NULL; } char *str = NULL; for (int i = 0; i < drm_prop->count_enums; i++) { if (drm_prop->enums[i].value == value) { str = strdup(drm_prop->enums[i].name); break; } } drmModeFreeProperty(drm_prop); return str; } void *drm_get_property_blob(int fd, uint32_t type, uint32_t obj, struct drm_prop_info *prop, size_t *ret_len) { uint64_t blob_id; if (!drm_get_property_value(fd, type, obj, prop, &blob_id)) { kywc_log(KYWC_WARN, "Failed to get Blob Id"); return NULL; } drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(fd, blob_id); if (!blob) { return NULL; } void *ptr = malloc(blob->length); if (!ptr) { drmModeFreePropertyBlob(blob); return NULL; } memcpy(ptr, blob->data, blob->length); *ret_len = blob->length; drmModeFreePropertyBlob(blob); return ptr; } bool drm_get_property_value(int fd, uint32_t type, uint32_t obj, struct drm_prop_info *prop, uint64_t *ret) { drmModeObjectProperties *drm_props = drmModeObjectGetProperties(fd, obj, type); if (!drm_props) { kywc_log_errno(KYWC_ERROR, "Failed to get DRM[%x] object[%d] properties", type, obj); return false; } bool found = false; for (uint32_t i = 0; i < drm_props->count_props; ++i) { if (drm_props->props[i] == prop->id) { *ret = drm_props->prop_values[i]; found = true; break; } } drmModeFreeObjectProperties(drm_props); return found; } bool drm_get_property_range(int fd, uint32_t prop_id, uint64_t *min, uint64_t *max) { drmModePropertyRes *prop = drmModeGetProperty(fd, prop_id); if (!prop) { return false; } if (drmModeGetPropertyType(prop) != DRM_MODE_PROP_RANGE) { drmModeFreeProperty(prop); return false; } assert(prop->count_values == 2); if (min != NULL) { *min = prop->values[0]; } if (max != NULL) { *max = prop->values[1]; } drmModeFreeProperty(prop); return true; } static bool scan_drm_properties(int fd, uint32_t obj, uint32_t type, struct drm_prop_info *out, const struct drm_prop_info *info, uint32_t info_len) { drmModeObjectProperties *drm_props = drmModeObjectGetProperties(fd, obj, type); if (!drm_props) { kywc_log(KYWC_ERROR, "Failed to get DRM [%s] properties", type == DRM_MODE_OBJECT_CRTC ? "CRTC" : type == DRM_MODE_OBJECT_PLANE ? "plane" : "connecotr"); return false; } for (uint32_t i = 0; i < drm_props->count_props; ++i) { drmModePropertyRes *prop = drmModeGetProperty(fd, drm_props->props[i]); if (!prop) { kywc_log_errno(KYWC_ERROR, "Failed to get DRM property"); continue; } for (uint32_t j = 0; j < info_len; ++j) { if (strcmp(prop->name, info[j].name)) { continue; } out[j].name = info[j].name; out[j].id = prop->prop_id; break; } drmModeFreeProperty(prop); } drmModeFreeObjectProperties(drm_props); return true; } bool drm_get_connector_props(int fd, uint32_t obj, struct drm_prop_info *out) { return scan_drm_properties(fd, obj, DRM_MODE_OBJECT_CONNECTOR, out, connector_infos, DRM_CONNECTOR_PROP_COUNT); } bool drm_get_plane_props(int fd, uint32_t obj, struct drm_prop_info *out) { return scan_drm_properties(fd, obj, DRM_MODE_OBJECT_PLANE, out, plane_infos, DRM_PLANE_PROP_COUNT); } bool drm_get_crtc_props(int fd, uint32_t obj, struct drm_prop_info *out) { return scan_drm_properties(fd, obj, DRM_MODE_OBJECT_CRTC, out, crtc_infos, DRM_CRTC_PROP_COUNT); } kylin-wayland-compositor/src/backend/drm/renderer.c0000664000175000017500000005614615160461067021413 0ustar fengfeng// SPDX-FileCopyrightText: 2025 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include "drm_p.h" #include "output.h" #include "render/allocator.h" #include "render/opengl.h" #include "render/renderer.h" #include "shaders/tex_frag_str.h" #include "shaders/tex_vert_str.h" #include "shaders/tex_yuv_frag_str.h" #define MAX_QUADS 86 // 4kb static bool set_renderer_drm_formats(struct drm_renderer *renderer, const struct wlr_drm_format_set *set) { if (!renderer || !set) { return false; } for (size_t idx = 0; idx < set->len; idx++) { struct wlr_drm_format *fmt = &set->formats[idx]; wlr_drm_format_set_add(&renderer->formats, fmt->format, DRM_FORMAT_MOD_LINEAR); } wlr_drm_format_set_add(&renderer->formats, DRM_FORMAT_ARGB8888, DRM_FORMAT_MOD_LINEAR); return true; } static bool drm_mgpu_renderer_with_pixman(struct drm_device *drm, struct drm_renderer *renderer) { renderer->wlr_rend = wlr_pixman_renderer_create(); if (!renderer->wlr_rend) { kywc_log(KYWC_ERROR, "Failed to create a pixman renderer for mGPU"); return false; } renderer->allocator = allocator_create(drm->wlr_backend, renderer->wlr_rend); if (!renderer->allocator) { kywc_log(KYWC_ERROR, "Failed to create allocator for renderer"); return false; } for (size_t i = 0; i < drm->num_planes; i++) { struct drm_plane *drm_plane = &drm->planes[i]; set_renderer_drm_formats(renderer, &drm_plane->formats); } return true; } bool drm_mgpu_renderer_init(struct drm_device *drm, struct drm_renderer *renderer) { kywc_log(KYWC_INFO, "DRM mgpu render init"); renderer->wlr_rend = ky_opengl_renderer_create_with_drm_fd(drm->fd); if (!renderer->wlr_rend) { return drm_mgpu_renderer_with_pixman(drm, renderer); } const struct wlr_drm_format_set *texture_formats = NULL; texture_formats = wlr_renderer_get_texture_formats(renderer->wlr_rend, WLR_BUFFER_CAP_DMABUF); if (!texture_formats) { kywc_log(KYWC_WARN, "Failed to query renderer texture formats"); wlr_renderer_destroy(renderer->wlr_rend); return drm_mgpu_renderer_with_pixman(drm, renderer); } renderer->allocator = allocator_create(drm->wlr_backend, renderer->wlr_rend); for (size_t i = 0; i < texture_formats->len; i++) { const struct wlr_drm_format *fmt = &texture_formats->formats[i]; for (size_t j = 0; j < fmt->len; j++) { uint64_t mod = fmt->modifiers[j]; if (mod == DRM_FORMAT_MOD_INVALID) { continue; } wlr_drm_format_set_add(&renderer->formats, fmt->format, mod); } } return true; } static void surface_swapchain_destroy(struct surf_swapchain *surf_swapchain) { if (!surf_swapchain) { return; } swapchain_destroy(surf_swapchain->swapchain); free(surf_swapchain); } void drm_mgpu_renderer_finish(struct drm_renderer *renderer) { if (!renderer) { return; } wlr_allocator_destroy(renderer->allocator); wlr_renderer_destroy(renderer->wlr_rend); wlr_drm_format_set_finish(&renderer->formats); } void drm_surface_finish(struct drm_surface *surf) { if (!surf || !surf->wlr_rend) { return; } if (surf->dumb_swapchain != surf->swapchain) { surface_swapchain_destroy(surf->swapchain); } surface_swapchain_destroy(surf->dumb_swapchain); *surf = (struct drm_surface){ 0 }; } static bool drm_format_intersect(struct wlr_drm_format *dst, const struct wlr_drm_format *a, const struct wlr_drm_format *b) { assert(a->format == b->format); size_t capacity = a->len < b->len ? a->len : b->len; uint64_t *modifiers = malloc(sizeof(*modifiers) * capacity); if (!modifiers) { return false; } struct wlr_drm_format fmt = { .capacity = capacity, .len = 0, .modifiers = modifiers, .format = a->format, }; for (size_t i = 0; i < a->len; i++) { for (size_t j = 0; j < b->len; j++) { if (a->modifiers[i] == b->modifiers[j]) { assert(fmt.len < fmt.capacity); fmt.modifiers[fmt.len++] = a->modifiers[i]; break; } } } wlr_drm_format_finish(dst); *dst = fmt; return true; } static bool create_opengl_shader(struct ky_opengl_renderer *gl_renderer, bool use_yuyv) { struct ky_opengl_surface_shader *gl_shader = use_yuyv ? &gl_renderer->shaders.surface_yuv_tex : &gl_renderer->shaders.surface_tex; if (gl_shader->program) { return true; } GLuint prog = ky_opengl_create_program(gl_renderer, tex_vert_str, use_yuyv ? tex_yuv_frag_str : tex_frag_str); if (prog == 0) { return false; } gl_shader->program = prog; gl_shader->in_position = glGetAttribLocation(prog, "in_position"); gl_shader->size = glGetUniformLocation(prog, "size"); gl_shader->contrast = glGetUniformLocation(prog, "contrast"); gl_shader->whitepoint = glGetUniformLocation(prog, "whitepoint"); gl_shader->color_matrix = glGetUniformLocation(prog, "color_matrix"); gl_shader->brightness = glGetUniformLocation(prog, "brightness"); gl_shader->texture = glGetUniformLocation(prog, "tex"); if (use_yuyv) { gl_shader->pixel_offset_x = glGetUniformLocation(prog, "pixel_offset_x"); } return true; } static bool render_pass_add_texture(struct wlr_renderer *renderer, struct wlr_texture *src_texture, struct drm_render_target *target) { if (target->damage && !pixman_region32_not_empty(target->damage)) { return true; } int width = src_texture->width; int height = src_texture->height; if (width <= 0 || height <= 0) { return true; } struct ky_opengl_renderer *gl_renderer = ky_opengl_renderer_from_wlr_renderer(renderer); if (!create_opengl_shader(gl_renderer, target->use_yuyv)) { return false; } struct ky_opengl_surface_shader *gl_shader = target->use_yuyv ? &gl_renderer->shaders.surface_yuv_tex : &gl_renderer->shaders.surface_tex; glUseProgram(gl_shader->program); glUniform2f(gl_shader->size, width, height); float contrast = 1.0f; glUniform1f(gl_shader->contrast, contrast); float whitepoint[3] = { 0 }; drm_color_temp_to_rgb(target->color_temp, whitepoint); glUniform3fv(gl_shader->whitepoint, 1, whitepoint); glUniformMatrix4fv(gl_shader->color_matrix, 1, GL_FALSE, target->color_mat); glUniform1f(gl_shader->brightness, (float)target->brightness * 0.01f); if (target->use_yuyv) { float pixel_offset_x = 1.0f / src_texture->width; glUniform1f(gl_shader->pixel_offset_x, pixel_offset_x); } glDisable(GL_BLEND); // bind texture glUniform1i(gl_shader->texture, 0); glActiveTexture(GL_TEXTURE0); struct ky_opengl_texture *texture = ky_opengl_texture_from_wlr_texture(src_texture); glBindTexture(GL_TEXTURE_2D, texture->tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // draw damage pixman_region32_t clipped; pixman_region32_init_rect(&clipped, 0, 0, width, height); if (target->damage) { float scale = target->output->base.state.scale; if (target->use_yuyv && floor(scale) == scale) { wlr_region_expand(&clipped, target->damage, 1); pixman_region32_intersect_rect(&clipped, &clipped, 0, 0, width, height); } else { pixman_region32_intersect_rect(&clipped, target->damage, 0, 0, width, height); } } glEnableVertexAttribArray(gl_shader->in_position); int rects_len = 0; const pixman_box32_t *rects = pixman_region32_rectangles(&clipped, &rects_len); for (int i = 0; i < rects_len;) { int batch = rects_len - i < MAX_QUADS ? rects_len - i : MAX_QUADS; int batch_end = batch + i; size_t vert_index = 0; GLfloat verts[MAX_QUADS * 6 * 2]; for (; i < batch_end; i++) { const pixman_box32_t *rect = &rects[i]; verts[vert_index++] = (GLfloat)rect->x1; verts[vert_index++] = (GLfloat)rect->y1; verts[vert_index++] = (GLfloat)rect->x2; verts[vert_index++] = (GLfloat)rect->y1; verts[vert_index++] = (GLfloat)rect->x1; verts[vert_index++] = (GLfloat)rect->y2; verts[vert_index++] = (GLfloat)rect->x2; verts[vert_index++] = (GLfloat)rect->y1; verts[vert_index++] = (GLfloat)rect->x2; verts[vert_index++] = (GLfloat)rect->y2; verts[vert_index++] = (GLfloat)rect->x1; verts[vert_index++] = (GLfloat)rect->y2; } glVertexAttribPointer(gl_shader->in_position, 2, GL_FLOAT, GL_FALSE, 0, verts); glDrawArrays(GL_TRIANGLES, 0, batch * 6); } glDisableVertexAttribArray(gl_shader->in_position); glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); pixman_region32_fini(&clipped); return true; } static bool readpixel_from_buffer(struct wlr_renderer *wlr_rend, struct wlr_buffer *source, struct wlr_buffer *dst, const pixman_region32_t *damage) { if (damage && !pixman_region32_not_empty(damage)) { return true; } size_t stride; uint32_t format = 0; void *dst_ptr = NULL; if (!wlr_buffer_begin_data_ptr_access(dst, WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &dst_ptr, &format, &stride)) { kywc_log(KYWC_ERROR, "Failed to get buffer data %p", dst); return false; } struct wlr_dmabuf_attributes attribs = { .format = format }; if (!wlr_buffer_get_dmabuf(source, &attribs) && !wlr_renderer_is_pixman(wlr_rend)) { wlr_buffer_end_data_ptr_access(dst); return false; } struct wlr_texture *texture = wlr_texture_from_buffer(wlr_rend, source); if (!texture) { wlr_buffer_end_data_ptr_access(dst); return false; } int x = 0, y = 0; int width = source->width; int height = source->height; if (damage) { pixman_region32_t clipped; pixman_region32_init(&clipped); pixman_region32_intersect_rect(&clipped, damage, 0, 0, width, height); y = clipped.extents.y1; height = clipped.extents.y2 - clipped.extents.y1; pixman_region32_fini(&clipped); } struct wlr_box box = { x, y, width, height }; wlr_texture_read_pixels( texture, &(struct wlr_texture_read_pixels_options){ .data = dst_ptr, .format = format, .stride = stride, .src_box = box }); wlr_texture_destroy(texture); wlr_buffer_end_data_ptr_access(dst); return true; } static struct wlr_buffer *blit_buffer_with_opengl(struct drm_surface *surf, struct drm_render_target *target) { struct wlr_renderer *renderer = target->wlr_rend; struct wlr_texture *tex = wlr_texture_from_buffer(renderer, target->source); if (!tex) { kywc_log(KYWC_ERROR, "Failed to import source buffer into renderer"); return NULL; } struct wlr_buffer *dst = swapchain_acquire(surf->swapchain->swapchain, NULL); if (!dst) { kywc_log(KYWC_ERROR, "Failed to acquire swapchain buffer"); goto error_tex; } struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, dst, NULL); if (!pass) { kywc_log(KYWC_ERROR, "Failed to begin render pass on destination buffer"); goto error_dst; } if (target->rgb_clear) { struct ky_render_rect_options option = (struct ky_render_rect_options){ .base={ .box = { .width = dst->width, .height = dst->height }, .clip = target->damage, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, }, }; ky_render_pass_add_rect(pass, &option); } render_pass_add_texture(renderer, tex, target); if (!ky_render_pass_submit(pass, target->output->quirks)) { kywc_log(KYWC_ERROR, "Failed to submit render pass"); goto error_dst; } wlr_texture_destroy(tex); return dst; error_dst: wlr_buffer_unlock(dst); error_tex: wlr_texture_destroy(tex); return NULL; } static bool drm_plane_pick_render_format(struct wlr_renderer *renderer, const struct wlr_drm_format_set *display_formats, struct wlr_drm_format *format, uint32_t fmt) { const struct wlr_drm_format *render_format = ky_renderer_get_render_format(renderer, fmt); if (render_format == NULL) { kywc_log(KYWC_ERROR, "Failed to get render formats"); return false; } const struct wlr_drm_format *display_format = wlr_drm_format_set_get(display_formats, fmt); if (display_format == NULL) { kywc_log(KYWC_ERROR, "Output plane doesn't support format 0x%" PRIX32, fmt); return false; } if (!drm_format_intersect(format, display_format, render_format)) { kywc_log(KYWC_ERROR, "Failed to intersect display and render modifiers for format 0x%" PRIX32 "", fmt); return false; } if (format->len == 0) { wlr_drm_format_finish(format); kywc_log(KYWC_ERROR, "Failed to pick output format"); return false; } return true; } static bool test_swapchain(struct wlr_output *output, struct swapchain *swapchain, const struct wlr_output_state *state) { struct wlr_buffer *buffer = swapchain_acquire(swapchain, NULL); if (buffer == NULL) { return false; } struct wlr_output_state copy = *state; copy.committed |= WLR_OUTPUT_STATE_BUFFER; copy.buffer = buffer; bool ok = wlr_output_test_state(output, ©); wlr_buffer_unlock(buffer); return ok; } static bool drm_format_has(const struct wlr_drm_format *fmt, uint64_t modifier) { for (size_t i = 0; i < fmt->len; ++i) { if (fmt->modifiers[i] == modifier) { return true; } } return false; } static bool drm_format_add(struct wlr_drm_format *fmt, uint64_t modifier) { if (drm_format_has(fmt, modifier)) { return true; } if (fmt->len == fmt->capacity) { size_t capacity = fmt->capacity ? fmt->capacity * 2 : 4; uint64_t *new_modifiers = realloc(fmt->modifiers, sizeof(*fmt->modifiers) * capacity); if (!new_modifiers) { kywc_log(KYWC_ERROR, "Allocation failed"); return false; } fmt->capacity = capacity; fmt->modifiers = new_modifiers; } fmt->modifiers[fmt->len++] = modifier; return true; } static struct surf_swapchain *surface_swapchain_create(struct wlr_allocator *allocator, enum allocator_buffer_type type, struct swapchain_option *option) { struct surf_swapchain *surf_swapchain = calloc(1, sizeof(*surf_swapchain)); if (!surf_swapchain) { return NULL; } struct wlr_drm_format *render_format = option->drm_format; struct wlr_output *wlr_output = option->output; char *format_name = drmGetFormatName(render_format->format); kywc_log(KYWC_DEBUG, "Choosing plane buffer format %s (0x%08" PRIX32 ") for output '%s'", format_name ? format_name : "", render_format->format, wlr_output->name); free(format_name); struct swapchain *swapchain = allocator_create_swapchain(allocator, type, option->width, option->height, render_format, 0); if (!swapchain) { wlr_drm_format_finish(render_format); free(swapchain); return NULL; } if (!option->state) { surf_swapchain->swapchain = swapchain; surf_swapchain->width = option->width; surf_swapchain->height = option->height; return surf_swapchain; } if (!test_swapchain(wlr_output, swapchain, option->state)) { kywc_log(KYWC_DEBUG, "Output test failed on '%s', retrying without modifiers", option->output->name); swapchain_destroy(swapchain); swapchain = NULL; if (render_format->len != 1 || render_format->modifiers[0] != DRM_FORMAT_MOD_LINEAR) { if (!drm_format_has(render_format, DRM_FORMAT_MOD_INVALID)) { kywc_log(KYWC_DEBUG, "Implicit modifiers not supported"); wlr_drm_format_finish(render_format); } } render_format->len = 0; if (!drm_format_add(render_format, DRM_FORMAT_MOD_INVALID)) { kywc_log(KYWC_DEBUG, "Failed to add implicit modifier to format"); goto error; } swapchain = allocator_create_swapchain(allocator, type, option->width, option->height, render_format, 0); if (!test_swapchain(wlr_output, swapchain, option->state)) { kywc_log(KYWC_DEBUG, "Output test failed on '%s', retrying without modifiers", option->output->name); goto error; } } surf_swapchain->width = option->width; surf_swapchain->height = option->height; surf_swapchain->swapchain = swapchain; return surf_swapchain; error: wlr_drm_format_finish(render_format); swapchain_destroy(swapchain); free(surf_swapchain); return NULL; } bool drm_plane_configure_surface_swapchain(struct drm_plane *plane, struct drm_connector *conn, struct drm_renderer *mgpu_renderer, int width, int height, uint32_t format, const struct wlr_output_state *state, bool use_yuyv) { struct drm_surface *surf = &plane->multi_surf; if (surf->swapchain && surf->swapchain->swapchain && surf->swapchain->width == width && surf->swapchain->height == height) { return true; } if (surf->dumb_swapchain != surf->swapchain) { surface_swapchain_destroy(surf->swapchain); } surface_swapchain_destroy(surf->dumb_swapchain); surf->swapchain = NULL; surf->dumb_swapchain = NULL; /* both primary and secondary gpu use pixman */ bool both_pixman = false; struct wlr_drm_format render_format = { 0 }; struct wlr_renderer *renderer = conn->output.renderer; struct wlr_allocator *allocator = conn->output.allocator; const struct wlr_drm_format_set *display_formats = &plane->formats; struct surf_swapchain *swapchain = NULL; struct surf_swapchain *dumb_swapchain = NULL; if (mgpu_renderer != NULL) { display_formats = &mgpu_renderer->formats; if (!wlr_renderer_is_pixman(mgpu_renderer->wlr_rend)) { renderer = mgpu_renderer->wlr_rend; allocator = mgpu_renderer->allocator; } else if (wlr_renderer_is_pixman(renderer)) { both_pixman = true; goto dumb_alloc; } } else if (wlr_renderer_is_pixman(renderer) && allocator->buffer_caps & WLR_BUFFER_CAP_DMABUF) { surf->wlr_rend = renderer; return true; } struct wlr_drm_format_set yuv_display_formats = { 0 }; if (!mgpu_renderer && use_yuyv) { wlr_drm_format_set_add(&yuv_display_formats, format, DRM_FORMAT_MOD_LINEAR); display_formats = &yuv_display_formats; } if (!drm_plane_pick_render_format(renderer, display_formats, &render_format, format)) { wlr_drm_format_set_finish(&yuv_display_formats); kywc_log(KYWC_WARN, "Renderer pick renderer format failed"); return false; } wlr_drm_format_set_finish(&yuv_display_formats); enum allocator_buffer_type type = wlr_renderer_is_pixman(renderer) ? ALLOCATOR_BUFFER_TYPE_DUMB : ALLOCATOR_BUFFER_TYPE_GBM; struct swapchain_option option = { .width = width, .height = height, .output = &conn->output, .drm_format = &render_format, .state = use_yuyv ? NULL : state, }; swapchain = surface_swapchain_create(allocator, type, &option); if (!swapchain) { return false; } wlr_drm_format_finish(&render_format); if (!mgpu_renderer && type == ALLOCATOR_BUFFER_TYPE_DUMB) { dumb_swapchain = swapchain; } dumb_alloc: if (mgpu_renderer && wlr_renderer_is_pixman(mgpu_renderer->wlr_rend)) { struct wlr_drm_format drm_format = { 0 }; uint32_t disp_format = format; if (!wlr_drm_format_set_get(&plane->formats, format)) { disp_format = DRM_FORMAT_XRGB8888; } if (!drm_plane_pick_render_format(mgpu_renderer->wlr_rend, &plane->formats, &drm_format, disp_format)) { kywc_log(KYWC_WARN, "surface dumb format format failed"); surface_swapchain_destroy(swapchain); return false; } struct swapchain_option option = { .width = width, .height = height, .output = &conn->output, .drm_format = &drm_format, .state = NULL, }; dumb_swapchain = surface_swapchain_create(mgpu_renderer->allocator, ALLOCATOR_BUFFER_TYPE_DUMB, &option); if (!dumb_swapchain) { surface_swapchain_destroy(swapchain); return false; } wlr_drm_format_finish(&drm_format); if (both_pixman) { swapchain = dumb_swapchain; } } surf->swapchain = swapchain; surf->dumb_swapchain = dumb_swapchain; surf->wlr_rend = renderer; return true; } struct wlr_buffer *drm_surface_blit(struct drm_surface *surface, struct drm_render_target *target) { if (wlr_renderer_is_pixman(surface->wlr_rend) && !surface->swapchain && !surface->dumb_swapchain) { // do not blit return wlr_buffer_lock(target->source); } struct wlr_buffer *buffer = target->source; if (wlr_renderer_is_opengl(surface->wlr_rend)) { buffer = blit_buffer_with_opengl(surface, target); } else { buffer = wlr_buffer_lock(buffer); } if (!surface->dumb_swapchain) { return buffer; } struct wlr_buffer *dumb_buffer = swapchain_acquire(surface->dumb_swapchain->swapchain, NULL); if (!dumb_buffer) { kywc_log(KYWC_ERROR, "Failed to acquire multi-GPU dumb swapchain buffer"); goto error; } if (!readpixel_from_buffer(target->wlr_rend, buffer, dumb_buffer, target->damage)) { goto error_pixel; } wlr_buffer_unlock(buffer); return dumb_buffer; error_pixel: wlr_buffer_unlock(dumb_buffer); error: wlr_buffer_unlock(buffer); return NULL; } kylin-wayland-compositor/src/backend/drm/lease.c0000664000175000017500000005446415160460057020675 0ustar fengfeng// SPDX-FileCopyrightText: 2025 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "drm-lease-v1-protocol.h" #include "drm_p.h" #include "util/wayland.h" #define DRM_LEASE_DEVICE_V1_VERSION 1 struct drm_lease_device_v1 { struct wl_list resources; struct wl_global *global; struct drm_backend *backend; struct wl_list connectors; // drm_lease_connector_v1.link struct wl_list leases; // drm_lease_v1.link struct wl_list requests; // drm_lease_request_v1.link void *data; struct wl_listener new_output; struct wl_listener backend_destroy; }; struct drm_lease_v1; struct drm_lease_connector_v1 { struct wl_list resources; // wl_resource_get_link() struct wlr_output *output; struct drm_lease_device_v1 *device; /** NULL if no client is currently leasing this connector */ struct drm_lease_v1 *active_lease; struct wl_list link; // drm_lease_device_v1.connectors struct wl_listener destroy; }; struct drm_lease_request_v1 { struct wl_resource *resource; struct drm_lease_device_v1 *device; struct drm_lease_connector_v1 **connectors; size_t n_connectors; struct wl_resource *lease_resource; bool invalid; struct wl_list link; // drm_lease_device_v1.requests }; struct drm_lease_v1 { struct wl_resource *resource; struct drm_lease *drm_lease; struct drm_lease_device_v1 *device; struct drm_lease_connector_v1 **connectors; size_t n_connectors; struct wl_list link; // drm_lease_device_v1.leases void *data; struct wl_listener destroy; }; static struct wp_drm_lease_device_v1_interface lease_device_impl; static struct wp_drm_lease_connector_v1_interface lease_connector_impl; static struct wp_drm_lease_request_v1_interface lease_request_impl; static struct wp_drm_lease_v1_interface lease_impl; static void drm_lease_request_v1_reject(struct drm_lease_request_v1 *request) { assert(request); kywc_log(KYWC_DEBUG, "Rejecting request %p", request); request->invalid = true; wp_drm_lease_v1_send_finished(request->lease_resource); } /* static void drm_lease_v1_revoke(struct drm_lease_v1 *lease) { assert(lease); kywc_log(KYWC_DEBUG, "Revoking lease %" PRIu32, lease->drm_lease->lessee_id); drm_lease_terminate(lease->drm_lease); } */ static struct drm_lease_device_v1 *drm_lease_device_v1_from_resource(struct wl_resource *resource) { assert( wl_resource_instance_of(resource, &wp_drm_lease_device_v1_interface, &lease_device_impl)); return wl_resource_get_user_data(resource); } static struct drm_lease_connector_v1 * drm_lease_connector_v1_from_resource(struct wl_resource *resource) { assert(wl_resource_instance_of(resource, &wp_drm_lease_connector_v1_interface, &lease_connector_impl)); return wl_resource_get_user_data(resource); } static struct drm_lease_request_v1 *drm_lease_request_v1_from_resource(struct wl_resource *resource) { assert( wl_resource_instance_of(resource, &wp_drm_lease_request_v1_interface, &lease_request_impl)); return wl_resource_get_user_data(resource); } static void drm_connector_v1_handle_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static struct drm_lease_v1 *drm_lease_v1_from_resource(struct wl_resource *resource) { assert(wl_resource_instance_of(resource, &wp_drm_lease_v1_interface, &lease_impl)); return wl_resource_get_user_data(resource); } static void drm_lease_request_v1_destroy(struct drm_lease_request_v1 *request) { if (!request) { return; } kywc_log(KYWC_DEBUG, "Destroying request %p", request); wl_list_remove(&request->link); wl_resource_set_user_data(request->resource, NULL); free(request->connectors); free(request); } static void drm_lease_request_v1_handle_resource_destroy(struct wl_resource *resource) { struct drm_lease_request_v1 *request = drm_lease_request_v1_from_resource(resource); drm_lease_request_v1_destroy(request); } static void lease_handle_destroy(struct wl_listener *listener, void *data) { struct drm_lease_v1 *lease = wl_container_of(listener, lease, destroy); kywc_log(KYWC_DEBUG, "Destroying lease %" PRIu32, lease->drm_lease->lessee_id); wp_drm_lease_v1_send_finished(lease->resource); for (size_t i = 0; i < lease->n_connectors; ++i) { lease->connectors[i]->active_lease = NULL; } wl_list_remove(&lease->destroy.link); wl_list_remove(&lease->link); wl_resource_set_user_data(lease->resource, NULL); free(lease->connectors); free(lease); } static void drm_lease_connector_v1_destroy(struct drm_lease_connector_v1 *connector) { if (!connector) { return; } kywc_log(KYWC_DEBUG, "Destroying connector %s", connector->output->name); if (connector->active_lease) { drm_lease_terminate(connector->active_lease->drm_lease); } struct wl_resource *resource, *tmp; wl_resource_for_each_safe(resource, tmp, &connector->resources) { wp_drm_lease_connector_v1_send_withdrawn(resource); wl_resource_set_user_data(resource, NULL); wl_list_remove(wl_resource_get_link(resource)); wl_list_init(wl_resource_get_link(resource)); } struct wl_resource *device_resource; wl_resource_for_each(device_resource, &connector->device->resources) { wp_drm_lease_device_v1_send_done(device_resource); } wl_list_remove(&connector->link); wl_list_remove(&connector->destroy.link); free(connector); } static void drm_connector_v1_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static struct wp_drm_lease_connector_v1_interface lease_connector_impl = { .destroy = drm_connector_v1_handle_destroy, }; static void drm_lease_connector_v1_send_to_client(struct drm_lease_connector_v1 *connector, struct wl_resource *resource) { if (connector->active_lease) { return; } struct wl_client *client = wl_resource_get_client(resource); uint32_t version = wl_resource_get_version(resource); struct wl_resource *connector_resource = wl_resource_create(client, &wp_drm_lease_connector_v1_interface, version, 0); if (!connector_resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(connector_resource, &lease_connector_impl, connector, drm_connector_v1_handle_resource_destroy); wp_drm_lease_device_v1_send_connector(resource, connector_resource); struct wlr_output *output = connector->output; wp_drm_lease_connector_v1_send_name(connector_resource, output->name); // TODO: re-send the description when it's updated wp_drm_lease_connector_v1_send_description(connector_resource, output->description); wp_drm_lease_connector_v1_send_connector_id(connector_resource, drm_connector_from_output(output)->id); wp_drm_lease_connector_v1_send_done(connector_resource); wl_list_insert(&connector->resources, wl_resource_get_link(connector_resource)); } static void drm_lease_request_v1_handle_request_connector(struct wl_client *client, struct wl_resource *request_resource, struct wl_resource *connector_resource) { struct drm_lease_request_v1 *request = drm_lease_request_v1_from_resource(request_resource); if (!request) { kywc_log(KYWC_ERROR, "Request has been destroyed"); return; } struct drm_lease_connector_v1 *connector = drm_lease_connector_v1_from_resource(connector_resource); if (!connector) { /* This connector offer has been withdrawn or is leased */ kywc_log(KYWC_ERROR, "Failed to request connector"); request->invalid = true; return; } kywc_log(KYWC_DEBUG, "Requesting connector %s", connector->output->name); if (request->device != connector->device) { kywc_log(KYWC_ERROR, "The connector belongs to another device"); wl_resource_post_error(request_resource, WP_DRM_LEASE_REQUEST_V1_ERROR_WRONG_DEVICE, "The requested connector belongs to another device"); return; } for (size_t i = 0; i < request->n_connectors; ++i) { struct drm_lease_connector_v1 *tmp = request->connectors[i]; if (connector == tmp) { kywc_log(KYWC_ERROR, "The connector has already been requested"); wl_resource_post_error(request_resource, WP_DRM_LEASE_REQUEST_V1_ERROR_DUPLICATE_CONNECTOR, "The connector has already been requested"); return; } } size_t n_connectors = request->n_connectors + 1; struct drm_lease_connector_v1 **tmp_connectors = realloc(request->connectors, n_connectors * sizeof(struct drm_lease_connector_v1 *)); if (!tmp_connectors) { kywc_log(KYWC_ERROR, "Failed to grow connectors request array"); return; } request->connectors = tmp_connectors; request->connectors[request->n_connectors] = connector; request->n_connectors = n_connectors; } static void drm_lease_v1_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static struct wp_drm_lease_v1_interface lease_impl = { .destroy = drm_lease_v1_handle_destroy, }; static void drm_lease_v1_handle_resource_destroy(struct wl_resource *resource) { struct drm_lease_v1 *lease = drm_lease_v1_from_resource(resource); if (lease != NULL) { drm_lease_terminate(lease->drm_lease); } } static struct drm_lease_v1 *grant_drm_lease_request_v1(struct drm_lease_request_v1 *request) { assert(!request->invalid); kywc_log(KYWC_DEBUG, "Attempting to grant request %p", request); struct drm_lease_v1 *lease = calloc(1, sizeof(*lease)); if (!lease) { wl_resource_post_no_memory(request->resource); return NULL; } lease->device = request->device; lease->resource = request->lease_resource; /* Transform connectors list into wlr_output for leasing */ struct wlr_output *outputs[request->n_connectors + 1]; for (size_t i = 0; i < request->n_connectors; ++i) { outputs[i] = request->connectors[i]->output; } int fd; lease->drm_lease = drm_create_lease(outputs, request->n_connectors, &fd); if (!lease->drm_lease) { kywc_log(KYWC_ERROR, "Failed with drm_create_lease"); wp_drm_lease_v1_send_finished(lease->resource); free(lease); return NULL; } lease->connectors = calloc(request->n_connectors, sizeof(struct drm_lease_connector_v1 *)); if (!lease->connectors) { kywc_log(KYWC_ERROR, "Failed to allocate lease connectors list"); close(fd); wp_drm_lease_v1_send_finished(lease->resource); free(lease); return NULL; } lease->n_connectors = request->n_connectors; for (size_t i = 0; i < request->n_connectors; ++i) { lease->connectors[i] = request->connectors[i]; lease->connectors[i]->active_lease = lease; } lease->destroy.notify = lease_handle_destroy; wl_signal_add(&lease->drm_lease->events.destroy, &lease->destroy); wl_list_insert(&lease->device->leases, &lease->link); wl_resource_set_user_data(lease->resource, lease); kywc_log(KYWC_DEBUG, "Granting request %p", request); wp_drm_lease_v1_send_lease_fd(lease->resource, fd); close(fd); return lease; } static void drm_lease_request_v1_handle_submit(struct wl_client *client, struct wl_resource *resource, uint32_t id) { uint32_t version = wl_resource_get_version(resource); struct wl_resource *lease_resource = wl_resource_create(client, &wp_drm_lease_v1_interface, version, id); if (!lease_resource) { kywc_log(KYWC_ERROR, "Failed to allocate wl_resource"); wl_resource_post_no_memory(resource); return; } wl_resource_set_implementation(lease_resource, &lease_impl, NULL, drm_lease_v1_handle_resource_destroy); struct drm_lease_request_v1 *request = drm_lease_request_v1_from_resource(resource); if (!request) { kywc_log(KYWC_DEBUG, "Request has been destroyed"); wp_drm_lease_v1_send_finished(lease_resource); return; } /* Pre-emptively reject invalid lease requests */ if (request->invalid) { kywc_log(KYWC_ERROR, "Invalid request"); wp_drm_lease_v1_send_finished(lease_resource); return; } else if (request->n_connectors == 0) { wl_resource_post_error(lease_resource, WP_DRM_LEASE_REQUEST_V1_ERROR_EMPTY_LEASE, "Lease request has no connectors"); return; } for (size_t i = 0; i < request->n_connectors; ++i) { struct drm_lease_connector_v1 *conn = request->connectors[i]; if (conn->active_lease) { kywc_log(KYWC_ERROR, "Failed to create lease, connector %s has " "already been leased", conn->output->name); wp_drm_lease_v1_send_finished(lease_resource); return; } } request->lease_resource = lease_resource; grant_drm_lease_request_v1(request); /* If the compositor didn't act upon the request, reject it */ if (!request->invalid && wl_resource_get_user_data(lease_resource) == NULL) { drm_lease_request_v1_reject(request); } /* Request is done */ wl_resource_destroy(resource); } static struct wp_drm_lease_request_v1_interface lease_request_impl = { .request_connector = drm_lease_request_v1_handle_request_connector, .submit = drm_lease_request_v1_handle_submit, }; static void drm_lease_device_v1_handle_resource_destroy(struct wl_resource *resource) { wl_list_remove(wl_resource_get_link(resource)); } static void drm_lease_device_v1_handle_release(struct wl_client *client, struct wl_resource *resource) { wp_drm_lease_device_v1_send_released(resource); wl_resource_destroy(resource); } static void drm_lease_device_v1_handle_create_lease_request(struct wl_client *client, struct wl_resource *resource, uint32_t id) { uint32_t version = wl_resource_get_version(resource); struct wl_resource *request_resource = wl_resource_create(client, &wp_drm_lease_request_v1_interface, version, id); if (!request_resource) { kywc_log(KYWC_ERROR, "Failed to allocate wl_resource"); return; } wl_resource_set_implementation(request_resource, &lease_request_impl, NULL, drm_lease_request_v1_handle_resource_destroy); struct drm_lease_device_v1 *device = drm_lease_device_v1_from_resource(resource); if (!device) { kywc_log(KYWC_DEBUG, "Failed to create lease request, " "drm_lease_device_v1 has been destroyed"); return; } struct drm_lease_request_v1 *req = calloc(1, sizeof(*req)); if (!req) { kywc_log(KYWC_ERROR, "Failed to allocate wlr_drm_lease_request_v1"); wl_resource_post_no_memory(resource); return; } kywc_log(KYWC_DEBUG, "Created request %p", req); req->device = device; req->resource = request_resource; req->connectors = NULL; req->n_connectors = 0; wl_resource_set_user_data(request_resource, req); wl_list_insert(&device->requests, &req->link); } static struct wp_drm_lease_device_v1_interface lease_device_impl = { .release = drm_lease_device_v1_handle_release, .create_lease_request = drm_lease_device_v1_handle_create_lease_request, }; static void lease_device_bind(struct wl_client *wl_client, void *data, uint32_t version, uint32_t id) { struct wl_resource *device_resource = wl_resource_create(wl_client, &wp_drm_lease_device_v1_interface, version, id); if (!device_resource) { wl_client_post_no_memory(wl_client); return; } wl_list_init(wl_resource_get_link(device_resource)); wl_resource_set_implementation(device_resource, &lease_device_impl, NULL, drm_lease_device_v1_handle_resource_destroy); struct drm_lease_device_v1 *device = data; if (!device) { kywc_log(KYWC_DEBUG, "Failed to bind lease device, " "the drm_lease_device_v1 has been destroyed"); return; } int fd = drm_backend_get_non_master_fd(&device->backend->wlr_backend); if (fd < 0) { kywc_log(KYWC_ERROR, "Unable to get read only DRM fd for leasing"); return; } wp_drm_lease_device_v1_send_drm_fd(device_resource, fd); close(fd); wl_resource_set_user_data(device_resource, device); wl_list_insert(&device->resources, wl_resource_get_link(device_resource)); struct drm_lease_connector_v1 *connector; wl_list_for_each(connector, &device->connectors, link) { drm_lease_connector_v1_send_to_client(connector, device_resource); } wp_drm_lease_device_v1_send_done(device_resource); } static void drm_lease_device_v1_destroy(struct drm_lease_device_v1 *device) { if (!device) { return; } struct drm_backend *backend = device->backend; kywc_log(KYWC_DEBUG, "Destroying wlr_drm_lease_device_v1 for %s", backend->drm->name); struct wl_resource *resource, *tmp_resource; wl_resource_for_each_safe(resource, tmp_resource, &device->resources) { wl_list_remove(wl_resource_get_link(resource)); wl_list_init(wl_resource_get_link(resource)); wl_resource_set_user_data(resource, NULL); } struct drm_lease_request_v1 *request, *tmp_request; wl_list_for_each_safe(request, tmp_request, &device->requests, link) { drm_lease_request_v1_destroy(request); } struct drm_lease_v1 *lease, *tmp_lease; wl_list_for_each_safe(lease, tmp_lease, &device->leases, link) { drm_lease_terminate(lease->drm_lease); } struct drm_lease_connector_v1 *connector, *tmp_connector; wl_list_for_each_safe(connector, tmp_connector, &device->connectors, link) { drm_lease_connector_v1_destroy(connector); } wl_list_remove(&device->backend_destroy.link); wl_list_remove(&device->new_output.link); wl_global_destroy_safe(device->global); free(device); } static void handle_backend_destroy(struct wl_listener *listener, void *data) { struct drm_lease_device_v1 *device = wl_container_of(listener, device, backend_destroy); drm_lease_device_v1_destroy(device); } static void handle_output_destroy(struct wl_listener *listener, void *data) { struct drm_lease_connector_v1 *conn = wl_container_of(listener, conn, destroy); if (!conn->active_lease) { wl_list_remove(&conn->destroy.link); wl_list_init(&conn->destroy.link); return; } kywc_log(KYWC_DEBUG, "Withdrawing output %s", conn->output->name); drm_lease_connector_v1_destroy(conn); } static void handle_new_output(struct wl_listener *listener, void *data) { struct drm_lease_device_v1 *device = wl_container_of(listener, device, new_output); struct wlr_output *output = data; if (!output->non_desktop) { return; } kywc_log(KYWC_DEBUG, "Drm lease get offering output %s", output->name); struct drm_lease_connector_v1 *tmp_connector; wl_list_for_each(tmp_connector, &device->connectors, link) { if (tmp_connector->output == output) { kywc_log(KYWC_ERROR, "Output %s has already been offered", output->name); wl_list_remove(&tmp_connector->destroy.link); wl_signal_add(&output->events.destroy, &tmp_connector->destroy); return; } } struct drm_lease_connector_v1 *connector = calloc(1, sizeof(*connector)); if (!connector) { kywc_log(KYWC_ERROR, "Failed to allocate wlr_drm_lease_connector_v1"); return; } connector->output = output; connector->device = device; connector->destroy.notify = handle_output_destroy; wl_signal_add(&output->events.destroy, &connector->destroy); wl_list_init(&connector->resources); wl_list_insert(&device->connectors, &connector->link); struct wl_resource *resource; wl_resource_for_each(resource, &device->resources) { drm_lease_connector_v1_send_to_client(connector, resource); wp_drm_lease_device_v1_send_done(resource); } } bool drm_lease_device_v1_create(struct drm_backend *drm_backend) { // Make sure we can get a non-master FD for the DRM backend. On some setups // we don't have the permission for this. struct drm_device *drm = drm_backend->drm; int fd = drm_backend_get_non_master_fd(&drm_backend->wlr_backend); if (fd < 0) { kywc_log(KYWC_INFO, "Skipping %s: failed to get read-only DRM FD", drm->name); return false; } close(fd); kywc_log(KYWC_DEBUG, "Creating drm_lease_device_v1 for %s", drm->name); struct drm_lease_device_v1 *lease_device = calloc(1, sizeof(*lease_device)); if (!lease_device) { kywc_log(KYWC_ERROR, "Failed to allocate drm_lease_device_v1"); return false; } lease_device->backend = drm_backend; wl_list_init(&lease_device->resources); wl_list_init(&lease_device->connectors); wl_list_init(&lease_device->requests); wl_list_init(&lease_device->leases); lease_device->global = wl_global_create(drm->display, &wp_drm_lease_device_v1_interface, DRM_LEASE_DEVICE_V1_VERSION, lease_device, lease_device_bind); if (!lease_device->global) { kywc_log(KYWC_ERROR, "Failed to allocate drm_lease_device_v1 global"); free(lease_device); return false; } struct wlr_backend *backend = drm->wlr_backend; lease_device->backend_destroy.notify = handle_backend_destroy; wl_signal_add(&backend->events.destroy, &lease_device->backend_destroy); lease_device->new_output.notify = handle_new_output; wl_signal_add(&backend->events.new_output, &lease_device->new_output); return true; } kylin-wayland-compositor/src/backend/drm/backend.c0000664000175000017500000001441515160461067021165 0ustar fengfeng// SPDX-FileCopyrightText: 2025 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include "backend/drm.h" #include "drm_p.h" struct drm_backend *drm_backend_from_wlr_backend(struct wlr_backend *wlr_backend) { assert(wlr_backend_is_drm(wlr_backend)); struct drm_backend *backend = wl_container_of(wlr_backend, backend, wlr_backend); return backend; } static bool backend_start(struct wlr_backend *wlr_backend) { struct drm_backend *backend = drm_backend_from_wlr_backend(wlr_backend); drm_scan_connectors(backend->drm, NULL); return true; } static int backend_get_drm_fd(struct wlr_backend *wlr_backend) { struct drm_backend *backend = drm_backend_from_wlr_backend(wlr_backend); return backend->drm->fd; } static void backend_destroy(struct wlr_backend *wlr_backend) { if (!wlr_backend) { return; } struct drm_backend *backend = drm_backend_from_wlr_backend(wlr_backend); wlr_backend_finish(wlr_backend); wl_list_remove(&backend->session_destroy.link); wl_list_remove(&backend->session_active.link); wl_list_remove(&backend->parent_destroy.link); wl_list_remove(&backend->device_change.link); wl_list_remove(&backend->device_remove.link); drm_device_destroy(backend->drm); wlr_session_close_file(backend->session, backend->dev); free(backend); } static const struct wlr_backend_impl backend_impl = { .start = backend_start, .destroy = backend_destroy, .get_drm_fd = backend_get_drm_fd, }; static void handle_session_active(struct wl_listener *listener, void *data) { struct drm_backend *backend = wl_container_of(listener, backend, session_active); struct wlr_session *session = backend->session; kywc_log(KYWC_INFO, "DRM device %s %s", backend->drm->name, session->active ? "resumed" : "paused"); backend->drm->session_active = session->active; if (session->active) { drm_restore_connectors(backend->drm); } } static void handle_device_change(struct wl_listener *listener, void *data) { struct drm_backend *backend = wl_container_of(listener, backend, device_change); struct wlr_device_change_event *change = data; if (!backend->session->active) { if (change->type == WLR_DEVICE_HOTPLUG) { drm_update_connector(backend->drm, &change->hotplug); } return; } switch (change->type) { case WLR_DEVICE_HOTPLUG: kywc_log(KYWC_DEBUG, "Received hotplug event for %s", backend->drm->name); drm_scan_connectors(backend->drm, &change->hotplug); break; case WLR_DEVICE_LEASE: kywc_log(KYWC_DEBUG, "Received lease event for %s", backend->drm->name); drm_scan_leases(backend->drm); break; default: kywc_log(KYWC_DEBUG, "Received unknown change event for %s", backend->drm->name); } } static void handle_device_remove(struct wl_listener *listener, void *data) { struct drm_backend *backend = wl_container_of(listener, backend, device_remove); kywc_log(KYWC_INFO, "Destroying DRM backend for %s", backend->drm->name); backend_destroy(&backend->wlr_backend); } static void handle_session_destroy(struct wl_listener *listener, void *data) { struct drm_backend *backend = wl_container_of(listener, backend, session_destroy); backend_destroy(&backend->wlr_backend); } static void handle_parent_destroy(struct wl_listener *listener, void *data) { struct drm_backend *backend = wl_container_of(listener, backend, parent_destroy); backend_destroy(&backend->wlr_backend); } bool wlr_backend_is_drm(struct wlr_backend *backend) { return backend->impl == &backend_impl; } int drm_backend_get_non_master_fd(struct wlr_backend *wlr_backend) { assert(wlr_backend); struct drm_backend *backend = drm_backend_from_wlr_backend(wlr_backend); int fd = open(backend->drm->name, O_RDWR | O_CLOEXEC); if (fd < 0) { kywc_log_errno(KYWC_ERROR, "Unable to clone DRM fd for client fd"); return -1; } if (drmIsMaster(fd) && drmDropMaster(fd) < 0) { kywc_log_errno(KYWC_ERROR, "Failed to drop master"); return -1; } return fd; } struct wlr_backend *drm_backend_create(struct wlr_session *session, struct wlr_device *dev, struct wlr_backend *parent) { struct drm_backend *backend = calloc(1, sizeof(*backend)); if (!backend) { kywc_log(KYWC_ERROR, "Failed to allocate drm_backend"); return NULL; } struct drm_device *drm = drm_device_create(dev->fd, &backend->wlr_backend, session->event_loop); if (!drm) { kywc_log(KYWC_ERROR, "Failed to create drm device"); free(backend); return NULL; } backend->drm = drm; backend->dev = dev; backend->session = session; wlr_backend_init(&backend->wlr_backend, &backend_impl); backend->wlr_backend.buffer_caps = WLR_BUFFER_CAP_DMABUF; if (parent) { backend->parent = drm_backend_from_wlr_backend(parent); /* create renderer to blit buffer */ if (!drm_mgpu_renderer_init(drm, &drm->mgpu_renderer)) { kywc_log(KYWC_ERROR, "Failed to initialize renderer for %s", drm->name); wlr_session_close_file(session, dev); drm_device_destroy(drm); free(backend); return NULL; } backend->parent_destroy.notify = handle_parent_destroy; wl_signal_add(&parent->events.destroy, &backend->parent_destroy); } else { wl_list_init(&backend->parent_destroy.link); } backend->device_change.notify = handle_device_change; wl_signal_add(&dev->events.change, &backend->device_change); backend->device_remove.notify = handle_device_remove; wl_signal_add(&dev->events.remove, &backend->device_remove); backend->session_active.notify = handle_session_active; wl_signal_add(&session->events.active, &backend->session_active); backend->session_destroy.notify = handle_session_destroy; wl_signal_add(&session->events.destroy, &backend->session_destroy); drm_lease_device_v1_create(backend); return &backend->wlr_backend; } kylin-wayland-compositor/src/backend/backend.c0000664000175000017500000002652315160461067020406 0ustar fengfeng// SPDX-FileCopyrightText: 2024 The wlroots contributors // SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include "backend/backend.h" #include "backend/drm.h" #include "backend/fbdev.h" #include "backend/libinput.h" #include "util/time.h" #define WAIT_SESSION_TIMEOUT 10000 // ms #define WAIT_GPU_TIMEOUT 5000 // ms #define MAX_NUM_GPUS 8 static struct wlr_session *session_create_and_wait(struct wl_event_loop *loop) { struct wlr_session *session = wlr_session_create(loop); if (!session) { kywc_log(KYWC_ERROR, "Failed to start a session"); return NULL; } if (!session->active) { kywc_log(KYWC_INFO, "Waiting for a session to become active"); int64_t started_at = current_time_msec(); int64_t timeout = WAIT_SESSION_TIMEOUT; while (!session->active) { int ret = wl_event_loop_dispatch(loop, (int)timeout); if (ret < 0) { kywc_log_errno(KYWC_ERROR, "Failed to wait for session active: " "wl_event_loop_dispatch failed"); return NULL; } int64_t now = current_time_msec(); if (now >= started_at + WAIT_SESSION_TIMEOUT) { break; } timeout = started_at + WAIT_SESSION_TIMEOUT - now; } if (!session->active) { kywc_log(KYWC_ERROR, "Timeout waiting session to become active"); return NULL; } } return session; } static int explicit_find_fbs(struct wlr_session *session, int dev_len, char *dev[static dev_len], const char *str) { char *fbs = strdup(str); if (!fbs) { kywc_log(KYWC_ERROR, "Allocation failed"); return -1; } int i = 0; char *save; char *ptr = strtok_r(fbs, ":", &save); do { if (i >= dev_len) { break; } dev[i] = strdup(ptr); if (!dev[i]) { kywc_log(KYWC_ERROR, "Allocation failed"); break; } ++i; } while ((ptr = strtok_r(NULL, ":", &save))); free(fbs); return i; } static int session_find_framebuffer_device(struct wlr_session *session, char **devices, int devices_len) { const char *explicit = getenv("KYWC_FB_DEVICES"); if (explicit) { return explicit_find_fbs(session, devices_len, devices, explicit); } struct udev_enumerate *enumerate = udev_enumerate_new(session->udev); if (!enumerate) { return -1; } udev_enumerate_add_match_sysname(enumerate, "fb[0-9]*"); if (udev_enumerate_add_match_subsystem(enumerate, "graphics") < 0) { udev_enumerate_unref(enumerate); kywc_log(KYWC_ERROR, "Failed to add match subsystem"); return -1; } if (udev_enumerate_scan_devices(enumerate) < 0) { udev_enumerate_unref(enumerate); kywc_log(KYWC_ERROR, "Failed to scan devices"); return -1; } int i = 0; struct udev_list_entry *entry; udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(enumerate)) { if (i >= devices_len) { break; } const char *path = udev_list_entry_get_name(entry); struct udev_device *device = udev_device_new_from_syspath(session->udev, path); if (!device) { continue; } const char *seat = udev_device_get_property_value(device, "ID_SEAT"); if (!seat) { seat = "seat0"; } if (session->seat[0] && strcmp(session->seat, seat) != 0) { udev_device_unref(device); continue; } /* a framebuffer device was found */ char *fbdev_path = strdup(udev_device_get_devnode(device)); udev_device_unref(device); if (!fbdev_path) { kywc_log(KYWC_ERROR, "Allocation failed"); break; } devices[i++] = fbdev_path; } udev_enumerate_unref(enumerate); return i; } static void frambffer_devices_release(char **devices, int n) { for (int i = 0; i < n; i++) { free((void *)devices[i]); } } static bool attempt_fbdev_backend(struct wl_event_loop *loop, struct wlr_backend *backend, struct wlr_session *session) { char *devices[MAX_NUM_GPUS]; int n = session_find_framebuffer_device(session, devices, MAX_NUM_GPUS); if (n <= 0) { kywc_log(KYWC_ERROR, "Failed to find framebuffer device, can not create backend"); return false; } kywc_log(KYWC_INFO, "Found %d framebuffer devices", n); struct wlr_backend *fbdev = fbdev_backend_create(session, (const char **)devices, n); if (!fbdev) { kywc_log(KYWC_ERROR, "Failed to create fbdev backend"); frambffer_devices_release(devices, n); return false; } wlr_multi_backend_add(backend, fbdev); frambffer_devices_release(devices, n); return true; } static struct wlr_device *session_open_if_kms(struct wlr_session *restrict session, const char *restrict path) { if (!path) { return NULL; } struct wlr_device *dev = wlr_session_open_file(session, path); if (!dev) { return NULL; } if (!drmIsKMS(dev->fd)) { kywc_log(KYWC_WARN, "Ignoring '%s': not a KMS device", path); wlr_session_close_file(session, dev); return NULL; } return dev; } struct add_drm_card_handler { bool found; struct wl_listener listener; struct wlr_session *session; }; static void handle_session_add_drm_card(struct wl_listener *listener, void *data) { struct wlr_session_add_event *event = data; struct add_drm_card_handler *handler = wl_container_of(listener, handler, listener); struct wlr_device *dev = session_open_if_kms(handler->session, event->path); if (dev) { wlr_session_close_file(handler->session, dev); handler->found = true; } } static bool find_drm_cards(struct wl_event_loop *loop, struct wlr_session *session) { bool found = false; struct udev_enumerate *en = udev_enumerate_new(session->udev); if (en) { udev_enumerate_add_match_subsystem(en, "drm"); udev_enumerate_add_match_sysname(en, "card[0-9]*"); if (udev_enumerate_scan_devices(en) == 0) { struct udev_list_entry *entry; udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(en)) { const char *path = udev_list_entry_get_name(entry); struct udev_device *dev = udev_device_new_from_syspath(session->udev, path); const char *seat = udev_device_get_property_value(dev, "ID_SEAT"); seat = seat ? seat : "seat0"; if (session->seat[0] && strcmp(session->seat, seat) != 0) { udev_device_unref(dev); continue; } struct wlr_device *wlr_dev = session_open_if_kms(session, udev_device_get_devnode(dev)); udev_device_unref(dev); if (wlr_dev) { wlr_session_close_file(session, wlr_dev); found = true; break; } } } udev_enumerate_unref(en); } if (found) { return found; } kywc_log(KYWC_WARN, "Waiting for a KMS device"); struct add_drm_card_handler handler = { 0 }; handler.session = session; handler.listener.notify = handle_session_add_drm_card; wl_signal_add(&session->events.add_drm_card, &handler.listener); int64_t started_at = current_time_msec(); do { int64_t now = current_time_msec(); if (now >= started_at + WAIT_GPU_TIMEOUT) { break; } int64_t timeout = started_at + WAIT_GPU_TIMEOUT - now; int ret = wl_event_loop_dispatch(loop, (int)timeout); if (ret < 0) { kywc_log_errno(KYWC_ERROR, "Failed with wl_event_loop_dispatch"); break; } } while (!handler.found); wl_list_remove(&handler.listener.link); return handler.found; } static struct wlr_backend *attempt_drm_backend(struct wlr_backend *backend, struct wlr_session *session) { struct wlr_device *devices[MAX_NUM_GPUS]; ssize_t n_gpus = wlr_session_find_gpus(session, MAX_NUM_GPUS, devices); if (n_gpus <= 0) { kywc_log(KYWC_ERROR, "Failed to find GPUS, can not create backend"); return NULL; } kywc_log(KYWC_INFO, "Found %zu GPUS", n_gpus); struct wlr_backend *primary_drm = NULL; for (size_t i = 0; i < (size_t)n_gpus; ++i) { struct wlr_backend *drm = drm_backend_create(session, devices[i], primary_drm); if (!drm) { kywc_log(KYWC_ERROR, "Failed to create DRM backend"); continue; } if (!primary_drm) { primary_drm = drm; } wlr_multi_backend_add(backend, drm); } if (!primary_drm) { kywc_log(KYWC_ERROR, "Could not successfully create backend on any GPU"); return NULL; } if (!getenv("WLR_DRM_DEVICES")) { drm_backend_monitor_create(backend, primary_drm, session); } return primary_drm; } struct wlr_backend *ky_backend_autocreate(struct wl_event_loop *loop, struct wlr_session **session_ptr) { /* try nested backend first */ if (getenv("WAYLAND_DISPLAY") || getenv("WAYLAND_SOCKET") || getenv("DISPLAY")) { return wlr_backend_autocreate(loop, session_ptr); } /* now fbdev or drm is used, session is needed */ if (session_ptr != NULL) { *session_ptr = NULL; } /* create multi backend for fbdev/drm + libinput */ struct wlr_backend *multi = wlr_multi_backend_create(loop); if (!multi) { kywc_log(KYWC_ERROR, "Could not allocate multibackend"); return NULL; } struct wlr_session *session = session_create_and_wait(loop); if (!session) { kywc_log(KYWC_ERROR, "Failed to start a valid session"); wlr_backend_destroy(multi); return NULL; } const char *env = getenv("KYWC_BACKEND"); bool use_fbdev_backend = env && strcmp(env, "fbdev") == 0; /* try drm backend if fbdev is not specified */ if (!use_fbdev_backend) { if (!find_drm_cards(loop, session) || !attempt_drm_backend(multi, session)) { kywc_log(KYWC_ERROR, "Failed to open any drm device, fallback to fbdev"); use_fbdev_backend = true; } } if (use_fbdev_backend && !attempt_fbdev_backend(loop, multi, session)) { kywc_log(KYWC_ERROR, "Failed to open any framebuffer device"); wlr_backend_destroy(multi); wlr_session_destroy(session); return NULL; } struct wlr_backend *libinput = libinput_backend_create(loop, session); if (libinput) { wlr_multi_backend_add(multi, libinput); } else { kywc_log(KYWC_ERROR, "Failed to start libinput backend"); wlr_backend_destroy(multi); wlr_session_destroy(session); return NULL; } if (session_ptr != NULL) { *session_ptr = session; } return multi; } kylin-wayland-compositor/src/scene/0000775000175000017500000000000015160461067016351 5ustar fengfengkylin-wayland-compositor/src/scene/linear_gradient.c0000664000175000017500000003541715160460057021654 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "render/opengl.h" #include "scene/linear_gradient.h" #include "scene/render.h" #include "scene_p.h" #include "util/macros.h" #include "util/matrix.h" #include "linear_gradient_frag.h" #include "linear_gradient_vert.h" struct ky_color_stop { float offset; float color[4]; }; struct ky_scene_linear_gradient { struct ky_scene_rect rect; ky_scene_node_destroy_func_t node_destroy; float x0, y0, x1, y1; struct ky_color_stop color_stops[2]; }; // opengl render static int32_t gl_shader = 0; struct gl_shader_location { // vs GLint in_uv; GLint uv2ndc; GLint uv_rotation; // fs GLint start; GLint end; GLint color_stop0_offset; GLint color_stop0_color; GLint color_stop1_offset; GLint color_stop1_color; GLint anti_aliasing; GLint aspect; GLint round_corner_radius; }; static struct gl_shader_location gl_locations = { 0 }; static int create_opengl_shader(struct ky_opengl_renderer *renderer) { GLuint prog = ky_opengl_create_program(renderer, linear_gradient_vert, linear_gradient_frag); if (prog <= 0) { return -1; } gl_locations.in_uv = glGetAttribLocation(prog, "inUV"); gl_locations.uv2ndc = glGetUniformLocation(prog, "uv2ndc"); gl_locations.uv_rotation = glGetUniformLocation(prog, "uvRotation"); gl_locations.start = glGetUniformLocation(prog, "start"); gl_locations.end = glGetUniformLocation(prog, "end"); gl_locations.color_stop0_offset = glGetUniformLocation(prog, "colorStops[0].offset"); gl_locations.color_stop0_color = glGetUniformLocation(prog, "colorStops[0].color"); gl_locations.color_stop1_offset = glGetUniformLocation(prog, "colorStops[1].offset"); gl_locations.color_stop1_color = glGetUniformLocation(prog, "colorStops[1].color"); gl_locations.anti_aliasing = glGetUniformLocation(prog, "antiAliasing"); gl_locations.aspect = glGetUniformLocation(prog, "aspect"); gl_locations.round_corner_radius = glGetUniformLocation(prog, "roundCornerRadius"); return prog; } static void scene_linear_gradient_opengl_render(struct ky_scene_linear_gradient *linear_gradient, int lx, int ly, bool render_with_visibility, struct ky_scene_render_target *target) { struct ky_scene_node *node = &linear_gradient->rect.node; struct kywc_box geo = { lx, ly, linear_gradient->rect.width, linear_gradient->rect.height }; pixman_region32_t render_region; if (render_with_visibility) { pixman_region32_init(&render_region); pixman_region32_union(&render_region, &node->visible_region, &node->extend_render_region); pixman_region32_intersect(&render_region, &render_region, &target->damage); } else { pixman_region32_init_rect(&render_region, 0, 0, geo.width, geo.height); if (pixman_region32_not_empty(&node->clip_region)) { pixman_region32_intersect(&render_region, &render_region, &node->clip_region); } pixman_region32_translate(&render_region, geo.x, geo.y); pixman_region32_intersect(&render_region, &render_region, &target->damage); } if (!pixman_region32_not_empty(&render_region)) { pixman_region32_fini(&render_region); return; } struct wlr_box dst_box = { .x = geo.x - target->logical.x, .y = geo.y - target->logical.y, .width = geo.width, .height = geo.height, }; ky_scene_render_box(&dst_box, target); pixman_region32_translate(&render_region, -target->logical.x, -target->logical.y); ky_scene_render_region(&render_region, target); // batch opengl draw pixman_region32_t region; pixman_region32_init_rect(®ion, dst_box.x, dst_box.y, dst_box.width, dst_box.height); pixman_region32_intersect(®ion, ®ion, &render_region); int rects_len; const pixman_box32_t *rects = pixman_region32_rectangles(®ion, &rects_len); if (rects_len == 0) { pixman_region32_fini(®ion); pixman_region32_fini(&render_region); return; } GLfloat verts[rects_len * 6 * 2]; size_t vert_index = 0; for (int i = 0; i < rects_len; i++) { const pixman_box32_t *rect = &rects[i]; verts[vert_index++] = (GLfloat)(rect->x1 - dst_box.x) / dst_box.width; verts[vert_index++] = (GLfloat)(rect->y1 - dst_box.y) / dst_box.height; verts[vert_index++] = (GLfloat)(rect->x2 - dst_box.x) / dst_box.width; verts[vert_index++] = (GLfloat)(rect->y1 - dst_box.y) / dst_box.height; verts[vert_index++] = (GLfloat)(rect->x1 - dst_box.x) / dst_box.width; verts[vert_index++] = (GLfloat)(rect->y2 - dst_box.y) / dst_box.height; verts[vert_index++] = (GLfloat)(rect->x2 - dst_box.x) / dst_box.width; verts[vert_index++] = (GLfloat)(rect->y1 - dst_box.y) / dst_box.height; verts[vert_index++] = (GLfloat)(rect->x2 - dst_box.x) / dst_box.width; verts[vert_index++] = (GLfloat)(rect->y2 - dst_box.y) / dst_box.height; verts[vert_index++] = (GLfloat)(rect->x1 - dst_box.x) / dst_box.width; verts[vert_index++] = (GLfloat)(rect->y2 - dst_box.y) / dst_box.height; } struct ky_mat3 uv2ndc; struct kywc_box dst_kywc_box = { .x = dst_box.x, .y = dst_box.y, .width = dst_box.width, .height = dst_box.height, }; ky_mat3_uvofbox_to_ndc(&uv2ndc, target->buffer->width, target->buffer->height, 0, &dst_kywc_box); struct ky_mat3 uv_rotation; ky_mat3_invert_output_transform(&uv_rotation, target->transform); glEnable(GL_BLEND); glUseProgram(gl_shader); glEnableVertexAttribArray(gl_locations.in_uv); glVertexAttribPointer(gl_locations.in_uv, 2, GL_FLOAT, GL_FALSE, 0, verts); glUniformMatrix3fv(gl_locations.uv2ndc, 1, GL_FALSE, uv2ndc.matrix); glUniformMatrix3fv(gl_locations.uv_rotation, 1, GL_FALSE, uv_rotation.matrix); float x0 = dst_box.x + linear_gradient->x0 * dst_box.width; float y0 = dst_box.y + linear_gradient->y0 * dst_box.height; ky_scene_render_point(&x0, &y0, target); x0 = (x0 - dst_box.x) / dst_box.width; y0 = (y0 - dst_box.y) / dst_box.height; glUniform2f(gl_locations.start, x0, y0); float x1 = dst_box.x + linear_gradient->x1 * dst_box.width; float y1 = dst_box.y + linear_gradient->y1 * dst_box.height; ky_scene_render_point(&x1, &y1, target); x1 = (x1 - dst_box.x) / dst_box.width; y1 = (y1 - dst_box.y) / dst_box.height; glUniform2f(gl_locations.end, x1, y1); glUniform1f(gl_locations.color_stop0_offset, linear_gradient->color_stops[0].offset); glUniform1f(gl_locations.color_stop1_offset, linear_gradient->color_stops[1].offset); glUniform4fv(gl_locations.color_stop0_color, 1, linear_gradient->color_stops[0].color); glUniform4fv(gl_locations.color_stop1_color, 1, linear_gradient->color_stops[1].color); float width = dst_box.width; float height = dst_box.height; if (target->transform & WL_OUTPUT_TRANSFORM_90) { width = dst_box.height; height = dst_box.width; } glUniform1f(gl_locations.aspect, width / height); bool render_with_radius = !(target->options & KY_SCENE_RENDER_DISABLE_ROUND_CORNER); float one_pixel_distance = 1.0f / height; glUniform1f(gl_locations.anti_aliasing, one_pixel_distance); float round_corner_radius[4] = { render_with_radius ? node->radius[0] * target->scale * one_pixel_distance : 0, render_with_radius ? node->radius[1] * target->scale * one_pixel_distance : 0, render_with_radius ? node->radius[2] * target->scale * one_pixel_distance : 0, render_with_radius ? node->radius[3] * target->scale * one_pixel_distance : 0 }; glUniform4f(gl_locations.round_corner_radius, round_corner_radius[0], round_corner_radius[1], round_corner_radius[2], round_corner_radius[3]); glDrawArrays(GL_TRIANGLES, 0, rects_len * 6); glUseProgram(0); glDisableVertexAttribArray(gl_locations.in_uv); pixman_region32_fini(®ion); pixman_region32_fini(&render_region); } static void scene_linear_gradient_render(struct ky_scene_node *node, int lx, int ly, struct ky_scene_render_target *target) { if (!node->enabled) { return; } bool render_with_visibility = !(target->options & KY_SCENE_RENDER_DISABLE_VISIBILITY); if (render_with_visibility && !pixman_region32_not_empty(&node->visible_region) && !pixman_region32_not_empty(&node->extend_render_region)) { return; } struct ky_scene_linear_gradient *linear_gradient = ky_scene_linear_gradientn_from_node(node); struct ky_scene_rect *rect = ky_scene_rect_from_linear_gradient(linear_gradient); if (!ky_scene_rect_render(node, (struct kywc_box){ lx, ly, rect->width, rect->height }, rect->color, render_with_visibility, target)) { return; } if (COLOR_INVALID(linear_gradient->color_stops[0].color) || COLOR_INVALID(linear_gradient->color_stops[1].color)) { return; } if (gl_shader >= 0 && wlr_renderer_is_opengl(target->output->output->renderer)) { if (gl_shader == 0) { struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(target->output->output->renderer); gl_shader = create_opengl_shader(renderer); } if (gl_shader > 0) { scene_linear_gradient_opengl_render(linear_gradient, lx, ly, render_with_visibility, target); } else { gl_shader = -1; } } } static void scene_linear_gradient_destroy(struct ky_scene_node *node) { if (!node) { return; } struct ky_scene_linear_gradient *linear_gradient = ky_scene_linear_gradientn_from_node(node); linear_gradient->node_destroy(node); } struct ky_scene_linear_gradient * ky_scene_linear_gradient_create(struct ky_scene_tree *parent, int width, int height, const float background_color[static 4]) { struct ky_scene_linear_gradient *linear_gradient = calloc(1, sizeof(*linear_gradient)); if (!linear_gradient) { return NULL; } ky_scene_rect_init(&linear_gradient->rect, parent, width, height, background_color); linear_gradient->node_destroy = linear_gradient->rect.node.impl.destroy; linear_gradient->rect.node.impl.destroy = scene_linear_gradient_destroy; linear_gradient->rect.node.impl.render = scene_linear_gradient_render; return linear_gradient; } struct ky_scene_node * ky_scene_node_from_linear_gradient(struct ky_scene_linear_gradient *linear_gradient) { return &linear_gradient->rect.node; } struct ky_scene_rect * ky_scene_rect_from_linear_gradient(struct ky_scene_linear_gradient *linear_gradient) { return &linear_gradient->rect; } struct ky_scene_linear_gradient *ky_scene_linear_gradientn_from_node(struct ky_scene_node *node) { struct ky_scene_rect *rect = ky_scene_rect_from_node(node); struct ky_scene_linear_gradient *linear_gradient = wl_container_of(rect, linear_gradient, rect); return linear_gradient; } void ky_scene_linear_gradient_set_background_color(struct ky_scene_linear_gradient *linear_gradient, const float color[static 4]) { ky_scene_rect_set_color(&linear_gradient->rect, color); } static void rect_center_ray_intersect(float dir_x, float dir_y, float *x, float *y) { const float center_x = 0.5f, center_y = 0.5f; const float right = 1.0f, left = 0.0f, top = 1.0f, bottom = 0.0f; float t_x_positive = (right - center_x) / dir_x; float t_x_negative = (left - center_x) / dir_x; float t_y_positive = (top - center_y) / dir_y; float t_y_negative = (bottom - center_y) / dir_y; float t = INFINITY; if (dir_x > 0.0f) { t = fminf(t, t_x_positive); } else if (dir_x < 0.0f) { t = fminf(t, t_x_negative); } if (dir_y > 0.0f) { t = fminf(t, t_y_positive); } else if (dir_y < 0.0f) { t = fminf(t, t_y_negative); } *x = center_x + dir_x * t; *y = center_y + dir_y * t; } void ky_scene_linear_gradient_set_linear(struct ky_scene_linear_gradient *linear_gradient, float degree) { if (gl_shader < 0) { return; } float aspect = (float)linear_gradient->rect.width / (float)linear_gradient->rect.height; float radian = (90.0f - degree) * M_PI / 180.0f; float dir_x = cosf(radian), dir_y = -sinf(radian) / aspect; float x0, y0, x1, y1; rect_center_ray_intersect(dir_x, dir_y, &x1, &y1); rect_center_ray_intersect(-dir_x, -dir_y, &x0, &y0); ky_scene_linear_gradient_set_linear_points(linear_gradient, x0, y0, x1, y1); } void ky_scene_linear_gradient_set_linear_points(struct ky_scene_linear_gradient *linear_gradient, float x0, float y0, float x1, float y1) { if (gl_shader < 0) { return; } if (FLOAT_EQUAL(linear_gradient->x0, x0) && FLOAT_EQUAL(linear_gradient->y0, y0) && FLOAT_EQUAL(linear_gradient->x1, x1) && FLOAT_EQUAL(linear_gradient->y1, y1)) { return; } linear_gradient->x0 = x0, linear_gradient->y0 = y0; linear_gradient->x1 = x1, linear_gradient->y1 = y1; ky_scene_node_push_damage(&linear_gradient->rect.node, KY_SCENE_DAMAGE_HARMLESS, NULL); } static void scene_linear_gradient_set_color_stop(struct ky_scene_linear_gradient *linear_gradient, struct ky_color_stop *color_stop, float offset, const float color[static 4]) { if (color_stop->offset == offset && memcmp(color_stop->color, color, sizeof(color_stop->color)) == 0) { return; } color_stop->offset = offset; memcpy(color_stop->color, color, sizeof(color_stop->color)); ky_scene_node_push_damage(&linear_gradient->rect.node, KY_SCENE_DAMAGE_HARMLESS, NULL); } void ky_scene_linear_gradient_set_color_stop_0(struct ky_scene_linear_gradient *linear_gradient, float offset, const float color[static 4]) { if (gl_shader < 0) { return; } struct ky_color_stop *color_stop = &linear_gradient->color_stops[0]; scene_linear_gradient_set_color_stop(linear_gradient, color_stop, offset, color); } void ky_scene_linear_gradient_set_color_stop_1(struct ky_scene_linear_gradient *linear_gradient, float offset, const float color[static 4]) { if (gl_shader < 0) { return; } struct ky_color_stop *color_stop = &linear_gradient->color_stops[1]; scene_linear_gradient_set_color_stop(linear_gradient, color_stop, offset, color); } kylin-wayland-compositor/src/scene/thumbnail.c0000664000175000017500000016175415160461067020516 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include "effect/effect.h" #include "output.h" #include "render/renderer.h" #include "scene/render.h" #include "scene/thumbnail.h" #include "server.h" #include "util/wayland.h" enum thumbnail_type { THUMBNAIL_TYPE_NONE = 0, THUMBNAIL_TYPE_NODE, THUMBNAIL_TYPE_VIEW, THUMBNAIL_TYPE_WORKSPACE, THUMBNAIL_TYPE_OUTPUT, }; struct thumbnail { struct wl_list link; struct thumbnail_buffer *buffer; struct { struct wl_signal update; // thumbnail_update_event struct wl_signal destroy; } events; bool force_update, wants_update; }; struct thumbnail_buffer { enum thumbnail_type type; struct wlr_buffer *buffer; struct wl_list thumbnails; float scale; bool single_plane; bool was_damaged; // buffer is damaged bool need_destroy; // need force destroyed bool can_destroy; // cannot be destroyed when render struct wlr_buffer *(*render)(struct thumbnail_buffer *buffer, struct ky_scene_output *output); void (*destroy)(struct thumbnail_buffer *buffer); }; struct node_thumbnail { struct thumbnail_buffer base; struct wl_list link; bool without_subeffect_node; int source_offset_x, source_offset_y; struct ky_scene_node *source_node; struct wl_listener source_damage; struct wl_listener source_destroy; }; struct view_thumbnail { struct thumbnail_buffer base; struct wl_list link; uint32_t options; struct view *view; struct wl_listener view_unmap; /* padding for shadow */ struct { int top, bottom, left, right; } padding; int source_offset_x, source_offset_y; struct ky_scene_node *source_node; struct wl_listener source_damage; }; struct workspace_thumbnail_entry { struct wl_list link; struct workspace_thumbnail *workspace_thumbnail; struct view *view; struct wl_listener view_move; struct wl_listener view_output; struct wl_listener workspace_leave; struct wl_listener view_unmap; struct thumbnail *thumbnail; struct wl_listener thumbnail_update; struct wl_listener thumbnail_destroy; struct wl_listener view_minimize; }; struct workspace_thumbnail { struct thumbnail_buffer base; struct wl_list link; uint32_t options; struct wl_list entries; // workspace_thumbnail_entry struct workspace *workspace; struct wl_listener view_enter; struct wl_listener workspace_destroy; int output_offset_x, output_offset_y; struct kywc_output *output; struct kywc_box output_geometry; struct wl_listener output_destroy; }; struct output_thumbnail_state { enum wl_output_transform transform; /* transformed output resolution */ int trans_width, trans_height; int width, height; float scale; /* lx, ly in scene */ struct wlr_box logical; }; struct output_thumbnail { struct thumbnail_buffer base; struct wl_list link; bool state_readonly; struct output_thumbnail_state state; int output_offset_x, output_offset_y; struct ky_scene_output *output; struct kywc_box output_geometry; struct wl_listener output_commit; struct wl_listener output_destroy; }; struct thumbnail_output { struct wl_list link; struct ky_scene_output *output; struct wl_listener frame; struct wl_listener destroy; }; static struct thumbnail_manager { struct wl_list node_thumbnails; struct wl_list view_thumbnails; struct wl_list workspace_thumbnails; struct wl_list output_thumbnails; struct wl_list outputs; struct wl_listener new_enabled_output; struct server *server; struct wl_listener destroy; } *manager = NULL; static void workspace_thumbnail_create_entry(struct workspace_thumbnail *workspace_thumbnail, struct kywc_output *kywc_output, struct view *view); static void workspace_thumbnail_entry_create_thumbnail(struct workspace_thumbnail_entry *entry); static void thumbnail_manager_schedule_frame(void) { struct thumbnail_output *output; wl_list_for_each(output, &manager->outputs, link) { output_schedule_frame(output->output->output); } } static void thumbnail_buffer_init(struct thumbnail_buffer *buffer, float scale, bool single_plane) { buffer->scale = scale; buffer->was_damaged = true; buffer->can_destroy = true; buffer->type = THUMBNAIL_TYPE_NONE; buffer->single_plane = single_plane; wl_list_init(&buffer->thumbnails); } static struct node_thumbnail *find_node_thumbnail(struct ky_scene_node *node, float scale, bool without_subeffect_node) { struct node_thumbnail *node_thumbnail; wl_list_for_each(node_thumbnail, &manager->node_thumbnails, link) { if (node_thumbnail->source_node == node && node_thumbnail->base.scale == scale && node_thumbnail->without_subeffect_node == without_subeffect_node) { return node_thumbnail; } } return NULL; } static struct view_thumbnail *find_view_thumbnail(struct view *view, uint32_t options, float scale) { struct view_thumbnail *view_thumbnail; wl_list_for_each(view_thumbnail, &manager->view_thumbnails, link) { if (view_thumbnail->view == view && view_thumbnail->options == options && view_thumbnail->base.scale == scale) { return view_thumbnail; } } return NULL; } static struct workspace_thumbnail *find_workspace_thumbnail(struct workspace *workspace, struct kywc_output *output, float scale, bool single_plane) { struct workspace_thumbnail *workspace_thumbnail; wl_list_for_each(workspace_thumbnail, &manager->workspace_thumbnails, link) { if (workspace_thumbnail->workspace == workspace && workspace_thumbnail->base.scale == scale && workspace_thumbnail->output == output && workspace_thumbnail->base.single_plane == single_plane) { return workspace_thumbnail; } } return NULL; } static void view_thumbnail_get_box(struct view_thumbnail *view_thumbnail, struct wlr_box *box) { struct kywc_view *view = &view_thumbnail->view->base; *box = (struct wlr_box){ 0, 0, view->geometry.width, view->geometry.height }; uint32_t options = // only care about these options view_thumbnail->options & (THUMBNAIL_DISABLE_DECOR | THUMBNAIL_DISABLE_SHADOW | THUMBNAIL_DISABLE_ROUND_CORNER); if (options & THUMBNAIL_DISABLE_DECOR) { return; } if (view->ssd == KYWC_SSD_NONE) { if (options & THUMBNAIL_DISABLE_SHADOW) { return; } box->x -= view->padding.left; box->y -= view->padding.top; box->width += view->padding.right + view->padding.left; box->height += view->padding.bottom + view->padding.top; memcpy(&view_thumbnail->padding, &view->padding, sizeof(view_thumbnail->padding)); return; } if (options & THUMBNAIL_DISABLE_SHADOW) { box->x -= view->margin.off_x; box->y -= view->margin.off_y; box->width += view->margin.off_width; box->height += view->margin.off_height; } else { box->x -= view->margin.off_x + view->padding.left; box->y -= view->margin.off_y + view->padding.top; box->width += view->margin.off_width + view->padding.right + view->padding.left; box->height += view->margin.off_height + view->padding.bottom + view->padding.top; memcpy(&view_thumbnail->padding, &view->padding, sizeof(view_thumbnail->padding)); } } static struct wlr_buffer *thumbnail_buffer_allocate(struct thumbnail_buffer *thumbnail_buffer, int width, int height, struct wlr_allocator *allocator) { bool change = !thumbnail_buffer->buffer || (thumbnail_buffer->buffer->width != width || thumbnail_buffer->buffer->height != height); if (!change) { return thumbnail_buffer->buffer; } struct wlr_buffer *buffer = ky_renderer_create_buffer(manager->server->renderer, manager->server->allocator, width, height, DRM_FORMAT_ARGB8888, thumbnail_buffer->single_plane); if (!buffer) { kywc_log(KYWC_ERROR, "Failed create wlr buffer with %d x %d", width, height); return NULL; } return buffer; } static struct wlr_buffer *node_thumbnail_render(struct thumbnail_buffer *thumbnail_buffer, struct ky_scene_output *scene_output) { struct node_thumbnail *node_thumbnail = wl_container_of(thumbnail_buffer, node_thumbnail, base); struct ky_scene_node *source_node = node_thumbnail->source_node; struct kywc_box bounding_box = { 0 }; enum ky_scene_bounding_type type = node_thumbnail->without_subeffect_node ? KY_SCENE_BOUNDING_WITHOUT_EFFECT_NODE : KY_SCENE_BOUNDING_WITHOUT_EFFECT; ky_scene_node_get_affected_bounding_box(source_node, type, &bounding_box); node_thumbnail->source_offset_x = -bounding_box.x; node_thumbnail->source_offset_y = -bounding_box.y; int buffer_width = round(bounding_box.width * thumbnail_buffer->scale); int buffer_height = round(bounding_box.height * thumbnail_buffer->scale); if (buffer_width <= 0 || buffer_height <= 0) { kywc_log(KYWC_DEBUG, "node thumbnail size is too small, %d x %d", buffer_width, buffer_height); return NULL; } struct wlr_buffer *buffer = thumbnail_buffer_allocate( thumbnail_buffer, buffer_width, buffer_height, scene_output->output->allocator); if (!buffer) { return NULL; } struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(scene_output->output->renderer, buffer, NULL); if (!render_pass) { wlr_buffer_drop(buffer); return NULL; } /* clear the target buffer */ wlr_render_pass_add_rect(render_pass, &(struct wlr_render_rect_options){ .color = { 0, 0, 0, 0 }, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, }); struct ky_scene_render_target target = { .logical = { 0, 0, buffer_width, buffer_height }, .scale = thumbnail_buffer->scale, .trans_width = buffer_width, .trans_height = buffer_height, .buffer = buffer, .output = scene_output, .render_pass = render_pass, .options = KY_SCENE_RENDER_DISABLE_VISIBILITY | KY_SCENE_RENDER_DISABLE_BLUR | KY_SCENE_RENDER_DISABLE_EFFECT | KY_SCENE_RENDER_ENABLE_FORCE_OPAQUE, }; pixman_region32_init_rect(&target.damage, 0, 0, bounding_box.width, bounding_box.height); if (node_thumbnail->without_subeffect_node) { target.options |= KY_SCENE_RENDER_DISABLE_SUBEFFECT_NODE; } bool old_state = source_node->enabled; source_node->enabled = true; source_node->impl.render(source_node, -bounding_box.x, -bounding_box.y, &target); source_node->enabled = old_state; wlr_render_pass_submit(target.render_pass); pixman_region32_fini(&target.damage); return buffer; } static struct wlr_buffer *view_thumbnail_render(struct thumbnail_buffer *thumbnail_buffer, struct ky_scene_output *scene_output) { struct view_thumbnail *view_thumbnail = wl_container_of(thumbnail_buffer, view_thumbnail, base); struct wlr_box bounding_box = { 0 }; view_thumbnail_get_box(view_thumbnail, &bounding_box); view_thumbnail->source_offset_x = -bounding_box.x; view_thumbnail->source_offset_y = -bounding_box.y; int buffer_width = round(bounding_box.width * thumbnail_buffer->scale); int buffer_height = round(bounding_box.height * thumbnail_buffer->scale); if (buffer_width <= 0 || buffer_height <= 0) { kywc_log(KYWC_DEBUG, "view thumbnail size is too small, %d x %d", buffer_width, buffer_height); return NULL; } struct wlr_buffer *buffer = thumbnail_buffer_allocate( thumbnail_buffer, buffer_width, buffer_height, scene_output->output->allocator); if (!buffer) { return NULL; } struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(scene_output->output->renderer, buffer, NULL); if (!render_pass) { wlr_buffer_drop(buffer); return NULL; } /* clear the target buffer */ wlr_render_pass_add_rect(render_pass, &(struct wlr_render_rect_options){ .color = { 0, 0, 0, 0 }, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, }); struct ky_scene_render_target target = { .logical = { 0, 0, buffer_width, buffer_height }, .scale = thumbnail_buffer->scale, .trans_width = buffer_width, .trans_height = buffer_height, .buffer = buffer, .output = scene_output, .render_pass = render_pass, .options = KY_SCENE_RENDER_DISABLE_VISIBILITY | KY_SCENE_RENDER_DISABLE_BLUR | KY_SCENE_RENDER_DISABLE_EFFECT | KY_SCENE_RENDER_ENABLE_FORCE_OPAQUE, }; if (view_thumbnail->options & THUMBNAIL_DISABLE_ROUND_CORNER) { target.options |= KY_SCENE_RENDER_DISABLE_ROUND_CORNER; } if (view_thumbnail->options & THUMBNAIL_ENABLE_SECURITY) { target.options |= KY_SCENE_RENDER_ENABLE_SECURITY; } pixman_region32_init_rect(&target.damage, 0, 0, bounding_box.width, bounding_box.height); struct ky_scene_node *source_node = view_thumbnail->source_node; bool old_state = source_node->enabled; source_node->enabled = true; source_node->impl.render(source_node, -bounding_box.x, -bounding_box.y, &target); source_node->enabled = old_state; wlr_render_pass_submit(target.render_pass); pixman_region32_fini(&target.damage); return buffer; } static bool thumbnail_buffer_destroy(struct thumbnail_buffer *thumbnail_buffer) { /* don't destroy if still have thumbnails */ if (!thumbnail_buffer->need_destroy && !wl_list_empty(&thumbnail_buffer->thumbnails)) { return false; } /* mark need_destroy if cannot be destroyed current */ if (!thumbnail_buffer->can_destroy) { thumbnail_buffer->need_destroy = true; return false; } /* force destroy all thumbnails */ struct thumbnail *thumbnail, *tmp; wl_list_for_each_safe(thumbnail, tmp, &thumbnail_buffer->thumbnails, link) { thumbnail->buffer = NULL; thumbnail_destroy(thumbnail); } if (thumbnail_buffer->buffer) { wlr_buffer_drop(thumbnail_buffer->buffer); } return true; } static void node_thumbnail_destroy(struct thumbnail_buffer *thumbnail_buffer) { if (!thumbnail_buffer_destroy(thumbnail_buffer)) { return; } struct node_thumbnail *node_thumbnail = wl_container_of(thumbnail_buffer, node_thumbnail, base); wl_list_remove(&node_thumbnail->source_destroy.link); wl_list_remove(&node_thumbnail->source_damage.link); wl_list_remove(&node_thumbnail->link); free(node_thumbnail); } static void view_thumbnail_destroy(struct thumbnail_buffer *thumbnail_buffer) { if (!thumbnail_buffer_destroy(thumbnail_buffer)) { return; } struct view_thumbnail *view_thumbnail = wl_container_of(thumbnail_buffer, view_thumbnail, base); ky_scene_node_force_damage_event(&view_thumbnail->view->surface_tree->node, false); ky_scene_node_force_damage_event(&view_thumbnail->view->tree->node, false); wl_list_remove(&view_thumbnail->view_unmap.link); wl_list_remove(&view_thumbnail->source_damage.link); wl_list_remove(&view_thumbnail->link); free(view_thumbnail); } static void node_thumbnail_handle_source_destroy(struct wl_listener *listener, void *data) { struct node_thumbnail *node_thumbnail = wl_container_of(listener, node_thumbnail, source_destroy); /* force destroyed when source node destroy */ node_thumbnail->base.need_destroy = true; node_thumbnail_destroy(&node_thumbnail->base); } static void view_thumbnail_handle_source_destroy(struct wl_listener *listener, void *data) { struct view_thumbnail *view_thumbnail = wl_container_of(listener, view_thumbnail, view_unmap); /* force destroyed when source node destroy */ view_thumbnail->base.need_destroy = true; view_thumbnail_destroy(&view_thumbnail->base); } static void node_thumbnail_handle_source_damage(struct wl_listener *listener, void *data) { struct node_thumbnail *node_thumbnail = wl_container_of(listener, node_thumbnail, source_damage); if (node_thumbnail->without_subeffect_node && ky_scene_node_find_upper_effect_node(data, node_thumbnail->source_node)) { return; } node_thumbnail->base.was_damaged = true; thumbnail_manager_schedule_frame(); } static void view_thumbnail_handle_source_damage(struct wl_listener *listener, void *data) { struct view_thumbnail *view_thumbnail = wl_container_of(listener, view_thumbnail, source_damage); view_thumbnail->base.was_damaged = true; thumbnail_manager_schedule_frame(); } static struct node_thumbnail *node_thumbnail_get_or_create(struct ky_scene_node *node, float scale, bool without_subeffect_node) { struct node_thumbnail *node_thumbnail = find_node_thumbnail(node, scale, without_subeffect_node); if (node_thumbnail) { return node_thumbnail; } node_thumbnail = calloc(1, sizeof(*node_thumbnail)); if (!node_thumbnail) { return NULL; } thumbnail_buffer_init(&node_thumbnail->base, scale, false); node_thumbnail->base.type = THUMBNAIL_TYPE_NODE; node_thumbnail->base.render = node_thumbnail_render; node_thumbnail->base.destroy = node_thumbnail_destroy; node_thumbnail->source_node = node; node_thumbnail->without_subeffect_node = without_subeffect_node; node_thumbnail->source_damage.notify = node_thumbnail_handle_source_damage; wl_signal_add(&node->events.damage, &node_thumbnail->source_damage); node_thumbnail->source_destroy.notify = node_thumbnail_handle_source_destroy; wl_signal_add(&node->events.destroy, &node_thumbnail->source_destroy); wl_list_insert(&manager->node_thumbnails, &node_thumbnail->link); return node_thumbnail; } static struct view_thumbnail *view_thumbnail_get_or_create(struct view *view, uint32_t options, float scale) { struct view_thumbnail *view_thumbnail = find_view_thumbnail(view, options, scale); if (view_thumbnail) { return view_thumbnail; } view_thumbnail = calloc(1, sizeof(*view_thumbnail)); if (!view_thumbnail) { return NULL; } thumbnail_buffer_init(&view_thumbnail->base, scale, options & THUMBNAIL_ENABLE_SINGLE_PLANE); view_thumbnail->base.type = THUMBNAIL_TYPE_VIEW; view_thumbnail->options = options; view_thumbnail->base.render = view_thumbnail_render; view_thumbnail->base.destroy = view_thumbnail_destroy; view_thumbnail->view = view; view_thumbnail->view_unmap.notify = view_thumbnail_handle_source_destroy; wl_signal_add(&view->base.events.unmap, &view_thumbnail->view_unmap); /* use surface_tree if has no server decoration */ view_thumbnail->source_node = options & THUMBNAIL_DISABLE_DECOR ? &view->surface_tree->node : &view->tree->node; ky_scene_node_force_damage_event(&view->surface_tree->node, true); ky_scene_node_force_damage_event(&view->tree->node, true); view_thumbnail->source_damage.notify = view_thumbnail_handle_source_damage; wl_signal_add(&view_thumbnail->source_node->events.damage, &view_thumbnail->source_damage); wl_list_insert(&manager->view_thumbnails, &view_thumbnail->link); return view_thumbnail; } static struct wlr_buffer *workspace_thumbnail_render(struct thumbnail_buffer *thumbnail_buffer, struct ky_scene_output *scene_output) { struct workspace_thumbnail *workspace_thumbnail = wl_container_of(thumbnail_buffer, workspace_thumbnail, base); struct output *output = output_from_kywc_output(workspace_thumbnail->output); workspace_thumbnail->output_geometry = output->geometry; /* use output effective size */ int buffer_width = output->geometry.width * thumbnail_buffer->scale; int buffer_height = output->geometry.height * thumbnail_buffer->scale; if (buffer_width <= 0 || buffer_height <= 0) { kywc_log(KYWC_DEBUG, "workspace thumbnail size is too small, %d x %d", buffer_width, buffer_height); return NULL; } struct wlr_buffer *buffer = thumbnail_buffer_allocate( thumbnail_buffer, buffer_width, buffer_height, scene_output->output->allocator); if (!buffer) { return NULL; } struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(scene_output->output->renderer, buffer, NULL); if (!render_pass) { wlr_buffer_drop(buffer); return NULL; } /* clear the target buffer */ wlr_render_pass_add_rect(render_pass, &(struct wlr_render_rect_options){ .color = { 0, 0, 0, 0 }, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, }); struct view_thumbnail *view_thumbnail; struct view_proxy *view_proxy; wl_list_for_each_reverse(view_proxy, &workspace_thumbnail->workspace->view_proxies, workspace_link) { if (!view_proxy->view->base.mapped || view_proxy->view->base.minimized || view_proxy->view->output != workspace_thumbnail->output || view_proxy->view->base.skip_switcher) { continue; } view_thumbnail = find_view_thumbnail(view_proxy->view, workspace_thumbnail->options, 1.0); if (!view_thumbnail || !view_thumbnail->base.buffer) { continue; } struct wlr_texture *tex = wlr_texture_from_buffer(scene_output->output->renderer, view_thumbnail->base.buffer); if (!tex) { continue; } struct kywc_box *geo = &view_thumbnail->view->base.geometry; struct wlr_box dst_box = { .x = (geo->x - view_thumbnail->source_offset_x - output->geometry.x) * thumbnail_buffer->scale, .y = (geo->y - view_thumbnail->source_offset_y - output->geometry.y) * thumbnail_buffer->scale, .width = view_thumbnail->base.buffer->width * thumbnail_buffer->scale, .height = view_thumbnail->base.buffer->height * thumbnail_buffer->scale, }; struct wlr_render_texture_options options = { .texture = tex, .dst_box = dst_box, }; wlr_render_pass_add_texture(render_pass, &options); wlr_texture_destroy(tex); } wlr_render_pass_submit(render_pass); return buffer; } static void workspace_thumbnail_entry_destroy(struct workspace_thumbnail_entry *entry) { if (entry->thumbnail) { wl_list_remove(&entry->thumbnail_destroy.link); wl_list_remove(&entry->thumbnail_update.link); wl_list_remove(&entry->view_minimize.link); thumbnail_destroy(entry->thumbnail); } wl_list_remove(&entry->link); wl_list_remove(&entry->view_move.link); wl_list_remove(&entry->view_output.link); wl_list_remove(&entry->workspace_leave.link); wl_list_remove(&entry->view_unmap.link); free(entry); } static void workspace_thumbnail_destroy(struct thumbnail_buffer *thumbnail_buffer) { if (!thumbnail_buffer_destroy(thumbnail_buffer)) { return; } struct workspace_thumbnail *workspace_thumbnail = wl_container_of(thumbnail_buffer, workspace_thumbnail, base); struct workspace_thumbnail_entry *entry, *tmp; wl_list_for_each_safe(entry, tmp, &workspace_thumbnail->entries, link) { workspace_thumbnail_entry_destroy(entry); } struct workspace *workspace = workspace_thumbnail->workspace; for (int i = 0; i < 3; i++) { ky_scene_node_force_damage_event(&workspace->layers[i].tree->node, false); } wl_list_remove(&workspace_thumbnail->view_enter.link); wl_list_remove(&workspace_thumbnail->output_destroy.link); wl_list_remove(&workspace_thumbnail->workspace_destroy.link); wl_list_remove(&workspace_thumbnail->link); free(workspace_thumbnail); } static void workspace_thumbnail_handle_view_move(struct wl_listener *listener, void *data) { struct workspace_thumbnail_entry *entry = wl_container_of(listener, entry, view_move); struct workspace_thumbnail *workspace_thumbnail = entry->workspace_thumbnail; if (workspace_thumbnail->output != entry->view->output) { return; } workspace_thumbnail->base.was_damaged = true; thumbnail_manager_schedule_frame(); } static void workspace_thumbnail_handle_view_output(struct wl_listener *listener, void *data) { struct workspace_thumbnail_entry *entry = wl_container_of(listener, entry, view_output); struct workspace_thumbnail *workspace_thumbnail = entry->workspace_thumbnail; if (workspace_thumbnail->output == entry->view->output) { assert(!entry->thumbnail); workspace_thumbnail_entry_create_thumbnail(entry); } else if (entry->thumbnail) { wl_list_remove(&entry->thumbnail_update.link); wl_list_remove(&entry->thumbnail_destroy.link); wl_list_remove(&entry->view_minimize.link); thumbnail_destroy(entry->thumbnail); entry->thumbnail = NULL; } workspace_thumbnail->base.was_damaged = true; thumbnail_manager_schedule_frame(); } static void workspace_thumbnail_handle_thumbnail_update(struct wl_listener *listener, void *data) { struct workspace_thumbnail_entry *entry = wl_container_of(listener, entry, thumbnail_update); struct workspace_thumbnail *workspace_thumbnail = entry->workspace_thumbnail; workspace_thumbnail->base.was_damaged = true; thumbnail_manager_schedule_frame(); } static void workspace_thumbnail_handle_view_enter(struct wl_listener *listener, void *data) { struct workspace_thumbnail *workspace_thumbnail = wl_container_of(listener, workspace_thumbnail, view_enter); struct view *view = data; workspace_thumbnail_create_entry(workspace_thumbnail, workspace_thumbnail->output, view); workspace_thumbnail->base.was_damaged = true; thumbnail_manager_schedule_frame(); } static void workspace_thumbnail_handle_workspace_leave(struct wl_listener *listener, void *data) { struct workspace_thumbnail_entry *entry = wl_container_of(listener, entry, workspace_leave); struct workspace_thumbnail *workspace_thumbnail = entry->workspace_thumbnail; struct workspace *workspace = data; /* not leave the workspace */ if (workspace_thumbnail->workspace != workspace) { return; } workspace_thumbnail_entry_destroy(entry); workspace_thumbnail->base.was_damaged = true; thumbnail_manager_schedule_frame(); } static void workspace_thumbnail_handle_thumbnail_destroy(struct wl_listener *listener, void *data) { struct workspace_thumbnail_entry *entry = wl_container_of(listener, entry, thumbnail_destroy); struct workspace_thumbnail *workspace_thumbnail = entry->workspace_thumbnail; wl_list_remove(&entry->thumbnail_update.link); wl_list_remove(&entry->thumbnail_destroy.link); wl_list_remove(&entry->view_minimize.link); entry->thumbnail = NULL; workspace_thumbnail_entry_destroy(entry); workspace_thumbnail->base.was_damaged = true; thumbnail_manager_schedule_frame(); } static void workspace_thumbnail_handle_view_unmap(struct wl_listener *listener, void *data) { struct workspace_thumbnail_entry *entry = wl_container_of(listener, entry, view_unmap); struct workspace_thumbnail *workspace_thumbnail = entry->workspace_thumbnail; workspace_thumbnail_entry_destroy(entry); workspace_thumbnail->base.was_damaged = true; thumbnail_manager_schedule_frame(); } static void workspace_thumbnail_handle_view_minimize(struct wl_listener *listener, void *data) { struct workspace_thumbnail_entry *entry = wl_container_of(listener, entry, view_minimize); struct workspace_thumbnail *workspace_thumbnail = entry->workspace_thumbnail; thumbnail_mark_wants_update(entry->thumbnail, !entry->view->base.minimized); workspace_thumbnail->base.was_damaged = true; thumbnail_manager_schedule_frame(); } static void workspace_thumbnail_handle_output_destroy(struct wl_listener *listener, void *data) { struct workspace_thumbnail *workspace_thumbnail = wl_container_of(listener, workspace_thumbnail, output_destroy); workspace_thumbnail->base.need_destroy = true; workspace_thumbnail_destroy(&workspace_thumbnail->base); } static void workspace_thumbnail_handle_source_destroy(struct wl_listener *listener, void *data) { struct workspace_thumbnail *workspace_thumbnail = wl_container_of(listener, workspace_thumbnail, workspace_destroy); /* force destroyed when source node destroy */ workspace_thumbnail->base.need_destroy = true; workspace_thumbnail_destroy(&workspace_thumbnail->base); } static void workspace_thumbnail_entry_create_thumbnail(struct workspace_thumbnail_entry *entry) { if (entry->view->base.skip_switcher) { return; } struct thumbnail *thumbnail = thumbnail_create_from_view(entry->view, entry->workspace_thumbnail->options, 1.0); if (!thumbnail) { return; } entry->thumbnail = thumbnail; entry->thumbnail_update.notify = workspace_thumbnail_handle_thumbnail_update; wl_signal_add(&thumbnail->events.update, &entry->thumbnail_update); entry->thumbnail_destroy.notify = workspace_thumbnail_handle_thumbnail_destroy; wl_signal_add(&thumbnail->events.destroy, &entry->thumbnail_destroy); entry->view_minimize.notify = workspace_thumbnail_handle_view_minimize; wl_signal_add(&entry->view->base.events.minimize, &entry->view_minimize); thumbnail_mark_wants_update(entry->thumbnail, !entry->view->base.minimized); } static void workspace_thumbnail_create_entry(struct workspace_thumbnail *workspace_thumbnail, struct kywc_output *kywc_output, struct view *view) { struct workspace_thumbnail_entry *entry = calloc(1, sizeof(*entry)); if (!entry) { return; } entry->workspace_thumbnail = workspace_thumbnail; entry->view = view; wl_list_insert(&workspace_thumbnail->entries, &entry->link); entry->view_output.notify = workspace_thumbnail_handle_view_output; wl_signal_add(&view->events.output, &entry->view_output); entry->view_move.notify = workspace_thumbnail_handle_view_move; wl_signal_add(&view->base.events.position, &entry->view_move); entry->workspace_leave.notify = workspace_thumbnail_handle_workspace_leave; wl_signal_add(&view->events.workspace_leave, &entry->workspace_leave); entry->view_unmap.notify = workspace_thumbnail_handle_view_unmap; wl_signal_add(&view->base.events.unmap, &entry->view_unmap); if (kywc_output == view->output) { workspace_thumbnail_entry_create_thumbnail(entry); } } static void workspace_thumbnail_create_entries(struct workspace_thumbnail *workspace_thumbnail, struct kywc_output *kywc_output, float scale) { struct view_proxy *view_proxy; struct workspace *workspace = workspace_thumbnail->workspace; wl_list_for_each(view_proxy, &workspace->view_proxies, workspace_link) { if (view_proxy->view->base.mapped) { workspace_thumbnail_create_entry(workspace_thumbnail, kywc_output, view_proxy->view); } } } static struct workspace_thumbnail * workspace_thumbnail_get_or_create(struct workspace *workspace, struct kywc_output *kywc_output, float scale, bool single_plane) { struct workspace_thumbnail *workspace_thumbnail = find_workspace_thumbnail(workspace, kywc_output, scale, single_plane); if (workspace_thumbnail) { return workspace_thumbnail; } workspace_thumbnail = calloc(1, sizeof(*workspace_thumbnail)); if (!workspace_thumbnail) { return NULL; } wl_list_init(&workspace_thumbnail->entries); thumbnail_buffer_init(&workspace_thumbnail->base, scale, single_plane); workspace_thumbnail->base.type = THUMBNAIL_TYPE_WORKSPACE; workspace_thumbnail->base.render = workspace_thumbnail_render; workspace_thumbnail->base.destroy = workspace_thumbnail_destroy; workspace_thumbnail->workspace = workspace; workspace_thumbnail->output = kywc_output; workspace_thumbnail->options = THUMBNAIL_DISABLE_ROUND_CORNER | THUMBNAIL_DISABLE_SHADOW | THUMBNAIL_ENABLE_SECURITY; if (single_plane) { workspace_thumbnail->options |= THUMBNAIL_ENABLE_SINGLE_PLANE; } for (int i = 0; i < 3; i++) { ky_scene_node_force_damage_event(&workspace->layers[i].tree->node, true); } workspace_thumbnail_create_entries(workspace_thumbnail, kywc_output, scale); struct output *output = output_from_kywc_output(kywc_output); workspace_thumbnail->view_enter.notify = workspace_thumbnail_handle_view_enter; wl_signal_add(&workspace->events.view_enter, &workspace_thumbnail->view_enter); workspace_thumbnail->output_destroy.notify = workspace_thumbnail_handle_output_destroy; wl_signal_add(&output->scene_output->events.destroy, &workspace_thumbnail->output_destroy); workspace_thumbnail->workspace_destroy.notify = workspace_thumbnail_handle_source_destroy; wl_signal_add(&workspace->events.destroy, &workspace_thumbnail->workspace_destroy); wl_list_insert(&manager->workspace_thumbnails, &workspace_thumbnail->link); return workspace_thumbnail; } static struct wlr_buffer *output_thumbnail_render(struct thumbnail_buffer *thumbnail_buffer, struct ky_scene_output *scene_output) { struct output_thumbnail *output_thumbnail = wl_container_of(thumbnail_buffer, output_thumbnail, base); struct ky_scene_output *src_output = output_thumbnail->output; if (!output_thumbnail->state_readonly) { output_thumbnail->state.logical.x = src_output->geometry.x; output_thumbnail->state.logical.y = src_output->geometry.y; } const struct output_thumbnail_state *state = &output_thumbnail->state; int buffer_width = state->width * thumbnail_buffer->scale; int buffer_height = state->height * thumbnail_buffer->scale; if (buffer_width <= 0 || buffer_height <= 0) { kywc_log(KYWC_DEBUG, "output thumbnail size is too small, %d x %d", buffer_width, buffer_height); return NULL; } struct wlr_output *output = src_output->output; struct wlr_buffer *buffer = thumbnail_buffer_allocate(thumbnail_buffer, buffer_width, buffer_height, output->allocator); if (!buffer) { return NULL; } struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer, NULL); if (!render_pass) { wlr_buffer_drop(buffer); return NULL; } /* clear the target buffer */ wlr_render_pass_add_rect(render_pass, &(struct wlr_render_rect_options){ .color = { 0, 0, 0, 0 }, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, }); struct ky_scene_render_target target = { .logical = state->logical, .scale = state->scale, .buffer = buffer, .transform = state->transform, .trans_width = state->trans_width, .trans_height = state->trans_height, .output = src_output, .render_pass = render_pass, .options = KY_SCENE_RENDER_DISABLE_VISIBILITY | KY_SCENE_RENDER_ENABLE_FORCE_OPAQUE, }; pixman_region32_init_rect(&target.damage, target.logical.x, target.logical.y, target.logical.width, target.logical.height); output_thumbnail->output_geometry.x = target.logical.x; output_thumbnail->output_geometry.y = target.logical.y; output_thumbnail->output_geometry.width = target.logical.width; output_thumbnail->output_geometry.height = target.logical.height; struct ky_scene_node *root = &src_output->scene->tree.node; root->impl.render(root, root->x, root->y, &target); wlr_render_pass_submit(target.render_pass); pixman_region32_fini(&target.damage); return buffer; } static void output_thumbnail_destroy(struct thumbnail_buffer *thumbnail_buffer) { if (!thumbnail_buffer_destroy(thumbnail_buffer)) { return; } struct output_thumbnail *output_thumbnail = wl_container_of(thumbnail_buffer, output_thumbnail, base); wl_list_remove(&output_thumbnail->output_destroy.link); wl_list_remove(&output_thumbnail->output_commit.link); wl_list_remove(&output_thumbnail->link); free(output_thumbnail); } static void output_thumbnail_handle_output_destroy(struct wl_listener *listener, void *data) { struct output_thumbnail *output_thumbnail = wl_container_of(listener, output_thumbnail, output_destroy); /* force destroyed when output node destroy */ output_thumbnail->base.need_destroy = true; output_thumbnail_destroy(&output_thumbnail->base); } static void get_state_from_wlr_output(struct output_thumbnail_state *state, struct wlr_output *wlr_output) { state->scale = wlr_output->scale; state->width = wlr_output->width; state->height = wlr_output->height; state->transform = wlr_output->transform; wlr_output_transformed_resolution(wlr_output, &state->trans_width, &state->trans_height); state->logical.width = state->trans_width / state->scale; state->logical.height = state->trans_height / state->scale; } static void output_thumbnail_handle_output_commit(struct wl_listener *listener, void *data) { struct output_thumbnail *output_thumbnail = wl_container_of(listener, output_thumbnail, output_commit); struct wlr_output_event_commit *event = data; if (!output_thumbnail->state_readonly && (event->state->committed & WLR_OUTPUT_STATE_MODE || event->state->committed & WLR_OUTPUT_STATE_SCALE || event->state->committed & WLR_OUTPUT_STATE_TRANSFORM)) { get_state_from_wlr_output(&output_thumbnail->state, event->output); } if (!(event->state->committed & WLR_OUTPUT_STATE_BUFFER)) { return; } bool has_damage = event->state->committed & WLR_OUTPUT_STATE_DAMAGE && pixman_region32_not_empty(&event->state->damage); if (has_damage) { output_thumbnail->base.was_damaged = true; thumbnail_manager_schedule_frame(); } } static bool output_state_is_same(struct output_thumbnail_state *thumbnail_state, struct kywc_output_state *output_state) { if (thumbnail_state == NULL && output_state == NULL) { return true; } else if (thumbnail_state == NULL || output_state == NULL) { return false; } if (thumbnail_state->scale != output_state->scale || thumbnail_state->width != output_state->width || thumbnail_state->height != output_state->height || thumbnail_state->logical.x != output_state->lx || thumbnail_state->logical.y != output_state->ly || thumbnail_state->transform != output_state->transform) { return false; } return true; } static struct output_thumbnail *find_output_thumbnail(struct ky_scene_output *output, struct kywc_output_state *state, float scale) { struct output_thumbnail *output_thumbnail; wl_list_for_each(output_thumbnail, &manager->output_thumbnails, link) { if (output_thumbnail->output == output && output_thumbnail->base.scale == scale && output_state_is_same(&output_thumbnail->state, state)) { return output_thumbnail; } } return NULL; } static struct output_thumbnail * output_thumbnail_get_or_create(struct ky_scene_output *output, struct kywc_output_state *output_state, float scale) { struct output_thumbnail *output_thumbnail = find_output_thumbnail(output, output_state, scale); if (output_thumbnail) { return output_thumbnail; } output_thumbnail = calloc(1, sizeof(*output_thumbnail)); if (!output_thumbnail) { return NULL; } thumbnail_buffer_init(&output_thumbnail->base, scale, false); output_thumbnail->base.type = THUMBNAIL_TYPE_OUTPUT; output_thumbnail->base.render = output_thumbnail_render; output_thumbnail->base.destroy = output_thumbnail_destroy; output_thumbnail->output = output; output_thumbnail->output_commit.notify = output_thumbnail_handle_output_commit; wl_signal_add(&output->output->events.commit, &output_thumbnail->output_commit); output_thumbnail->output_destroy.notify = output_thumbnail_handle_output_destroy; wl_signal_add(&output->events.destroy, &output_thumbnail->output_destroy); wl_list_insert(&manager->output_thumbnails, &output_thumbnail->link); struct output_thumbnail_state *state = &output_thumbnail->state; if (!output_state) { struct wlr_output *wlr_output = output_thumbnail->output->output; state->logical.x = output_thumbnail->output->geometry.x; state->logical.y = output_thumbnail->output->geometry.y; get_state_from_wlr_output(state, wlr_output); output_thumbnail->state_readonly = false; return output_thumbnail; } state->scale = output_state->scale; state->width = output_state->width; state->height = output_state->height; state->transform = output_state->transform; if (state->transform % 2 == 0) { state->trans_width = state->width; state->trans_height = state->height; } else { state->trans_height = state->width; state->trans_width = state->height; } state->logical.x = output_state->lx; state->logical.y = output_state->ly; state->logical.width = state->trans_width / state->scale; state->logical.height = state->trans_height / state->scale; output_thumbnail->state_readonly = true; return output_thumbnail; } struct thumbnail *thumbnail_create_from_node(struct ky_scene_node *node, float scale, bool without_subeffect_node) { if (!manager) { return NULL; } struct thumbnail *thumbnail = calloc(1, sizeof(*thumbnail)); if (!thumbnail) { return NULL; } struct node_thumbnail *node_thumbnail = node_thumbnail_get_or_create(node, scale, without_subeffect_node); if (!node_thumbnail) { free(thumbnail); return NULL; } thumbnail->buffer = &node_thumbnail->base; wl_list_insert(&node_thumbnail->base.thumbnails, &thumbnail->link); wl_signal_init(&thumbnail->events.update); wl_signal_init(&thumbnail->events.destroy); thumbnail->force_update = thumbnail->wants_update = true; /* buffer update is needed */ thumbnail_manager_schedule_frame(); return thumbnail; } struct thumbnail *thumbnail_create_from_view(struct view *view, uint32_t options, float scale) { if (!manager) { return NULL; } struct thumbnail *thumbnail = calloc(1, sizeof(*thumbnail)); if (!thumbnail) { return NULL; } struct view_thumbnail *view_thumbnail = view_thumbnail_get_or_create(view, options, scale); if (!view_thumbnail) { free(thumbnail); return NULL; } thumbnail->buffer = &view_thumbnail->base; wl_list_insert(&view_thumbnail->base.thumbnails, &thumbnail->link); wl_signal_init(&thumbnail->events.update); wl_signal_init(&thumbnail->events.destroy); thumbnail->force_update = thumbnail->wants_update = true; /* buffer update is needed */ thumbnail_manager_schedule_frame(); return thumbnail; } struct thumbnail *thumbnail_create_from_workspace(struct workspace *workspace, struct kywc_output *kywc_output, float scale, bool single_plane) { if (!manager) { return NULL; } struct thumbnail *thumbnail = calloc(1, sizeof(*thumbnail)); if (!thumbnail) { return NULL; } struct workspace_thumbnail *workspace_thumbnail = workspace_thumbnail_get_or_create(workspace, kywc_output, scale, single_plane); if (!workspace_thumbnail) { free(thumbnail); return NULL; } thumbnail->buffer = &workspace_thumbnail->base; wl_list_insert(&workspace_thumbnail->base.thumbnails, &thumbnail->link); wl_signal_init(&thumbnail->events.update); wl_signal_init(&thumbnail->events.destroy); thumbnail->force_update = thumbnail->wants_update = true; /* buffer update is needed */ thumbnail_manager_schedule_frame(); return thumbnail; } struct thumbnail *thumbnail_create_from_output(struct ky_scene_output *output, struct kywc_output_state *output_state, float scale) { if (!manager) { return NULL; } struct thumbnail *thumbnail = calloc(1, sizeof(*thumbnail)); if (!thumbnail) { return NULL; } struct output_thumbnail *output_thumbnail = output_thumbnail_get_or_create(output, output_state, scale); if (!output_thumbnail) { free(thumbnail); return NULL; } thumbnail->buffer = &output_thumbnail->base; wl_list_insert(&output_thumbnail->base.thumbnails, &thumbnail->link); wl_signal_init(&thumbnail->events.update); wl_signal_init(&thumbnail->events.destroy); thumbnail->force_update = thumbnail->wants_update = true; /* buffer update is needed */ output_schedule_frame(output->output); return thumbnail; } void thumbnail_destroy(struct thumbnail *thumbnail) { if (!thumbnail) { return; } wl_signal_emit_mutable(&thumbnail->events.destroy, NULL); assert(wl_list_empty(&thumbnail->events.update.listener_list)); assert(wl_list_empty(&thumbnail->events.destroy.listener_list)); wl_list_remove(&thumbnail->link); /* thumbnail_buffer may not be destroyed caused by can_destroy == false */ if (thumbnail->buffer) { thumbnail->buffer->destroy(thumbnail->buffer); } free(thumbnail); } void thumbnail_add_update_listener(struct thumbnail *thumbnail, struct wl_listener *listener) { assert(wl_list_empty(&thumbnail->events.update.listener_list)); wl_signal_add(&thumbnail->events.update, listener); } void thumbnail_add_destroy_listener(struct thumbnail *thumbnail, struct wl_listener *listener) { assert(wl_list_empty(&thumbnail->events.destroy.listener_list)); wl_signal_add(&thumbnail->events.destroy, listener); } void thumbnail_mark_wants_update(struct thumbnail *thumbnail, bool wants) { if (thumbnail->wants_update == wants) { return; } struct thumbnail_buffer *thumbnail_buffer = thumbnail->buffer; if (thumbnail_buffer->type == THUMBNAIL_TYPE_WORKSPACE) { struct workspace_thumbnail *workspace_thumbnail = wl_container_of(thumbnail_buffer, workspace_thumbnail, base); struct workspace_thumbnail_entry *entry; wl_list_for_each(entry, &workspace_thumbnail->entries, link) { if (entry->thumbnail) { entry->thumbnail->wants_update = wants; } } } thumbnail->wants_update = wants; /* should send update if buffer was damaged */ if (wants) { thumbnail_manager_schedule_frame(); } } bool thumbnail_get_node_offset(struct thumbnail *thumbnail, struct ky_scene_node *node, int32_t *x, int32_t *y) { struct thumbnail_buffer *thumbnail_buffer = thumbnail->buffer; if (!thumbnail_buffer->buffer) { return false; } struct ky_scene_node *source_node = NULL; struct kywc_box output_geometry = { 0 }; switch (thumbnail_buffer->type) { case THUMBNAIL_TYPE_NODE:; struct node_thumbnail *node_thumbnail = wl_container_of(thumbnail_buffer, node_thumbnail, base); source_node = node_thumbnail->source_node; *x = node_thumbnail->source_offset_x; *y = node_thumbnail->source_offset_y; break; case THUMBNAIL_TYPE_VIEW:; struct view_thumbnail *view_thumbnail = wl_container_of(thumbnail_buffer, view_thumbnail, base); source_node = view_thumbnail->source_node; *x = view_thumbnail->source_offset_x; *y = view_thumbnail->source_offset_y; break; case THUMBNAIL_TYPE_WORKSPACE:; struct workspace_thumbnail *workspace_thumbnail = wl_container_of(thumbnail_buffer, workspace_thumbnail, base); *x = workspace_thumbnail->output_offset_x; *y = workspace_thumbnail->output_offset_y; output_geometry = workspace_thumbnail->output_geometry; break; case THUMBNAIL_TYPE_OUTPUT:; struct output_thumbnail *output_thumbnail = wl_container_of(thumbnail_buffer, output_thumbnail, base); *x = output_thumbnail->output_offset_x; *y = output_thumbnail->output_offset_y; output_geometry = output_thumbnail->output_geometry; break; case THUMBNAIL_TYPE_NONE: return false; } if (thumbnail_buffer->type == THUMBNAIL_TYPE_NODE || thumbnail_buffer->type == THUMBNAIL_TYPE_VIEW) { if (node == source_node) { return true; } struct ky_scene_node *tmp_node = node; struct ky_scene_tree *parent = node->parent; while (parent) { *x += tmp_node->x; *y += tmp_node->y; if (&parent->node == source_node) { break; } tmp_node = &parent->node; parent = parent->node.parent; } if (!parent) { *x = *y = 0; return false; } return true; } if (thumbnail_buffer->type == THUMBNAIL_TYPE_WORKSPACE || thumbnail_buffer->type == THUMBNAIL_TYPE_OUTPUT) { int lx, ly; ky_scene_node_coords(node, &lx, &ly); *x += (lx - output_geometry.x); *y += (ly - output_geometry.y); } if (*x < 0 || *y < 0 || *x > output_geometry.width || *y > output_geometry.height) { *x = *y = 0; return false; } return true; } bool thumbnail_get_padding(struct thumbnail *thumbnail, int *top, int *left, int *right, int *bottom) { struct thumbnail_buffer *thumbnail_buffer = thumbnail->buffer; if (!thumbnail_buffer->buffer) { return false; } if (thumbnail_buffer->type != THUMBNAIL_TYPE_VIEW) { return false; } struct view_thumbnail *view_thumbnail = wl_container_of(thumbnail_buffer, view_thumbnail, base); *top = view_thumbnail->padding.top; *left = view_thumbnail->padding.left; *right = view_thumbnail->padding.right; *bottom = view_thumbnail->padding.bottom; return true; } static bool thumbnail_buffer_render(struct thumbnail_buffer *thumbnail_buffer, struct thumbnail_output *output) { struct thumbnail *thumbnail, *tmp; if (!thumbnail_buffer->was_damaged) { /* must have a buffer because was_damaged == false */ struct thumbnail_update_event event = { .buffer = thumbnail_buffer->buffer, .buffer_changed = true, }; /* thumbnail_buffer cannot be destroyed in here */ thumbnail_buffer->can_destroy = false; wl_list_for_each_safe(thumbnail, tmp, &thumbnail_buffer->thumbnails, link) { if (thumbnail->wants_update && thumbnail->force_update) { thumbnail->force_update = false; wl_signal_emit_oneshot(&thumbnail->events.update, &event); } } /* thumbnail may need be destroyed in update */ thumbnail_buffer->can_destroy = true; thumbnail_buffer->destroy(thumbnail_buffer); return true; } bool has_wants_update = false; wl_list_for_each(thumbnail, &thumbnail_buffer->thumbnails, link) { has_wants_update |= thumbnail->wants_update; if (has_wants_update) { break; } } /* skip rendering when no thumbnail want update */ if (!has_wants_update) { return true; } struct wlr_buffer *buffer = thumbnail_buffer->render(thumbnail_buffer, output->output); /* destroy it when render failed */ if (!buffer) { thumbnail_buffer->need_destroy = true; thumbnail_buffer->destroy(thumbnail_buffer); return false; } /* mark buffer is not damaged */ thumbnail_buffer->was_damaged = false; /* drop the prev buffer */ bool buffer_changed = buffer != thumbnail_buffer->buffer; if (buffer_changed) { wlr_buffer_drop(thumbnail_buffer->buffer); thumbnail_buffer->buffer = buffer; } struct thumbnail_update_event event = { .buffer = buffer }; thumbnail_buffer->can_destroy = false; wl_list_for_each_safe(thumbnail, tmp, &thumbnail_buffer->thumbnails, link) { if (thumbnail->wants_update) { event.buffer_changed = buffer_changed || thumbnail->force_update; thumbnail->force_update = false; wl_signal_emit_oneshot(&thumbnail->events.update, &event); } else { thumbnail->force_update |= buffer_changed; } } thumbnail_buffer->can_destroy = true; thumbnail_buffer->destroy(thumbnail_buffer); return true; } static void thumbnail_output_handle_frame(struct wl_listener *listener, void *data) { struct thumbnail_output *output = wl_container_of(listener, output, frame); struct node_thumbnail *node_thumbnail, *tmp; wl_list_for_each_safe(node_thumbnail, tmp, &manager->node_thumbnails, link) { thumbnail_buffer_render(&node_thumbnail->base, output); } struct view_thumbnail *view_thumbnail, *view_tmp; wl_list_for_each_safe(view_thumbnail, view_tmp, &manager->view_thumbnails, link) { thumbnail_buffer_render(&view_thumbnail->base, output); } struct workspace_thumbnail *workspace_thumbnail, *_tmp; wl_list_for_each_safe(workspace_thumbnail, _tmp, &manager->workspace_thumbnails, link) { thumbnail_buffer_render(&workspace_thumbnail->base, output); } struct output_thumbnail *output_thumbnail, *output_tmp; wl_list_for_each_safe(output_thumbnail, output_tmp, &manager->output_thumbnails, link) { thumbnail_buffer_render(&output_thumbnail->base, output); } } void thumbnail_update(struct thumbnail *thumbnail) { struct thumbnail_output *output; wl_list_for_each(output, &manager->outputs, link) { if (thumbnail->buffer->type == THUMBNAIL_TYPE_WORKSPACE) { thumbnail_output_handle_frame(&output->frame, NULL); } else { thumbnail_buffer_render(thumbnail->buffer, output); } break; } } static void thumbnail_output_handle_destroy(struct wl_listener *listener, void *data) { struct thumbnail_output *output = wl_container_of(listener, output, destroy); wl_list_remove(&output->destroy.link); wl_list_remove(&output->frame.link); wl_list_remove(&output->link); free(output); } static void handle_new_enabled_output(struct wl_listener *listener, void *data) { struct thumbnail_output *thumbnail_output = calloc(1, sizeof(*thumbnail_output)); if (!thumbnail_output) { return; } struct kywc_output *kywc_output = data; thumbnail_output->output = output_from_kywc_output(kywc_output)->scene_output; thumbnail_output->frame.notify = thumbnail_output_handle_frame; wl_signal_add(&thumbnail_output->output->events.frame, &thumbnail_output->frame); thumbnail_output->destroy.notify = thumbnail_output_handle_destroy; wl_signal_add(&thumbnail_output->output->events.destroy, &thumbnail_output->destroy); wl_list_insert(&manager->outputs, &thumbnail_output->link); } static void handle_server_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->destroy.link); wl_list_remove(&manager->new_enabled_output.link); free(manager); manager = NULL; } bool thumbnail_manager_create(struct server *server) { manager = calloc(1, sizeof(*manager)); if (!manager) { return false; } wl_list_init(&manager->node_thumbnails); wl_list_init(&manager->view_thumbnails); wl_list_init(&manager->workspace_thumbnails); wl_list_init(&manager->output_thumbnails); wl_list_init(&manager->outputs); manager->new_enabled_output.notify = handle_new_enabled_output; output_manager_add_new_enabled_listener(&manager->new_enabled_output); manager->server = server; manager->destroy.notify = handle_server_destroy; server_add_destroy_listener(server, &manager->destroy); return true; } kylin-wayland-compositor/src/scene/meson.build0000664000175000017500000000051415160461067020513 0ustar fengfengwlcom_sources += files( 'animation.c', 'box.c', 'buffer.c', 'decoration.c', 'linear_gradient.c', 'output.c', 'rect.c', 'render.c', 'scene.c', 'surface.c', 'subsurface.c', 'thumbnail.c', 'xdg_shell.c', ) if have_alpha_modifier wlcom_sources += files( 'alpha_modifier.c', ) endif subdir('shaders') kylin-wayland-compositor/src/scene/scene_p.h0000664000175000017500000000320415160461067020135 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _SCENE_P_H_ #define _SCENE_P_H_ #include #include "scene/scene.h" void ky_scene_node_init(struct ky_scene_node *node, struct ky_scene_tree *parent); void ky_scene_rect_init(struct ky_scene_rect *rect, struct ky_scene_tree *parent, int width, int height, const float color[static 4]); void ky_scene_buffer_init(struct ky_scene_buffer *scene_buffer, struct ky_scene_tree *parent); /** * update output states for buffer node in the tree or the single buffer node, when * 1. scene buffer state: * position: set_position, reparent * dest_size * create with buffer * destroy: direct emit output_leave * 2. scene output state: * position * mode * create/destroy * scale/transform/subpixel: force update */ void ky_scene_node_update_outputs(struct ky_scene_node *node, struct wl_list *outputs, struct ky_scene_output *ignore, struct ky_scene_output *force); /** * collect current damage and distribute to outputs */ void ky_scene_collect_damage(struct ky_scene *scene); void ky_scene_corner_region(pixman_region32_t *region, int width, int height, const int radius[static 4]); void ky_scene_log_region(enum kywc_log_level level, const char *desc, const pixman_region32_t *region); void ky_scene_log_box(enum kywc_log_level level, const char *desc, struct kywc_box *box); void ky_scene_log_fbox(enum kywc_log_level level, const char *desc, struct kywc_fbox *box); #endif /* _SCENE_P_H_ */ kylin-wayland-compositor/src/scene/shaders/0000775000175000017500000000000015160460057020000 5ustar fengfengkylin-wayland-compositor/src/scene/shaders/decoration.frag0000664000175000017500000000301115160460057022763 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform float antiAliasing; uniform float aspect; // width / height uniform vec4 rect; // distance. x y w h uniform vec4 roundRadius; uniform float borderThickness; uniform vec4 borderColor; uniform float titleHeight; uniform vec4 titleColor; varying vec2 vUV; float sdRoundedBox(in vec2 p, in vec2 b, in vec4 r) { r.xy = (p.x > 0.0) ? r.xy : r.zw; r.x = (p.y > 0.0) ? r.x : r.y; vec2 q = abs(p) - b + r.x; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x; } float sdOpSubtraction(float a, float b) { return max(a, -b); } void main() { vec2 st = vUV * 2.0; st.x *= aspect; vec2 offset = -rect.xy * 2.0; float outerDist = sdRoundedBox(st + offset, rect.zw, roundRadius); float innerDist = outerDist + borderThickness; float borderDist = sdOpSubtraction(outerDist, innerDist); float borderShape = 1.0 - smoothstep(-antiAliasing, antiAliasing, borderDist); // title float titleShape = 0.0; if (titleHeight > 0.001) { float titleDist = sdRoundedBox( st + offset + vec2(0.0, rect.w - titleHeight - borderThickness), vec2(rect.z - borderThickness, titleHeight), vec4(0.0, roundRadius[1] - borderThickness, 0.0, roundRadius[3] - borderThickness)); titleShape = 1.0 - smoothstep(-antiAliasing, antiAliasing, titleDist); } gl_FragColor = titleShape * titleColor + borderShape * borderColor; } kylin-wayland-compositor/src/scene/shaders/meson.build0000664000175000017500000000061015160460057022137 0ustar fengfengshaders = [ 'decoration.vert', 'decoration.frag', 'linear_gradient.vert', 'linear_gradient.frag', 'shadow.vert', 'shadow.frag', ] foreach name : shaders output = name.underscorify() + '.h' var = name.underscorify() wlcom_sources += custom_target( output, command: [embed, var], input: name, output: output, feed: true, capture: true, ) endforeach kylin-wayland-compositor/src/scene/shaders/linear_gradient.frag0000664000175000017500000000265315160460057023776 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform vec2 start; uniform vec2 end; struct ColorStop { float offset; vec4 color; }; uniform int colorStopCount; uniform ColorStop colorStops[2]; uniform float antiAliasing; uniform float aspect; uniform vec4 roundCornerRadius; varying vec2 vUV; varying vec2 vUVSDF; float linearStep(float t1, float t2, float x) { return clamp((x - t1) / (t2 - t1), 0.0, 1.0); } float linearInterpolate(vec2 p, vec2 p0, vec2 p1) { vec2 v = p1 - p0; vec2 d = p - p0; return dot(v, d) / dot(v, v); } float sdRoundedBox(in vec2 p, in vec2 b, in vec4 r) { r.xy = (p.x >0.0) ? r.xy : r.zw; r.x = (p.y > 0.0) ? r.x : r.y; vec2 q = abs(p) - b + r.x; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x; } float roundedRectMask() { vec2 st = vUVSDF; st.x *= aspect; vec2 rectHalfSize = vec2(aspect, 1.0) * 0.5; vec2 rectCenter = st - rectHalfSize; float dist = sdRoundedBox(rectCenter, rectHalfSize, roundCornerRadius); float halfAA = antiAliasing * 0.5; float shape = 1.0 - smoothstep(-halfAA, halfAA, dist); return shape; } void main() { float t = linearInterpolate(vUV, start, end); vec4 out_color = mix(colorStops[0].color, colorStops[1].color, linearStep(colorStops[0].offset, colorStops[1].offset, t)); gl_FragColor = out_color * roundedRectMask(); } kylin-wayland-compositor/src/scene/shaders/linear_gradient.vert0000664000175000017500000000056015160460057024032 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform mat3 uv2ndc; uniform mat3 uvRotation; attribute vec2 inUV; varying vec2 vUV; varying vec2 vUVSDF; void main() { vec3 uv3 = vec3(inUV, 1.0); gl_Position = vec4(uv2ndc * uv3, 1.0); vUV = inUV; vUVSDF = (uvRotation * uv3).xy; } kylin-wayland-compositor/src/scene/shaders/shadow.vert0000664000175000017500000000061115160460057022165 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform vec2 size; uniform mat3 uv2ndc; uniform mat3 inverseTransform; attribute vec2 inUV; varying vec2 vPos; varying vec2 vUV; void main() { gl_Position = vec4(uv2ndc * vec3(inUV, 1.0), 1.0); vUV = (inverseTransform * vec3(inUV, 1.0)).xy; vPos = vUV * size; } kylin-wayland-compositor/src/scene/shaders/shadow.frag0000664000175000017500000000550715160460057022135 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform vec4 color; uniform float sigma; uniform vec4 rect; uniform float roundRadius; uniform float windowAntiAliasing; uniform float windowAspect; uniform vec4 windowRect; uniform vec4 windowRoundRadius; varying vec2 vPos; varying vec2 vUV; // License: CC0 (http://creativecommons.org/publicdomain/zero/1.0/) // A standard gaussian function, used for weighting samples float gaussian(float x, float sigma) { const float pi = 3.141592653589793; return exp(-(x * x) / (2.0 * sigma * sigma)) / (sqrt(2.0 * pi) * sigma); } // This approximates the error function, needed for the gaussian integral vec2 erf(vec2 x) { vec2 s = sign(x), a = abs(x); x = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a; x *= x; return s - s / (x * x); } // Return the blurred mask along the x dimension float roundedBoxShadowX(float x, float y, float sigma, float corner, vec2 halfSize) { float delta = min(halfSize.y - corner - abs(y), 0.0); float curved = halfSize.x - corner + sqrt(max(0.0, corner * corner - delta * delta)); vec2 integral = 0.5 + 0.5 * erf((x + vec2(-curved, curved)) * (sqrt(0.5) / sigma)); return integral.y - integral.x; } #define MULTI_SAMPLE_COUNT 3 // Return the mask for the shadow of a box from lower to upper float roundedBoxShadow(vec2 lower, vec2 upper, vec2 point, float sigma, float corner) { // Center everything to make the math easier vec2 center = (lower + upper) * 0.5; vec2 halfSize = (upper - lower) * 0.5; point -= center; // The signal is only non-zero in a limited range, so don't waste samples float low = point.y - halfSize.y; float high = point.y + halfSize.y; float start = clamp(-3.0 * sigma, low, high); float end = clamp(3.0 * sigma, low, high); // Accumulate samples float step = (end - start) / float(MULTI_SAMPLE_COUNT); float y = start + step * 0.5; float value = 0.0; for (int i = 0; i < MULTI_SAMPLE_COUNT; i++) { value += roundedBoxShadowX(point.x, point.y - y, sigma, corner, halfSize) * gaussian(y, sigma) * step; y += step; } return value; } float sdRoundedBox(in vec2 p, in vec2 b, in vec4 r) { r.xy = (p.x > 0.0) ? r.xy : r.zw; r.x = (p.y > 0.0) ? r.x : r.y; vec2 q = abs(p) - b + r.x; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x; } void main() { vec2 st = vUV * 2.0; st.x *= windowAspect; vec2 windowOffset = -windowRect.xy * 2.0; float windowDist = sdRoundedBox(st + windowOffset, windowRect.zw, windowRoundRadius); float windowMask = smoothstep(-windowAntiAliasing, windowAntiAliasing, windowDist); float shadow = roundedBoxShadow(rect.xy, rect.zw, vPos, sigma, roundRadius); gl_FragColor = color * shadow * windowMask; } kylin-wayland-compositor/src/scene/shaders/decoration.vert0000664000175000017500000000051415160460057023031 0ustar fengfeng#ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif uniform mat3 uv2ndc; uniform mat3 inverseTransform; attribute vec2 inUV; varying vec2 vUV; void main() { gl_Position = vec4(uv2ndc * vec3(inUV, 1.0), 1.0); vUV = (inverseTransform * vec3(inUV, 1.0)).xy; } kylin-wayland-compositor/src/scene/rect.c0000664000175000017500000002772515160461067017467 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include "effect/blur.h" #include "render/opengl.h" #include "render/pass.h" #include "render/profile.h" #include "scene_p.h" struct ky_scene_rect *ky_scene_rect_from_node(struct ky_scene_node *node) { assert(node->type == KY_SCENE_NODE_RECT); struct ky_scene_rect *rect = wl_container_of(node, rect, node); return rect; }; static struct ky_scene_node *rect_accept_input(struct ky_scene_node *node, int lx, int ly, double px, double py, double *rx, double *ry) { /* skip disabled or input bypassed nodes */ if (!node->enabled || node->input_bypassed) { return NULL; } struct ky_scene_rect *rect = ky_scene_rect_from_node(node); struct wlr_box box = { floor(px), floor(py), 1, 1 }; struct wlr_box node_box = { lx, ly, rect->width, rect->height }; if (!wlr_box_intersection(&node_box, &node_box, &box)) { return NULL; } if (pixman_region32_not_empty(&node->input_region) && !pixman_region32_contains_point(&node->input_region, box.x - lx, box.y - ly, NULL)) { return NULL; } *rx = px - lx; *ry = py - ly; return node; } static void rect_update_outputs(struct ky_scene_node *node, int lx, int ly, struct wl_list *outputs, struct ky_scene_output *ignore, struct ky_scene_output *force) { // Do nothing } static void rect_get_opaque_region(struct ky_scene_node *node, pixman_region32_t *opaque) { pixman_region32_clear(opaque); if (!node->enabled) { return; } struct ky_scene_rect *rect = ky_scene_rect_from_node(node); if (rect->color[3] != 1) { return; } pixman_region32_union_rect(opaque, opaque, 0, 0, rect->width, rect->height); if (pixman_region32_not_empty(&node->clip_region)) { pixman_region32_intersect(opaque, opaque, &node->clip_region); } /* subtract round corners */ pixman_region32_t corner; pixman_region32_init(&corner); ky_scene_corner_region(&corner, rect->width, rect->height, node->radius); pixman_region32_subtract(opaque, opaque, &corner); pixman_region32_fini(&corner); } static void rect_collect_invisible(struct ky_scene_rect *rect, struct kywc_box *ly_box, pixman_region32_t *invisible) { pixman_region32_t opaque; pixman_region32_init(&opaque); rect_get_opaque_region(&rect->node, &opaque); if (!pixman_region32_not_empty(&opaque)) { pixman_region32_fini(&opaque); return; } pixman_region32_translate(&opaque, ly_box->x, ly_box->y); pixman_region32_union(invisible, invisible, &opaque); pixman_region32_fini(&opaque); } static void rect_collect_damage(struct ky_scene_node *node, int lx, int ly, bool parent_enabled, uint32_t damage_type, pixman_region32_t *damage, pixman_region32_t *invisible, pixman_region32_t *affected) { bool node_enabled = parent_enabled && node->enabled; /* node is still disabled, skip it */ if (!node_enabled && !node->last_enabled) { node->damage_type = KY_SCENE_DAMAGE_NONE; return; } struct ky_scene_rect *rect = ky_scene_rect_from_node(node); // if node state is changed, it must in the affected region if (rect->width > 0 && rect->height > 0 && pixman_region32_contains_rectangle( affected, &(pixman_box32_t){ lx, ly, lx + rect->width, ly + rect->height }) == PIXMAN_REGION_OUT) { if (node_enabled) { rect_collect_invisible(rect, &(struct kywc_box){ lx, ly, rect->width, rect->height }, invisible); } return; } /** * we may need to do 3 things: * 1. add node damage region to the scene collected_damage * 2. update node visible region * 3. add node opaque region to the scene collected_invisible */ // no damage if node state is not changed bool no_damage = node->last_enabled && node_enabled && damage_type == KY_SCENE_DAMAGE_NONE; if (!no_damage) { /* node last visible region is added to damgae */ if (node->last_enabled && (!node_enabled || (damage_type & KY_SCENE_DAMAGE_HARMFUL))) { pixman_region32_union(damage, damage, &node->visible_region); } } // update node visible region always pixman_region32_clear(&node->visible_region); if (node_enabled && rect->color[3] != 0) { // current visible region bool has_clip_region = pixman_region32_not_empty(&node->clip_region); if (has_clip_region) { pixman_region32_intersect_rect(&node->visible_region, &node->clip_region, 0, 0, rect->width, rect->height); pixman_region32_translate(&node->visible_region, lx, ly); } else { pixman_region32_init_rect(&node->visible_region, lx, ly, rect->width, rect->height); } pixman_region32_subtract(&node->visible_region, &node->visible_region, invisible); if (!no_damage) { pixman_region32_union(damage, damage, &node->visible_region); } rect_collect_invisible(rect, &(struct kywc_box){ lx, ly, rect->width, rect->height }, invisible); } node->last_enabled = node_enabled; node->damage_type = KY_SCENE_DAMAGE_NONE; } bool ky_scene_rect_render(struct ky_scene_node *node, struct kywc_box geo, float color[4], bool render_with_visibility, struct ky_scene_render_target *target) { pixman_region32_t render_region; if (render_with_visibility) { pixman_region32_init(&render_region); pixman_region32_union(&render_region, &node->visible_region, &node->extend_render_region); pixman_region32_intersect(&render_region, &render_region, &target->damage); } else { pixman_region32_init_rect(&render_region, 0, 0, geo.width, geo.height); if (pixman_region32_not_empty(&node->clip_region)) { pixman_region32_intersect(&render_region, &render_region, &node->clip_region); } pixman_region32_translate(&render_region, geo.x, geo.y); pixman_region32_intersect(&render_region, &render_region, &target->damage); } if (!pixman_region32_not_empty(&render_region)) { pixman_region32_fini(&render_region); return false; } struct wlr_box dst_box = { .x = geo.x - target->logical.x, .y = geo.y - target->logical.y, .width = geo.width, .height = geo.height, }; ky_scene_render_box(&dst_box, target); pixman_region32_translate(&render_region, -target->logical.x, -target->logical.y); ky_scene_render_region(&render_region, target); KY_PROFILE_RENDER_ZONE(ky_render_pass_get_renderer(target->render_pass), gzone, __func__); bool render_with_radius = !(target->options & KY_SCENE_RENDER_DISABLE_ROUND_CORNER); struct ky_render_rect_options options = { .base = { .box = dst_box, .color = { .r = color[0], .g = color[1], .b = color[2], .a = color[3], }, .clip = &render_region, .blend_mode = color[3] != 1 ? WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, }, .transform = target->transform, .radius = { .rb = render_with_radius ? node->radius[0] * target->scale : 0, .rt = render_with_radius ? node->radius[1] * target->scale : 0, .lb = render_with_radius ? node->radius[2] * target->scale : 0, .lt = render_with_radius ? node->radius[3] * target->scale : 0, }, }; if (!(target->options & KY_SCENE_RENDER_DISABLE_BLUR)) { struct blur_render_options opts = { .lx = geo.x, .ly = geo.y, .dst_box = &dst_box, .clip = &render_region, .radius = &options.radius, .blur = node->has_blur ? &node->blur : NULL, }; blur_render_with_target(target, &opts); } ky_render_pass_add_rect(target->render_pass, &options); pixman_region32_fini(&render_region); KY_PROFILE_RENDER_ZONE_END(ky_render_pass_get_renderer(target->render_pass)); return true; } static void rect_render(struct ky_scene_node *node, int lx, int ly, struct ky_scene_render_target *target) { if (!node->enabled) { return; } struct ky_scene_rect *rect = ky_scene_rect_from_node(node); if (rect->color[3] == 0) { return; } bool render_with_visibility = !(target->options & KY_SCENE_RENDER_DISABLE_VISIBILITY); if (render_with_visibility && !pixman_region32_not_empty(&node->visible_region) && !pixman_region32_not_empty(&node->extend_render_region)) { return; } ky_scene_rect_render(node, (struct kywc_box){ lx, ly, rect->width, rect->height }, rect->color, render_with_visibility, target); } static void rect_get_bounding_box(struct ky_scene_node *node, struct wlr_box *box) { struct ky_scene_rect *rect = ky_scene_rect_from_node(node); *box = (struct wlr_box){ 0, 0, rect->width, rect->height }; } static void rect_destroy(struct ky_scene_node *node) { if (!node) { return; } struct ky_scene_rect *rect = ky_scene_rect_from_node(node); if (node->last_enabled) { struct ky_scene *scene = ky_scene_from_node(node); ky_scene_add_damage(scene, &node->visible_region); } rect->node_destroy(node); } void ky_scene_rect_init(struct ky_scene_rect *rect, struct ky_scene_tree *parent, int width, int height, const float color[static 4]) { *rect = (struct ky_scene_rect){ 0 }; ky_scene_node_init(&rect->node, parent); rect->node.type = KY_SCENE_NODE_RECT; rect->node_destroy = rect->node.impl.destroy; rect->node.impl.destroy = rect_destroy; rect->node.impl.accept_input = rect_accept_input; rect->node.impl.update_outputs = rect_update_outputs; rect->node.impl.collect_damage = rect_collect_damage; rect->node.impl.get_opaque_region = rect_get_opaque_region; rect->node.impl.render = rect_render; rect->node.impl.get_bounding_box = rect_get_bounding_box; rect->width = width; rect->height = height; memcpy(rect->color, color, sizeof(rect->color)); } struct ky_scene_rect *ky_scene_rect_create(struct ky_scene_tree *parent, int width, int height, const float color[static 4]) { struct ky_scene_rect *scene_rect = calloc(1, sizeof(*scene_rect)); if (!scene_rect) { return NULL; } ky_scene_rect_init(scene_rect, parent, width, height, color); ky_scene_node_push_damage(&scene_rect->node, KY_SCENE_DAMAGE_HARMFUL, NULL); return scene_rect; } void ky_scene_rect_set_size(struct ky_scene_rect *rect, int width, int height) { if (rect->width == width && rect->height == height) { return; } bool update_later = false; if ((rect->width > width || rect->height > height)) { ky_scene_node_push_damage(&rect->node, KY_SCENE_DAMAGE_BOTH, NULL); } if (rect->width < width || rect->height < height) { update_later = true; } rect->width = width; rect->height = height; if (update_later) { ky_scene_node_push_damage(&rect->node, KY_SCENE_DAMAGE_BOTH, NULL); } } void ky_scene_rect_set_color(struct ky_scene_rect *rect, const float color[static 4]) { if (memcmp(rect->color, color, sizeof(rect->color)) == 0) { return; } bool harmful = rect->color[3] == 0 || color[3] == 0 || rect->color[3] == 1 || color[3] == 1; memcpy(rect->color, color, sizeof(rect->color)); ky_scene_node_push_damage(&rect->node, harmful ? KY_SCENE_DAMAGE_BOTH : KY_SCENE_DAMAGE_HARMLESS, NULL); } kylin-wayland-compositor/src/scene/box.c0000664000175000017500000001314715160461067017313 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "scene/box.h" #include "util/macros.h" struct ky_scene_box { struct ky_scene_rect *rect; struct wl_listener destroy; int radius[4]; int border_width; }; static bool box_create_rounded_region(struct ky_scene_box *scene_box, pixman_region32_t *region) { int width = scene_box->rect->width, height = scene_box->rect->height; cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_A1, width, height); if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { return false; } cairo_t *cairo = cairo_create(surface); double half = scene_box->border_width / 2.0; cairo_set_line_width(cairo, scene_box->border_width); int lt = scene_box->radius[KY_SCENE_ROUND_CORNER_LT]; if (lt > 0) { cairo_move_to(cairo, half, lt); cairo_arc(cairo, lt, lt, lt - half, ANGLE(-180), ANGLE(-90)); } else { cairo_move_to(cairo, half, half); } int rt = scene_box->radius[KY_SCENE_ROUND_CORNER_RT]; if (rt > 0) { cairo_line_to(cairo, width - rt, half); cairo_arc(cairo, width - rt, rt, rt - half, ANGLE(-90), ANGLE(0)); } else { cairo_line_to(cairo, width - half, half); } int rb = scene_box->radius[KY_SCENE_ROUND_CORNER_RB]; if (rb > 0) { cairo_line_to(cairo, width - half, height - rb); cairo_arc(cairo, width - rb, height - rb, rb - half, ANGLE(0), ANGLE(90)); } else { cairo_line_to(cairo, width - half, height - half); } int lb = scene_box->radius[KY_SCENE_ROUND_CORNER_LB]; if (lb > 0) { cairo_line_to(cairo, lb, height - half); cairo_arc(cairo, lb, height - lb, lb - half, ANGLE(90), ANGLE(180)); } else { cairo_line_to(cairo, half, height - half); } cairo_close_path(cairo); cairo_set_source_rgba(cairo, 0, 0, 0, 1); cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); cairo_stroke(cairo); cairo_surface_flush(surface); void *data = cairo_image_surface_get_data(surface); int stride = cairo_image_surface_get_stride(surface); pixman_image_t *image = pixman_image_create_bits_no_clear(PIXMAN_a1, width, height, data, stride); if (!image) { cairo_destroy(cairo); cairo_surface_destroy(surface); return false; } pixman_region32_init_from_image(region, image); pixman_image_unref(image); cairo_destroy(cairo); cairo_surface_destroy(surface); return true; } static bool box_has_radius(struct ky_scene_box *scene_box) { return scene_box->radius[0] > 0 || scene_box->radius[1] > 0 || scene_box->radius[2] > 0 || scene_box->radius[3] > 0; } /** * update input region and clip region */ static void box_update_region(struct ky_scene_box *scene_box) { pixman_region32_t region; pixman_region32_init(®ion); int width = scene_box->rect->width; int height = scene_box->rect->height; if (width > 0 && height > 0 && (!box_has_radius(scene_box) || !box_create_rounded_region(scene_box, ®ion))) { // fallback to no rounded corners pixman_region32_init_rect(®ion, 0, 0, width, height); int off = scene_box->border_width; pixman_region32_t reg; pixman_region32_init_rect(®, off, off, width - 2 * off, height - 2 * off); pixman_region32_subtract(®ion, ®ion, ®); pixman_region32_fini(®); } ky_scene_node_set_input_region(&scene_box->rect->node, ®ion); ky_scene_node_set_clip_region(&scene_box->rect->node, ®ion); pixman_region32_fini(®ion); } static void box_handle_destroy(struct wl_listener *listener, void *data) { struct ky_scene_box *scene_box = wl_container_of(listener, scene_box, destroy); wl_list_remove(&scene_box->destroy.link); free(scene_box); } struct ky_scene_box *ky_scene_box_create(struct ky_scene_tree *parent, int width, int height, const float color[static 4], int border_width) { struct ky_scene_box *scene_box = calloc(1, sizeof(*scene_box)); if (!scene_box) { return NULL; } scene_box->rect = ky_scene_rect_create(parent, width, height, color); if (!scene_box->rect) { free(scene_box); return NULL; } scene_box->destroy.notify = box_handle_destroy; wl_signal_add(&scene_box->rect->node.events.destroy, &scene_box->destroy); scene_box->border_width = border_width; box_update_region(scene_box); return scene_box; } struct ky_scene_node *ky_scene_node_from_box(struct ky_scene_box *scene_box) { return &scene_box->rect->node; } void ky_scene_box_set_color(struct ky_scene_box *scene_box, const float color[static 4]) { ky_scene_rect_set_color(scene_box->rect, color); } void ky_scene_box_set_size(struct ky_scene_box *scene_box, int width, int height) { if (scene_box->rect->width == width && scene_box->rect->height == height) { return; } ky_scene_rect_set_size(scene_box->rect, width, height); box_update_region(scene_box); } void ky_scene_box_set_border_width(struct ky_scene_box *scene_box, int width) { if (scene_box->border_width == width) { return; } scene_box->border_width = width; box_update_region(scene_box); } void ky_scene_box_set_radius(struct ky_scene_box *scene_box, const int radius[static 4]) { if (memcmp(scene_box->radius, radius, sizeof(scene_box->radius)) == 0) { return; } memcpy(scene_box->radius, radius, sizeof(scene_box->radius)); box_update_region(scene_box); } kylin-wayland-compositor/src/scene/subsurface.c0000664000175000017500000002756615160460057020675 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include "scene/surface.h" /** * A tree for a surface and all of its child sub-surfaces. * * `tree` contains `scene_surface` and one node per sub-surface. */ struct ky_scene_subsurface_tree { struct ky_scene_tree *tree; struct wlr_surface *surface; struct ky_scene_surface *scene_surface; struct wl_listener tree_destroy; struct wl_listener surface_destroy; struct wl_listener surface_commit; struct wl_listener surface_map; struct wl_listener surface_unmap; struct wl_listener surface_new_subsurface; struct ky_scene_subsurface_tree *parent; // NULL for the top-level surface // Only valid if the surface is a sub-surface struct wlr_addon surface_addon; struct wl_listener subsurface_destroy; }; static void subsurface_get_radius(struct wlr_subsurface *subsurface, int child_radius[4]) { memset(child_radius, 0, sizeof(int) * 4); struct wlr_surface *parent = subsurface->parent; if (!parent) { return; } struct ky_scene_buffer *buffer = ky_scene_buffer_try_from_surface(parent); if (!buffer) { return; } int parent_radius[4] = { 0 }; ky_scene_node_get_radius(&buffer->node, KY_SCENE_ROUND_CORNERS_FOR_BOUNDING, parent_radius); int x1 = 0, y1 = 0; int x2 = parent->current.width; int y2 = parent->current.height; struct { int x, y; } parent_vertex[4] = { { x2, y2 }, { x2, y1 }, { x1, y2 }, { x1, y1 } }; x1 = subsurface->current.x; y1 = subsurface->current.y; x2 = x1 + subsurface->surface->current.width; y2 = y1 + subsurface->surface->current.height; struct { int x, y; } child_vertex[4] = { { x2, y2 }, { x2, y1 }, { x1, y2 }, { x1, y1 } }; for (int i = 0; i < 4; ++i) { if (parent_radius[i] > 0 && abs(parent_vertex[i].x - child_vertex[i].x) < parent_radius[i] && abs(parent_vertex[i].y - child_vertex[i].y) < parent_radius[i]) { child_radius[i] = parent_radius[i]; } } } static void subsurface_collect_damage(struct ky_scene_node *node, int lx, int ly, bool parent_enabled, uint32_t damage_type, pixman_region32_t *damage, pixman_region32_t *invisible, pixman_region32_t *affected) { struct ky_scene_surface *scene_surface = ky_scene_surface_try_from_buffer(ky_scene_buffer_from_node(node)); subsurface_get_radius(wlr_subsurface_try_from_wlr_surface(scene_surface->surface), node->radius); scene_surface->buffer_default_impl.collect_damage(node, lx, ly, parent_enabled, damage_type, damage, invisible, affected); } static void subsurface_tree_handle_tree_destroy(struct wl_listener *listener, void *data) { struct ky_scene_subsurface_tree *subsurface_tree = wl_container_of(listener, subsurface_tree, tree_destroy); // tree and scene_surface will be cleaned up by scene_node_finish if (subsurface_tree->parent) { wlr_addon_finish(&subsurface_tree->surface_addon); wl_list_remove(&subsurface_tree->subsurface_destroy.link); } wl_list_remove(&subsurface_tree->tree_destroy.link); wl_list_remove(&subsurface_tree->surface_destroy.link); wl_list_remove(&subsurface_tree->surface_commit.link); wl_list_remove(&subsurface_tree->surface_map.link); wl_list_remove(&subsurface_tree->surface_unmap.link); wl_list_remove(&subsurface_tree->surface_new_subsurface.link); free(subsurface_tree); } static const struct wlr_addon_interface subsurface_tree_addon_impl; static struct ky_scene_subsurface_tree * subsurface_tree_from_subsurface(struct ky_scene_subsurface_tree *parent, struct wlr_subsurface *subsurface) { struct wlr_addon *addon = wlr_addon_find(&subsurface->surface->addons, parent, &subsurface_tree_addon_impl); assert(addon != NULL); struct ky_scene_subsurface_tree *subsurface_tree = wl_container_of(addon, subsurface_tree, surface_addon); return subsurface_tree; } static void subsurface_tree_reconfigure(struct ky_scene_subsurface_tree *subsurface_tree) { struct wlr_surface *surface = subsurface_tree->surface; struct ky_scene_node *prev = NULL; struct wlr_subsurface *subsurface; wl_list_for_each(subsurface, &surface->current.subsurfaces_below, current.link) { struct ky_scene_subsurface_tree *child = subsurface_tree_from_subsurface(subsurface_tree, subsurface); if (prev != NULL) { ky_scene_node_place_above(&child->tree->node, prev); } prev = &child->tree->node; ky_scene_node_set_position(&child->tree->node, subsurface->current.x, subsurface->current.y); } if (prev != NULL) { ky_scene_node_place_above(&subsurface_tree->scene_surface->buffer->node, prev); } prev = &subsurface_tree->scene_surface->buffer->node; wl_list_for_each(subsurface, &surface->current.subsurfaces_above, current.link) { struct ky_scene_subsurface_tree *child = subsurface_tree_from_subsurface(subsurface_tree, subsurface); ky_scene_node_place_above(&child->tree->node, prev); prev = &child->tree->node; ky_scene_node_set_position(&child->tree->node, subsurface->current.x, subsurface->current.y); } } static void subsurface_tree_handle_surface_destroy(struct wl_listener *listener, void *data) { struct ky_scene_subsurface_tree *subsurface_tree = wl_container_of(listener, subsurface_tree, surface_destroy); ky_scene_node_destroy(&subsurface_tree->tree->node); } static void subsurface_tree_handle_surface_commit(struct wl_listener *listener, void *data) { struct ky_scene_subsurface_tree *subsurface_tree = wl_container_of(listener, subsurface_tree, surface_commit); // TODO: only do this on subsurface order or position change subsurface_tree_reconfigure(subsurface_tree); } static void subsurface_tree_handle_subsurface_destroy(struct wl_listener *listener, void *data) { struct ky_scene_subsurface_tree *subsurface_tree = wl_container_of(listener, subsurface_tree, subsurface_destroy); ky_scene_node_destroy(&subsurface_tree->tree->node); } static void subsurface_tree_handle_surface_map(struct wl_listener *listener, void *data) { struct ky_scene_subsurface_tree *subsurface_tree = wl_container_of(listener, subsurface_tree, surface_map); ky_scene_node_set_enabled(&subsurface_tree->tree->node, true); } static void subsurface_tree_handle_surface_unmap(struct wl_listener *listener, void *data) { struct ky_scene_subsurface_tree *subsurface_tree = wl_container_of(listener, subsurface_tree, surface_unmap); ky_scene_node_set_enabled(&subsurface_tree->tree->node, false); } static void subsurface_tree_addon_destroy(struct wlr_addon *addon) { struct ky_scene_subsurface_tree *subsurface_tree = wl_container_of(addon, subsurface_tree, surface_addon); ky_scene_node_destroy(&subsurface_tree->tree->node); } static const struct wlr_addon_interface subsurface_tree_addon_impl = { .name = "ky_scene_subsurface_tree", .destroy = subsurface_tree_addon_destroy, }; static struct ky_scene_subsurface_tree *scene_surface_tree_create(struct ky_scene_tree *parent, struct wlr_surface *surface); static bool subsurface_tree_create_subsurface(struct ky_scene_subsurface_tree *parent, struct wlr_subsurface *subsurface) { struct ky_scene_subsurface_tree *child = scene_surface_tree_create(parent->tree, subsurface->surface); if (child == NULL) { return false; } child->parent = parent; child->surface_map.notify = subsurface_tree_handle_surface_map; wl_signal_add(&subsurface->surface->events.map, &child->surface_map); child->surface_unmap.notify = subsurface_tree_handle_surface_unmap; wl_signal_add(&subsurface->surface->events.unmap, &child->surface_unmap); ky_scene_node_set_enabled(&child->tree->node, subsurface->surface->mapped); wlr_addon_init(&child->surface_addon, &subsurface->surface->addons, parent, &subsurface_tree_addon_impl); child->subsurface_destroy.notify = subsurface_tree_handle_subsurface_destroy; wl_signal_add(&subsurface->events.destroy, &child->subsurface_destroy); return true; } static void subsurface_tree_handle_surface_new_subsurface(struct wl_listener *listener, void *data) { struct ky_scene_subsurface_tree *subsurface_tree = wl_container_of(listener, subsurface_tree, surface_new_subsurface); struct wlr_subsurface *subsurface = data; if (!subsurface_tree_create_subsurface(subsurface_tree, subsurface)) { wl_resource_post_no_memory(subsurface->resource); } } static struct ky_scene_subsurface_tree *scene_surface_tree_create(struct ky_scene_tree *parent, struct wlr_surface *surface) { struct ky_scene_subsurface_tree *subsurface_tree = calloc(1, sizeof(*subsurface_tree)); if (subsurface_tree == NULL) { return NULL; } subsurface_tree->tree = ky_scene_tree_create(parent); if (subsurface_tree->tree == NULL) { goto error_surface_tree; } subsurface_tree->scene_surface = ky_scene_surface_create(subsurface_tree->tree, surface); if (subsurface_tree->scene_surface == NULL) { goto error_scene_surface; } subsurface_tree->surface = surface; if (wlr_subsurface_try_from_wlr_surface(surface)) { subsurface_tree->scene_surface->buffer->node.impl.collect_damage = subsurface_collect_damage; } struct wlr_subsurface *subsurface; wl_list_for_each(subsurface, &surface->current.subsurfaces_below, current.link) { if (!subsurface_tree_create_subsurface(subsurface_tree, subsurface)) { goto error_scene_surface; } } wl_list_for_each(subsurface, &surface->current.subsurfaces_above, current.link) { if (!subsurface_tree_create_subsurface(subsurface_tree, subsurface)) { goto error_scene_surface; } } subsurface_tree_reconfigure(subsurface_tree); subsurface_tree->tree_destroy.notify = subsurface_tree_handle_tree_destroy; wl_signal_add(&subsurface_tree->tree->node.events.destroy, &subsurface_tree->tree_destroy); subsurface_tree->surface_destroy.notify = subsurface_tree_handle_surface_destroy; wl_signal_add(&surface->events.destroy, &subsurface_tree->surface_destroy); subsurface_tree->surface_commit.notify = subsurface_tree_handle_surface_commit; wl_signal_add(&surface->events.commit, &subsurface_tree->surface_commit); subsurface_tree->surface_new_subsurface.notify = subsurface_tree_handle_surface_new_subsurface; wl_signal_add(&surface->events.new_subsurface, &subsurface_tree->surface_new_subsurface); wl_list_init(&subsurface_tree->surface_map.link); wl_list_init(&subsurface_tree->surface_unmap.link); return subsurface_tree; error_scene_surface: ky_scene_node_destroy(&subsurface_tree->tree->node); error_surface_tree: free(subsurface_tree); return NULL; } struct ky_scene_tree *ky_scene_subsurface_tree_create(struct ky_scene_tree *parent, struct wlr_surface *surface) { struct ky_scene_subsurface_tree *subsurface_tree = scene_surface_tree_create(parent, surface); if (subsurface_tree == NULL) { return NULL; } return subsurface_tree->tree; } kylin-wayland-compositor/src/scene/decoration.c0000664000175000017500000014201115160461067020643 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "effect/blur.h" #include "render/opengl.h" #include "render/profile.h" #include "scene/decoration.h" #include "scene/render.h" #include "scene_p.h" #include "theme.h" #include "util/macros.h" #include "util/matrix.h" #include "decoration_frag.h" #include "decoration_vert.h" #include "shadow_frag.h" #include "shadow_vert.h" enum deco_update_cause { DECO_UPDATE_CAUSE_NONE = 0, DECO_UPDATE_CAUSE_SURFACE_SIZE = 1 << 0, DECO_UPDATE_CAUSE_MARGIN = 1 << 1, DECO_UPDATE_CAUSE_MARGIN_COLOR = 1 << 2, DECO_UPDATE_CAUSE_MASK = 1 << 3, DECO_UPDATE_CAUSE_CORNER_RADIUS = 1 << 4, DECO_UPDATE_CAUSE_BLURRED = 1 << 5, DECO_UPDATE_CAUSE_SURFACE_COLOR = 1 << 6, }; struct ky_scene_shadow { int offset_x; int offset_y; int spread; int blur; float color[4]; struct kywc_box box; }; struct ky_scene_decoration { /* based on scene_rect */ struct ky_scene_rect rect; ky_scene_node_destroy_func_t node_destroy; uint32_t pending_cause; /* mask for border, shadow, corner */ uint32_t mask; // TODO: resize region ? /* surface size */ int surface_width; int surface_height; /* color in surface size */ float surface_color[4]; /* blurred in surface size */ bool surface_blurred; /* margin */ int border_thickness; int title_height; /* margin color */ float border_color[4]; float title_color[4]; /* region used to resize */ int resize_width; // window round rect // 0=right-bottom, 1=right-top, 2=left-bottom, 3=left-top int round_corner_radius[4]; int shadows_size; struct ky_scene_shadow shadows[THEME_MAX_SHADOW_LAYERS]; /** * render_box = window_box + shadow_boxs * window_box = 0, 0, rect.width, rect.height */ struct kywc_box render_box; pixman_region32_t surface_region; pixman_region32_t title_region; pixman_region32_t border_region; pixman_region32_t round_corner_region; /* clip region without round_corner_region */ pixman_region32_t pure_clip_region; pixman_region32_t clip_region; }; // opengl render static struct { int32_t program; // vs GLint in_uv; GLint uv2ndc; GLint inverse_transform; // fs GLint anti_aliasing; GLint aspect; GLint rect; GLint round_radius; GLint border_thickness; GLint border_color; GLint title_height; GLint title_color; } gl_shader = { 0 }; static struct { int32_t program; // vs GLint in_uv; GLint size; GLint uv2ndc; GLint inverse_transform; // fs GLint rect; GLint sigma; GLint round_radius; GLint color; GLint window_anti_aliasing; GLint window_aspect; GLint window_rect; GLint window_round_radius; } gl_shader_shadow = { 0 }; static void scene_decoration_create_opengl_shader(struct ky_opengl_renderer *renderer) { gl_shader.program = ky_opengl_create_program(renderer, decoration_vert, decoration_frag); if (gl_shader.program <= 0) { gl_shader.program = -1; gl_shader_shadow.program = -1; return; } gl_shader_shadow.program = ky_opengl_create_program(renderer, shadow_vert, shadow_frag); if (gl_shader_shadow.program <= 0) { gl_shader.program = -1; gl_shader_shadow.program = -1; return; } gl_shader.in_uv = glGetAttribLocation(gl_shader.program, "inUV"); gl_shader.uv2ndc = glGetUniformLocation(gl_shader.program, "uv2ndc"); gl_shader.inverse_transform = glGetUniformLocation(gl_shader.program, "inverseTransform"); gl_shader.anti_aliasing = glGetUniformLocation(gl_shader.program, "antiAliasing"); gl_shader.aspect = glGetUniformLocation(gl_shader.program, "aspect"); gl_shader.rect = glGetUniformLocation(gl_shader.program, "rect"); gl_shader.round_radius = glGetUniformLocation(gl_shader.program, "roundRadius"); gl_shader.border_thickness = glGetUniformLocation(gl_shader.program, "borderThickness"); gl_shader.border_color = glGetUniformLocation(gl_shader.program, "borderColor"); gl_shader.title_height = glGetUniformLocation(gl_shader.program, "titleHeight"); gl_shader.title_color = glGetUniformLocation(gl_shader.program, "titleColor"); gl_shader_shadow.in_uv = glGetAttribLocation(gl_shader_shadow.program, "inUV"); gl_shader_shadow.uv2ndc = glGetUniformLocation(gl_shader_shadow.program, "uv2ndc"); gl_shader_shadow.inverse_transform = glGetUniformLocation(gl_shader_shadow.program, "inverseTransform"); gl_shader_shadow.size = glGetUniformLocation(gl_shader_shadow.program, "size"); gl_shader_shadow.color = glGetUniformLocation(gl_shader_shadow.program, "color"); gl_shader_shadow.sigma = glGetUniformLocation(gl_shader_shadow.program, "sigma"); gl_shader_shadow.rect = glGetUniformLocation(gl_shader_shadow.program, "rect"); gl_shader_shadow.round_radius = glGetUniformLocation(gl_shader_shadow.program, "roundRadius"); gl_shader_shadow.window_anti_aliasing = glGetUniformLocation(gl_shader_shadow.program, "windowAntiAliasing"); gl_shader_shadow.window_aspect = glGetUniformLocation(gl_shader_shadow.program, "windowAspect"); gl_shader_shadow.window_rect = glGetUniformLocation(gl_shader_shadow.program, "windowRect"); gl_shader_shadow.window_round_radius = glGetUniformLocation(gl_shader_shadow.program, "windowRoundRadius"); } static void get_render_region_with_mask(struct ky_scene_decoration *deco, int lx, int ly, struct ky_scene_render_target *target, struct wlr_box *region) { struct wlr_box render_box = { .x = lx - target->logical.x + deco->render_box.x, .y = ly - target->logical.y + deco->render_box.y, .width = deco->render_box.width, .height = deco->render_box.height, }; struct wlr_box window_box = { .x = lx - target->logical.x, .y = ly - target->logical.y, .width = deco->rect.width, .height = deco->rect.height, }; bool left_shadow_mask = deco->mask & DECORATION_MASK_LEFT; bool right_shadow_mask = deco->mask & DECORATION_MASK_RIGHT; if (left_shadow_mask && right_shadow_mask) { region->x = render_box.x; region->width = render_box.width; } else if (!left_shadow_mask && right_shadow_mask) { region->x = window_box.x; region->width = (render_box.x + render_box.width) - window_box.x; } else if (left_shadow_mask && !right_shadow_mask) { region->x = render_box.x; region->width = (window_box.x + window_box.width) - render_box.x; } else { region->x = window_box.x; region->width = window_box.width; } // x2 - x1 bool top_shadow_mask = deco->mask & DECORATION_MASK_TOP; bool bottom_shadow_mask = deco->mask & DECORATION_MASK_BOTTOM; if (top_shadow_mask && bottom_shadow_mask) { region->y = render_box.y; region->height = render_box.height; } else if (!top_shadow_mask && bottom_shadow_mask) { region->y = window_box.y; region->height = (render_box.y + render_box.height) - window_box.y; } else if (top_shadow_mask && !bottom_shadow_mask) { region->y = render_box.y; region->height = (window_box.y + window_box.height) - render_box.y; } else { region->y = window_box.y; region->height = window_box.height; } ky_scene_render_box(region, target); } static void get_window_box_from_surface_box(struct ky_scene_decoration *deco, struct ky_scene_render_target *target, int border_thickness, int title_height, const struct wlr_box *box, const struct wlr_box *surface_box, struct wlr_box *window_box) { int border_thickness_2 = border_thickness * 2; if (target->transform == WL_OUTPUT_TRANSFORM_90) { window_box->x = surface_box->y - border_thickness - box->y; window_box->y = surface_box->x - title_height - border_thickness - box->x; window_box->width = surface_box->height + border_thickness_2; window_box->height = surface_box->width + title_height + border_thickness_2; // rotation correct window_box->x = box->height - surface_box->height - border_thickness_2 - window_box->x; } else if (target->transform == WL_OUTPUT_TRANSFORM_180) { window_box->x = surface_box->x - border_thickness - box->x; window_box->y = surface_box->y - border_thickness - box->y; window_box->width = surface_box->width + border_thickness_2; window_box->height = surface_box->height + title_height + border_thickness_2; // rotation correct window_box->x = box->width - surface_box->width - border_thickness_2 - window_box->x; window_box->y = box->height - surface_box->height - border_thickness_2 - title_height - window_box->y; } else if (target->transform == WL_OUTPUT_TRANSFORM_270) { window_box->x = surface_box->y - border_thickness - box->y; window_box->y = surface_box->x - border_thickness - box->x; window_box->width = surface_box->height + border_thickness_2; window_box->height = surface_box->width + title_height + border_thickness_2; // rotation correct window_box->y = box->width - surface_box->width - border_thickness_2 - title_height - window_box->y; } else if (target->transform == WL_OUTPUT_TRANSFORM_FLIPPED) { window_box->x = surface_box->x - border_thickness - box->x; window_box->y = surface_box->y - title_height - border_thickness - box->y; window_box->width = surface_box->width + border_thickness_2; window_box->height = surface_box->height + title_height + border_thickness_2; // rotation correct window_box->x = box->width - surface_box->width - border_thickness_2 - window_box->x; } else if (target->transform == WL_OUTPUT_TRANSFORM_FLIPPED_90) { window_box->x = surface_box->y - border_thickness - box->y; window_box->y = surface_box->x - title_height - border_thickness - box->x; window_box->width = surface_box->height + border_thickness_2; window_box->height = surface_box->width + title_height + border_thickness_2; } else if (target->transform == WL_OUTPUT_TRANSFORM_FLIPPED_180) { window_box->x = surface_box->x - border_thickness - box->x; window_box->y = surface_box->y - border_thickness - box->y; window_box->width = surface_box->width + border_thickness_2; window_box->height = surface_box->height + title_height + border_thickness_2; // rotation correct window_box->y = box->height - surface_box->height - border_thickness_2 - title_height - window_box->y; } else if (target->transform == WL_OUTPUT_TRANSFORM_FLIPPED_270) { window_box->x = surface_box->y - border_thickness - box->y; window_box->y = surface_box->x - border_thickness - box->x; window_box->width = surface_box->height + border_thickness_2; window_box->height = surface_box->width + title_height + border_thickness_2; // rotation correct window_box->x = box->height - surface_box->height - border_thickness_2 - window_box->x; window_box->y = box->width - surface_box->width - border_thickness_2 - title_height - window_box->y; } else { window_box->x = surface_box->x - border_thickness - box->x; window_box->y = surface_box->y - title_height - border_thickness - box->y; window_box->width = surface_box->width + border_thickness_2; window_box->height = surface_box->height + title_height + border_thickness_2; } } static void scene_decoration_opengl_render(struct ky_scene_decoration *deco, int lx, int ly, struct ky_scene_render_target *target, const struct wlr_box *box, const pixman_region32_t *clip) { struct wlr_box region_box = { 0 }; get_render_region_with_mask(deco, lx, ly, target, ®ion_box); pixman_region32_t region; pixman_region32_init_rect(®ion, region_box.x, region_box.y, region_box.width, region_box.height); pixman_region32_intersect(®ion, ®ion, clip); int rects_len; const pixman_box32_t *rects = pixman_region32_rectangles(®ion, &rects_len); if (rects_len == 0) { pixman_region32_fini(®ion); return; } GLfloat verts[rects_len * 6 * 2]; size_t vert_index = 0; for (int i = 0; i < rects_len; i++) { const pixman_box32_t *rect = &rects[i]; verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; } float scale = target->scale; float width = box->width; float height = box->height; if (target->transform & WL_OUTPUT_TRANSFORM_90) { width = box->height; height = box->width; } // keep border ceil scale. avoid non-integer scale different thickness int border_thickness = floorf(deco->border_thickness * scale); int title_height = round(deco->title_height * scale); float half_height = height * 0.5f; // shader distance scale float round_corner_radius[4] = { 0 }; if (!(target->options & KY_SCENE_RENDER_DISABLE_ROUND_CORNER)) { round_corner_radius[0] = deco->round_corner_radius[0] > 0 ? deco->round_corner_radius[0] * scale + border_thickness : 0.0f; round_corner_radius[1] = deco->round_corner_radius[1] > 0 ? deco->round_corner_radius[1] * scale + border_thickness : 0.0f; round_corner_radius[2] = deco->round_corner_radius[2] > 0 ? deco->round_corner_radius[2] * scale + border_thickness : 0.0f; round_corner_radius[3] = deco->round_corner_radius[3] > 0 ? deco->round_corner_radius[3] * scale + border_thickness : 0.0f; } // surface rect as reference use framebuffer coord. avoid non-integer scale issuse struct wlr_box surface_box = { .x = lx + deco->border_thickness - target->logical.x, .y = ly + deco->border_thickness + deco->title_height - target->logical.y, .width = deco->surface_width, .height = deco->surface_height, }; ky_scene_render_box(&surface_box, target); struct wlr_box window_box = { 0 }; get_window_box_from_surface_box(deco, target, border_thickness, title_height, box, &surface_box, &window_box); struct kywc_box dst_box = { .x = box->x, .y = box->y, .width = box->width, .height = box->height, }; struct ky_mat3 uv2ndc; ky_mat3_uvofbox_to_ndc(&uv2ndc, target->buffer->width, target->buffer->height, 0, &dst_box); struct ky_mat3 inverseTransform; ky_mat3_invert_output_transform(&inverseTransform, target->transform); float one_pixel_distance = 1.0 / half_height; // 1 pixel border thickness keep 1 pixel less soft aa float anti_aliasing = one_pixel_distance * 0.5f; float aspect = width / height; float width_distance = window_box.width / height; float height_distance = window_box.height / height; float offset_x_distance = width_distance * 0.5f + window_box.x / height; float offset_y_distance = height_distance * 0.5f + window_box.y / height; float round_radius[4] = { deco->mask & DECORATION_MASK_BOTTOM_RIGHT ? round_corner_radius[0] * one_pixel_distance : 0.0f, deco->mask & DECORATION_MASK_TOP_RIGHT ? round_corner_radius[1] * one_pixel_distance : 0.0f, deco->mask & DECORATION_MASK_BOTTOM_LEFT ? round_corner_radius[2] * one_pixel_distance : 0.0f, deco->mask & DECORATION_MASK_TOP_LEFT ? round_corner_radius[3] * one_pixel_distance : 0.0f }; // round rect shadow canot specify every corner round_radius float shadow_round_radius = 0.f; if (deco->mask & DECORATION_MASK_BOTTOM_RIGHT) { shadow_round_radius = round_corner_radius[0]; } else if (deco->mask & DECORATION_MASK_TOP_RIGHT) { shadow_round_radius = round_corner_radius[1]; } else if (deco->mask & DECORATION_MASK_BOTTOM_LEFT) { shadow_round_radius = round_corner_radius[2]; } else if (deco->mask & DECORATION_MASK_TOP_LEFT) { shadow_round_radius = round_corner_radius[3]; } shadow_round_radius = MAX(shadow_round_radius, FLT_MIN); KY_PROFILE_RENDER_ZONE(target->output->output->renderer, gzone, __func__); glEnable(GL_BLEND); glEnableVertexAttribArray(gl_shader.in_uv); glVertexAttribPointer(gl_shader.in_uv, 2, GL_FLOAT, GL_FALSE, 0, verts); // reverse multi draw shadow with shape mask glUseProgram(gl_shader_shadow.program); glUniformMatrix3fv(gl_shader_shadow.uv2ndc, 1, GL_FALSE, uv2ndc.matrix); glUniformMatrix3fv(gl_shader_shadow.inverse_transform, 1, GL_FALSE, inverseTransform.matrix); glUniform2f(gl_shader_shadow.size, width, height); for (int i = deco->shadows_size - 1; i >= 0; i--) { struct ky_scene_shadow *shadow = &deco->shadows[i]; int shadow_offset_x = roundf(shadow->offset_x * scale); int shadow_offset_y = roundf(shadow->offset_y * scale); float shadow_blur = shadow->blur * scale; // rect is before blur. box = rect expand spread struct wlr_box shadow_rect = { .x = window_box.x + shadow_offset_x - shadow->spread, .y = window_box.y + shadow_offset_y - shadow->spread, .width = window_box.width + shadow->spread * 2, .height = window_box.height + shadow->spread * 2, }; glUniform4f(gl_shader_shadow.rect, shadow_rect.x, shadow_rect.y, shadow_rect.x + shadow_rect.width, shadow_rect.y + shadow_rect.height); glUniform1f(gl_shader_shadow.round_radius, shadow_round_radius); // blur with to gaussian sigma. scale = 1.0 / (2.0 * sqrt(2.0 * log(2.0))) = 0.424660891 glUniform1f(gl_shader_shadow.sigma, shadow_blur * 0.424660891f); glUniform4fv(gl_shader_shadow.color, 1, shadow->color); glUniform1f(gl_shader_shadow.window_anti_aliasing, anti_aliasing); glUniform1f(gl_shader_shadow.window_aspect, aspect); glUniform4f(gl_shader_shadow.window_rect, offset_x_distance, offset_y_distance, width_distance, height_distance); glUniform4fv(gl_shader_shadow.window_round_radius, 1, round_radius); glDrawArrays(GL_TRIANGLES, 0, rects_len * 6); } // draw shape glUseProgram(gl_shader.program); glUniformMatrix3fv(gl_shader.uv2ndc, 1, GL_FALSE, uv2ndc.matrix); glUniformMatrix3fv(gl_shader.inverse_transform, 1, GL_FALSE, inverseTransform.matrix); glUniform1f(gl_shader.anti_aliasing, anti_aliasing); glUniform1f(gl_shader.aspect, aspect); glUniform4f(gl_shader.rect, offset_x_distance, offset_y_distance, width_distance, height_distance); glUniform4fv(gl_shader.round_radius, 1, round_radius); glUniform1f(gl_shader.border_thickness, border_thickness * one_pixel_distance); glUniform4fv(gl_shader.border_color, 1, deco->border_color); glUniform1f(gl_shader.title_height, title_height / height); glUniform4fv(gl_shader.title_color, 1, deco->title_color); glDrawArrays(GL_TRIANGLES, 0, rects_len * 6); glDisableVertexAttribArray(gl_shader.in_uv); glUseProgram(0); KY_PROFILE_RENDER_ZONE_END(target->output->output->renderer); pixman_region32_fini(®ion); } static void scene_decoration_update_round_corner_region(struct ky_scene_decoration *deco) { pixman_region32_clear(&deco->round_corner_region); if (gl_shader.program < 0) { return; } int width = deco->surface_width; int height = deco->surface_height; // include border round_corner float round_corner_radius[4] = { deco->round_corner_radius[0] + deco->border_thickness, deco->round_corner_radius[1] + deco->border_thickness, deco->round_corner_radius[2] + deco->border_thickness, deco->round_corner_radius[3] + deco->border_thickness, }; int x1 = 0; int x2 = width + 2 * deco->border_thickness; int y1 = 0; int y2 = height + deco->title_height + 2 * deco->border_thickness; if ((deco->mask & DECORATION_MASK_TOP_LEFT) && round_corner_radius[3] > 0) { pixman_region32_union_rect(&deco->round_corner_region, &deco->round_corner_region, x1, y1, round_corner_radius[3], round_corner_radius[3]); } if ((deco->mask & DECORATION_MASK_TOP_RIGHT) && round_corner_radius[1] > 0) { pixman_region32_union_rect(&deco->round_corner_region, &deco->round_corner_region, x2 - round_corner_radius[1], y1, round_corner_radius[1], round_corner_radius[1]); } if ((deco->mask & DECORATION_MASK_BOTTOM_LEFT) && round_corner_radius[2] > 0) { pixman_region32_union_rect(&deco->round_corner_region, &deco->round_corner_region, x1, y2 - round_corner_radius[2], round_corner_radius[2], round_corner_radius[2]); } if ((deco->mask & DECORATION_MASK_BOTTOM_RIGHT) && round_corner_radius[0] > 0) { pixman_region32_union_rect(&deco->round_corner_region, &deco->round_corner_region, x2 - round_corner_radius[0], y2 - round_corner_radius[0], round_corner_radius[0], round_corner_radius[0]); } } static void scene_decoration_update_region(struct ky_scene_decoration *deco) { pixman_region32_clear(&deco->surface_region); pixman_region32_clear(&deco->title_region); pixman_region32_clear(&deco->border_region); pixman_region32_clear(&deco->pure_clip_region); int width = deco->surface_width, height = deco->surface_height; int title = deco->title_height, border = deco->border_thickness; pixman_region32_init_rect(&deco->surface_region, border, border + title, width, height); pixman_region32_init_rect(&deco->title_region, border, border, width, title); pixman_region32_t reg1, reg2; pixman_region32_init_rect(®1, 0, 0, deco->rect.width, deco->rect.height); pixman_region32_init_rect(®2, border, border, width, title + height); pixman_region32_subtract(&deco->border_region, ®1, ®2); pixman_region32_fini(®1); pixman_region32_fini(®2); pixman_region32_init_rect(&deco->pure_clip_region, deco->render_box.x, deco->render_box.y, deco->render_box.width, deco->render_box.height); pixman_region32_subtract(&deco->pure_clip_region, &deco->pure_clip_region, &deco->surface_region); } static void scene_decoration_update(struct ky_scene_decoration *deco, uint32_t cause) { int width = deco->surface_width; int height = deco->surface_height; int title = deco->title_height; int border = deco->border_thickness; if (width <= 0 || height <= 0) { deco->pending_cause |= cause; return; } uint32_t pending_cause = deco->pending_cause | cause; deco->pending_cause = DECO_UPDATE_CAUSE_NONE; if (pending_cause & (DECO_UPDATE_CAUSE_SURFACE_SIZE | DECO_UPDATE_CAUSE_MARGIN)) { int border2 = 2 * border; struct kywc_box window = { 0, 0, width + border2, title + height + border2 }; int x_min = window.x; int y_min = window.y; int x_max = window.x + window.width; int y_max = window.y + window.height; for (int i = 0; i < deco->shadows_size; i++) { struct ky_scene_shadow *shadow = &deco->shadows[i]; int radius2 = (shadow->spread + shadow->blur) * 2; shadow->box = kywc_box_adjusted( &window, shadow->offset_x - shadow->spread - shadow->blur, shadow->offset_y - shadow->spread - shadow->blur, radius2, radius2); x_min = MIN(x_min, shadow->box.x); y_min = MIN(y_min, shadow->box.y); x_max = MAX(x_max, shadow->box.x + shadow->box.width); y_max = MAX(y_max, shadow->box.y + shadow->box.height); } struct kywc_box render = { x_min, y_min, x_max - x_min, y_max - y_min }; if (!kywc_box_equal(&render, &deco->render_box)) { ky_scene_node_push_damage(&deco->rect.node, KY_SCENE_DAMAGE_BOTH, NULL); deco->render_box = render; ky_scene_node_push_damage(&deco->rect.node, KY_SCENE_DAMAGE_BOTH, NULL); /* clear some masks once the region is damaged */ pending_cause &= ~(DECO_UPDATE_CAUSE_SURFACE_COLOR | DECO_UPDATE_CAUSE_MARGIN_COLOR); } ky_scene_rect_set_size(&deco->rect, window.width, window.height); scene_decoration_update_region(deco); } if (pending_cause & (DECO_UPDATE_CAUSE_SURFACE_SIZE | DECO_UPDATE_CAUSE_MARGIN | DECO_UPDATE_CAUSE_CORNER_RADIUS | DECO_UPDATE_CAUSE_MASK)) { scene_decoration_update_round_corner_region(deco); /* update clip region with corner region */ pixman_region32_clear(&deco->clip_region); pixman_region32_union(&deco->clip_region, &deco->pure_clip_region, &deco->round_corner_region); } if (pending_cause & (DECO_UPDATE_CAUSE_SURFACE_SIZE | DECO_UPDATE_CAUSE_BLURRED)) { ky_scene_node_set_blur_region(&deco->rect.node, deco->surface_blurred ? &deco->surface_region : NULL); pending_cause &= ~DECO_UPDATE_CAUSE_BLURRED; } pending_cause &= ~(DECO_UPDATE_CAUSE_SURFACE_SIZE | DECO_UPDATE_CAUSE_MARGIN); if (pending_cause == DECO_UPDATE_CAUSE_SURFACE_COLOR) { ky_scene_node_push_damage(&deco->rect.node, KY_SCENE_DAMAGE_BOTH, &deco->surface_region); pending_cause &= ~DECO_UPDATE_CAUSE_SURFACE_COLOR; } if (pending_cause != DECO_UPDATE_CAUSE_NONE) { bool damage_whole = pending_cause & ~(DECO_UPDATE_CAUSE_MASK | DECO_UPDATE_CAUSE_MARGIN_COLOR); ky_scene_node_push_damage(&deco->rect.node, KY_SCENE_DAMAGE_BOTH, damage_whole ? NULL : &deco->clip_region); } } struct ky_scene_decoration *ky_scene_decoration_from_node(struct ky_scene_node *node) { struct ky_scene_rect *rect = ky_scene_rect_from_node(node); struct ky_scene_decoration *decoration = wl_container_of(rect, decoration, rect); return decoration; } struct ky_scene_node *ky_scene_node_from_decoration(struct ky_scene_decoration *decoration) { return &decoration->rect.node; } static struct ky_scene_node *scene_decoration_accept_input(struct ky_scene_node *node, int lx, int ly, double px, double py, double *rx, double *ry) { if (!node->enabled || node->input_bypassed) { return NULL; } int x = floor(px) - lx; int y = floor(py) - ly; if (pixman_region32_not_empty(&node->input_region) && !pixman_region32_contains_point(&node->input_region, x, y, NULL)) { return NULL; } struct ky_scene_decoration *deco = ky_scene_decoration_from_node(node); int border = deco->border_thickness; int resize = deco->resize_width; struct kywc_box window_box = { .width = deco->rect.width, .height = deco->rect.height }; int off = resize > 0 ? resize : border; int lt = border - off; int rb = (off - border) * 2; struct kywc_box extend_input_box = kywc_box_adjusted(&window_box, lt, lt, rb, rb); if (!kywc_box_contains_point(&extend_input_box, x, y)) { return NULL; } *rx = px - lx; *ry = py - ly; return node; } static void scene_decoration_get_opaque_region(struct ky_scene_node *node, pixman_region32_t *opaque) { pixman_region32_clear(opaque); if (!node->enabled) { return; } /** * the border is not included in the calculation of opaque areas * because when the border is opaque but the window is transparent, * it is easy to cut the visible area of the lower window, * causing fragmentation of the visible area. */ struct ky_scene_decoration *deco = ky_scene_decoration_from_node(node); bool title_is_opaque = deco->title_height > 0 && deco->title_color[3] == 1; bool surface_is_opaque = deco->surface_color[3] == 1; bool has_opaque_region = title_is_opaque || surface_is_opaque; if (!has_opaque_region) { return; } if (title_is_opaque) { pixman_region32_union(opaque, opaque, &deco->title_region); } if (surface_is_opaque) { pixman_region32_union(opaque, opaque, &deco->surface_region); } pixman_region32_subtract(opaque, opaque, &deco->round_corner_region); if (pixman_region32_not_empty(&node->clip_region)) { pixman_region32_intersect(opaque, opaque, &node->clip_region); } } static void decoration_collect_invisible(struct ky_scene_decoration *deco, int lx, int ly, pixman_region32_t *invisible) { pixman_region32_t opaque_region; pixman_region32_init(&opaque_region); scene_decoration_get_opaque_region(&deco->rect.node, &opaque_region); if (!pixman_region32_not_empty(&opaque_region)) { pixman_region32_fini(&opaque_region); return; } pixman_region32_translate(&opaque_region, lx, ly); pixman_region32_union(invisible, invisible, &opaque_region); pixman_region32_fini(&opaque_region); } static void scene_decoration_collect_damage(struct ky_scene_node *node, int lx, int ly, bool parent_enabled, uint32_t damage_type, pixman_region32_t *damage, pixman_region32_t *invisible, pixman_region32_t *affected) { bool node_enabled = parent_enabled && node->enabled; /* node is still disabled, skip it */ if (!node_enabled && !node->last_enabled) { node->damage_type = KY_SCENE_DAMAGE_NONE; return; } struct ky_scene_decoration *deco = ky_scene_decoration_from_node(node); // if node state is changed, it must in the affected region if (deco->render_box.width > 0 && deco->render_box.height > 0 && pixman_region32_contains_rectangle( affected, &(pixman_box32_t){ lx + deco->render_box.x, ly + deco->render_box.y, lx + deco->render_box.x + deco->render_box.width, ly + deco->render_box.y + deco->render_box.height }) == PIXMAN_REGION_OUT) { if (node_enabled) { decoration_collect_invisible(deco, lx, ly, invisible); } return; } // no damage if node state is not changed bool no_damage = node->last_enabled && node_enabled && damage_type == KY_SCENE_DAMAGE_NONE; if (!no_damage) { /* node last visible region is added to damgae */ if (node->last_enabled && (!node_enabled || (damage_type & KY_SCENE_DAMAGE_HARMFUL))) { pixman_region32_union(damage, damage, &node->visible_region); } } // update node visible region always pixman_region32_clear(&node->visible_region); if (node_enabled) { bool has_clip_region = pixman_region32_not_empty(&node->clip_region); if (has_clip_region) { pixman_region32_intersect_rect(&node->visible_region, &node->clip_region, deco->render_box.x, deco->render_box.y, deco->render_box.width, deco->render_box.height); pixman_region32_translate(&node->visible_region, lx, ly); } else { pixman_region32_init_rect(&node->visible_region, lx + deco->render_box.x, ly + deco->render_box.y, deco->render_box.width, deco->render_box.height); } pixman_region32_subtract(&node->visible_region, &node->visible_region, invisible); if (!no_damage) { pixman_region32_union(damage, damage, &node->visible_region); } decoration_collect_invisible(deco, lx, ly, invisible); } node->last_enabled = node_enabled; node->damage_type = KY_SCENE_DAMAGE_NONE; } static void scene_decoration_blur_render(struct ky_scene_decoration *deco, int lx, int ly, struct ky_scene_render_target *target, const pixman_region32_t *region) { if (deco->surface_color[3] == 1 || !deco->surface_blurred || target->options & KY_SCENE_RENDER_DISABLE_BLUR) { return; } pixman_region32_t clip; pixman_region32_init(&clip); pixman_region32_copy(&clip, region); ky_scene_render_region(&clip, target); struct wlr_box blur_box = { .x = lx - target->logical.x + deco->surface_region.extents.x1, .y = ly - target->logical.y + deco->surface_region.extents.y1, .width = deco->surface_width, .height = deco->surface_height, }; ky_scene_render_box(&blur_box, target); struct ky_render_round_corner round_corner_radius = { 0 }; if (!(target->options & KY_SCENE_RENDER_DISABLE_ROUND_CORNER)) { round_corner_radius.rb = deco->round_corner_radius[0] > 0 ? deco->round_corner_radius[0] * target->scale : 0.0f; round_corner_radius.rt = deco->round_corner_radius[1] > 0 ? deco->round_corner_radius[1] * target->scale : 0.0f; round_corner_radius.lb = deco->round_corner_radius[2] > 0 ? deco->round_corner_radius[2] * target->scale : 0.0f; round_corner_radius.lt = deco->round_corner_radius[3] > 0 ? deco->round_corner_radius[3] * target->scale : 0.0f; } struct blur_render_options opts = { .lx = lx, .ly = ly, .dst_box = &blur_box, .clip = &clip, .radius = &round_corner_radius, .blur = deco->surface_blurred ? &deco->rect.node.blur : NULL, }; blur_render_with_target(target, &opts); pixman_region32_fini(&clip); } static void scene_decoration_render(struct ky_scene_node *node, int lx, int ly, struct ky_scene_render_target *target) { if (!node->enabled) { return; } bool render_with_visibility = !(target->options & KY_SCENE_RENDER_DISABLE_VISIBILITY); if (render_with_visibility && !pixman_region32_not_empty(&node->visible_region) && !pixman_region32_not_empty(&node->extend_render_region)) { return; } struct ky_scene_decoration *deco = ky_scene_decoration_from_node(node); pixman_region32_t render_region; if (render_with_visibility) { pixman_region32_init(&render_region); pixman_region32_union(&render_region, &node->visible_region, &node->extend_render_region); pixman_region32_intersect(&render_region, &render_region, &target->damage); } else { pixman_region32_init_rect(&render_region, deco->render_box.x, deco->render_box.y, deco->render_box.width, deco->render_box.height); if (pixman_region32_not_empty(&node->clip_region)) { pixman_region32_intersect(&render_region, &render_region, &node->clip_region); } pixman_region32_translate(&render_region, lx, ly); pixman_region32_intersect(&render_region, &render_region, &target->damage); } if (!pixman_region32_not_empty(&render_region)) { pixman_region32_fini(&render_region); return; } /* actual render region exclude the blur region */ pixman_region32_t clip_region; pixman_region32_init(&clip_region); pixman_region32_copy(&clip_region, &deco->clip_region); pixman_region32_translate(&clip_region, lx, ly); pixman_region32_intersect(&clip_region, &clip_region, &render_region); bool need_render = pixman_region32_not_empty(&clip_region); struct wlr_box dst_box = { .x = lx - target->logical.x + deco->render_box.x, .y = ly - target->logical.y + deco->render_box.y, .width = deco->render_box.width, .height = deco->render_box.height, }; if (need_render) { ky_scene_render_box(&dst_box, target); pixman_region32_translate(&clip_region, -target->logical.x, -target->logical.y); } pixman_region32_translate(&render_region, -target->logical.x, -target->logical.y); // try opengl render if opengl is used if (wlr_renderer_is_opengl(target->output->output->renderer) && gl_shader.program >= 0 && gl_shader_shadow.program >= 0) { if (gl_shader.program == 0 && gl_shader_shadow.program == 0) { struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(target->output->output->renderer); scene_decoration_create_opengl_shader(renderer); } if (gl_shader.program > 0 && gl_shader_shadow.program > 0) { scene_decoration_blur_render(deco, lx, ly, target, &render_region); if (need_render) { ky_scene_render_region(&clip_region, target); scene_decoration_opengl_render(deco, lx, ly, target, &dst_box, &clip_region); } } } if (deco->surface_color[3] != 0) { pixman_region32_t surface; pixman_region32_init(&surface); pixman_region32_copy(&surface, &deco->surface_region); pixman_region32_translate(&surface, lx - target->logical.x, ly - target->logical.y); pixman_region32_intersect(&surface, &surface, &render_region); ky_scene_render_region(&surface, target); struct wlr_box surface_box = { .x = lx - target->logical.x + deco->surface_region.extents.x1, .y = ly - target->logical.y + deco->surface_region.extents.y1, .width = deco->surface_width, .height = deco->surface_height, }; ky_scene_render_box(&surface_box, target); ky_render_pass_add_rect(target->render_pass, &(struct ky_render_rect_options){ .base = { .box = surface_box, .color = { .r = deco->surface_color[0], .g = deco->surface_color[1], .b = deco->surface_color[2], .a = deco->surface_color[3], }, .clip = &surface, .blend_mode = WLR_RENDER_BLEND_MODE_PREMULTIPLIED, }, .radius = { .rb = deco->round_corner_radius[0] * target->scale, .rt = deco->title_height ? 0 : deco->round_corner_radius[1] * target->scale, .lb = deco->round_corner_radius[2] * target->scale, .lt = deco->title_height ? 0 : deco->round_corner_radius[3] * target->scale, }, }); pixman_region32_fini(&surface); } if ((gl_shader.program > 0 && gl_shader_shadow.program > 0) || !need_render) { pixman_region32_fini(&render_region); pixman_region32_fini(&clip_region); return; } /* draw border with border color */ if (deco->border_thickness > 0) { pixman_region32_t border; pixman_region32_init(&border); pixman_region32_copy(&border, &deco->border_region); pixman_region32_translate(&border, lx - target->logical.x, ly - target->logical.y); pixman_region32_intersect(&border, &border, &clip_region); ky_scene_render_region(&border, target); wlr_render_pass_add_rect(target->render_pass, &(struct wlr_render_rect_options){ .box = dst_box, .color = { .r = deco->border_color[0], .g = deco->border_color[1], .b = deco->border_color[2], .a = deco->border_color[3], }, .clip = &border, .blend_mode = deco->border_color[3] != 1 ? WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, }); pixman_region32_fini(&border); } /* draw title with title color */ if (deco->title_height > 0) { pixman_region32_t title; pixman_region32_init(&title); pixman_region32_copy(&title, &deco->title_region); pixman_region32_translate(&title, lx - target->logical.x, ly - target->logical.y); pixman_region32_intersect(&title, &title, &clip_region); ky_scene_render_region(&title, target); wlr_render_pass_add_rect(target->render_pass, &(struct wlr_render_rect_options){ .box = dst_box, .color = { .r = deco->title_color[0], .g = deco->title_color[1], .b = deco->title_color[2], .a = deco->title_color[3], }, .clip = &title, .blend_mode = deco->title_color[3] != 1 ? WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, }); pixman_region32_fini(&title); } pixman_region32_fini(&render_region); pixman_region32_fini(&clip_region); } static void scene_decoration_get_bounding_box(struct ky_scene_node *node, struct wlr_box *box) { struct ky_scene_decoration *deco = ky_scene_decoration_from_node(node); *box = (struct wlr_box){ deco->render_box.x, deco->render_box.y, deco->render_box.width, deco->render_box.height }; } static void scene_decoration_destroy(struct ky_scene_node *node) { if (!node) { return; } struct ky_scene_decoration *decoration = ky_scene_decoration_from_node(node); pixman_region32_fini(&decoration->surface_region); pixman_region32_fini(&decoration->title_region); pixman_region32_fini(&decoration->border_region); pixman_region32_fini(&decoration->round_corner_region); pixman_region32_fini(&decoration->pure_clip_region); pixman_region32_fini(&decoration->clip_region); decoration->node_destroy(node); } struct ky_scene_decoration *ky_scene_decoration_create(struct ky_scene_tree *parent) { struct ky_scene_decoration *decoration = calloc(1, sizeof(*decoration)); if (!decoration) { return NULL; } float color[4] = { 0, 0, 0, 0.25 }; ky_scene_rect_init(&decoration->rect, parent, 0, 0, color); memset(decoration->surface_color, 0, sizeof(decoration->surface_color)); memcpy(decoration->title_color, color, sizeof(decoration->title_color)); memcpy(decoration->border_color, color, sizeof(decoration->border_color)); decoration->node_destroy = decoration->rect.node.impl.destroy; decoration->rect.node.impl.destroy = scene_decoration_destroy; decoration->rect.node.impl.accept_input = scene_decoration_accept_input; decoration->rect.node.impl.collect_damage = scene_decoration_collect_damage; decoration->rect.node.impl.get_opaque_region = scene_decoration_get_opaque_region; decoration->rect.node.impl.render = scene_decoration_render; decoration->rect.node.impl.get_bounding_box = scene_decoration_get_bounding_box; /* no need to update_region and push_damage, it is invisible */ pixman_region32_init(&decoration->surface_region); pixman_region32_init(&decoration->title_region); pixman_region32_init(&decoration->border_region); pixman_region32_init(&decoration->round_corner_region); pixman_region32_init(&decoration->pure_clip_region); pixman_region32_init(&decoration->clip_region); return decoration; } void ky_scene_decoration_set_surface_size(struct ky_scene_decoration *decoration, int width, int height) { if (decoration->surface_width == width && decoration->surface_height == height) { return; } decoration->surface_width = width; decoration->surface_height = height; scene_decoration_update(decoration, DECO_UPDATE_CAUSE_SURFACE_SIZE); } void ky_scene_decoration_set_round_corner_radius(struct ky_scene_decoration *decoration, const int round_corner_radius[static 4]) { if (gl_shader.program < 0) { return; } if (memcmp(decoration->round_corner_radius, round_corner_radius, sizeof(decoration->round_corner_radius)) == 0) { return; } memcpy(decoration->round_corner_radius, round_corner_radius, sizeof(decoration->round_corner_radius)); ky_scene_node_set_radius(&decoration->rect.node, round_corner_radius); scene_decoration_update(decoration, DECO_UPDATE_CAUSE_CORNER_RADIUS); } void ky_scene_decoration_set_margin(struct ky_scene_decoration *decoration, int title_height, int border_thickness) { if (decoration->title_height == title_height && decoration->border_thickness == border_thickness) { return; } decoration->title_height = title_height; decoration->border_thickness = border_thickness; scene_decoration_update(decoration, DECO_UPDATE_CAUSE_MARGIN); } void ky_scene_decoration_set_margin_color(struct ky_scene_decoration *decoration, const float title_color[static 4], const float border_color[static 4]) { if (memcmp(decoration->title_color, title_color, sizeof(decoration->title_color)) == 0 && memcmp(decoration->border_color, border_color, sizeof(decoration->border_color)) == 0) { return; } memcpy(decoration->title_color, title_color, sizeof(decoration->title_color)); memcpy(decoration->border_color, border_color, sizeof(decoration->border_color)); scene_decoration_update(decoration, DECO_UPDATE_CAUSE_MARGIN_COLOR); } void ky_scene_decoration_set_mask(struct ky_scene_decoration *decoration, uint32_t masks) { if (decoration->mask == masks) { return; } decoration->mask = masks; scene_decoration_update(decoration, DECO_UPDATE_CAUSE_MASK); } void ky_scene_decoration_set_shadow_count(struct ky_scene_decoration *decoration, int size) { if (gl_shader.program < 0) { return; } if (size < 0 || size > THEME_MAX_SHADOW_LAYERS) { return; } if (decoration->shadows_size == size) { return; } decoration->shadows_size = size; scene_decoration_update(decoration, DECO_UPDATE_CAUSE_MARGIN | DECO_UPDATE_CAUSE_MARGIN_COLOR); } void ky_scene_decoration_set_shadow(struct ky_scene_decoration *decoration, int index, int offset_x, int offset_y, int spread, int blur, const float color[static 4]) { if (gl_shader.program < 0) { return; } if (index < 0 || index >= THEME_MAX_SHADOW_LAYERS) { return; } uint32_t update = DECO_UPDATE_CAUSE_NONE; struct ky_scene_shadow *shadow = &decoration->shadows[index]; if (shadow->offset_x != offset_x || shadow->offset_y != offset_y || shadow->spread != spread || shadow->blur != blur) { shadow->offset_x = offset_x; shadow->offset_y = offset_y; shadow->spread = spread; shadow->blur = blur; update |= DECO_UPDATE_CAUSE_MARGIN; } if (memcmp(shadow->color, color, sizeof(shadow->color))) { memcpy(shadow->color, color, sizeof(shadow->color)); update |= DECO_UPDATE_CAUSE_MARGIN_COLOR; } if (update != DECO_UPDATE_CAUSE_NONE) { scene_decoration_update(decoration, update); } } void ky_scene_decoration_set_resize_width(struct ky_scene_decoration *decoration, int resize_with) { if (decoration->resize_width == resize_with) { return; } decoration->resize_width = resize_with; } void ky_scene_decoration_set_surface_blurred(struct ky_scene_decoration *decoration, bool blurred) { if (decoration->surface_blurred == blurred) { return; } decoration->surface_blurred = blurred; scene_decoration_update(decoration, DECO_UPDATE_CAUSE_BLURRED); } void ky_scene_decoration_set_surface_color(struct ky_scene_decoration *decoration, const float color[static 4]) { if (memcmp(decoration->surface_color, color, sizeof(decoration->surface_color)) == 0) { return; } memcpy(decoration->surface_color, color, sizeof(decoration->surface_color)); scene_decoration_update(decoration, DECO_UPDATE_CAUSE_SURFACE_COLOR); } kylin-wayland-compositor/src/scene/alpha_modifier.c0000664000175000017500000001562515160461067021471 0ustar fengfeng// SPDX-FileCopyrightText: 2024 The wlroots contributors // SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "alpha-modifier-v1-protocol.h" #include "scene/surface.h" #include "server.h" #define ALPHA_MODIFIER_VERSION 1 struct alpha_modifier_surface_v1_state { double multiplier; // between 0 and 1 }; struct alpha_modifier_v1 { struct wl_global *global; // private state struct wl_listener display_destroy; }; struct alpha_modifier_surface { struct wl_resource *resource; struct wlr_surface *surface; struct wlr_addon addon; struct alpha_modifier_surface_v1_state state; struct wl_listener surface_commit; }; static const struct wp_alpha_modifier_surface_v1_interface surface_impl; // Returns NULL if the wl_surface was destroyed static struct alpha_modifier_surface *surface_from_resource(struct wl_resource *resource) { assert( wl_resource_instance_of(resource, &wp_alpha_modifier_surface_v1_interface, &surface_impl)); return wl_resource_get_user_data(resource); } static void surface_destroy(struct alpha_modifier_surface *surface) { if (surface == NULL) { return; } wl_list_remove(&surface->surface_commit.link); wlr_addon_finish(&surface->addon); wl_resource_set_user_data(surface->resource, NULL); free(surface); } static void surface_handle_resource_destroy(struct wl_resource *resource) { struct alpha_modifier_surface *surface = surface_from_resource(resource); surface_destroy(surface); } static void surface_handle_destroy(struct wl_client *client, struct wl_resource *resource) { struct alpha_modifier_surface *surface = surface_from_resource(resource); if (surface) { struct ky_scene_buffer *scene_buffer = ky_scene_buffer_try_from_surface(surface->surface); if (scene_buffer) { ky_scene_buffer_set_opacity(scene_buffer, 1.0); } } wl_resource_destroy(resource); } static void alpha_modifier_surface_handle_commit(struct wl_listener *listener, void *data) { struct alpha_modifier_surface *surface = wl_container_of(listener, surface, surface_commit); struct ky_scene_buffer *scene_buffer = ky_scene_buffer_try_from_surface(surface->surface); if (!scene_buffer) { return; } ky_scene_buffer_set_opacity(scene_buffer, surface->state.multiplier); wl_list_remove(&surface->surface_commit.link); wl_list_init(&surface->surface_commit.link); } static void surface_handle_set_multiplier(struct wl_client *client, struct wl_resource *resource, uint32_t multiplier) { struct alpha_modifier_surface *surface = surface_from_resource(resource); if (surface == NULL) { wl_resource_post_error(resource, WP_ALPHA_MODIFIER_SURFACE_V1_ERROR_NO_SURFACE, "The wl_surface object has been destroyed"); return; } surface->state.multiplier = (double)multiplier / UINT32_MAX; wl_signal_add(&surface->surface->events.commit, &surface->surface_commit); } static const struct wp_alpha_modifier_surface_v1_interface surface_impl = { .destroy = surface_handle_destroy, .set_multiplier = surface_handle_set_multiplier, }; static void surface_addon_destroy(struct wlr_addon *addon) { struct alpha_modifier_surface *surface = wl_container_of(addon, surface, addon); surface_destroy(surface); } static const struct wlr_addon_interface surface_addon_impl = { .name = "wp_alpha_modifier_surface_v1", .destroy = surface_addon_destroy, }; static struct alpha_modifier_surface *surface_from_wlr_surface(struct wlr_surface *wlr_surface) { struct wlr_addon *addon = wlr_addon_find(&wlr_surface->addons, NULL, &surface_addon_impl); if (addon == NULL) { return NULL; } struct alpha_modifier_surface *surface = wl_container_of(addon, surface, addon); return surface; } static void manager_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void manager_handle_get_surface(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id, struct wl_resource *surface_resource) { struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface_resource); if (surface_from_wlr_surface(wlr_surface) != NULL) { wl_resource_post_error( manager_resource, WP_ALPHA_MODIFIER_V1_ERROR_ALREADY_CONSTRUCTED, "The wl_surface object already has a wp_alpha_modifier_surface_v1 object"); return; } struct alpha_modifier_surface *surface = calloc(1, sizeof(*surface)); if (surface == NULL) { wl_resource_post_no_memory(manager_resource); return; } uint32_t version = wl_resource_get_version(manager_resource); surface->resource = wl_resource_create(client, &wp_alpha_modifier_surface_v1_interface, version, id); if (surface->resource == NULL) { free(surface); wl_resource_post_no_memory(manager_resource); return; } wl_resource_set_implementation(surface->resource, &surface_impl, surface, surface_handle_resource_destroy); surface->surface = wlr_surface; surface->state.multiplier = 1.0; wl_list_init(&surface->surface_commit.link); surface->surface_commit.notify = alpha_modifier_surface_handle_commit; wlr_addon_init(&surface->addon, &wlr_surface->addons, NULL, &surface_addon_impl); } static const struct wp_alpha_modifier_v1_interface manager_impl = { .destroy = manager_handle_destroy, .get_surface = manager_handle_get_surface, }; static void manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource = wl_resource_create(client, &wp_alpha_modifier_v1_interface, version, id); if (resource == NULL) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &manager_impl, NULL, NULL); } static void handle_display_destroy(struct wl_listener *listener, void *data) { struct alpha_modifier_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); } bool alpha_modifier_v1_create(struct wl_display *display) { struct alpha_modifier_v1 *manager = calloc(1, sizeof(*manager)); if (manager == NULL) { return NULL; } manager->global = wl_global_create(display, &wp_alpha_modifier_v1_interface, ALPHA_MODIFIER_VERSION, NULL, manager_bind); if (manager->global == NULL) { free(manager); return NULL; } manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(display, &manager->display_destroy); return manager; } kylin-wayland-compositor/src/scene/surface.c0000664000175000017500000003262515160461067020155 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include "output.h" #include "scene/surface.h" #include "util/macros.h" static double get_surface_preferred_buffer_scale(struct wlr_surface *surface) { double scale = 1; struct wlr_surface_output *surface_output; wl_list_for_each(surface_output, &surface->current_outputs, link) { if (surface_output->output->scale > scale) { scale = surface_output->output->scale; } } return scale; } static void handle_scene_buffer_outputs_update(struct wl_listener *listener, void *data) { struct ky_scene_surface *surface = wl_container_of(listener, surface, outputs_update); double scale = get_surface_preferred_buffer_scale(surface->surface); wlr_fractional_scale_v1_notify_scale(surface->surface, scale); wlr_surface_set_preferred_buffer_scale(surface->surface, ceil(scale)); } static void handle_scene_buffer_output_enter(struct wl_listener *listener, void *data) { struct ky_scene_surface *surface = wl_container_of(listener, surface, output_enter); struct ky_scene_output *output = data; wlr_surface_send_enter(surface->surface, output->output); } static void handle_scene_buffer_output_leave(struct wl_listener *listener, void *data) { struct ky_scene_surface *surface = wl_container_of(listener, surface, output_leave); struct ky_scene_output *output = data; wlr_surface_send_leave(surface->surface, output->output); } static void handle_scene_buffer_output_sample(struct wl_listener *listener, void *data) { struct ky_scene_surface *surface = wl_container_of(listener, surface, output_sample); const struct ky_scene_output_sample_event *event = data; struct ky_scene_output *scene_output = event->output; if (surface->buffer->primary_output != scene_output) { return; } if (event->direct_scanout) { wlr_presentation_surface_scanned_out_on_output(surface->surface, scene_output->output); } else { wlr_presentation_surface_textured_on_output(surface->surface, scene_output->output); } } static void handle_scene_buffer_frame_done(struct wl_listener *listener, void *data) { struct ky_scene_surface *surface = wl_container_of(listener, surface, frame_done); struct timespec *now = data; wlr_surface_send_frame_done(surface->surface, now); } static void scene_surface_handle_surface_destroy(struct wl_listener *listener, void *data) { struct ky_scene_surface *surface = wl_container_of(listener, surface, surface_destroy); ky_scene_node_destroy(&surface->buffer->node); } // This is used for wlr_scene where it unconditionally locks buffers preventing // reuse of the existing texture for shm clients. With the usage pattern of // wlr_scene surface handling, we can mark its locked buffer as safe // for mutation. static void client_buffer_mark_next_can_damage(struct wlr_client_buffer *buffer) { buffer->WLR_PRIVATE.n_ignore_locks++; } static void scene_buffer_unmark_client_buffer(struct ky_scene_buffer *scene_buffer) { if (!scene_buffer->buffer) { return; } struct wlr_client_buffer *buffer = wlr_client_buffer_get(scene_buffer->buffer); if (!buffer) { return; } assert(buffer->WLR_PRIVATE.n_ignore_locks > 0); buffer->WLR_PRIVATE.n_ignore_locks--; } static void set_buffer_with_surface_state(struct ky_scene_buffer *scene_buffer, struct wlr_surface *surface, uint32_t committed) { struct wlr_surface_state *state = &surface->current; /* don't use surface->opaque_region */ if (committed & WLR_SURFACE_STATE_OPAQUE_REGION) { ky_scene_buffer_set_opaque_region(scene_buffer, &state->opaque); } if (committed & (WLR_SURFACE_STATE_VIEWPORT | WLR_SURFACE_STATE_BUFFER | WLR_SURFACE_STATE_SCALE | WLR_SURFACE_STATE_TRANSFORM)) { struct wlr_fbox src_box; wlr_surface_get_buffer_source_box(surface, &src_box); ky_scene_buffer_set_source_box(scene_buffer, &src_box); ky_scene_buffer_set_dest_size(scene_buffer, state->width, state->height); } if (committed & WLR_SURFACE_STATE_TRANSFORM) { ky_scene_buffer_set_transform(scene_buffer, state->transform); } scene_buffer_unmark_client_buffer(scene_buffer); if (surface->buffer) { client_buffer_mark_next_can_damage(surface->buffer); } if (committed & WLR_SURFACE_STATE_BUFFER) { if (surface->buffer) { ky_scene_buffer_set_buffer_with_damage(scene_buffer, &surface->buffer->base, &surface->buffer_damage); } else if (surface->current.buffer) { ky_scene_buffer_set_buffer_with_damage(scene_buffer, surface->current.buffer, &surface->buffer_damage); } else { ky_scene_buffer_set_buffer(scene_buffer, NULL); } } else if (committed & (WLR_SURFACE_STATE_SURFACE_DAMAGE | WLR_SURFACE_STATE_BUFFER_DAMAGE) && scene_buffer->buffer) { ky_scene_buffer_set_buffer_with_damage(scene_buffer, scene_buffer->buffer, &surface->buffer_damage); } } static void handle_scene_surface_surface_commit(struct wl_listener *listener, void *data) { struct ky_scene_surface *surface = wl_container_of(listener, surface, surface_commit); struct ky_scene_buffer *scene_buffer = surface->buffer; set_buffer_with_surface_state(scene_buffer, surface->surface, surface->surface->current.committed); // If the surface has requested a frame done event, honour that. The // frame_callback_list will be populated in this case. We should only // schedule the frame however if the node is enabled and there is an // output intersecting, otherwise the frame done events would never reach // the surface anyway. int lx, ly; bool enabled = ky_scene_node_coords(&scene_buffer->node, &lx, &ly); struct ky_scene_output *primary_output = surface->buffer->primary_output; if (!wl_list_empty(&surface->surface->current.frame_callback_list) && primary_output != NULL && enabled) { output_schedule_frame(primary_output->output); } } static void handle_scene_surface_surface_map(struct wl_listener *listener, void *data) { struct ky_scene_surface *surface = wl_container_of(listener, surface, surface_map); set_buffer_with_surface_state(surface->buffer, surface->surface, surface->surface->current.committed); } static bool scene_buffer_point_accepts_input(struct ky_scene_buffer *scene_buffer, double *sx, double *sy) { struct ky_scene_surface *scene_surface = ky_scene_surface_try_from_buffer(scene_buffer); return wlr_surface_point_accepts_input(scene_surface->surface, *sx, *sy); } static void surface_addon_destroy(struct wlr_addon *addon) { struct ky_scene_surface *surface = wl_container_of(addon, surface, addon); scene_buffer_unmark_client_buffer(surface->buffer); wlr_addon_finish(&surface->addon); wlr_addon_finish(&surface->node_addon); wl_list_remove(&surface->outputs_update.link); wl_list_remove(&surface->output_enter.link); wl_list_remove(&surface->output_leave.link); wl_list_remove(&surface->output_sample.link); wl_list_remove(&surface->frame_done.link); wl_list_remove(&surface->surface_destroy.link); wl_list_remove(&surface->surface_commit.link); wl_list_remove(&surface->surface_map.link); free(surface); } static const struct wlr_addon_interface surface_addon_impl = { .name = "ky_scene_surface", .destroy = surface_addon_destroy, }; struct ky_scene_surface *ky_scene_surface_try_from_buffer(struct ky_scene_buffer *scene_buffer) { struct wlr_addon *addon = wlr_addon_find(&scene_buffer->node.addons, scene_buffer, &surface_addon_impl); if (!addon) { return NULL; } struct ky_scene_surface *surface = wl_container_of(addon, surface, addon); return surface; } static void surface_node_addon_destroy(struct wlr_addon *addon) { /* do nothing, surface destroy signal emitted before surface addon_set finish * scene node destroy will call surface_addon_destroy. */ } static const struct wlr_addon_interface surface_node_addon_impl = { .name = "ky_scene_surface_node", .destroy = surface_node_addon_destroy, }; static struct ky_scene_surface *ky_scene_surface_try_from_surface(struct wlr_surface *wlr_surface) { struct wlr_addon *node_addon = wlr_addon_find(&wlr_surface->addons, wlr_surface, &surface_node_addon_impl); if (!node_addon) { return NULL; } struct ky_scene_surface *surface = wl_container_of(node_addon, surface, node_addon); return surface; } struct ky_scene_surface *ky_scene_surface_create(struct ky_scene_tree *parent, struct wlr_surface *wlr_surface) { struct ky_scene_surface *surface = calloc(1, sizeof(*surface)); if (surface == NULL) { return NULL; } struct ky_scene_buffer *scene_buffer = ky_scene_buffer_create(parent, NULL); if (!scene_buffer) { free(surface); return NULL; } surface->buffer = scene_buffer; surface->surface = wlr_surface; surface->buffer_default_impl = scene_buffer->node.impl; scene_buffer->point_accepts_input = scene_buffer_point_accepts_input; surface->outputs_update.notify = handle_scene_buffer_outputs_update; wl_signal_add(&scene_buffer->events.outputs_update, &surface->outputs_update); surface->output_enter.notify = handle_scene_buffer_output_enter; wl_signal_add(&scene_buffer->events.output_enter, &surface->output_enter); surface->output_leave.notify = handle_scene_buffer_output_leave; wl_signal_add(&scene_buffer->events.output_leave, &surface->output_leave); surface->output_sample.notify = handle_scene_buffer_output_sample; wl_signal_add(&scene_buffer->events.output_sample, &surface->output_sample); surface->frame_done.notify = handle_scene_buffer_frame_done; wl_signal_add(&scene_buffer->events.frame_done, &surface->frame_done); surface->surface_destroy.notify = scene_surface_handle_surface_destroy; wl_signal_add(&wlr_surface->events.destroy, &surface->surface_destroy); surface->surface_commit.notify = handle_scene_surface_surface_commit; wl_signal_add(&wlr_surface->events.commit, &surface->surface_commit); surface->surface_map.notify = handle_scene_surface_surface_map; wl_signal_add(&wlr_surface->events.map, &surface->surface_map); wlr_addon_init(&surface->addon, &scene_buffer->node.addons, scene_buffer, &surface_addon_impl); wlr_addon_init(&surface->node_addon, &wlr_surface->addons, wlr_surface, &surface_node_addon_impl); /* sync current states from surface */ set_buffer_with_surface_state(scene_buffer, wlr_surface, WLR_SURFACE_STATE_OPAQUE_REGION | WLR_SURFACE_STATE_BUFFER | WLR_SURFACE_STATE_TRANSFORM); return surface; } struct wlr_surface *wlr_surface_try_from_node(struct ky_scene_node *node) { if (node->type != KY_SCENE_NODE_BUFFER) { return NULL; } struct ky_scene_buffer *scene_buffer = ky_scene_buffer_from_node(node); if (!scene_buffer) { return NULL; } struct ky_scene_surface *scene_surface = ky_scene_surface_try_from_buffer(scene_buffer); if (!scene_surface) { return NULL; } return scene_surface->surface; } struct ky_scene_buffer *ky_scene_buffer_try_from_surface(struct wlr_surface *wlr_surface) { struct ky_scene_surface *scene_surface = ky_scene_surface_try_from_surface(wlr_surface); if (!scene_surface) { return NULL; } return scene_surface->buffer; } float ky_scene_surface_get_scale(struct wlr_surface *wlr_surface) { struct wlr_surface_state *surface_state = &wlr_surface->current; if (surface_state->scale > 1) { return surface_state->scale; } int width = surface_state->width, height = surface_state->height; struct ky_scene_buffer *scene_buffer = ky_scene_buffer_try_from_surface(wlr_surface); if (scene_buffer) { width = scene_buffer->dst_width; height = scene_buffer->dst_height; } int buffer_width = surface_state->buffer_width; int buffer_height = surface_state->buffer_height; if (surface_state->viewport.has_src) { buffer_width = surface_state->viewport.src.width; buffer_height = surface_state->viewport.src.height; } float scale; if (width == 0 || height == 0 || width >= buffer_width || height >= buffer_height) { scale = 1.0f; } else { scale = buffer_width > 0 ? (float)buffer_width / (float)width : 1.0f; } if (!scene_buffer) { return scale; } struct ky_scene *scene = ky_scene_from_node(&scene_buffer->node); struct ky_scene_output *scene_output; wl_list_for_each(scene_output, &scene->outputs, link) { if (scene_buffer->active_outputs & (1ull << scene_output->index)) { scale = MAX(scale, scene_output->output->scale); } } return scale; } kylin-wayland-compositor/src/scene/output.c0000664000175000017500000005541015160461067020062 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include "backend/drm.h" #include "effect/effect.h" #include "output.h" #include "render/pass.h" #include "scene/scene.h" #include "scene/surface.h" #include "scene_p.h" #include "util/debug.h" #include "util/quirks.h" void ky_scene_output_damage_whole(struct ky_scene_output *scene_output) { wlr_damage_ring_add_whole(&scene_output->damage_ring); output_schedule_frame(scene_output->output); } static void scene_output_update_geometry(struct ky_scene_output *scene_output, bool force_update) { wlr_damage_ring_add_whole(&scene_output->damage_ring); output_schedule_frame(scene_output->output); ky_scene_node_update_outputs(&scene_output->scene->tree.node, &scene_output->scene->outputs, NULL, force_update ? scene_output : NULL); } static void scene_output_handle_destroy(struct wlr_addon *addon) { struct ky_scene_output *scene_output = wl_container_of(addon, scene_output, addon); ky_scene_output_destroy(scene_output); } static const struct wlr_addon_interface output_addon_impl = { .name = "ky_scene_output", .destroy = scene_output_handle_destroy, }; static void scene_output_handle_configure(struct wl_listener *listener, void *data) { struct ky_scene_output *scene_output = wl_container_of(listener, scene_output, output_configure); struct output *output = output_from_wlr_output(scene_output->output); struct output_configure_event *event = data; /* already called in ky_scene_output_create */ if (!output->state_usable) { return; } bool force_update = event->changes & (KYWC_OUTPUT_STATE_TRANSFORM | KYWC_OUTPUT_STATE_SCALE); if (force_update || event->changes & (KYWC_OUTPUT_STATE_MODE | KYWC_OUTPUT_STATE_POSITION | KYWC_OUTPUT_STATE_POWER)) { scene_output->geometry = output->geometry; scene_output_update_geometry(scene_output, force_update); } } static void scene_output_handle_damage(struct wl_listener *listener, void *data) { struct ky_scene_output *scene_output = wl_container_of(listener, scene_output, output_damage); struct wlr_output_event_damage *event = data; pixman_region32_t damage; pixman_region32_init(&damage); wlr_region_scale(&damage, event->damage, 1 / event->output->scale); wlr_damage_ring_add(&scene_output->damage_ring, &damage); output_schedule_frame(scene_output->output); pixman_region32_fini(&damage); } static void scene_output_handle_needs_frame(struct wl_listener *listener, void *data) { struct ky_scene_output *scene_output = wl_container_of(listener, scene_output, output_needs_frame); output_schedule_frame(scene_output->output); } struct ky_scene_output *ky_scene_output_create(struct ky_scene *scene, struct wlr_output *output) { struct ky_scene_output *scene_output = calloc(1, sizeof(*scene_output)); if (!scene_output) { return NULL; } scene_output->output = output; scene_output->scene = scene; wlr_addon_init(&scene_output->addon, &output->addons, scene, &output_addon_impl); wlr_damage_ring_init(&scene_output->damage_ring); pixman_region32_init(&scene_output->collected_damage); pixman_region32_init(&scene_output->frame_damage); int prev_output_index = -1; struct wl_list *prev_output_link = &scene->outputs; struct ky_scene_output *current_output; wl_list_for_each(current_output, &scene->outputs, link) { if (prev_output_index + 1 != current_output->index) { break; } prev_output_index = current_output->index; prev_output_link = ¤t_output->link; } scene_output->index = prev_output_index + 1; assert(scene_output->index < 64); wl_list_insert(prev_output_link, &scene_output->link); wl_signal_init(&scene_output->events.viewport); wl_signal_init(&scene_output->events.frame); wl_signal_init(&scene_output->events.destroy); scene_output->output_damage.notify = scene_output_handle_damage; wl_signal_add(&output->events.damage, &scene_output->output_damage); scene_output->output_needs_frame.notify = scene_output_handle_needs_frame; wl_signal_add(&output->events.needs_frame, &scene_output->output_needs_frame); struct output *_output = output_from_wlr_output(output); char *env = getenv("KYWC_DISABLE_DIRECT_SCANOUT"); scene_output->direct_scanout = !(env && strcmp(env, "true") == 0) && !(_output->quirks & QUIRKS_MASK_DISABLE_DIRECT_SCANOUT); scene_output->geometry = _output->geometry; scene_output->output_configure.notify = scene_output_handle_configure; wl_signal_add(&_output->events.configure, &scene_output->output_configure); scene_output_update_geometry(scene_output, false); return scene_output; } void ky_scene_output_destroy(struct ky_scene_output *scene_output) { if (!scene_output) { return; } wl_signal_emit_mutable(&scene_output->events.destroy, NULL); assert(wl_list_empty(&scene_output->events.viewport.listener_list)); assert(wl_list_empty(&scene_output->events.frame.listener_list)); assert(wl_list_empty(&scene_output->events.destroy.listener_list)); ky_scene_node_update_outputs(&scene_output->scene->tree.node, &scene_output->scene->outputs, scene_output, NULL); wlr_addon_finish(&scene_output->addon); wlr_damage_ring_finish(&scene_output->damage_ring); pixman_region32_fini(&scene_output->collected_damage); pixman_region32_fini(&scene_output->frame_damage); wlr_buffer_unlock(scene_output->buffer); wl_list_remove(&scene_output->link); wl_list_remove(&scene_output->output_damage.link); wl_list_remove(&scene_output->output_needs_frame.link); wl_list_remove(&scene_output->output_configure.link); free(scene_output); } struct ky_scene_output *ky_scene_get_scene_output(struct ky_scene *scene, struct wlr_output *output) { struct wlr_addon *addon = wlr_addon_find(&output->addons, scene, &output_addon_impl); if (!addon) { return NULL; } struct ky_scene_output *scene_output = wl_container_of(addon, scene_output, addon); return scene_output; } void ky_scene_output_set_viewport_source_box(struct ky_scene_output *scene_output, const struct wlr_box *src_box) { if (wlr_box_equal(&scene_output->viewport.src, src_box)) { return; } if (src_box) { scene_output->viewport.src = *src_box; } else { scene_output->viewport.src = (struct wlr_box){ 0 }; } ky_scene_output_damage_whole(scene_output); wl_signal_emit_mutable(&scene_output->events.viewport, NULL); } static void scene_node_send_frame_done(struct ky_scene_node *node, struct ky_scene_output *scene_output, struct timespec *now) { if (!node->enabled && !node->force_damage_event) { return; } if (node->type == KY_SCENE_NODE_BUFFER) { struct ky_scene_buffer *scene_buffer = ky_scene_buffer_from_node(node); if (scene_buffer->primary_output == scene_output) { wl_signal_emit_mutable(&scene_buffer->events.frame_done, now); } } else if (node->type == KY_SCENE_NODE_TREE) { struct ky_scene_tree *scene_tree = ky_scene_tree_from_node(node); struct ky_scene_node *child; wl_list_for_each(child, &scene_tree->children, link) { if (child->type != KY_SCENE_NODE_RECT) { scene_node_send_frame_done(child, scene_output, now); } } } } void ky_scene_output_send_frame_done(struct ky_scene_output *scene_output, struct timespec *now) { scene_node_send_frame_done(&scene_output->scene->tree.node, scene_output, now); } static bool scene_output_render(struct ky_scene_output *scene_output, struct wlr_output_state *state, struct ky_scene_render_target *target) { KY_PROFILE_ZONE(zone, __func__); struct wlr_output *wlr_output = scene_output->output; if (!wlr_output_configure_primary_swapchain(wlr_output, state, &wlr_output->swapchain)) { KY_PROFILE_ZONE_END(zone); return false; } struct wlr_buffer *buffer = wlr_swapchain_acquire(wlr_output->swapchain); if (buffer == NULL) { KY_PROFILE_ZONE_END(zone); return false; } struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(wlr_output->renderer, buffer, NULL); if (render_pass == NULL) { wlr_buffer_unlock(buffer); KY_PROFILE_ZONE_END(zone); return false; } target->buffer = buffer; target->render_pass = render_pass; pixman_region32_t frame_damage, acc_damage; pixman_region32_init(&frame_damage); pixman_region32_init(&acc_damage); // target damage is accumulated damage in the output, but in layout coord wlr_damage_ring_rotate_buffer(&scene_output->damage_ring, buffer, &target->damage); if (pixman_region32_not_empty(&target->damage)) { // translate to scene layout coord pixman_region32_translate(&target->damage, target->logical.x, target->logical.y); pixman_region32_copy(&frame_damage, &scene_output->damage_ring.current); pixman_region32_copy(&acc_damage, &target->damage); // render all nodes and effects ky_scene_render_damage_in_target(scene_output->scene, target); // update target excluded_buffer_damage from excluded_damage pixman_region32_copy(&target->excluded_buffer_damage, &target->excluded_damage); pixman_region32_translate(&target->excluded_buffer_damage, -target->logical.x, -target->logical.y); ky_scene_render_region(&target->excluded_buffer_damage, target); // trying to render software cursors ky_scene_render_target_add_software_cursors(target); /** * damage extended in render, translate to output coord * the expansion area consists of two parts, one is the two expansion areas of blur, * and the other is the damage area formed effects rendering ( in target expand_damage). * all expansion areas don't add to current damage ring. the damage area * formed effects rendering add to the next frame, it will be in next damage ring. */ pixman_region32_t expand_damage; pixman_region32_init(&expand_damage); pixman_region32_subtract(&expand_damage, &target->damage, &acc_damage); // subtract the excluded damage pixman_region32_subtract(&expand_damage, &expand_damage, &target->excluded_damage); pixman_region32_union(&expand_damage, &expand_damage, &target->expand_damage); pixman_region32_translate(&expand_damage, -target->logical.x, -target->logical.y); pixman_region32_intersect_rect(&expand_damage, &expand_damage, 0, 0, scene_output->output->width, scene_output->output->height); pixman_region32_translate(&acc_damage, -target->logical.x, -target->logical.y); pixman_region32_union(&acc_damage, &acc_damage, &expand_damage); pixman_region32_union(&frame_damage, &frame_damage, &expand_damage); pixman_region32_fini(&expand_damage); } /* damage in post is added in next frame */ ky_scene_output_render_post(target); struct output *output = output_from_wlr_output(wlr_output); if (!ky_render_pass_submit(render_pass, output->quirks)) { wlr_buffer_unlock(buffer); pixman_region32_fini(&acc_damage); pixman_region32_fini(&frame_damage); wlr_damage_ring_add_whole(&scene_output->damage_ring); KY_PROFILE_ZONE_END(zone); return false; } if (pixman_region32_not_empty(&frame_damage)) { pixman_region32_copy(&scene_output->frame_damage, &frame_damage); pixman_region32_translate(&scene_output->frame_damage, scene_output->geometry.x, scene_output->geometry.y); ky_scene_render_region(&frame_damage, target); } if (pixman_region32_not_empty(&acc_damage)) { ky_scene_render_region(&acc_damage, target); } /* set damage even if frame/acc damage is empty */ wlr_output_set_buffer_damage(wlr_output, &acc_damage); wlr_output_state_set_damage(state, &frame_damage); wlr_output_state_set_buffer(state, buffer); wlr_buffer_unlock(buffer); pixman_region32_fini(&acc_damage); pixman_region32_fini(&frame_damage); KY_PROFILE_ZONE_END(zone); return true; } static bool ky_scene_get_fullscreen_buffer(struct ky_scene_node *node, int lx, int ly, const struct wlr_box *box, struct ky_scene_buffer **buffer) { if (!ky_scene_node_is_visible(node)) { return false; } if (node->type == KY_SCENE_NODE_TREE) { struct ky_scene_tree *tree = ky_scene_tree_from_node(node); struct ky_scene_node *child; wl_list_for_each_reverse(child, &tree->children, link) { if (ky_scene_get_fullscreen_buffer(child, lx + child->x, ly + child->y, box, buffer)) { return true; } } return false; } struct wlr_box bound = { 0 }; node->impl.get_bounding_box(node, &bound); bound.x += lx, bound.y += ly; struct wlr_box dest; if (!wlr_box_intersection(&dest, &bound, box)) { return false; } if (node->type == KY_SCENE_NODE_RECT) { return true; } if (dest.width != box->width || dest.height != box->height) { return true; } struct ky_scene_buffer *scene_buffer = ky_scene_buffer_from_node(node); if (scene_buffer->opacity != 1 || !pixman_region32_not_empty(&scene_buffer->opaque_region)) { return true; } if (pixman_region32_contains_rectangle(&scene_buffer->opaque_region, &(pixman_box32_t){ 0, 0, box->width, box->height }) == PIXMAN_REGION_IN) { *buffer = scene_buffer; } return true; } static struct ky_scene_buffer * scene_output_get_fullscreen_buffer(struct ky_scene_output *scene_output) { struct ky_scene_node *root = &scene_output->scene->tree.node; int lx = root->x, ly = root->y; struct wlr_box box = { scene_output->geometry.x, scene_output->geometry.y, scene_output->geometry.width, scene_output->geometry.height }; struct ky_scene_buffer *buffer = NULL; ky_scene_get_fullscreen_buffer(root, lx, ly, &box, &buffer); return buffer; } static bool scene_output_direct_scanout_is_allowed(struct ky_scene_render_target *target) { struct ky_scene_output *scene_output = target->output; if (!scene_output->direct_scanout) { return false; } struct wlr_output *output = scene_output->output; if (!wlr_output_is_direct_scanout_allowed(output)) { return false; } if (!output_use_hardware_color(output_from_wlr_output(output))) { return false; } if (!effect_manager_allow_direct_scanout(target)) { return false; } return true; } static bool ky_scene_try_direct_scanout(struct ky_scene_output *scene_output, struct wlr_output_state *state, struct ky_scene_buffer *scene_buffer) { if (!scene_buffer) { return false; } struct wlr_output *output = scene_output->output; if (scene_buffer->transform != output->transform) { return false; } struct wlr_fbox default_box = { 0 }; if (scene_buffer->transform & WL_OUTPUT_TRANSFORM_90) { default_box.width = scene_buffer->buffer->height; default_box.height = scene_buffer->buffer->width; } else { default_box.width = scene_buffer->buffer->width; default_box.height = scene_buffer->buffer->height; } if (!wlr_fbox_empty(&scene_buffer->src_box) && !wlr_fbox_equal(&scene_buffer->src_box, &default_box)) { return false; } if (scene_buffer->buffer->width != output->width || scene_buffer->buffer->height != output->height) { kywc_log(KYWC_DEBUG, "Scene buffer size mismatch"); return false; } if (scene_buffer->primary_output == scene_output) { struct wlr_linux_dmabuf_feedback_v1_init_options options = { .main_renderer = scene_output->output->renderer, .scanout_primary_output = scene_output->output, }; ky_scene_buffer_send_dmabuf_feedback(scene_output->scene, scene_buffer, &options); scene_buffer->node.sent_dmabuf_feedback = true; } struct wlr_output_state pending; wlr_output_state_init(&pending); if (!wlr_output_state_copy(&pending, state)) { return false; } struct wlr_buffer *wlr_buffer = scene_buffer->buffer; struct wlr_client_buffer *client_buffer = wlr_client_buffer_get(wlr_buffer); if (client_buffer != NULL && client_buffer->source != NULL && client_buffer->source->n_locks > 0) { wlr_buffer = client_buffer->source; } wlr_output_state_set_buffer(&pending, wlr_buffer); if (!wlr_output_test_state(scene_output->output, &pending)) { wlr_output_state_finish(&pending); return false; } wlr_output_state_copy(state, &pending); wlr_output_state_finish(&pending); struct ky_scene_output_sample_event sample_event = { .output = scene_output, .direct_scanout = true, }; wl_signal_emit_mutable(&scene_buffer->events.output_sample, &sample_event); return true; } static bool is_tearing_allowed(struct ky_scene *scene, struct ky_scene_buffer *buffer) { if (!buffer) { return false; } struct wlr_surface *surface = wlr_surface_try_from_node(&buffer->node); if (!surface) { return false; } return ky_scene_surface_is_tearing_allowed(scene, surface); } bool ky_scene_output_commit(struct ky_scene_output *scene_output, const struct ky_scene_output_state_options *options) { /* make sure something is done before commit */ wl_signal_emit_mutable(&scene_output->events.frame, NULL); struct wlr_output *wlr_output = scene_output->output; struct ky_scene_render_target target = { .transform = wlr_output->transform, .scale = wlr_output->scale, .logical = { .x = scene_output->geometry.x, .y = scene_output->geometry.y }, .output = scene_output, .options = KY_SCENE_RENDER_ENABLE_PRESENTATION, }; wlr_output_transformed_resolution(wlr_output, &target.trans_width, &target.trans_height); target.logical.width = ceil(target.trans_width / wlr_output->scale); target.logical.height = ceil(target.trans_height / wlr_output->scale); ky_scene_output_render_pre(&target); pixman_region32_t damage; // current scene damage in the output box pixman_region32_init_rect(&damage, target.logical.x, target.logical.y, target.logical.width, target.logical.height); ky_scene_collect_damage(scene_output->scene); pixman_region32_intersect(&damage, &damage, &scene_output->collected_damage); pixman_region32_clear(&scene_output->collected_damage); // union all damage in the output layout box pixman_region32_translate(&damage, -target.logical.x, -target.logical.y); if (floor(target.scale) != target.scale) { wlr_region_expand(&damage, &damage, 1); } /* union damage from output damage event */ wlr_damage_ring_add(&scene_output->damage_ring, &damage); pixman_region32_fini(&damage); if (!wlr_output->needs_frame && !pixman_region32_not_empty(&scene_output->damage_ring.current)) { return false; } struct ky_scene_buffer *fullscreen = NULL; bool is_tearing = false; if (scene_output_direct_scanout_is_allowed(&target) || ky_scene_is_tearing_needed(scene_output->scene)) { fullscreen = scene_output_get_fullscreen_buffer(scene_output); is_tearing = is_tearing_allowed(scene_output->scene, fullscreen); } struct output *output = output_from_wlr_output(wlr_output); struct wlr_output_state state; wlr_output_state_init(&state); output_state_attempt_vrr(output, &state, fullscreen != NULL); bool scanout = ky_scene_try_direct_scanout(scene_output, &state, fullscreen); if (scene_output->prev_scanout != scanout) { scene_output->prev_scanout = scanout; kywc_log(KYWC_INFO, "%s: Direct scan-out %s", wlr_output->name, scanout ? "enabled" : "disabled"); if (!scanout) { // When exiting direct scan-out, damage everything wlr_damage_ring_add_whole(&scene_output->damage_ring); } } if (scanout) { pixman_region32_t frame_damage; pixman_region32_init(&frame_damage); if (pixman_region32_not_empty(&scene_output->damage_ring.current)) { pixman_region32_copy(&frame_damage, &scene_output->damage_ring.current); ky_scene_render_region(&frame_damage, &target); /* translate to layout coord */ pixman_region32_copy(&scene_output->frame_damage, &scene_output->damage_ring.current); pixman_region32_translate(&scene_output->frame_damage, scene_output->geometry.x, scene_output->geometry.y); } wlr_output_state_set_damage(&state, &frame_damage); pixman_region32_fini(&frame_damage); output_state_attempt_tearing(output, &state, is_tearing); bool ok = wlr_output_commit_state(wlr_output, &state); wlr_output_state_finish(&state); pixman_region32_clear(&scene_output->frame_damage); return ok; } pixman_region32_init(&target.damage); pixman_region32_init(&target.expand_damage); pixman_region32_init(&target.excluded_damage); pixman_region32_init(&target.excluded_buffer_damage); // ky_scene_log_region(KYWC_ERROR, "frame damage", &scene_output->damage_ring.current); bool ok = false; if (scene_output_render(scene_output, &state, &target)) { output_state_attempt_tearing(output, &state, is_tearing); ok = wlr_output_commit_state(wlr_output, &state); wlr_buffer_unlock(scene_output->buffer); scene_output->buffer = wlr_buffer_lock(target.buffer); if (!ok) { } } wlr_output_state_finish(&state); pixman_region32_clear(&scene_output->frame_damage); pixman_region32_fini(&target.damage); pixman_region32_fini(&target.expand_damage); pixman_region32_fini(&target.excluded_damage); pixman_region32_fini(&target.excluded_buffer_damage); return ok; } kylin-wayland-compositor/src/scene/render.c0000664000175000017500000003305215160461067017777 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include "effect/blur.h" #include "effect/effect.h" #include "render/pass.h" #include "render/profile.h" #include "scene/render.h" static int scale_length(int length, int offset, float scale) { return round((offset + length) * scale) - round(offset * scale); } void ky_scene_render_box(struct wlr_box *box, struct ky_scene_render_target *target) { box->width = scale_length(box->width, box->x, target->scale); box->height = scale_length(box->height, box->y, target->scale); box->x = round(box->x * target->scale); box->y = round(box->y * target->scale); enum wl_output_transform transform = wlr_output_transform_invert(target->transform); wlr_box_transform(box, box, transform, target->trans_width, target->trans_height); } void ky_scene_render_target_box(const struct ky_scene_render_target *target, struct kywc_box *box) { box->x = box->x - target->logical.x; box->y = box->y - target->logical.y; box->width = scale_length(box->width, box->x, target->scale); box->height = scale_length(box->height, box->y, target->scale); box->x = round(box->x * target->scale); box->y = round(box->y * target->scale); enum wl_output_transform transform = wlr_output_transform_invert(target->transform); kywc_box_transform(box, box, transform, target->trans_width, target->trans_height); } void ky_scene_render_region(pixman_region32_t *region, struct ky_scene_render_target *target) { wlr_region_scale(region, region, target->scale); enum wl_output_transform transform = wlr_output_transform_invert(target->transform); wlr_region_transform(region, region, transform, target->trans_width, target->trans_height); } void ky_scene_render_point(float *x, float *y, struct ky_scene_render_target *target) { *x = roundf(*x * target->scale); *y = roundf(*y * target->scale); enum wl_output_transform transform = wlr_output_transform_invert(target->transform); struct wlr_box temp_box = { *x, *y, 1, 1 }; wlr_box_transform(&temp_box, &temp_box, transform, target->trans_width, target->trans_height); *x = temp_box.x; *y = temp_box.y; } void ky_scene_render_damage_in_target(struct ky_scene *scene, struct ky_scene_render_target *target) { ky_scene_output_render_begin(target); // clear current output buffer damage region pixman_region32_t background; pixman_region32_init(&background); pixman_region32_subtract(&background, &target->damage, &scene->collected_invisible); pixman_region32_translate(&background, -target->logical.x, -target->logical.y); ky_scene_render_region(&background, target); wlr_render_pass_add_rect(target->render_pass, &(struct wlr_render_rect_options){ .box = { .width = target->buffer->width, .height = target->buffer->height }, .color = { .r = 0, .g = 0, .b = 0, .a = 1 }, .clip = &background, }); pixman_region32_fini(&background); if (!ky_scene_output_render(target)) { struct ky_scene_node *root = &scene->tree.node; // render each node with damage region and visible region root->impl.render(root, root->x, root->y, target); } ky_scene_output_render_end(target); } void ky_scene_render_target_add_software_cursors(struct ky_scene_render_target *target) { struct wlr_output *output = target->output->output; bool need_render = false; pixman_region32_t damage; pixman_region32_init(&damage); struct wlr_output_cursor *cursor; wl_list_for_each(cursor, &output->cursors, link) { if ((!cursor->enabled || !cursor->visible || output->hardware_cursor == cursor) && !(target->options & KY_SCENE_RENDER_ENABLE_CURSORS)) { continue; } struct wlr_texture *texture = cursor->texture; if (texture == NULL) { continue; } if (!need_render) { pixman_region32_copy(&damage, &target->damage); pixman_region32_translate(&damage, -target->logical.x, -target->logical.y); ky_scene_render_region(&damage, target); pixman_region32_subtract(&damage, &damage, &target->excluded_buffer_damage); need_render = true; } /* cursor is the buffer coordinate of the output, but does not include rotation */ /* lx ly is the logical coordinate in the scene */ float scale = target->output->output->scale; int lx = (cursor->x - cursor->hotspot_x) / scale + target->output->geometry.x; int ly = (cursor->y - cursor->hotspot_y) / scale + target->output->geometry.y; struct wlr_box box = { .x = lx - target->logical.x, .y = ly - target->logical.y, .width = cursor->width / scale, .height = cursor->height / scale, }; ky_scene_render_box(&box, target); pixman_region32_t cursor_damage; pixman_region32_init_rect(&cursor_damage, box.x, box.y, box.width, box.height); pixman_region32_intersect(&cursor_damage, &cursor_damage, &damage); if (!pixman_region32_not_empty(&cursor_damage)) { pixman_region32_fini(&cursor_damage); continue; } struct wlr_render_texture_options options = { .texture = texture, .src_box = cursor->src_box, .dst_box = box, .clip = &cursor_damage, .transform = output->transform, }; wlr_render_pass_add_texture(target->render_pass, &options); pixman_region32_fini(&cursor_damage); } pixman_region32_fini(&damage); } static void ky_region_scale(pixman_region32_t *dst, const pixman_region32_t *src, float scale_x, float scale_y) { if (scale_x == 1.0 && scale_y == 1.0) { pixman_region32_copy(dst, src); return; } int nrects; const pixman_box32_t *src_rects = pixman_region32_rectangles(src, &nrects); pixman_box32_t *dst_rects = malloc(nrects * sizeof(pixman_box32_t)); if (dst_rects == NULL) { return; } for (int i = 0; i < nrects; ++i) { dst_rects[i].x1 = (int32_t)roundf(src_rects[i].x1 * scale_x); dst_rects[i].x2 = (int32_t)roundf(src_rects[i].x2 * scale_x); dst_rects[i].y1 = (int32_t)roundf(src_rects[i].y1 * scale_y); dst_rects[i].y2 = (int32_t)roundf(src_rects[i].y2 * scale_y); } pixman_region32_fini(dst); pixman_region32_init_rects(dst, dst_rects, nrects); free(dst_rects); } void ky_scene_render_target_add_texture(struct ky_scene_render_target *target, const struct ky_scene_render_texture_options *opts) { struct wlr_box dst_box = { .x = opts->geometry_box->x - target->logical.x, .y = opts->geometry_box->y - target->logical.y, .width = opts->geometry_box->width, .height = opts->geometry_box->height, }; ky_scene_render_box(&dst_box, target); pixman_region32_t render_region; pixman_region32_init(&render_region); pixman_region32_copy(&render_region, &target->damage); pixman_region32_translate(&render_region, -target->logical.x, -target->logical.y); ky_scene_render_region(&render_region, target); pixman_region32_intersect_rect(&render_region, &render_region, dst_box.x, dst_box.y, dst_box.width, dst_box.height); if (!pixman_region32_not_empty(&render_region)) { pixman_region32_fini(&render_region); return; } enum wl_output_transform transform = wlr_output_transform_invert(opts->transform); transform = wlr_output_transform_compose(transform, target->transform); struct ky_render_texture_options tex_opts = { .base = { .texture = opts->texture, .dst_box = dst_box, .transform = transform, .alpha = opts->alpha, .clip = &render_region, }, .radius = { .rb = opts->radius ? (*opts->radius)[0] * target->scale : 0, .rt = opts->radius ? (*opts->radius)[1] * target->scale : 0, .lb = opts->radius ? (*opts->radius)[2] * target->scale : 0, .lt = opts->radius ? (*opts->radius)[3] * target->scale : 0, }, .rotation_angle = opts->angle, }; if (opts->src) { tex_opts.base.src_box.x = opts->src->x; tex_opts.base.src_box.y = opts->src->y; tex_opts.base.src_box.width = opts->src->width; tex_opts.base.src_box.height = opts->src->height; } KY_PROFILE_RENDER_ZONE(ky_render_pass_get_renderer(target->render_pass), gzone, __func__); if (!(target->options & KY_SCENE_RENDER_DISABLE_BLUR) && opts->blur.info) { const struct blur_info *info = opts->blur.info; struct blur_info blur = { .iterations = info->iterations, .offset = info->offset, .alpha = info->alpha, }; pixman_region32_init(&blur.region); /* region in texture */ pixman_region32_copy(&blur.region, &info->region); pixman_region32_translate(&blur.region, opts->blur.offset_x, opts->blur.offset_y); struct wlr_fbox *src_fbox = &tex_opts.base.src_box; struct wlr_fbox effective_src = *src_fbox; if (*opts->blur.scale != 1.0f && !wlr_fbox_empty(src_fbox)) { float scale = 1.0f / *opts->blur.scale; effective_src.y = src_fbox->y * scale; effective_src.x = src_fbox->x * scale; effective_src.width = src_fbox->width * scale; effective_src.height = src_fbox->height * scale; } if (pixman_region32_not_empty(&blur.region) && !wlr_fbox_empty(&effective_src)) { int x = floorf(effective_src.x); int y = floorf(effective_src.y); int width = ceilf(effective_src.x + effective_src.width) - x; int height = ceilf(effective_src.y + effective_src.height) - y; pixman_region32_intersect_rect(&blur.region, &blur.region, x, y, width, height); pixman_region32_translate(&blur.region, -x, -y); } /* region in tex geometry box, logic coord */ float src_width = wlr_fbox_empty(src_fbox) ? opts->texture->width : src_fbox->width; float src_height = wlr_fbox_empty(src_fbox) ? opts->texture->height : src_fbox->height; float scale_w = opts->geometry_box->width / src_width; float scale_h = opts->geometry_box->height / src_height; ky_region_scale(&blur.region, &blur.region, *opts->blur.scale * scale_w, *opts->blur.scale * scale_h); struct wlr_box blur_dst = dst_box; if (pixman_region32_not_empty(&blur.region)) { pixman_region32_t blur_region; pixman_region32_init(&blur_region); pixman_region32_copy(&blur_region, &blur.region); pixman_region32_translate(&blur_region, opts->geometry_box->x - target->logical.x, opts->geometry_box->y - target->logical.y); ky_scene_render_region(&blur_region, target); /* blur beyond blur_dst (sdfbox) will be discarded by the fillet shader */ pixman_box32_t *blur_bbox = pixman_region32_extents(&blur_region); blur_dst.x = blur_bbox->x1; blur_dst.y = blur_bbox->y1; blur_dst.width = blur_bbox->x2 - blur_bbox->x1; blur_dst.height = blur_bbox->y2 - blur_bbox->y1; pixman_region32_fini(&blur_region); bool fractional_scale_w = floor(scale_w) != scale_w; bool fractional_scale_h = floor(scale_h) != scale_h; bool fractional_scale_target = floor(target->scale) != target->scale; int x_expand = fractional_scale_w || fractional_scale_target ? 1 : 0; int y_expand = fractional_scale_h || fractional_scale_target ? 1 : 0; struct wlr_box bbox = { .x = blur_dst.x + x_expand, .y = blur_dst.y + y_expand, .width = blur_dst.width - 2 * x_expand, .height = blur_dst.height - 2 * y_expand, }; if (bbox.width > 0 && bbox.height > 0) { blur_dst = bbox; } } struct ky_render_round_corner radius = { .rb = opts->blur.radius ? (*opts->blur.radius)[0] * target->scale : 0, .rt = opts->blur.radius ? (*opts->blur.radius)[1] * target->scale : 0, .lb = opts->blur.radius ? (*opts->blur.radius)[2] * target->scale : 0, .lt = opts->blur.radius ? (*opts->blur.radius)[3] * target->scale : 0, }; /* blur region in tex geometry box, logic coord */ struct blur_render_options blur_opts = { .lx = opts->geometry_box->x, .ly = opts->geometry_box->y, .dst_box = &blur_dst, .clip = &render_region, .radius = opts->blur.radius ? &radius : NULL, .blur = pixman_region32_not_empty(&blur.region) ? &blur : NULL, .alpha = opts->blur.alpha, }; blur_render_with_target(target, &blur_opts); pixman_region32_fini(&blur.region); } ky_render_pass_add_texture(target->render_pass, &tex_opts); pixman_region32_fini(&render_region); KY_PROFILE_RENDER_ZONE_END(ky_render_pass_get_renderer(target->render_pass)); } kylin-wayland-compositor/src/scene/buffer.c0000664000175000017500000007133415160461067017776 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include "effect/blur.h" #include "render/pass.h" #include "render/pixel_format.h" #include "render/profile.h" #include "scene/surface.h" #include "scene_p.h" #include "security.h" struct ky_scene_buffer *ky_scene_buffer_from_node(struct ky_scene_node *node) { assert(node->type == KY_SCENE_NODE_BUFFER); struct ky_scene_buffer *scene_buffer = wl_container_of(node, scene_buffer, node); return scene_buffer; } static void buffer_get_dest_size(struct ky_scene_buffer *scene_buffer, int *width, int *height) { if (scene_buffer->dst_width > 0 && scene_buffer->dst_height > 0) { *width = scene_buffer->dst_width; *height = scene_buffer->dst_height; } else if (scene_buffer->buffer) { if (scene_buffer->transform & WL_OUTPUT_TRANSFORM_90) { *height = scene_buffer->buffer->width; *width = scene_buffer->buffer->height; } else { *width = scene_buffer->buffer->width; *height = scene_buffer->buffer->height; } } else { *width = *height = 0; } } static bool buffer_is_opaque(struct wlr_buffer *buffer) { void *data; uint32_t format; size_t stride; struct wlr_dmabuf_attributes dmabuf; struct wlr_shm_attributes shm; if (wlr_buffer_get_dmabuf(buffer, &dmabuf)) { format = dmabuf.format; } else if (wlr_buffer_get_shm(buffer, &shm)) { format = shm.format; } else if (wlr_buffer_begin_data_ptr_access(buffer, WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { bool opaque = false; if (buffer->width == 1 && buffer->height == 1 && format == DRM_FORMAT_ARGB8888) { // Special case for single-pixel-buffer-v1 const uint8_t *argb8888 = data; // little-endian byte order opaque = argb8888[3] == 0xFF; } wlr_buffer_end_data_ptr_access(buffer); if (opaque) { return true; } } else { return false; } const struct ky_pixel_format *format_info = ky_pixel_format_from_drm(format); if (format_info == NULL) { return false; } return !format_info->has_alpha; } static void scene_buffer_get_opaque_region(struct ky_scene_buffer *scene_buffer, int width, int height, pixman_region32_t *region) { if (!scene_buffer->buffer || scene_buffer->opacity != 1) { return; } if (buffer_is_opaque(scene_buffer->buffer)) { pixman_region32_init_rect(region, 0, 0, width, height); } else if (pixman_region32_not_empty(&scene_buffer->opaque_region)) { pixman_region32_intersect_rect(region, &scene_buffer->opaque_region, 0, 0, width, height); } } static void buffer_get_opaque_region(struct ky_scene_node *node, pixman_region32_t *opaque) { pixman_region32_clear(opaque); if (!node->enabled) { return; } int width, height; struct ky_scene_buffer *scene_buffer = ky_scene_buffer_from_node(node); buffer_get_dest_size(scene_buffer, &width, &height); scene_buffer_get_opaque_region(scene_buffer, width, height, opaque); } static struct ky_scene_node *buffer_accept_input(struct ky_scene_node *node, int lx, int ly, double px, double py, double *rx, double *ry) { /* skip disabled or input bypassed nodes */ if (!node->enabled || node->input_bypassed) { return NULL; } struct ky_scene_buffer *scene_buffer = ky_scene_buffer_from_node(node); struct wlr_box box = { floor(px), floor(py), 1, 1 }; struct wlr_box node_box = { .x = lx, .y = ly }; buffer_get_dest_size(scene_buffer, &node_box.width, &node_box.height); if (!wlr_box_intersection(&node_box, &node_box, &box)) { return NULL; } *rx = px - lx; *ry = py - ly; if (pixman_region32_not_empty(&node->input_region) && !pixman_region32_contains_point(&node->input_region, box.x - lx, box.y - ly, NULL)) { return NULL; } /* check buffer_point_accepts_input */ if (scene_buffer->point_accepts_input && !scene_buffer->point_accepts_input(scene_buffer, rx, ry)) { return NULL; } return node; } static uint32_t region_area(pixman_region32_t *region) { uint32_t area = 0; int nrects; pixman_box32_t *rects = pixman_region32_rectangles(region, &nrects); for (int i = 0; i < nrects; ++i) { area += (rects[i].x2 - rects[i].x1) * (rects[i].y2 - rects[i].y1); } return area; } static void buffer_update_outputs(struct ky_scene_node *node, int lx, int ly, struct wl_list *outputs, struct ky_scene_output *ignore, struct ky_scene_output *force) { struct ky_scene_buffer *scene_buffer = ky_scene_buffer_from_node(node); uint32_t largest_overlap = 0; struct ky_scene_output *old_primary_output = scene_buffer->primary_output; scene_buffer->primary_output = NULL; size_t count = 0; uint64_t active_outputs = 0; int width, height; // let's update the outputs in two steps: // - the primary outputs // - the enter/leave signals // This ensures that the enter/leave signals can rely on the primary output // to have a reasonable value. Otherwise, they may get a value that's in // the middle of a calculation. struct ky_scene_output *scene_output; wl_list_for_each(scene_output, outputs, link) { if (scene_output == ignore) { continue; } if (!scene_output->output->enabled) { continue; } pixman_region32_t intersection, node_region; pixman_region32_init(&intersection); buffer_get_dest_size(scene_buffer, &width, &height); pixman_region32_init_rect(&node_region, lx, ly, width, height); pixman_region32_intersect_rect(&intersection, &node_region, scene_output->geometry.x, scene_output->geometry.y, scene_output->geometry.width, scene_output->geometry.height); if (pixman_region32_not_empty(&intersection)) { uint32_t overlap = region_area(&intersection); if (overlap > largest_overlap) { largest_overlap = overlap; scene_buffer->primary_output = scene_output; } active_outputs |= 1ull << scene_output->index; count++; } pixman_region32_fini(&intersection); pixman_region32_fini(&node_region); } uint64_t old_active = scene_buffer->active_outputs; scene_buffer->active_outputs = active_outputs; wl_list_for_each(scene_output, outputs, link) { uint64_t mask = 1ull << scene_output->index; bool intersects = active_outputs & mask; bool intersects_before = old_active & mask; if (intersects && !intersects_before) { wl_signal_emit_mutable(&scene_buffer->events.output_enter, scene_output); } else if (!intersects && intersects_before) { wl_signal_emit_mutable(&scene_buffer->events.output_leave, scene_output); } } // if there are active outputs on this node, we should always have a primary output assert(!scene_buffer->active_outputs || scene_buffer->primary_output); // Skip output update event if nothing was updated if (old_active == active_outputs && (!force || ((1ull << force->index) & ~active_outputs)) && old_primary_output == scene_buffer->primary_output) { return; } struct ky_scene_output *outputs_array[64]; struct ky_scene_outputs_update_event event = { .primary = scene_buffer->primary_output, .active = outputs_array, .size = count, }; size_t i = 0; wl_list_for_each(scene_output, outputs, link) { if (~active_outputs & (1ull << scene_output->index)) { continue; } assert(i < count); outputs_array[i++] = scene_output; } wl_signal_emit_mutable(&scene_buffer->events.outputs_update, &event); } static void buffer_collect_invisible(struct ky_scene_buffer *scene_buffer, struct kywc_box *box, pixman_region32_t *invisible) { pixman_region32_t region; pixman_region32_init(®ion); scene_buffer_get_opaque_region(scene_buffer, box->width, box->height, ®ion); if (!pixman_region32_not_empty(®ion)) { pixman_region32_fini(®ion); return; } struct ky_scene_node *node = &scene_buffer->node; if (pixman_region32_not_empty(&node->clip_region)) { pixman_region32_intersect(®ion, ®ion, &node->clip_region); } /* subtract round corners */ pixman_region32_t corner; pixman_region32_init(&corner); ky_scene_corner_region(&corner, box->width, box->height, node->radius); pixman_region32_subtract(®ion, ®ion, &corner); pixman_region32_fini(&corner); pixman_region32_translate(®ion, box->x, box->y); pixman_region32_union(invisible, invisible, ®ion); pixman_region32_fini(®ion); } static void buffer_collect_damage(struct ky_scene_node *node, int lx, int ly, bool parent_enabled, uint32_t damage_type, pixman_region32_t *damage, pixman_region32_t *invisible, pixman_region32_t *affected) { bool node_enabled = parent_enabled && node->enabled; /* node is still disabled, skip it */ if (!node_enabled && !node->last_enabled) { node->damage_type = KY_SCENE_DAMAGE_NONE; return; } struct ky_scene_buffer *scene_buffer = ky_scene_buffer_from_node(node); int width, height; buffer_get_dest_size(scene_buffer, &width, &height); // if node state is changed, it must in the affected region if (width > 0 && height > 0 && pixman_region32_contains_rectangle( affected, &(pixman_box32_t){ lx, ly, lx + width, ly + height }) == PIXMAN_REGION_OUT) { if (node_enabled) { buffer_collect_invisible(scene_buffer, &(struct kywc_box){ lx, ly, width, height }, invisible); } return; } bool no_damage = node->last_enabled && node_enabled && damage_type == KY_SCENE_DAMAGE_NONE; if (!no_damage) { /* node last visible region is added to damgae */ if (node->last_enabled && (!node_enabled || (damage_type & KY_SCENE_DAMAGE_HARMFUL))) { pixman_region32_union(damage, damage, &node->visible_region); } } bool visible = node_enabled && scene_buffer->opacity != 0 && scene_buffer->buffer; pixman_region32_clear(&node->visible_region); if (visible) { bool has_clip_region = pixman_region32_not_empty(&node->clip_region); if (has_clip_region) { pixman_region32_intersect_rect(&node->visible_region, &node->clip_region, 0, 0, width, height); pixman_region32_translate(&node->visible_region, lx, ly); } else { pixman_region32_init_rect(&node->visible_region, lx, ly, width, height); } pixman_region32_subtract(&node->visible_region, &node->visible_region, invisible); if (!no_damage) { pixman_region32_union(damage, damage, &node->visible_region); } buffer_collect_invisible(scene_buffer, &(struct kywc_box){ lx, ly, width, height }, invisible); } node->last_enabled = node_enabled; node->damage_type = KY_SCENE_DAMAGE_NONE; } struct wlr_texture *ky_scene_buffer_get_texture(struct ky_scene_buffer *scene_buffer, struct wlr_renderer *renderer) { struct wlr_client_buffer *client_buffer = wlr_client_buffer_get(scene_buffer->buffer); if (client_buffer != NULL) { return client_buffer->texture; } if (scene_buffer->texture != NULL) { return scene_buffer->texture; } scene_buffer->texture = wlr_texture_from_buffer(renderer, scene_buffer->buffer); return scene_buffer->texture; } static void buffer_render(struct ky_scene_node *node, int lx, int ly, struct ky_scene_render_target *target) { if (!node->enabled) { return; } struct ky_scene_buffer *scene_buffer = ky_scene_buffer_from_node(node); if (!scene_buffer->buffer || scene_buffer->opacity == 0) { return; } if (target->options & KY_SCENE_RENDER_ENABLE_SECURITY && security_check_node(&scene_buffer->node)) { return; } bool render_with_visibility = !(target->options & KY_SCENE_RENDER_DISABLE_VISIBILITY); if (render_with_visibility && !pixman_region32_not_empty(&node->visible_region) && !pixman_region32_not_empty(&node->extend_render_region)) { return; } int width, height; buffer_get_dest_size(scene_buffer, &width, &height); pixman_region32_t render_region; if (render_with_visibility) { pixman_region32_init(&render_region); pixman_region32_union(&render_region, &node->visible_region, &node->extend_render_region); pixman_region32_intersect(&render_region, &render_region, &target->damage); } else { pixman_region32_init_rect(&render_region, 0, 0, width, height); if (pixman_region32_not_empty(&node->clip_region)) { pixman_region32_intersect(&render_region, &render_region, &node->clip_region); } pixman_region32_translate(&render_region, lx, ly); pixman_region32_intersect(&render_region, &render_region, &target->damage); } if (!pixman_region32_not_empty(&render_region)) { pixman_region32_fini(&render_region); return; } struct wlr_texture *texture = ky_scene_buffer_get_texture(scene_buffer, target->output->output->renderer); if (texture == NULL) { pixman_region32_fini(&render_region); return; } struct wlr_box dst_box = { .x = lx - target->logical.x, .y = ly - target->logical.y, .width = width, .height = height, }; ky_scene_render_box(&dst_box, target); pixman_region32_translate(&render_region, -target->logical.x, -target->logical.y); pixman_region32_t opaque; pixman_region32_init(&opaque); scene_buffer_get_opaque_region(scene_buffer, width, height, &opaque); pixman_region32_translate(&opaque, lx - target->logical.x, ly - target->logical.y); pixman_region32_subtract(&opaque, &render_region, &opaque); ky_scene_render_region(&render_region, target); enum wl_output_transform transform = wlr_output_transform_invert(scene_buffer->transform); transform = wlr_output_transform_compose(transform, target->transform); KY_PROFILE_RENDER_ZONE(ky_render_pass_get_renderer(target->render_pass), gzone, __func__); bool render_with_radius = !(target->options & KY_SCENE_RENDER_DISABLE_ROUND_CORNER); struct ky_render_texture_options options = { .base = { .texture = texture, .src_box = scene_buffer->src_box, .dst_box = dst_box, .transform = transform, .alpha = &scene_buffer->opacity, .clip = &render_region, .blend_mode = pixman_region32_not_empty(&opaque) ? WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, }, .radius = { .rb = render_with_radius ? node->radius[0] * target->scale : 0, .rt = render_with_radius ? node->radius[1] * target->scale : 0, .lb = render_with_radius ? node->radius[2] * target->scale : 0, .lt = render_with_radius ? node->radius[3] * target->scale : 0, }, .repeated = scene_buffer->repeated, .enable_force_opaque = target->options & KY_SCENE_RENDER_ENABLE_FORCE_OPAQUE, }; if (scene_buffer->repeated) { struct wlr_fbox *box = &options.base.src_box; if (wlr_fbox_empty(box)) { box->x = box->y = 0; box->width = scene_buffer->buffer->width; box->height = scene_buffer->buffer->height; } box->x *= target->scale; box->y *= target->scale; box->width *= target->scale; box->height *= target->scale; } if (!(target->options & KY_SCENE_RENDER_DISABLE_BLUR)) { struct blur_render_options opts = { .lx = lx, .ly = ly, .dst_box = &dst_box, .clip = &render_region, .radius = &options.radius, .blur = node->has_blur ? &node->blur : NULL, }; blur_render_with_target(target, &opts); } ky_render_pass_add_texture(target->render_pass, &options); pixman_region32_fini(&render_region); pixman_region32_fini(&opaque); KY_PROFILE_RENDER_ZONE_END(ky_render_pass_get_renderer(target->render_pass)); if (target->options & KY_SCENE_RENDER_ENABLE_PRESENTATION) { struct ky_scene_output_sample_event sample_event = { .output = target->output, .direct_scanout = false, }; wl_signal_emit_mutable(&scene_buffer->events.output_sample, &sample_event); } if (scene_buffer->primary_output == target->output && !node->sent_dmabuf_feedback) { struct wlr_linux_dmabuf_feedback_v1_init_options options = { .main_renderer = target->output->output->renderer, .scanout_primary_output = NULL, }; ky_scene_buffer_send_dmabuf_feedback(target->output->scene, scene_buffer, &options); } } static void buffer_get_bounding_box(struct ky_scene_node *node, struct wlr_box *box) { struct ky_scene_buffer *scene_buffer = ky_scene_buffer_from_node(node); box->x = box->y = 0; buffer_get_dest_size(scene_buffer, &box->width, &box->height); } static void buffer_destroy(struct ky_scene_node *node) { if (!node) { return; } struct ky_scene_buffer *scene_buffer = ky_scene_buffer_from_node(node); ky_scene_buffer_set_buffer(scene_buffer, NULL); if (node->last_enabled) { struct ky_scene *scene = ky_scene_from_node(node); ky_scene_add_damage(scene, &node->visible_region); } pixman_region32_fini(&scene_buffer->opaque_region); scene_buffer->node_destroy(node); } static void scene_buffer_set_buffer(struct ky_scene_buffer *scene_buffer, struct wlr_buffer *buffer) { if (scene_buffer->buffer == buffer) { return; } wlr_buffer_unlock(scene_buffer->buffer); scene_buffer->buffer = NULL; if (!buffer) { return; } scene_buffer->buffer = wlr_buffer_lock(buffer); } void ky_scene_buffer_init(struct ky_scene_buffer *scene_buffer, struct ky_scene_tree *parent) { *scene_buffer = (struct ky_scene_buffer){ .opacity = 1, }; ky_scene_node_init(&scene_buffer->node, parent); scene_buffer->node.type = KY_SCENE_NODE_BUFFER; scene_buffer->node_destroy = scene_buffer->node.impl.destroy; scene_buffer->node.impl.destroy = buffer_destroy; scene_buffer->node.impl.accept_input = buffer_accept_input; scene_buffer->node.impl.update_outputs = buffer_update_outputs; scene_buffer->node.impl.get_opaque_region = buffer_get_opaque_region; scene_buffer->node.impl.collect_damage = buffer_collect_damage; scene_buffer->node.impl.render = buffer_render; scene_buffer->node.impl.get_bounding_box = buffer_get_bounding_box; wl_signal_init(&scene_buffer->events.outputs_update); wl_signal_init(&scene_buffer->events.output_enter); wl_signal_init(&scene_buffer->events.output_leave); wl_signal_init(&scene_buffer->events.output_sample); wl_signal_init(&scene_buffer->events.frame_done); pixman_region32_init(&scene_buffer->opaque_region); } struct ky_scene_buffer *ky_scene_buffer_create(struct ky_scene_tree *parent, struct wlr_buffer *buffer) { struct ky_scene_buffer *scene_buffer = calloc(1, sizeof(*scene_buffer)); if (!scene_buffer) { return NULL; } ky_scene_buffer_init(scene_buffer, parent); if (buffer) { scene_buffer_set_buffer(scene_buffer, buffer); ky_scene_node_push_damage(&scene_buffer->node, KY_SCENE_DAMAGE_HARMFUL, NULL); ky_scene_node_update_outputs(&scene_buffer->node, NULL, NULL, NULL); } return scene_buffer; } static void buffer_get_scene_damage(struct ky_scene_buffer *scene_buffer, pixman_region32_t *dst, const pixman_region32_t *src) { struct wlr_buffer *buffer = scene_buffer->buffer; struct wlr_fbox box = scene_buffer->src_box; if (wlr_fbox_empty(&box)) { box = (struct wlr_fbox){ 0, 0, buffer->width, buffer->height }; } wlr_fbox_transform(&box, &box, scene_buffer->transform, buffer->width, buffer->height); float scale_x, scale_y; int width, height; buffer_get_dest_size(scene_buffer, &width, &height); scale_x = width / box.width; scale_y = height / box.height; wlr_region_transform(dst, src, scene_buffer->transform, buffer->width, buffer->height); pixman_region32_intersect_rect(dst, dst, box.x, box.y, box.width, box.height); pixman_region32_translate(dst, -box.x, -box.y); wlr_region_scale_xy(dst, dst, scale_x, scale_y); } void ky_scene_buffer_set_buffer_with_damage(struct ky_scene_buffer *scene_buffer, struct wlr_buffer *buffer, const pixman_region32_t *damage) { assert(buffer || !damage); /* do nothing when still no buffer */ if (!scene_buffer->buffer && !buffer) { return; } int old_width, old_height, new_width, new_height; bool get_or_lost_buffer = !scene_buffer->buffer || !buffer; wlr_texture_destroy(scene_buffer->texture); scene_buffer->texture = NULL; buffer_get_dest_size(scene_buffer, &old_width, &old_height); scene_buffer_set_buffer(scene_buffer, buffer); buffer_get_dest_size(scene_buffer, &new_width, &new_height); /* return early if the scene buffer output no need to update */ if (old_width == new_width && old_height == new_height) { pixman_region32_t region; pixman_region32_init_rect(®ion, 0, 0, new_width, new_height); if (get_or_lost_buffer) { ky_scene_node_push_damage(&scene_buffer->node, KY_SCENE_DAMAGE_BOTH, ®ion); } else if (damage && pixman_region32_not_empty(damage)) { buffer_get_scene_damage(scene_buffer, ®ion, damage); ky_scene_node_push_damage(&scene_buffer->node, KY_SCENE_DAMAGE_HARMLESS, ®ion); } else { ky_scene_node_push_damage(&scene_buffer->node, KY_SCENE_DAMAGE_HARMLESS, ®ion); } pixman_region32_fini(®ion); return; } if (old_width > new_width || old_height > new_height) { pixman_region32_t region; pixman_region32_init_rect(®ion, 0, 0, old_width, old_height); ky_scene_node_push_damage(&scene_buffer->node, KY_SCENE_DAMAGE_BOTH, ®ion); pixman_region32_fini(®ion); } if (old_width < new_width || old_height < new_height) { ky_scene_node_push_damage(&scene_buffer->node, KY_SCENE_DAMAGE_BOTH, NULL); } // buffer update outputs, leave active outputs when no buffer ky_scene_node_update_outputs(&scene_buffer->node, NULL, NULL, NULL); } void ky_scene_buffer_set_buffer(struct ky_scene_buffer *scene_buffer, struct wlr_buffer *buffer) { ky_scene_buffer_set_buffer_with_damage(scene_buffer, buffer, NULL); } void ky_scene_buffer_set_opaque_region(struct ky_scene_buffer *scene_buffer, const pixman_region32_t *region) { if (pixman_region32_equal(&scene_buffer->opaque_region, region)) { return; } pixman_region32_copy(&scene_buffer->opaque_region, region); ky_scene_node_push_damage(&scene_buffer->node, KY_SCENE_DAMAGE_HARMFUL, NULL); } void ky_scene_buffer_set_source_box(struct ky_scene_buffer *scene_buffer, const struct wlr_fbox *box) { if (wlr_fbox_equal(&scene_buffer->src_box, box)) { return; } if (box != NULL) { scene_buffer->src_box = *box; } else { scene_buffer->src_box = (struct wlr_fbox){ 0 }; } ky_scene_node_push_damage(&scene_buffer->node, KY_SCENE_DAMAGE_HARMLESS, NULL); } void ky_scene_buffer_set_dest_size(struct ky_scene_buffer *scene_buffer, int width, int height) { if (scene_buffer->dst_width == width && scene_buffer->dst_height == height) { return; } bool update_later = false; if (scene_buffer->dst_width > width || scene_buffer->dst_height > height) { ky_scene_node_push_damage(&scene_buffer->node, KY_SCENE_DAMAGE_BOTH, NULL); } if (scene_buffer->dst_width < width || scene_buffer->dst_height < height) { update_later = true; } scene_buffer->dst_width = width; scene_buffer->dst_height = height; if (update_later) { ky_scene_node_push_damage(&scene_buffer->node, KY_SCENE_DAMAGE_BOTH, NULL); } ky_scene_node_update_outputs(&scene_buffer->node, NULL, NULL, NULL); } void ky_scene_buffer_set_transform(struct ky_scene_buffer *scene_buffer, enum wl_output_transform transform) { if (scene_buffer->transform == transform) { return; } /* size not changed when has dest size */ bool use_dest_size = scene_buffer->dst_width > 0 && scene_buffer->dst_height > 0; ky_scene_node_push_damage( &scene_buffer->node, use_dest_size ? KY_SCENE_DAMAGE_HARMLESS : KY_SCENE_DAMAGE_BOTH, NULL); scene_buffer->transform = transform; if (!use_dest_size) { ky_scene_node_push_damage(&scene_buffer->node, KY_SCENE_DAMAGE_BOTH, NULL); } } void ky_scene_buffer_set_opacity(struct ky_scene_buffer *scene_buffer, float opacity) { if (scene_buffer->opacity == opacity) { return; } bool harmful = scene_buffer->opacity == 0 || opacity == 0 || scene_buffer->opacity == 1 || opacity == 1; scene_buffer->opacity = opacity; /* push damage return early when has no buffer */ ky_scene_node_push_damage(&scene_buffer->node, harmful ? KY_SCENE_DAMAGE_BOTH : KY_SCENE_DAMAGE_HARMLESS, NULL); } void ky_scene_buffer_set_repeated(struct ky_scene_buffer *scene_buffer, bool repeated) { if (scene_buffer->repeated == repeated) { return; } scene_buffer->repeated = repeated; ky_scene_node_push_damage(&scene_buffer->node, KY_SCENE_DAMAGE_HARMLESS, NULL); } void ky_scene_buffer_send_dmabuf_feedback( const struct ky_scene *scene, struct ky_scene_buffer *scene_buffer, const struct wlr_linux_dmabuf_feedback_v1_init_options *options) { if (!scene->linux_dmabuf_v1) { return; } struct ky_scene_surface *surface = ky_scene_surface_try_from_buffer(scene_buffer); if (!surface) { return; } // compare to the previous options so that we don't send duplicate feedback events. if (memcmp(options, &scene_buffer->prev_feedback_options, sizeof(*options)) == 0) { return; } scene_buffer->prev_feedback_options = *options; struct wlr_linux_dmabuf_feedback_v1 feedback = { 0 }; if (!wlr_linux_dmabuf_feedback_v1_init_with_options(&feedback, options)) { return; } wlr_linux_dmabuf_v1_set_surface_feedback(scene->linux_dmabuf_v1, surface->surface, &feedback); wlr_linux_dmabuf_feedback_v1_finish(&feedback); } bool ky_scene_buffer_is_clipped(struct ky_scene_buffer *scene_buffer) { struct wlr_buffer *buffer = scene_buffer->buffer; if (!buffer) { return false; } struct wlr_fbox fbox = { 0, 0, buffer->width, buffer->height }; if (!wlr_fbox_empty(&scene_buffer->src_box) && !wlr_fbox_equal(&scene_buffer->src_box, &fbox)) { return true; } if (!pixman_region32_not_empty(&scene_buffer->node.clip_region)) { return false; } int width, height; buffer_get_dest_size(scene_buffer, &width, &height); pixman_region32_t hide_region; pixman_region32_init_rect(&hide_region, 0, 0, width, height); pixman_region32_subtract(&hide_region, &hide_region, &scene_buffer->node.clip_region); if (pixman_region32_not_empty(&hide_region)) { pixman_region32_fini(&hide_region); return true; } pixman_region32_fini(&hide_region); return false; } void ky_scene_node_update_outputs(struct ky_scene_node *node, struct wl_list *outputs, struct ky_scene_output *ignore, struct ky_scene_output *force) { if (!outputs) { outputs = &ky_scene_from_node(node)->outputs; } int x, y; ky_scene_node_coords(node, &x, &y); node->impl.update_outputs(node, x, y, outputs, ignore, force); } kylin-wayland-compositor/src/scene/animation.c0000664000175000017500000001517415160461067020504 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "output.h" #include "scene/animation.h" #include "util/time.h" enum animation_mask { ANIMATION_NONE = 0, ANIMATION_POSITION = 1 << 0, ANIMATION_SIZE = 1 << 1, }; union state { struct { int x, y; }; struct { int width, height; }; }; struct animation_state { struct animation *animation; uint32_t start; uint32_t duration; // ms union state src, dst; }; struct animation_entity { struct ky_scene_node *node; struct wlr_addon addon; struct kywc_output *output; struct wl_listener output_frame; uint32_t mask; struct animation_state state[4]; }; static void animation_entity_update_output(struct animation_entity *entity, bool clear) { int lx = 0, ly = 0; if (clear || !ky_scene_node_coords(entity->node, &lx, &ly)) { wl_list_remove(&entity->output_frame.link); wl_list_init(&entity->output_frame.link); entity->output = NULL; return; } struct kywc_output *output = kywc_output_at_point(lx, ly); struct output *o = output_from_kywc_output(output); output_schedule_frame(o->wlr_output); if (output == entity->output) { return; } entity->output = output; // kywc_log(KYWC_INFO, "Animation output to %s", output->name); wl_list_remove(&entity->output_frame.link); wl_signal_add(&o->scene_output->events.frame, &entity->output_frame); } static void entity_handle_output_frame(struct wl_listener *listener, void *data) { struct animation_entity *entity = wl_container_of(listener, entity, output_frame); if (entity->mask == ANIMATION_NONE) { return; } uint32_t current = current_time_msec(); // kywc_log(KYWC_INFO, "Animation start at %d", current); if (entity->mask & ANIMATION_POSITION) { struct animation_state *state = &entity->state[0]; uint32_t elapse = current - state->start; if (elapse >= state->duration) { ky_scene_node_set_position(entity->node, state->dst.x, state->dst.y); entity->mask &= ~ANIMATION_POSITION; } else { float percent = (float)elapse / state->duration; float value = animation_value(state->animation, percent); int delta_x = state->dst.x - state->src.x; int delta_y = state->dst.y - state->src.y; int x = state->src.x + value * delta_x; int y = state->src.y + value * delta_y; // kywc_log(KYWC_INFO, "Animation set pos to (%d, %d) ", x, y); ky_scene_node_set_position(entity->node, x, y); } } if (entity->mask & ANIMATION_SIZE) { struct animation_state *state = &entity->state[1]; uint32_t elapse = current - state->start; if (elapse >= state->duration) { ky_scene_rect_set_size(ky_scene_rect_from_node(entity->node), state->dst.width, state->dst.height); entity->mask &= ~ANIMATION_SIZE; } else { float percent = (float)elapse / state->duration; float value = animation_value(state->animation, percent); int delta_width = state->dst.width - state->src.width; int delta_height = state->dst.height - state->src.height; int width = state->src.width + value * delta_width; int height = state->src.height + value * delta_height; struct ky_scene_rect *rect = ky_scene_rect_from_node(entity->node); // kywc_log(KYWC_INFO, "Animation set size to (%d, %d) ", width, height); ky_scene_rect_set_size(rect, width, height); } } animation_entity_update_output(entity, entity->mask == ANIMATION_NONE); } static void animation_entity_addon_destroy(struct wlr_addon *addon) { struct animation_entity *entity = wl_container_of(addon, entity, addon); wl_list_remove(&entity->output_frame.link); wlr_addon_finish(addon); free(entity); } static const struct wlr_addon_interface animation_entity_addon_impl = { .name = "animation_entity", .destroy = animation_entity_addon_destroy, }; static struct animation_entity *animation_entity_create(struct ky_scene_node *node) { struct animation_entity *entity = calloc(1, sizeof(*entity)); if (!entity) { return NULL; } entity->node = node; wl_list_init(&entity->output_frame.link); entity->output_frame.notify = entity_handle_output_frame; wlr_addon_init(&entity->addon, &node->addons, node, &animation_entity_addon_impl); return entity; } static struct animation_entity *animation_entity_get(struct ky_scene_node *node) { struct wlr_addon *addon = wlr_addon_find(&node->addons, node, &animation_entity_addon_impl); struct animation_entity *entity = addon ? wl_container_of(addon, entity, addon) : animation_entity_create(node); if (entity) { animation_entity_update_output(entity, false); } return entity; } void ky_scene_node_set_position_with_animation(struct ky_scene_node *node, int x, int y, struct animation *animation, uint32_t duration) { if (!animation) { ky_scene_node_set_position(node, x, y); return; } struct animation_entity *entity = animation_entity_get(node); if (!entity) { ky_scene_node_set_position(node, x, y); return; } struct animation_state *state = &entity->state[0]; state->animation = animation; state->duration = duration; state->dst.x = x; state->dst.y = y; if (entity->mask & ANIMATION_POSITION) { return; } entity->mask |= ANIMATION_POSITION; state->start = current_time_msec(); state->src.x = node->x; state->src.y = node->y; } void ky_scene_rect_set_size_with_animation(struct ky_scene_rect *rect, int width, int height, struct animation *animation, uint32_t duration) { if (!animation) { ky_scene_rect_set_size(rect, width, height); return; } struct animation_entity *entity = animation_entity_get(&rect->node); if (!entity) { ky_scene_rect_set_size(rect, width, height); return; } struct animation_state *state = &entity->state[1]; state->animation = animation; state->duration = duration; state->dst.width = width; state->dst.height = height; if (entity->mask & ANIMATION_SIZE) { return; } entity->mask |= ANIMATION_SIZE; state->start = current_time_msec(); state->src.width = rect->width; state->src.height = rect->height; } kylin-wayland-compositor/src/scene/xdg_shell.c0000664000175000017500000001034215160461067020466 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "scene/xdg_shell.h" struct ky_scene_xdg_surface { struct ky_scene_tree *tree; struct wlr_xdg_surface *xdg_surface; struct ky_scene_tree *surface_tree; struct wl_listener tree_destroy; struct wl_listener xdg_surface_destroy; struct wl_listener xdg_surface_commit; struct wl_listener xdg_surface_map; }; static void scene_xdg_surface_handle_tree_destroy(struct wl_listener *listener, void *data) { struct ky_scene_xdg_surface *scene_xdg_surface = wl_container_of(listener, scene_xdg_surface, tree_destroy); // tree and surface_node will be cleaned up by scene_node_finish wl_list_remove(&scene_xdg_surface->tree_destroy.link); wl_list_remove(&scene_xdg_surface->xdg_surface_destroy.link); wl_list_remove(&scene_xdg_surface->xdg_surface_commit.link); wl_list_remove(&scene_xdg_surface->xdg_surface_map.link); free(scene_xdg_surface); } static void scene_xdg_surface_handle_xdg_surface_destroy(struct wl_listener *listener, void *data) { struct ky_scene_xdg_surface *scene_xdg_surface = wl_container_of(listener, scene_xdg_surface, xdg_surface_destroy); ky_scene_node_destroy(&scene_xdg_surface->tree->node); } static void scene_xdg_surface_update_position(struct ky_scene_xdg_surface *scene_xdg_surface) { struct wlr_xdg_surface *xdg_surface = scene_xdg_surface->xdg_surface; ky_scene_node_set_position(&scene_xdg_surface->surface_tree->node, -xdg_surface->geometry.x, -xdg_surface->geometry.y); if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { struct wlr_xdg_popup *popup = xdg_surface->popup; if (popup != NULL) { ky_scene_node_set_position(&scene_xdg_surface->tree->node, popup->current.geometry.x, popup->current.geometry.y); } } } static void scene_xdg_surface_handle_xdg_surface_commit(struct wl_listener *listener, void *data) { struct ky_scene_xdg_surface *scene_xdg_surface = wl_container_of(listener, scene_xdg_surface, xdg_surface_commit); scene_xdg_surface_update_position(scene_xdg_surface); } static void scene_xdg_surface_handle_xdg_surface_map(struct wl_listener *listener, void *data) { struct ky_scene_xdg_surface *scene_xdg_surface = wl_container_of(listener, scene_xdg_surface, xdg_surface_map); scene_xdg_surface_update_position(scene_xdg_surface); } struct ky_scene_tree *ky_scene_xdg_surface_create(struct ky_scene_tree *parent, struct wlr_xdg_surface *xdg_surface) { struct ky_scene_xdg_surface *scene_xdg_surface = calloc(1, sizeof(*scene_xdg_surface)); if (scene_xdg_surface == NULL) { return NULL; } scene_xdg_surface->xdg_surface = xdg_surface; scene_xdg_surface->tree = ky_scene_tree_create(parent); if (scene_xdg_surface->tree == NULL) { free(scene_xdg_surface); return NULL; } scene_xdg_surface->surface_tree = ky_scene_subsurface_tree_create(scene_xdg_surface->tree, xdg_surface->surface); if (scene_xdg_surface->surface_tree == NULL) { ky_scene_node_destroy(&scene_xdg_surface->tree->node); free(scene_xdg_surface); return NULL; } scene_xdg_surface->tree_destroy.notify = scene_xdg_surface_handle_tree_destroy; wl_signal_add(&scene_xdg_surface->tree->node.events.destroy, &scene_xdg_surface->tree_destroy); scene_xdg_surface->xdg_surface_destroy.notify = scene_xdg_surface_handle_xdg_surface_destroy; wl_signal_add(&xdg_surface->events.destroy, &scene_xdg_surface->xdg_surface_destroy); scene_xdg_surface->xdg_surface_commit.notify = scene_xdg_surface_handle_xdg_surface_commit; wl_signal_add(&xdg_surface->surface->events.commit, &scene_xdg_surface->xdg_surface_commit); scene_xdg_surface->xdg_surface_map.notify = scene_xdg_surface_handle_xdg_surface_map; wl_signal_add(&xdg_surface->surface->events.map, &scene_xdg_surface->xdg_surface_map); scene_xdg_surface_update_position(scene_xdg_surface); return scene_xdg_surface->tree; } kylin-wayland-compositor/src/scene/scene.c0000664000175000017500000011002615160461067017612 0ustar fengfeng// SPDX-FileCopyrightText: 2023 The wlroots contributors // SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include "output.h" #include "scene/render.h" #include "scene_p.h" #include "util/debug.h" #include "util/macros.h" /** * basic scene node */ static struct ky_scene_node *node_accept_input(struct ky_scene_node *node, int lx, int ly, double px, double py, double *rx, double *ry) { kywc_log(KYWC_ERROR, "Need to implement accept_input interface!"); assert(false); return NULL; } static void node_update_outputs(struct ky_scene_node *node, int lx, int ly, struct wl_list *outputs, struct ky_scene_output *ignore, struct ky_scene_output *force) { kywc_log(KYWC_ERROR, "Need to implement update_outputs interface!"); assert(false); } static void node_collect_damage(struct ky_scene_node *node, int lx, int ly, bool parent_enabled, uint32_t update_mask, pixman_region32_t *damage, pixman_region32_t *invisible, pixman_region32_t *affected) { kywc_log(KYWC_ERROR, "Need to implement collect_damage interface!"); assert(false); } static void node_get_opaque_region(struct ky_scene_node *node, pixman_region32_t *opaque) { pixman_region32_clear(opaque); } static void node_render(struct ky_scene_node *node, int lx, int ly, struct ky_scene_render_target *target) { kywc_log(KYWC_ERROR, "Need to implement render interface!"); assert(false); } static void node_get_bounding_box(struct ky_scene_node *node, struct wlr_box *box) { kywc_log(KYWC_ERROR, "Need to implement get_bounding_box interface!"); assert(false); } static void node_get_affected_bounding_box(struct ky_scene_node *node, uint32_t type, struct kywc_box *box) { struct wlr_box bbox; node->impl.get_bounding_box(node, &bbox); *box = (struct kywc_box){ bbox.x, bbox.y, bbox.width, bbox.height }; } static void node_push_damage(struct ky_scene_node *node, struct ky_scene_node *damage_node, uint32_t damage_type, pixman_region32_t *damage) { if (!node->enabled && !node->force_damage_event) { return; } assert(damage != NULL); /* root node has own push_damage */ assert(node->parent); /* emit damage when node content damaged or children damaged */ if ((node == damage_node && node->damage_type & KY_SCENE_DAMAGE_HARMLESS) || node != damage_node) { wl_signal_emit_mutable(&node->events.damage, damage_node); } if (!node->enabled) { return; } pixman_region32_translate(damage, node->x, node->y); node->parent->node.impl.push_damage(&node->parent->node, damage_node, damage_type | node->damage_type, damage); } static void node_destroy(struct ky_scene_node *node) { wlr_addon_set_finish(&node->addons); ky_scene_node_set_enabled(node, false); pixman_region32_fini(&node->extend_render_region); pixman_region32_fini(&node->visible_region); pixman_region32_fini(&node->input_region); pixman_region32_fini(&node->clip_region); pixman_region32_fini(&node->blur.region); if (node->type == KY_SCENE_NODE_BUFFER) { struct ky_scene_buffer *scene_buffer = ky_scene_buffer_from_node(node); assert(wl_list_empty(&scene_buffer->events.outputs_update.listener_list)); assert(wl_list_empty(&scene_buffer->events.output_enter.listener_list)); assert(wl_list_empty(&scene_buffer->events.output_leave.listener_list)); assert(wl_list_empty(&scene_buffer->events.output_sample.listener_list)); assert(wl_list_empty(&scene_buffer->events.frame_done.listener_list)); } assert(wl_list_empty(&node->events.add_effect.listener_list)); assert(wl_list_empty(&node->events.damage.listener_list)); assert(wl_list_empty(&node->events.destroy.listener_list)); wl_list_remove(&node->link); free(node); } void ky_scene_node_init(struct ky_scene_node *node, struct ky_scene_tree *parent) { *node = (struct ky_scene_node){ .parent = parent, .enabled = true, .damage_type = KY_SCENE_DAMAGE_HARMFUL, .blur.iterations = 4, .blur.offset = 5.5f, .blur.alpha = 1.0f, .impl = { .accept_input = node_accept_input, .update_outputs = node_update_outputs, .collect_damage = node_collect_damage, .get_opaque_region = node_get_opaque_region, .render = node_render, .get_bounding_box = node_get_bounding_box, .get_affected_bounding_box = node_get_affected_bounding_box, .push_damage = node_push_damage, .destroy = node_destroy, }, }; wl_list_init(&node->link); wl_signal_init(&node->events.damage); wl_signal_init(&node->events.destroy); wl_signal_init(&node->events.add_effect); pixman_region32_init(&node->extend_render_region); pixman_region32_init(&node->visible_region); pixman_region32_init(&node->input_region); pixman_region32_init(&node->clip_region); pixman_region32_init(&node->blur.region); if (parent != NULL) { wl_list_insert(parent->children.prev, &node->link); } wlr_addon_set_init(&node->addons); } /** * scene tree */ struct ky_scene_tree *ky_scene_tree_from_node(struct ky_scene_node *node) { struct ky_scene_tree *tree = wl_container_of(node, tree, node); return tree; } static struct ky_scene_node *tree_accept_input(struct ky_scene_node *node, int lx, int ly, double px, double py, double *rx, double *ry) { /* skip disabled or input bypassed nodes */ if (!node->enabled || node->input_bypassed) { return NULL; } struct ky_scene_tree *scene_tree = ky_scene_tree_from_node(node); struct ky_scene_node *child, *found; double nx, ny; wl_list_for_each_reverse(child, &scene_tree->children, link) { found = child->impl.accept_input(child, lx + child->x, ly + child->y, px, py, &nx, &ny); if (found) { *rx = nx; *ry = ny; return found; } } return NULL; } static void tree_update_outputs(struct ky_scene_node *node, int lx, int ly, struct wl_list *outputs, struct ky_scene_output *ignore, struct ky_scene_output *force) { // skip node enabled check struct ky_scene_tree *scene_tree = ky_scene_tree_from_node(node); struct ky_scene_node *child; wl_list_for_each(child, &scene_tree->children, link) { child->impl.update_outputs(child, lx + child->x, ly + child->y, outputs, ignore, force); } } static void tree_collect_damage(struct ky_scene_node *node, int lx, int ly, bool parent_enabled, uint32_t damage_type, pixman_region32_t *damage, pixman_region32_t *invisible, pixman_region32_t *affected) { bool node_enabled = parent_enabled && node->enabled; struct ky_scene_tree *scene_tree = ky_scene_tree_from_node(node); struct ky_scene_node *child; wl_list_for_each_reverse(child, &scene_tree->children, link) { child->impl.collect_damage(child, lx + child->x, ly + child->y, node_enabled, damage_type | child->damage_type, damage, invisible, affected); } node->last_enabled = node_enabled; node->damage_type = KY_SCENE_DAMAGE_NONE; } static void tree_get_opaque_region(struct ky_scene_node *node, pixman_region32_t *opaque) { /* skip disabled */ pixman_region32_clear(opaque); if (!node->enabled) { return; } struct ky_scene_node *child; pixman_region32_t child_opaque; pixman_region32_init(&child_opaque); struct ky_scene_tree *scene_tree = ky_scene_tree_from_node(node); wl_list_for_each_reverse(child, &scene_tree->children, link) { child->impl.get_opaque_region(child, &child_opaque); pixman_region32_translate(&child_opaque, child->x, child->y); pixman_region32_union(opaque, opaque, &child_opaque); } pixman_region32_fini(&child_opaque); } static void tree_render(struct ky_scene_node *node, int lx, int ly, struct ky_scene_render_target *target) { struct ky_scene_tree *scene_tree = ky_scene_tree_from_node(node); if (!node->enabled || wl_list_empty(&scene_tree->children)) { return; } /* from bottom to top */ struct ky_scene_node *child; wl_list_for_each(child, &scene_tree->children, link) { if (child->has_effect && (target->options & KY_SCENE_RENDER_DISABLE_SUBEFFECT_NODE)) { continue; } child->impl.render(child, lx + child->x, ly + child->y, target); } } static void tree_get_affected_bounding_box(struct ky_scene_node *node, uint32_t type, struct kywc_box *box) { struct ky_scene_tree *tree = ky_scene_tree_from_node(node); if (wl_list_empty(&tree->children)) { *box = (struct kywc_box){ 0 }; return; } int min_x1 = INT_MAX, min_y1 = INT_MAX; int max_x2 = INT_MIN, max_y2 = INT_MIN; struct kywc_box child_box; struct ky_scene_node *child; wl_list_for_each(child, &tree->children, link) { if (!ky_scene_node_is_visible(child)) { continue; } child->impl.get_affected_bounding_box(child, type, &child_box); if (kywc_box_empty(&child_box)) { continue; } child_box.x += child->x; child_box.y += child->y; min_x1 = MIN(min_x1, child_box.x); min_y1 = MIN(min_y1, child_box.y); max_x2 = MAX(max_x2, child_box.x + child_box.width); max_y2 = MAX(max_y2, child_box.y + child_box.height); } box->x = min_x1 == INT_MAX ? 0 : min_x1; box->y = min_y1 == INT_MAX ? 0 : min_y1; box->width = max_x2 == INT_MIN ? 0 : max_x2 - min_x1; box->height = max_y2 == INT_MIN ? 0 : max_y2 - min_y1; } static void tree_get_bounding_box(struct ky_scene_node *node, struct wlr_box *box) { struct kywc_box bbox; tree_get_affected_bounding_box(node, KY_SCENE_BOUNDING_WITHOUT_EFFECT, &bbox); *box = (struct wlr_box){ bbox.x, bbox.y, bbox.width, bbox.height }; } static void tree_destroy(struct ky_scene_node *node) { struct ky_scene_tree *tree = ky_scene_tree_from_node(node); struct ky_scene_node *child, *child_tmp; wl_list_for_each_safe(child, child_tmp, &tree->children, link) { ky_scene_node_destroy(child); } /* just call node_destroy, we not save destroy_func in scene_tree */ node_destroy(node); } static void ky_scene_tree_init(struct ky_scene_tree *tree, struct ky_scene_tree *parent) { *tree = (struct ky_scene_tree){ 0 }; ky_scene_node_init(&tree->node, parent); tree->node.type = KY_SCENE_NODE_TREE; /* override node interface */ tree->node.impl.accept_input = tree_accept_input; tree->node.impl.update_outputs = tree_update_outputs; tree->node.impl.collect_damage = tree_collect_damage; tree->node.impl.get_opaque_region = tree_get_opaque_region; tree->node.impl.render = tree_render; tree->node.impl.get_bounding_box = tree_get_bounding_box; tree->node.impl.get_affected_bounding_box = tree_get_affected_bounding_box; tree->node.impl.destroy = tree_destroy; wl_list_init(&tree->children); } /** * scene root based on tree */ static void scene_damage_outputs(struct ky_scene *scene, const pixman_region32_t *damage) { struct wlr_box geo; struct ky_scene_output *scene_output; wl_list_for_each(scene_output, &scene->outputs, link) { if (!wlr_box_empty(&scene_output->viewport.src)) { geo = scene_output->viewport.src; } else { geo = (struct wlr_box){ scene_output->geometry.x, scene_output->geometry.y, scene_output->geometry.width, scene_output->geometry.height }; } if (pixman_region32_contains_rectangle( damage, &(pixman_box32_t){ geo.x, geo.y, geo.x + geo.width, geo.y + geo.height }) != PIXMAN_REGION_OUT) { output_schedule_frame(scene_output->output); } } } static void scene_push_damage(struct ky_scene_node *node, struct ky_scene_node *damage_node, uint32_t damage_type, pixman_region32_t *damage) { if (!node->enabled) { return; } assert(damage != NULL); pixman_region32_translate(damage, node->x, node->y); struct ky_scene *scene = ky_scene_from_node(node); damage_type |= node->damage_type; assert(damage_type != KY_SCENE_DAMAGE_NONE); if (damage_type == KY_SCENE_DAMAGE_HARMLESS && damage_node->last_enabled) { assert(damage_node->type == KY_SCENE_NODE_RECT || damage_node->type == KY_SCENE_NODE_BUFFER); pixman_region32_intersect(damage, damage, &damage_node->visible_region); ky_scene_add_damage(scene, damage); return; } scene_damage_outputs(scene, damage); pixman_region32_union(&scene->pushed_damage, &scene->pushed_damage, damage); } static void scene_destroy(struct ky_scene_node *node) { struct ky_scene *scene = ky_scene_from_node(node); struct ky_scene_output *output, *tmp; wl_list_for_each_safe(output, tmp, &scene->outputs, link) { ky_scene_output_destroy(output); } pixman_region32_fini(&scene->collected_damage); pixman_region32_fini(&scene->collected_invisible); pixman_region32_fini(&scene->pushed_damage); scene->tree_destroy(node); } struct ky_scene *ky_scene_create(void) { struct ky_scene *scene = calloc(1, sizeof(*scene)); if (!scene) { return NULL; } ky_scene_tree_init(&scene->tree, NULL); scene->tree.node.role.type = KY_SCENE_ROLE_ROOT; scene->tree.node.role.data = scene; scene->tree_destroy = scene->tree.node.impl.destroy; scene->tree.node.impl.destroy = scene_destroy; scene->tree.node.impl.push_damage = scene_push_damage; wl_list_init(&scene->outputs); pixman_region32_init(&scene->collected_damage); pixman_region32_init(&scene->collected_invisible); pixman_region32_init(&scene->pushed_damage); return scene; } struct ky_scene *ky_scene_from_node(struct ky_scene_node *node) { struct ky_scene_tree *tree = node->parent; if (!tree) { tree = ky_scene_tree_from_node(node); } else { while (tree->node.parent != NULL) { tree = tree->node.parent; } } struct ky_scene *scene = wl_container_of(tree, scene, tree); return scene; } void ky_scene_damage_whole(struct ky_scene *scene) { struct ky_scene_output *output; wl_list_for_each(output, &scene->outputs, link) { ky_scene_output_damage_whole(output); } } struct ky_scene_tree *ky_scene_tree_create(struct ky_scene_tree *parent) { assert(parent); struct ky_scene_tree *tree = calloc(1, sizeof(*tree)); if (!tree) { return NULL; } ky_scene_tree_init(tree, parent); return tree; } static void scene_handle_linux_dmabuf_v1_destroy(struct wl_listener *listener, void *data) { struct ky_scene *scene = wl_container_of(listener, scene, linux_dmabuf_v1_destroy); wl_list_remove(&scene->linux_dmabuf_v1_destroy.link); wl_list_init(&scene->linux_dmabuf_v1_destroy.link); scene->linux_dmabuf_v1 = NULL; } void ky_scene_set_linux_dmabuf_v1(struct ky_scene *scene, struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1) { assert(scene->linux_dmabuf_v1 == NULL); scene->linux_dmabuf_v1 = linux_dmabuf_v1; scene->linux_dmabuf_v1_destroy.notify = scene_handle_linux_dmabuf_v1_destroy; wl_signal_add(&linux_dmabuf_v1->events.destroy, &scene->linux_dmabuf_v1_destroy); } static void handle_tearing_control_v1_destroy(struct wl_listener *listener, void *data) { struct ky_scene *scene = wl_container_of(listener, scene, tearing_control_v1_destroy); wl_list_remove(&scene->tearing_control_v1_destroy.link); scene->tearing_control_v1 = NULL; } void ky_scene_set_tearing_control_v1(struct ky_scene *scene, struct wlr_tearing_control_manager_v1 *tearing_control_v1) { assert(scene->tearing_control_v1 == NULL); scene->tearing_control_v1 = tearing_control_v1; scene->tearing_control_v1_destroy.notify = handle_tearing_control_v1_destroy; wl_signal_add(&tearing_control_v1->events.destroy, &scene->tearing_control_v1_destroy); } bool ky_scene_surface_is_tearing_allowed(struct ky_scene *scene, struct wlr_surface *surface) { if (!scene->tearing_control_v1 || !surface) { return false; } enum wp_tearing_control_v1_presentation_hint hint = wlr_tearing_control_manager_v1_surface_hint_from_surface(scene->tearing_control_v1, surface); kywc_log(KYWC_DEBUG, "Get surface tearing hint: %d", hint); return hint == WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC; } bool ky_scene_is_tearing_needed(struct ky_scene *scene) { if (!scene->tearing_control_v1) { return false; } struct wlr_tearing_control_v1 *hint; wl_list_for_each(hint, &scene->tearing_control_v1->surface_hints, link) { if (hint->current == WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC) { return true; } } return false; } void ky_scene_collect_damage(struct ky_scene *scene) { KY_PROFILE_ZONE(zone, __func__); /* skip collect damage if no pushed_damage */ if (pixman_region32_not_empty(&scene->pushed_damage)) { // invisible region will be recalculated pixman_region32_clear(&scene->collected_invisible); // get current damage in all scene nodes struct ky_scene_node *root = &scene->tree.node; root->impl.collect_damage(root, root->x, root->y, true, false, &scene->collected_damage, &scene->collected_invisible, &scene->pushed_damage); pixman_region32_clear(&scene->pushed_damage); } if (!pixman_region32_not_empty(&scene->collected_damage)) { KY_PROFILE_ZONE_END(zone); return; } /* distribute damage to outputs */ pixman_region32_t region; struct wlr_box box; struct ky_scene_output *output; wl_list_for_each(output, &scene->outputs, link) { if (!wlr_box_empty(&output->viewport.src)) { box = output->viewport.src; } else { box = (struct wlr_box){ output->geometry.x, output->geometry.y, output->geometry.width, output->geometry.height }; } pixman_region32_init_rect(®ion, box.x, box.y, box.width, box.height); pixman_region32_intersect(®ion, ®ion, &scene->collected_damage); pixman_region32_union(&output->collected_damage, &output->collected_damage, ®ion); pixman_region32_fini(®ion); } pixman_region32_clear(&scene->collected_damage); KY_PROFILE_ZONE_END(zone); } void ky_scene_add_damage(struct ky_scene *scene, const pixman_region32_t *damage) { if (!pixman_region32_not_empty(damage)) { return; } pixman_region32_union(&scene->collected_damage, &scene->collected_damage, damage); scene_damage_outputs(scene, damage); } /** * scene node operations */ void ky_scene_node_destroy(struct ky_scene_node *node) { if (!node) { return; } /** * Destroy node must emit signal first, the destroy_node doesn't emit destroy signal. */ wl_signal_emit_mutable(&node->events.destroy, NULL); node->impl.destroy(node); } void ky_scene_node_set_enabled(struct ky_scene_node *node, bool enabled) { if (node->enabled == enabled) { return; } ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_HARMFUL, NULL); node->enabled = enabled; ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_HARMFUL, NULL); } void ky_scene_node_set_input_bypassed(struct ky_scene_node *node, bool bypassed) { node->input_bypassed = bypassed; } void ky_scene_node_force_damage_event(struct ky_scene_node *node, bool force) { /** * damage event for this node is may still not emitted, * early returned in ky_scene_node_push_damage by node->enabled */ node->force_damage_event = force; } void ky_scene_node_set_position(struct ky_scene_node *node, int x, int y) { if (node->x == x && node->y == y) { return; } ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_HARMFUL, NULL); node->x = x; node->y = y; ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_HARMFUL, NULL); ky_scene_node_update_outputs(node, NULL, NULL, NULL); } void ky_scene_node_place_above(struct ky_scene_node *node, struct ky_scene_node *sibling) { assert(node != sibling); assert(node->parent == sibling->parent); if (node->link.prev == &sibling->link) { return; } ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_HARMFUL, NULL); wl_list_remove(&node->link); wl_list_insert(&sibling->link, &node->link); ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_HARMFUL, NULL); } void ky_scene_node_place_below(struct ky_scene_node *node, struct ky_scene_node *sibling) { assert(node != sibling); assert(node->parent == sibling->parent); if (node->link.next == &sibling->link) { return; } ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_HARMFUL, NULL); wl_list_remove(&node->link); wl_list_insert(sibling->link.prev, &node->link); ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_HARMFUL, NULL); } void ky_scene_node_raise_to_top(struct ky_scene_node *node) { struct ky_scene_node *current_top = wl_container_of(node->parent->children.prev, current_top, link); if (node == current_top) { return; } ky_scene_node_place_above(node, current_top); } void ky_scene_node_lower_to_bottom(struct ky_scene_node *node) { struct ky_scene_node *current_bottom = wl_container_of(node->parent->children.next, current_bottom, link); if (node == current_bottom) { return; } ky_scene_node_place_below(node, current_bottom); } void ky_scene_node_reparent(struct ky_scene_node *node, struct ky_scene_tree *new_parent) { assert(new_parent != NULL); if (node->parent == new_parent) { return; } /* Ensure that a node cannot become its own ancestor. */ for (struct ky_scene_tree *ancestor = new_parent; ancestor != NULL; ancestor = ancestor->node.parent) { assert(&ancestor->node != node); } ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_HARMFUL, NULL); wl_list_remove(&node->link); node->parent = new_parent; wl_list_insert(new_parent->children.prev, &node->link); ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_HARMFUL, NULL); ky_scene_node_update_outputs(node, NULL, NULL, NULL); } bool ky_scene_node_coords(struct ky_scene_node *node, int *lx_ptr, int *ly_ptr) { assert(node); *lx_ptr = 0, *ly_ptr = 0; bool enabled = true; while (true) { enabled = enabled && node->enabled; *lx_ptr += node->x; *ly_ptr += node->y; if (!node->parent) { break; } node = &node->parent->node; } return enabled; } struct ky_scene_node *ky_scene_node_at(struct ky_scene_node *node, double lx, double ly, double *nx, double *ny) { struct ky_scene_node *found; double sx, sy; int x, y; ky_scene_node_coords(node, &x, &y); found = node->impl.accept_input(node, x, y, lx, ly, &sx, &sy); if (!found) { return NULL; } if (nx) { *nx = sx; } if (ny) { *ny = sy; } return found; } void ky_scene_node_set_input_region(struct ky_scene_node *node, const pixman_region32_t *region) { /* tree is not support currently */ assert(node->type == KY_SCENE_NODE_RECT || node->type == KY_SCENE_NODE_BUFFER); if (pixman_region32_equal(&node->input_region, region)) { return; } pixman_region32_copy(&node->input_region, region); } void ky_scene_node_set_clip_region(struct ky_scene_node *node, const pixman_region32_t *region) { /* tree is not support currently */ assert(node->type == KY_SCENE_NODE_RECT || node->type == KY_SCENE_NODE_BUFFER); if (pixman_region32_equal(&node->clip_region, region)) { return; } pixman_region32_copy(&node->clip_region, region); ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_BOTH, NULL); } void ky_scene_node_set_blur_region(struct ky_scene_node *node, const pixman_region32_t *region) { /* tree is not support currently */ assert(node->type == KY_SCENE_NODE_RECT || node->type == KY_SCENE_NODE_BUFFER); bool has_blur = region != NULL; /* early return if still has no blur */ if (!has_blur && has_blur == node->has_blur) { return; } /* add blur or remove blur */ if (node->has_blur != has_blur) { node->has_blur = has_blur; if (has_blur) { pixman_region32_copy(&node->blur.region, region); } else { pixman_region32_clear(&node->blur.region); } } else { /* change blur region */ if (pixman_region32_equal(&node->blur.region, region)) { return; } pixman_region32_copy(&node->blur.region, region); } ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_HARMFUL, NULL); } void ky_scene_node_set_blur_level(struct ky_scene_node *node, uint32_t iterations, float offset) { /* tree is not support currently */ assert(node->type == KY_SCENE_NODE_RECT || node->type == KY_SCENE_NODE_BUFFER); if (node->blur.iterations == iterations && node->blur.offset == offset) { return; } node->blur.iterations = iterations; node->blur.offset = offset; if (node->has_blur) { pixman_region32_t *region = pixman_region32_not_empty(&node->blur.region) ? &node->blur.region : NULL; ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_HARMFUL, region); } } void ky_scene_node_set_blur_opacity(struct ky_scene_node *node, float opacity) { /* tree is not support currently */ assert(node->type == KY_SCENE_NODE_RECT || node->type == KY_SCENE_NODE_BUFFER); if (DOUBLE_EQUAL(node->blur.alpha, opacity)) { return; } node->blur.alpha = pow(opacity, 0.4); if (node->has_blur) { pixman_region32_t *region = pixman_region32_not_empty(&node->blur.region) ? &node->blur.region : NULL; ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_HARMFUL, region); } } static void scene_node_get_blur_region(struct ky_scene_node *node, pixman_region32_t *region) { /* tree is not support currently */ assert(node->type == KY_SCENE_NODE_RECT || node->type == KY_SCENE_NODE_BUFFER); if (!node->has_blur) { pixman_region32_clear(region); return; } if (pixman_region32_not_empty(&node->blur.region)) { pixman_region32_copy(region, &node->blur.region); } else { struct wlr_box box; node->impl.get_bounding_box(node, &box); pixman_region32_fini(region); pixman_region32_init_rect(region, box.x, box.y, box.width, box.height); if (pixman_region32_not_empty(&node->clip_region)) { pixman_region32_intersect(region, region, &node->clip_region); } } } /* the x、y value is the offset relative to the first node */ static void node_for_each_enabled(struct ky_scene_node *node, int x, int y, void (*callback)(struct ky_scene_node *, int, int, void *), void *data) { if (!node->enabled) { return; } x += node->x; y += node->y; callback(node, x, y, data); if (node->type != KY_SCENE_NODE_TREE) { return; } struct ky_scene_tree *tree = ky_scene_tree_from_node(node); struct ky_scene_node *pos; wl_list_for_each(pos, &tree->children, link) { node_for_each_enabled(pos, x, y, callback, data); } } static void get_node_blur_info(struct ky_scene_node *node, int x, int y, void *data) { /* tree is not support currently */ if (!node->has_blur || (node->type != KY_SCENE_NODE_RECT && node->type != KY_SCENE_NODE_BUFFER)) { return; } pixman_region32_t blur_region; pixman_region32_init(&blur_region); scene_node_get_blur_region(node, &blur_region); pixman_region32_translate(&blur_region, x, y); struct blur_info *info = data; pixman_region32_union(&info->region, &info->region, &blur_region); pixman_region32_fini(&blur_region); info->iterations = info->iterations < node->blur.iterations ? node->blur.iterations : info->iterations; info->offset = info->offset < node->blur.offset ? node->blur.offset : info->offset; /* use the minimum alpha */ info->alpha = info->alpha < node->blur.alpha ? info->alpha : node->blur.alpha; } void ky_scene_node_get_blur_info(struct ky_scene_node *node, struct blur_info *info) { info->alpha = 1; info->offset = 0; info->iterations = 0; pixman_region32_clear(&info->region); if (node->type != KY_SCENE_NODE_TREE) { get_node_blur_info(node, 0, 0, info); return; } struct ky_scene_node *pos; struct ky_scene_tree *tree = ky_scene_tree_from_node(node); wl_list_for_each(pos, &tree->children, link) { node_for_each_enabled(pos, 0, 0, get_node_blur_info, info); } } void ky_scene_node_set_radius(struct ky_scene_node *node, const int radius[static 4]) { /* tree is not support currently */ assert(node->type == KY_SCENE_NODE_RECT || node->type == KY_SCENE_NODE_BUFFER); if (memcmp(node->radius, radius, sizeof(node->radius)) == 0) { return; } memcpy(node->radius, radius, sizeof(node->radius)); ky_scene_node_push_damage(node, KY_SCENE_DAMAGE_BOTH, NULL); } struct round_corner { int x, y; int radius; }; static void get_node_radius(struct ky_scene_node *node, int x, int y, void *data) { if (node->type != KY_SCENE_NODE_RECT && node->type != KY_SCENE_NODE_BUFFER) { return; } int x1 = x, y1 = y; int x2 = x1, y2 = y1; if (node->type == KY_SCENE_NODE_RECT) { struct ky_scene_rect *rect = ky_scene_rect_from_node(node); x2 += rect->width; y2 += rect->height; } else if (node->type == KY_SCENE_NODE_BUFFER) { struct ky_scene_buffer *buffer = ky_scene_buffer_from_node(node); x2 += buffer->dst_width; y2 += buffer->dst_height; } /* don't include decoration radius, because decoration radius isn't in rect node radius */ struct round_corner *corner = data; int index = KY_SCENE_ROUND_CORNER_RB; if (node->radius[index] > 0 && (corner[index].x < x2 || corner[index].y < y2)) { corner[index].x = x2; corner[index].y = y2; corner[index].radius = node->radius[index]; } index = KY_SCENE_ROUND_CORNER_RT; if (node->radius[index] > 0 && (corner[index].x < x2 || corner[index].y > y1)) { corner[index].x = x2; corner[index].y = y1; corner[index].radius = node->radius[index]; } index = KY_SCENE_ROUND_CORNER_LB; if (node->radius[index] > 0 && (corner[index].x > x1 || corner[index].y < y2)) { corner[index].x = x1; corner[index].y = y2; corner[index].radius = node->radius[index]; } index = KY_SCENE_ROUND_CORNER_LT; if (node->radius[index] > 0 && (corner[index].x < x1 || corner[index].y < y1)) { corner[index].x = x1; corner[index].y = y1; corner[index].radius = node->radius[index]; } } void ky_scene_node_get_radius(struct ky_scene_node *node, enum ky_scene_round_corners_type type, int radius[static 4]) { if (node->type == KY_SCENE_NODE_RECT || node->type == KY_SCENE_NODE_BUFFER) { memcpy(radius, node->radius, sizeof(node->radius)); return; } /* 0 = right-bottom, 1 = right-top, 2 = left-bottom, 3 = left-top */ struct round_corner corner[4] = { { .x = INT_MIN, .y = INT_MIN }, { .x = INT_MIN, .y = INT_MIN }, { .x = INT_MIN, .y = INT_MIN }, { .x = INT_MIN, .y = INT_MIN }, }; node_for_each_enabled(node, -node->x, -node->y, get_node_radius, &corner); if (type == KY_SCENE_ROUND_CORNERS_FOR_OUTERMOST) { for (int i = 0; i < 4; ++i) { radius[i] = corner[i].radius; } return; } struct wlr_box box = { 0 }; node->impl.get_bounding_box(node, &box); int x1 = box.x, y1 = box.y; int x2 = box.x + box.width, y2 = box.y + box.height; struct point { int x, y; } points[4] = { { x2, y2 }, { x2, y1 }, { x1, y2 }, { x1, y1 } }; for (int i = 0; i < 4; ++i) { if (corner[i].radius > 0 && corner[i].x == points[i].x && corner[i].y == points[i].y) { radius[i] = corner[i].radius; } } } bool ky_scene_node_is_visible(struct ky_scene_node *node) { if (!node->enabled) { return false; } if (node->type == KY_SCENE_NODE_TREE) { struct ky_scene_tree *tree = ky_scene_tree_from_node(node); return !wl_list_empty(&tree->children); } else if (node->type == KY_SCENE_NODE_RECT) { struct ky_scene_rect *rect = ky_scene_rect_from_node(node); return rect->color[3] != 0; } else if (node->type == KY_SCENE_NODE_BUFFER) { struct ky_scene_buffer *buffer = ky_scene_buffer_from_node(node); return buffer->buffer && buffer->opacity != 0; } return false; } void ky_scene_node_push_damage(struct ky_scene_node *node, enum ky_scene_damage_type damage_type, const pixman_region32_t *damage) { if (!node->enabled) { return; } pixman_region32_t damage_region; pixman_region32_init(&damage_region); if (damage == NULL) { struct wlr_box box = { 0 }; node->impl.get_bounding_box(node, &box); pixman_region32_init_rect(&damage_region, box.x, box.y, box.width, box.height); } else { pixman_region32_copy(&damage_region, damage); } if (!pixman_region32_not_empty(&damage_region)) { pixman_region32_fini(&damage_region); return; } node->damage_type |= damage_type; node->impl.push_damage(node, node, 0, &damage_region); pixman_region32_fini(&damage_region); } void ky_scene_corner_region(pixman_region32_t *region, int width, int height, const int radius[static 4]) { int rb = radius[KY_SCENE_ROUND_CORNER_RB]; int rt = radius[KY_SCENE_ROUND_CORNER_RT]; int lb = radius[KY_SCENE_ROUND_CORNER_LB]; int lt = radius[KY_SCENE_ROUND_CORNER_LT]; if (rb > 0) { pixman_region32_union_rect(region, region, width - rb, height - rb, rb, rb); } if (rt > 0) { pixman_region32_union_rect(region, region, width - rt, 0, rt, rt); } if (lb > 0) { pixman_region32_union_rect(region, region, 0, height - lb, lb, lb); } if (lt > 0) { pixman_region32_union_rect(region, region, 0, 0, lt, lt); } } void ky_scene_log_region(enum kywc_log_level level, const char *desc, const pixman_region32_t *region) { int rects_len; const pixman_box32_t *rects = pixman_region32_rectangles(region, &rects_len); kywc_log(level, "%s region dump: %d nrects with extend (%d, %d) %d x %d", desc, rects_len, region->extents.x1, region->extents.y1, region->extents.x2 - region->extents.x1, region->extents.y2 - region->extents.y1); for (int i = 0; i < rects_len; i++) { const pixman_box32_t *rect = &rects[i]; kywc_log(level, "\trect[%d]: (%d, %d) %d x %d", i, rect->x1, rect->y1, rect->x2 - rect->x1, rect->y2 - rect->y1); } } void ky_scene_log_box(enum kywc_log_level level, const char *desc, struct kywc_box *box) { kywc_log(level, "%s box: (%d, %d) %d x %d", desc, box->x, box->y, box->width, box->height); } void ky_scene_log_fbox(enum kywc_log_level level, const char *desc, struct kywc_fbox *box) { kywc_log(level, "%s box: (%.3f, %.3f) %.3f x %.3f", desc, box->x, box->y, box->width, box->height); } kylin-wayland-compositor/src/painter/0000775000175000017500000000000015160460057016714 5ustar fengfengkylin-wayland-compositor/src/painter/painter_p.h0000664000175000017500000000150415160460057021046 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _PAINTER_P_H_ #define _PAINTER_P_H_ #include #include #include "painter.h" struct painter_buffer { struct wlr_buffer base; size_t stride; float scale; int dst_width; int dst_height; void *data; bool own_data; }; bool render_buffer(struct painter_buffer *buffer, struct draw_info *info); void text_get_size(const char *font_name, int font_size, const char *text, int *width, int *height); void text_get_metrics(const char *font_name, int font_size, int *ascent, int *descent); uint8_t *image_read_from_file(const char *filename, int *width, int *height); bool image_write_to_file(struct painter_buffer *buffer, const char *filename); #endif /* _PAINTER_P_H_ */ kylin-wayland-compositor/src/painter/meson.build0000664000175000017500000000010415160460057021051 0ustar fengfengwlcom_sources += files( 'image.c', 'painter.c', 'render.c', ) kylin-wayland-compositor/src/painter/image.c0000664000175000017500000004171515160460057020152 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include #include "painter_p.h" #include "util/file.h" #include "util/hash_table.h" static uint8_t *do_decode_jpeg(const uint8_t *data, size_t size, int *width, int *height) { struct jpeg_decompress_struct jpg; struct jpeg_error_mgr err; jpg.err = jpeg_std_error(&err); jpeg_create_decompress(&jpg); jpeg_mem_src(&jpg, data, size); jpeg_read_header(&jpg, TRUE); jpeg_start_decompress(&jpg); #ifdef LIBJPEG_TURBO_VERSION jpg.out_color_space = JCS_EXT_BGRA; #endif // LIBJPEG_TURBO_VERSION uint32_t *buffer = malloc(jpg.output_width * jpg.output_height * 4); if (!buffer) { jpeg_destroy_decompress(&jpg); return NULL; } while (jpg.output_scanline < jpg.output_height) { uint8_t *line = (uint8_t *)&buffer[jpg.output_scanline * jpg.output_width]; jpeg_read_scanlines(&jpg, &line, 1); // convert grayscale to argb if (jpg.out_color_components == 1) { uint32_t *pixel = (uint32_t *)line; for (int x = jpg.output_width - 1; x >= 0; --x) { const uint8_t src = *(line + x); pixel[x] = ((uint32_t)0xff << 24) | (uint32_t)src << 16 | (uint32_t)src << 8 | src; } } #ifndef LIBJPEG_TURBO_VERSION // convert rgb to argb if (jpg.out_color_components == 3) { uint32_t *pixel = (uint32_t *)line; for (int x = jpg.output_width - 1; x >= 0; --x) { const uint8_t *src = line + x * 3; pixel[x] = ((uint32_t)0xff << 24) | (uint32_t)src[0] << 16 | (uint32_t)src[1] << 8 | src[2]; } } #endif // LIBJPEG_TURBO_VERSION } jpeg_finish_decompress(&jpg); jpeg_destroy_decompress(&jpg); *width = jpg.output_width; *height = jpg.output_height; return (uint8_t *)buffer; } // PNG memory reader struct mem_reader { const uint8_t *data; const size_t size; size_t position; }; static void png_reader(png_structp png, png_bytep buffer, size_t size) { struct mem_reader *reader = (struct mem_reader *)png_get_io_ptr(png); if (reader && reader->position + size < reader->size) { memcpy(buffer, reader->data + reader->position, size); reader->position += size; } else { png_error(png, "No data in PNG reader"); } } static inline uint8_t multiply_alpha(uint8_t alpha, uint8_t color) { int temp = alpha * color + 0x80; return (temp + (temp >> 8)) >> 8; } static void premultiply_data(png_structp png, png_row_infop row_info, png_bytep data) { const size_t num_pixels = row_info->rowbytes / 4; uint32_t *dst = (uint32_t *)data; for (size_t i = 0; i < num_pixels; ++i) { uint8_t r = data[i * 4 + 0]; uint8_t g = data[i * 4 + 1]; uint8_t b = data[i * 4 + 2]; uint8_t a = data[i * 4 + 3]; if (a == 0) { dst[i] = 0; } else if (a == 0xFF) { dst[i] = ((uint32_t)a << 24) | (r << 16) | (g << 8) | b; } else { dst[i] = ((uint32_t)a << 24) | (multiply_alpha(a, r) << 16) | (multiply_alpha(a, g) << 8) | multiply_alpha(a, b); } } } static uint8_t *do_decode_png(const uint8_t *data, size_t size, int *width, int *height) { png_struct *png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) { return NULL; } png_info *info = png_create_info_struct(png); if (!info) { png_destroy_read_struct(&png, NULL, NULL); return NULL; } // setup error handling if (setjmp(png_jmpbuf(png))) { png_destroy_read_struct(&png, &info, NULL); return NULL; } struct mem_reader reader = { .data = data, .size = size, .position = 0 }; // get general image info png_set_read_fn(png, &reader, &png_reader); png_read_info(png, info); int w = png_get_image_width(png, info); int h = png_get_image_height(png, info); uint8_t *buffer = malloc(w * h * 4); if (!buffer) { png_destroy_read_struct(&png, &info, NULL); return NULL; } png_byte color_type = png_get_color_type(png, info); png_byte bit_depth = png_get_bit_depth(png, info); // setup decoder if (png_get_interlace_type(png, info) != PNG_INTERLACE_NONE) { png_set_interlace_handling(png); } if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_palette_to_rgb(png); } if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { png_set_gray_to_rgb(png); if (bit_depth < 8) { png_set_expand_gray_1_2_4_to_8(png); } } if (png_get_valid(png, info, PNG_INFO_tRNS)) { png_set_tRNS_to_alpha(png); } if (bit_depth == 16) { png_set_strip_16(png); } png_set_filler(png, 0xff, PNG_FILLER_AFTER); png_set_packing(png); png_set_packswap(png); png_set_expand(png); png_read_update_info(png, info); png_bytep *row_ptrs = malloc(h * sizeof(*row_ptrs)); if (!row_ptrs) { free(buffer); png_destroy_read_struct(&png, &info, NULL); return NULL; } for (int i = 0; i < h; i++) { row_ptrs[i] = buffer + w * 4 * i; } if (setjmp(png_jmpbuf(png))) { free(row_ptrs); free(buffer); png_destroy_read_struct(&png, &info, NULL); return NULL; } png_set_read_user_transform_fn(png, premultiply_data); png_read_image(png, row_ptrs); png_destroy_read_struct(&png, &info, NULL); free(row_ptrs); *width = w; *height = h; return buffer; } typedef struct __attribute__((__packed__)) { uint16_t type; uint32_t file_size; uint16_t reserved1; uint16_t reserved2; uint32_t offset; } bmp_file_header; typedef struct __attribute__((__packed__)) { uint32_t dib_size; int32_t width; int32_t height; uint16_t planes; uint16_t bpp; uint32_t compression; uint32_t img_size; uint32_t hres; uint32_t vres; uint32_t clr_palette; uint32_t clr_important; } bmp_info_header; static bool image_is_png(const uint8_t *data) { return data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47 && data[4] == 0x0D && data[5] == 0x0A && data[6] == 0x1A && data[7] == 0x0A; } static uint8_t *do_decode_bmp(const uint8_t *data, const uint8_t *pixel, int *width, int *height) { const bmp_info_header *header = (bmp_info_header *)data; if (header->bpp != 32 || header->compression != 0) { return NULL; } uint32_t stride = header->width * 4; // The height of the BMP image must be twice the height in ico uint32_t h = pixel ? abs(header->height) : abs(header->height) / 2; uint8_t *buffer = malloc(stride * h); if (!buffer) { return NULL; } const uint8_t *src = pixel ? pixel : data + sizeof(*header); // default is bottom-to-top if (header->height < 0) { memcpy(buffer, src, stride * h); } else { for (uint32_t i = 0; i < h; i++) { memcpy(buffer + i * stride, src + (h - 1 - i) * stride, stride); } } *width = header->width; *height = h; return buffer; } static uint8_t *do_decode_ico(const uint8_t *data, size_t size, int *width, int *height) { struct __attribute__((__packed__)) ico_header { uint16_t reserved; uint16_t type; // 1 for icon, 2 for cursor uint16_t count; }; struct __attribute__((__packed__)) ico_directory { uint8_t width; // should be 0 if 256 pixels uint8_t height; // should be 0 if 256 pixels uint8_t count; // should be 0 if more than 256 colors uint8_t reserved; uint16_t planes; // should be 0 or 1 uint16_t bpp; uint32_t size; uint32_t offset; }; struct __attribute__((__packed__)) ico { struct ico_header header; struct ico_directory dict[]; }; const struct ico *ico = (struct ico *)data; // header is checked in image_decode_file const struct ico_directory *best_dict = NULL, *max_dict = NULL; // use max size 256 if not set uint32_t best_width = *width ? *width : 256; uint32_t min_width = UINT_MAX, max_width = 0; for (uint16_t i = 0; i < ico->header.count; i++) { const struct ico_directory *dict = &ico->dict[i]; if (dict->bpp != 32) { continue; } uint32_t width = dict->width ? dict->width : 256; if (width == best_width) { best_dict = dict; break; } if (width > best_width && width < min_width) { min_width = width; best_dict = dict; } if (width > max_width) { max_width = width; max_dict = dict; } } if (!best_dict && max_dict) { best_dict = max_dict; } if (!best_dict) { return NULL; } const uint8_t *pixels = data + best_dict->offset; if (image_is_png(pixels)) { return do_decode_png(pixels, best_dict->size, width, height); } else if (*(uint32_t *)pixels == 0x28) { // it must exclude the opening BITMAPFILEHEADER structure return do_decode_bmp(pixels, NULL, width, height); } else { return NULL; } } struct xpm_color { char *name; uint32_t argb; }; struct xpm_parse_state { uint8_t *buffer; int width, height; int num_colors; int chars_per_pixel; char *names; struct xpm_color *colors; struct hash_table *ht_colors; int lines, cnt_colors, cnt_pixels; }; static bool xpm_parse_color(struct xpm_parse_state *state, const char *str) { int name_stride = state->chars_per_pixel + 1; char *name = &state->names[state->cnt_colors * name_stride]; struct xpm_color *color = &state->colors[state->cnt_colors]; memcpy(name, str, state->chars_per_pixel); color->name = name; char *visual = strchr(str + name_stride, 'c'); if (!visual) { return false; } visual++; // skip 'c' and space while (*visual == ' ') { visual++; } if (strncmp(visual, "None", 4) == 0) { color->argb = 0x0; } else { uint32_t red = 0, green = 0, blue = 0; sscanf(visual, "#%2x%2x%2x", &red, &green, &blue); color->argb = (255u << 24) | (red << 16) | (green << 8) | blue; } hash_table_insert(state->ht_colors, name, color); state->cnt_colors++; return true; } static uint32_t xpm_color_hash(const void *key, void *data) { struct xpm_parse_state *state = data; return hash_string_with_length(key, state->chars_per_pixel); } static bool xpm_color_equal(const void *a, const void *b, void *data) { struct xpm_parse_state *state = data; return strncmp(a, b, state->chars_per_pixel) == 0; } static bool xpm_parse(struct file *file, const char *key, const char *value, void *data) { if (*key != '"') { return false; } struct xpm_parse_state *state = data; state->lines++; key++; // skip '"' /* first line must be values */ if (state->lines == 1) { int items = sscanf(key, "%d %d %d %d %*d %*d", &state->width, &state->height, &state->num_colors, &state->chars_per_pixel); if (items != 4 || state->width > 1024 || state->height > 1024) { state->width = state->height = 0; kywc_log(KYWC_ERROR, "XPM file parse failed"); return true; } return false; } if (state->lines == 2) { // pre-alloc color maps state->names = calloc(state->num_colors, state->chars_per_pixel + 1); state->colors = calloc(state->num_colors, sizeof(struct xpm_color)); state->ht_colors = hash_table_create(xpm_color_hash, xpm_color_equal, state); state->buffer = malloc(state->width * state->height * 4); if (!state->names || !state->colors || !state->ht_colors || !state->buffer) { free(state->buffer); state->buffer = NULL; return true; } hash_table_set_max_entries(state->ht_colors, state->num_colors); } /* color maps */ if (state->cnt_colors < state->num_colors) { return !xpm_parse_color(state, key); } /* write pixels to buffer */ uint32_t *buffer = (uint32_t *)(state->buffer + state->cnt_pixels * state->width * 4); for (int i = 0; i < state->width; i++) { struct hash_entry *entry = hash_table_search(state->ht_colors, key + i * state->chars_per_pixel); buffer[i] = entry ? ((struct xpm_color *)entry->data)->argb : 0; } state->cnt_pixels++; if (state->cnt_pixels == state->height) { return true; } return false; } static uint8_t *do_decode_xpm(struct file *file, int *width, int *height) { struct xpm_parse_state state = { 0 }; file_parse(file, xpm_parse, &state); *width = state.width; *height = state.height; free(state.names); free(state.colors); hash_table_destroy(state.ht_colors); return state.buffer; } uint8_t *image_read_from_file(const char *filename, int *width, int *height) { struct file *file = file_open(filename, NULL, NULL); if (!file) { return NULL; } size_t size = 0; const uint8_t *data = (const uint8_t *)file_get_data(file, &size); if (size <= 8) { file_close(file); return NULL; } uint8_t *buffer = NULL; if (image_is_png(data)) { buffer = do_decode_png(data, size, width, height); } else if (data[0] == 0xFF && data[1] == 0xD8) { buffer = do_decode_jpeg(data, size, width, height); } else if (data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x01 && data[3] == 0x00) { buffer = do_decode_ico(data, size, width, height); } else if (data[0] == 0x42 && data[1] == 0x4D && *(uint32_t *)(data + sizeof(bmp_file_header)) == 0x28) { buffer = do_decode_bmp(data + sizeof(bmp_file_header), data + ((bmp_file_header *)data)->offset, width, height); } else if (strncmp((char *)data, "/* XPM */", 9) == 0) { /* reopen the file for file_parse */ file_close(file); file = file_open(filename, "\n", NULL); if (file) { buffer = do_decode_xpm(file, width, height); } } else { kywc_log(KYWC_WARN, "%s: unsupported image format", filename); } file_close(file); return buffer; } static bool do_encode_bmp(FILE *file, int width, int height, size_t stride, uint8_t *data) { bmp_info_header info_header = { .dib_size = sizeof(info_header), .width = width, .height = -height, .planes = 1, .bpp = 32, .img_size = stride * height, }; bmp_file_header file_header = { .type = 'B' | ('M' << 8), .file_size = sizeof(file_header) + sizeof(info_header) + info_header.img_size, .offset = sizeof(file_header) + sizeof(info_header), }; fwrite(&file_header, sizeof(file_header), 1, file); fwrite(&info_header, sizeof(info_header), 1, file); fwrite(data, stride, height, file); return true; } static bool do_encode_png(FILE *file, int width, int height, size_t stride, uint8_t *data) { png_struct *png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) { return false; } png_info *info = png_create_info_struct(png); if (!info) { png_destroy_write_struct(&png, NULL); return false; } png_init_io(png, file); // png_set_compression_level(png, 6); png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_set_alpha_mode(png, PNG_ALPHA_PREMULTIPLIED, PNG_GAMMA_LINEAR); png_set_bgr(png); png_write_info(png, info); png_bytep *row_ptrs = malloc(height * sizeof(*row_ptrs)); if (!row_ptrs) { png_destroy_write_struct(&png, &info); return false; } for (int i = 0; i < height; ++i) { row_ptrs[i] = data + i * stride; } png_write_image(png, row_ptrs); png_write_end(png, NULL); png_destroy_write_struct(&png, &info); free(row_ptrs); return true; } bool image_write_to_file(struct painter_buffer *buffer, const char *filename) { FILE *file = fopen(filename, "wb"); if (!file) { kywc_log(KYWC_ERROR, "Cannot open file: %s", filename); return false; } int width = buffer->base.width; int height = buffer->base.height; size_t stride = buffer->stride; uint8_t *data = buffer->data; bool ok = false; size_t len = strlen(filename); const char *suffix = filename + len - 3; if (len > 3 && strncmp(suffix, "bmp", 3) == 0) { ok = do_encode_bmp(file, width, height, stride, data); } else { ok = do_encode_png(file, width, height, stride, data); } fclose(file); return ok; } kylin-wayland-compositor/src/painter/painter.c0000664000175000017500000001272415160460057020530 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "painter_p.h" #include "util/macros.h" static const struct wlr_buffer_impl painter_buffer_impl; static struct painter_buffer *painter_buffer_from_wlr_buffer(struct wlr_buffer *wlr_buffer) { if (wlr_buffer->impl != &painter_buffer_impl) { return NULL; } struct painter_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); return buffer; } static void painter_buffer_destroy(struct wlr_buffer *wlr_buffer) { struct painter_buffer *buffer = painter_buffer_from_wlr_buffer(wlr_buffer); if (buffer->own_data) { free(buffer->data); } free(buffer); } static bool painter_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, uint32_t flags, void **data, uint32_t *format, size_t *stride) { struct painter_buffer *buffer = painter_buffer_from_wlr_buffer(wlr_buffer); *format = DRM_FORMAT_ARGB8888; *data = buffer->data; *stride = buffer->stride; return true; } static void painter_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { /* noop */ } static const struct wlr_buffer_impl painter_buffer_impl = { .destroy = painter_buffer_destroy, .begin_data_ptr_access = painter_buffer_begin_data_ptr_access, .end_data_ptr_access = painter_buffer_end_data_ptr_access, }; static struct painter_buffer *painter_buffer_create(int width, int height, int dst_width, int dst_height, float scale, void *data) { struct painter_buffer *buffer = calloc(1, sizeof(*buffer)); if (!buffer) { return NULL; } buffer->stride = width * 4; buffer->own_data = data == NULL; buffer->data = data ? data : malloc(buffer->stride * height); if (!buffer->data) { free(buffer); return NULL; } wlr_buffer_init(&buffer->base, &painter_buffer_impl, width, height); buffer->scale = scale ? scale : 1.0; buffer->dst_width = dst_width ? dst_width : width; buffer->dst_height = dst_height ? dst_height : height; return buffer; } struct wlr_buffer *painter_create_buffer(int width, int height, float scale) { int buffer_width = ceil(width * scale); int buffer_height = ceil(height * scale); struct painter_buffer *buffer = painter_buffer_create(buffer_width, buffer_height, width, height, scale, NULL); return buffer ? &buffer->base : NULL; } static void painter_get_buffer_size(struct draw_info *info, int *buffer_width, int *buffer_height) { if (info->pixel.data) { *buffer_width = info->pixel.width; *buffer_height = info->pixel.height; return; } /* auto resize to text size */ if (info->auto_resize && STRING_VALID(info->text)) { int width, height; text_get_size(info->font_name, info->font_size, info->text, &width, &height); if (info->auto_resize == AUTO_RESIZE_EXTEND) { height += 14; width += 18; info->align = TEXT_ALIGN_CENTER; } if (width < info->width) { info->width = width; } if (height < info->height) { info->height = height; } info->text_attrs &= ~(TEXT_ATTR_SUBMENU | TEXT_ATTR_CHECKED); } /* draw hover together */ if (info->hover_rgba) { info->height *= 2; } *buffer_width = ceil(info->width * info->scale); *buffer_height = ceil(info->height * info->scale); } struct wlr_buffer *painter_draw_buffer(struct draw_info *info) { int width = 0, height = 0; painter_get_buffer_size(info, &width, &height); uint8_t *data = info->pixel.data; if (!data && info->image) { data = image_read_from_file(info->image, &width, &height); if (!data) { return NULL; } } if (width == 0 || height == 0) { return NULL; } struct painter_buffer *buffer = painter_buffer_create(width, height, info->width, info->height, info->scale, data); if (!buffer) { return NULL; } buffer->own_data = !info->pixel.data; render_buffer(buffer, info); return &buffer->base; } bool painter_buffer_redraw(struct wlr_buffer *buffer, struct draw_info *info) { struct painter_buffer *buf = painter_buffer_from_wlr_buffer(buffer); if (!buf) { return false; } /* fix size to buffer unscaled size */ info->width = buf->dst_width; info->height = buf->dst_height; info->scale = buf->scale; return render_buffer(buf, info); } void painter_buffer_get_dest_size(struct wlr_buffer *buffer, int *width, int *height) { struct painter_buffer *buf = painter_buffer_from_wlr_buffer(buffer); if (buf && width) { *width = buf->dst_width; } if (buf && height) { *height = buf->dst_height; } } void painter_buffer_write_to_file(struct wlr_buffer *buffer, const char *name) { struct painter_buffer *buf = painter_buffer_from_wlr_buffer(buffer); if (!buf) { return; } image_write_to_file(buf, name); } void painter_get_text_size(const char *text, const char *font, int font_size, int *width, int *height) { text_get_size(font, font_size, text, width, height); } void painter_get_text_metrics(const char *font, int font_size, int *ascent, int *descent) { text_get_metrics(font, font_size, ascent, descent); } kylin-wayland-compositor/src/painter/render.c0000664000175000017500000003503015160460057020340 0ustar fengfeng// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wexpansion-to-defined" #include #pragma GCC diagnostic pop #include #include "painter_p.h" #include "util/macros.h" #define FONT_WEIGHT (500) void text_get_metrics(const char *font_name, int font_size, int *ascent, int *descent) { PangoFontDescription *desc = pango_font_description_new(); pango_font_description_set_family(desc, font_name); pango_font_description_set_size(desc, font_size * PANGO_SCALE); pango_font_description_set_weight(desc, FONT_WEIGHT); PangoFontMap *map = pango_cairo_font_map_get_default(); PangoContext *context = pango_font_map_create_context(map); PangoLanguage *language = pango_context_get_language(context); PangoFontMetrics *metrics = pango_context_get_metrics(context, desc, language); *ascent = pango_font_metrics_get_ascent(metrics) / 1024; *descent = pango_font_metrics_get_descent(metrics) / 1024; g_object_unref(context); pango_font_description_free(desc); pango_font_metrics_unref(metrics); } void text_get_size(const char *font_name, int font_size, const char *text, int *width, int *height) { PangoFontDescription *desc = pango_font_description_new(); pango_font_description_set_family(desc, font_name); pango_font_description_set_size(desc, font_size * PANGO_SCALE); pango_font_description_set_weight(desc, FONT_WEIGHT); PangoFontMap *map = pango_cairo_font_map_get_default(); PangoContext *context = pango_font_map_create_context(map); PangoLayout *layout = pango_layout_new(context); pango_layout_set_font_description(layout, desc); pango_layout_set_text(layout, text, -1); pango_layout_set_single_paragraph_mode(layout, TRUE); pango_layout_set_width(layout, -1); pango_layout_get_pixel_size(layout, width, height); g_object_unref(layout); g_object_unref(context); pango_font_description_free(desc); } static void draw_text(cairo_surface_t *surface, cairo_t *cairo, struct draw_info *info, struct kywc_fbox *box) { cairo_set_source_rgba(cairo, info->font_rgba[0], info->font_rgba[1], info->font_rgba[2], info->font_rgba[3]); cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); PangoFontDescription *desc = pango_font_description_new(); pango_font_description_set_family(desc, info->font_name); pango_font_description_set_size(desc, info->font_size * PANGO_SCALE); pango_font_description_set_weight(desc, FONT_WEIGHT); if (info->text_attrs & TEXT_ATTR_SLANT) { pango_font_description_set_style(desc, PANGO_STYLE_ITALIC); } PangoLayout *layout = pango_cairo_create_layout(cairo); pango_layout_set_font_description(layout, desc); pango_layout_set_width(layout, -1); // no TEXT_ATTR_CHECKED and TEXT_ATTR_SUBMENU when auto_resize double reserved = info->auto_resize ? 0 : box->height; bool use_rtl_layout = info->text_attrs & TEXT_ATTR_RTL; double lx = box->x, lrx = box->x + box->width, gap; // reserved space for checked text and submenu lx += use_rtl_layout ? reserved * 0.5 : reserved; lrx -= use_rtl_layout ? reserved : reserved * 0.5; int width, height, left = lrx - lx; if (info->text_attrs & TEXT_ATTR_CHECKED) { pango_layout_set_text(layout, "✓", -1); pango_layout_get_pixel_size(layout, &width, &height); gap = MAX(0, (box->height - height) * 0.5); cairo_move_to(cairo, use_rtl_layout ? lrx + gap : lx - width - gap, box->y + gap); pango_cairo_show_layout(cairo, layout); } if (info->text_attrs & TEXT_ATTR_SUBMENU) { pango_layout_set_text(layout, use_rtl_layout ? "<" : ">", -1); pango_layout_get_pixel_size(layout, &width, &height); gap = MAX(0, (box->height - height) * 0.5); cairo_move_to(cairo, use_rtl_layout ? lx : lrx - width, box->y + gap); pango_cairo_show_layout(cairo, layout); left -= width + gap; use_rtl_layout ? (lx += width + gap) : (lrx -= width + gap); } if (STRING_VALID(info->shortcut)) { pango_layout_set_text(layout, info->shortcut, -1); pango_layout_get_pixel_size(layout, &width, &height); gap = MAX(0, (box->height - height) * 0.5); cairo_move_to(cairo, use_rtl_layout ? lx : lrx - width, box->y + gap); pango_cairo_show_layout(cairo, layout); left -= width + gap; use_rtl_layout ? (lx += width + gap) : (lrx -= width + gap); } if (info->text_attrs & TEXT_ATTR_ACCEL) { pango_layout_set_markup_with_accel(layout, info->text, -1, '_', NULL); } else { pango_layout_set_text(layout, info->text, -1); } pango_layout_get_pixel_size(layout, &width, &height); gap = MAX(0, (box->height - height) * 0.5); pango_layout_set_width(layout, left * PANGO_SCALE); cairo_move_to(cairo, lx, box->y + gap); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_MIDDLE); pango_layout_set_alignment(layout, (PangoAlignment)info->align); pango_cairo_show_layout(cairo, layout); g_object_unref(layout); pango_font_description_free(desc); } static void draw_color(cairo_surface_t *surface, cairo_t *cairo, struct draw_info *info, struct kywc_fbox *box, bool hover) { double width = box->width; double height = box->height; double radius = info->corner_radius; if (info->solid_rgba) { if (info->corner_mask & CORNER_MASK_TOP_LEFT) { cairo_arc(cairo, box->x + radius, box->y + radius, radius, ANGLE(-180), ANGLE(-90)); } else { cairo_line_to(cairo, box->x, box->y); } if (info->corner_mask & CORNER_MASK_TOP_RIGHT) { cairo_arc(cairo, box->x + width - radius, box->y + radius, radius, ANGLE(-90), ANGLE(0)); } else { cairo_line_to(cairo, box->x + width, box->y); } if (info->corner_mask & CORNER_MASK_BOTTOM_RIGHT) { cairo_arc(cairo, box->x + width - radius, box->y + height - radius, radius, ANGLE(0), ANGLE(90)); } else { cairo_line_to(cairo, box->x + width, box->y + height); } if (info->corner_mask & CORNER_MASK_BOTTOM_LEFT) { cairo_arc(cairo, box->x + radius, box->y + height - radius, radius, ANGLE(90), ANGLE(180)); } else { cairo_line_to(cairo, box->x, box->y + height); } cairo_close_path(cairo); cairo_set_source_rgba(cairo, info->solid_rgba[0], info->solid_rgba[1], info->solid_rgba[2], info->solid_rgba[3]); cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); cairo_fill(cairo); } if (hover) { double offset = box->height * 0.1; double x = box->x + offset; double y = box->y + offset; double w = box->width - 2 * offset; double h = box->height - 2 * offset; float radius = info->hover_radius; cairo_arc(cairo, x + radius, y + radius, radius, ANGLE(-180), ANGLE(-90)); cairo_arc(cairo, x + w - radius, y + radius, radius, ANGLE(-90), ANGLE(0)); cairo_arc(cairo, x + w - radius, y + h - radius, radius, ANGLE(0), ANGLE(90)); cairo_arc(cairo, x + radius, y + h - radius, radius, ANGLE(90), ANGLE(180)); cairo_close_path(cairo); cairo_set_source_rgba(cairo, info->hover_rgba[0], info->hover_rgba[1], info->hover_rgba[2], info->hover_rgba[3]); cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); cairo_fill(cairo); } if (!info->border_rgba || !info->border_width || !info->border_mask) { return; } /* border line */ double half = info->border_width / 2.0; cairo_set_source_rgba(cairo, info->border_rgba[0], info->border_rgba[1], info->border_rgba[2], info->border_rgba[3]); cairo_set_line_width(cairo, info->border_width); if (info->border_mask & BORDER_MASK_TOP) { if (info->corner_mask & CORNER_MASK_TOP_LEFT) { cairo_move_to(cairo, box->x + half, box->y + radius); cairo_arc(cairo, box->x + radius, box->y + radius, radius - half, ANGLE(-180), ANGLE(-90)); } else { cairo_move_to(cairo, box->x + half, box->y + half); } if (info->corner_mask & CORNER_MASK_TOP_RIGHT) { cairo_line_to(cairo, box->x + width - radius, box->y + half); cairo_arc(cairo, box->x + width - radius, box->y + radius, radius - half, ANGLE(-90), ANGLE(0)); } else { cairo_line_to(cairo, box->x + width - half, box->y + half); } cairo_stroke(cairo); } if (info->border_mask & BORDER_MASK_RIGHT) { if (info->corner_mask & CORNER_MASK_TOP_RIGHT) { if (info->border_mask & BORDER_MASK_TOP) { cairo_move_to(cairo, box->x + width - half, box->y + radius); } else { cairo_move_to(cairo, box->x + width - radius, box->y + half); cairo_arc(cairo, box->x + width - radius, box->y + radius, radius - half, ANGLE(-90), ANGLE(0)); } } else { cairo_move_to(cairo, box->x + width - half, box->y + half); } if (info->corner_mask & CORNER_MASK_BOTTOM_RIGHT) { cairo_line_to(cairo, box->x + width - half, box->y + height - radius); cairo_arc(cairo, box->x + width - radius, box->y + height - radius, radius - half, ANGLE(0), ANGLE(90)); } else { cairo_line_to(cairo, box->x + width - half, box->y + height - half); } cairo_stroke(cairo); } if (info->border_mask & BORDER_MASK_BOTTOM) { if (info->corner_mask & CORNER_MASK_BOTTOM_RIGHT) { if (info->border_mask & BORDER_MASK_RIGHT) { cairo_move_to(cairo, box->x + width - radius, box->y + height - half); } else { cairo_move_to(cairo, box->x + width - half, box->y + height - radius); cairo_arc(cairo, box->x + width - radius, box->y + height - radius, radius - half, ANGLE(0), ANGLE(90)); } } else { cairo_move_to(cairo, box->x + width - half, box->y + height - half); } if (info->corner_mask & CORNER_MASK_BOTTOM_LEFT) { cairo_line_to(cairo, box->x + radius, box->y + height - half); cairo_arc(cairo, box->x + radius, box->y + height - radius, radius - half, ANGLE(90), ANGLE(180)); } else { cairo_line_to(cairo, box->x + half, box->y + height - half); } cairo_stroke(cairo); } if (info->border_mask & BORDER_MASK_LEFT) { if (info->corner_mask & CORNER_MASK_BOTTOM_LEFT) { if (info->border_mask & BORDER_MASK_BOTTOM) { cairo_move_to(cairo, box->x + half, box->y + height - radius); } else { cairo_move_to(cairo, box->x + radius, box->y + height - half); cairo_arc(cairo, box->x + radius, box->y + height - radius, radius - half, ANGLE(90), ANGLE(180)); } } else { cairo_move_to(cairo, box->x + half, box->y + height - half); } if (info->corner_mask & CORNER_MASK_TOP_LEFT) { if (info->border_mask & BORDER_MASK_TOP) { cairo_line_to(cairo, box->x + half, box->y + radius); } else { cairo_line_to(cairo, box->x + half, box->y + radius); cairo_arc(cairo, box->x + radius, box->y + radius, radius - half, ANGLE(-180), ANGLE(-90)); } } else { cairo_line_to(cairo, box->x + half, box->y + half); } cairo_stroke(cairo); } } static bool draw_svg(cairo_t *cairo, const char *data, size_t size, struct kywc_fbox *box) { // check signature, this an xml, so skip spaces from the start while (size && isspace(*data) != 0) { ++data; --size; } const uint8_t signature[] = { '<' }; if (size <= sizeof(signature) || memcmp(data, signature, sizeof(signature))) { return false; } GError *err = NULL; RsvgHandle *svg = rsvg_handle_new_from_data((guint8 *)data, size, &err); if (!svg) { kywc_log(KYWC_ERROR, "Invalid SVG format"); if (err && err->message) { kywc_log(KYWC_ERROR, "%s: %s", data, err->message); } return false; } RsvgRectangle viewport = { .x = box->x, .y = box->y, .width = box->width, .height = box->height }; // render svg to surface gboolean ok = rsvg_handle_render_document(svg, cairo, &viewport, &err); g_object_unref(svg); if (!ok && err && err->message) { kywc_log(KYWC_ERROR, "%s", err->message); } return ok; } bool render_buffer(struct painter_buffer *buffer, struct draw_info *info) { if (buffer->own_data && !info->image) { memset(buffer->data, 0x0, buffer->base.height * buffer->stride); } cairo_surface_t *surface = cairo_image_surface_create_for_data( buffer->data, CAIRO_FORMAT_ARGB32, buffer->base.width, buffer->base.height, buffer->stride); if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { return false; } cairo_surface_set_device_scale(surface, buffer->scale, buffer->scale); cairo_t *cairo = cairo_create(surface); double half = info->height / 2.0; struct kywc_fbox upper = { 0, 0, info->width, half }; struct kywc_fbox lower = { 0, half, info->width, half }; struct kywc_fbox whole = { 0, 0, info->width, info->height }; /* svg picture */ if (info->svg.data && info->svg.size > 0) { draw_svg(cairo, info->svg.data, info->svg.size, &whole); } /* corner, solid and border */ if (info->hover_rgba) { draw_color(surface, cairo, info, &upper, false); draw_color(surface, cairo, info, &lower, true); } else { draw_color(surface, cairo, info, &whole, false); } /* text */ if (STRING_VALID(info->text) && info->font_rgba) { if (info->hover_rgba) { draw_text(surface, cairo, info, &upper); draw_text(surface, cairo, info, &lower); } else { draw_text(surface, cairo, info, &whole); } } cairo_surface_flush(surface); cairo_destroy(cairo); cairo_surface_destroy(surface); return true; } kylin-wayland-compositor/.clang-format0000664000175000017500000000107715160460057017043 0ustar fengfengBasedOnStyle: LLVM IndentWidth: 4 BreakBeforeBraces: Linux AllowShortFunctionsOnASingleLine: Inline Cpp11BracedListStyle: false ColumnLimit: 100 SpaceBeforeParens: ControlStatementsExceptForEachMacros ForEachMacros: - 'wl_list_for_each' - 'wl_list_for_each_safe' - 'wl_list_for_each_reverse' - 'wl_list_for_each_reverse_safe' - 'wl_array_for_each' - 'wl_resource_for_each' - 'wl_resource_for_each_safe' - 'json_object_object_foreach' - 'xorg_list_for_each_entry' - 'xorg_list_for_each_entry_safe' - 'udev_list_entry_foreach' - 'hash_table_for_each' kylin-wayland-compositor/LICENSE0000664000175000017500000000216415160460057015473 0ustar fengfengCopyright (c) 2017, 2018 Drew DeVault Copyright (c) 2014 Jari Vetoniemi Copyright (c) 2023 The wlroots contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. kylin-wayland-compositor/LICENSES/0000775000175000017500000000000015160460057015670 5ustar fengfengkylin-wayland-compositor/LICENSES/MIT-CMU.txt0000664000175000017500000000217615160460057017512 0ustar fengfeng By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read, understood, and will comply with the following terms and conditions: Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holder not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. THE COPYRIGHT HOLDER DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM THE 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. kylin-wayland-compositor/LICENSES/Expat.txt0000664000175000017500000000216415160460057017515 0ustar fengfengCopyright (c) 2017, 2018 Drew DeVault Copyright (c) 2014 Jari Vetoniemi Copyright (c) 2023 The wlroots contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. kylin-wayland-compositor/LICENSES/LGPL-2.1-or-later.txt0000664000175000017500000006255715160460057021227 0ustar fengfengGNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. one line to give the library's name and an idea of what it does. Copyright (C) year name of author This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. signature of Ty Coon, 1 April 1990 Ty Coon, President of Vice That's all there is to it! kylin-wayland-compositor/meson.build0000664000175000017500000002336215160461067016635 0ustar fengfengproject( 'kylin-wayland-compositor', 'c', license: 'Expat', version: '1.3.0', default_options: [ 'b_ndebug=if-release', 'c_std=c11', 'warning_level=2', ], ) cc = meson.get_compiler('c') add_project_arguments(cc.get_supported_arguments([ '-Wundef', '-Wlogical-op', '-Wmissing-include-dirs', '-Wold-style-definition', '-Wpointer-arith', '-Winit-self', '-Wstrict-prototypes', '-Wimplicit-fallthrough=2', '-Wendif-labels', '-Wstrict-aliasing=2', '-Woverflow', '-Wmissing-prototypes', '-Walloca', '-Wunused-macros', '-Wno-unused-parameter', ]), language: 'c') if get_option('buildtype') == 'release' add_project_arguments(cc.get_supported_arguments([ '-Wno-unused-variable', '-Wno-unused-but-set-variable' ]), language: 'c') endif version = '"@0@"'.format(meson.project_version()) dpkg = find_program('dpkg-parsechangelog', native: true, required: false) if dpkg.found() dpkg_version = run_command([dpkg, '-SVersion'], check: false) if dpkg_version.returncode() == 0 version = '"@0@"'.format(dpkg_version.stdout().strip()) endif endif git = find_program('git', native: true, required: false) if git.found() git_commit = run_command([git, 'rev-parse', '--short=7', 'HEAD'], check: false) if git_commit.returncode() == 0 version += '".g@0@"'.format(git_commit.stdout().strip()) endif endif add_project_arguments('-DKYWC_VERSION=@0@'.format(version), language: 'c') wlroots = dependency( 'wlroots-0.19', default_options: ['default_library=static', 'examples=false', 'renderers=vulkan', 'backends=x11'], version: ['>=0.19.0', '<0.20.0'], fallback: 'wlroots' ) add_project_arguments('-DWLR_USE_UNSTABLE', language: 'c') wlroots_has_xwayland = wlroots.get_variable('have_xwayland') == 'true' xcb = dependency('xcb') have_xwayland = xcb.found() and wlroots_has_xwayland add_project_arguments( '-DHAVE_XWAYLAND=@0@'.format(have_xwayland.to_int()), language: 'c', ) wayland_server = dependency('wayland-server', version: '>=1.23.0') wayland_protos = dependency('wayland-protocols', version: '>=1.36') xkbcommon = dependency('xkbcommon') drm = dependency('libdrm') input = dependency('libinput', version: '>=1.19') pixman = dependency('pixman-1') math = cc.find_library('m') jsonc = dependency('json-c', version: '>=0.13') sdbus = dependency('libsystemd') dl = cc.find_library('dl') rt = cc.find_library('rt') thread = dependency('threads') cairo = dependency('cairo') pango = dependency('pango') pangocairo = dependency('pangocairo') rsvg = dependency('librsvg-2.0', version: '>=2.46') jpeg = dependency('libjpeg') png = dependency('libpng') udev = dependency('libudev') unwind = dependency('libunwind', required : get_option('libunwind')) gbm = dependency('gbm', version: '>=21.1') dispinfo = dependency('libdisplay-info') plugindir = get_option('plugin_directory') if plugindir == '' plugindir = '@0@/lib/kylin-wlcom/plugins'.format(get_option('prefix')) endif add_project_arguments('-DPLUGIN_DIR="@0@"'.format(plugindir), language: 'c') have_raw_tap = cc.has_header_symbol('libinput.h', 'LIBINPUT_EVENT_RAW_TAP', dependencies: [input]) add_project_arguments('-DHAVE_LIBINPUT_RAW_TAP=@0@'.format(have_raw_tap.to_int()), language: 'c') wlcom_sources = [] wlcom_deps = [ wayland_server, wayland_protos, wlroots, xkbcommon, drm, input, udev, dispinfo, pixman, xcb, jsonc, sdbus, math, dl, rt, thread, unwind, cairo, pango, pangocairo, rsvg, jpeg, png, ] wlcom_inc = include_directories('include') have_examples = get_option('examples') have_tools = get_option('tools') have_portal = get_option('portal') have_drm_lease_device = get_option('drm_lease_device') have_alpha_modifier = get_option('alpha_modifier') have_kde_output = get_option('kde_output') have_kde_virtual_desktop = get_option('kde_virtual_desktop') have_wlr_output = get_option('wlr_output') have_wlr_foreign_toplevel = get_option('wlr_foreign_toplevel') have_wlr_layer_shell = get_option('wlr_layer_shell') have_kde_global_accel = get_option('kde_global_accel') have_kde_input = get_option('kde_input') have_kde_plasma_shell = get_option('kde_plasma_shell') have_kde_plasma_window_management = get_option('kde_plasma_window_management') have_kde_blur = get_option('kde_blur') have_kde_slide = get_option('kde_slide') have_ukui_screenshot = get_option('ukui_screenshot') have_ukui_shortcut = get_option('ukui_shortcut') have_ukui_gsettings = get_option('ukui_gsettings') have_ukui_theme = get_option('ukui_theme') have_kde_keystate = get_option('kde_keystate') have_libkywc = get_option('libkywc') have_kde_clipboard = get_option('kde_clipboard') have_ukui_watermark = get_option('ukui_watermark') have_ukui_shell = get_option('ukui_shell') have_ukui_window_management = get_option('ukui_window_management') have_ukui_output = get_option('ukui_output') have_ukui_blur = get_option('ukui_blur') have_ukui_effect = get_option('ukui_effect') have_ukui_startup = get_option('ukui_startup') have_ukui_view_mode = get_option('ukui_view_mode') have_ukui_output_config = get_option('ukui_output_config') have_xdg_toplevel_icon = get_option('xdg_toplevel_icon') extra_icon_path = get_option('extra_icon_path') extra_apps_path = get_option('extra_apps_path') extra_pixmap_path = get_option('extra_pixmap_path') add_project_arguments([ '-DHAVE_LIBUNWIND=@0@'.format(unwind.found().to_int()), '-DHAVE_DRM_LEASE_DEVICE=@0@'.format(have_drm_lease_device.to_int()), '-DHAVE_ALPHA_MODIFIER=@0@'.format(have_alpha_modifier.to_int()), '-DHAVE_KDE_OUTPUT=@0@'.format(have_kde_output.to_int()), '-DHAVE_KDE_VIRTUAL_DESKTOP=@0@'.format(have_kde_virtual_desktop.to_int()), '-DHAVE_WLR_OUTPUT=@0@'.format(have_wlr_output.to_int()), '-DHAVE_WLR_FOREIGN_TOPLEVEL=@0@'.format(have_wlr_foreign_toplevel.to_int()), '-DHAVE_WLR_LAYER_SHELL=@0@'.format(have_wlr_layer_shell.to_int()), '-DHAVE_KDE_GLOBAL_ACCEL=@0@'.format(have_kde_global_accel.to_int()), '-DHAVE_KDE_INPUT=@0@'.format(have_kde_input.to_int()), '-DHAVE_KDE_PLASMA_SHELL=@0@'.format(have_kde_plasma_shell.to_int()), '-DHAVE_KDE_PLASMA_WINDOW_MANAGEMENT=@0@'.format(have_kde_plasma_window_management.to_int()), '-DHAVE_KDE_BLUR=@0@'.format(have_kde_blur.to_int()), '-DHAVE_KDE_SLIDE=@0@'.format(have_kde_slide.to_int()), '-DHAVE_UKUI_SCREENSHOT=@0@'.format(have_ukui_screenshot.to_int()), '-DHAVE_UKUI_SHORTCUT=@0@'.format(have_ukui_shortcut.to_int()), '-DHAVE_UKUI_GSETTINGS=@0@'.format(have_ukui_gsettings.to_int()), '-DHAVE_UKUI_THEME=@0@'.format(have_ukui_theme.to_int()), '-DHAVE_KDE_KEYSTATE=@0@'.format(have_kde_keystate.to_int()), '-DHAVE_KDE_CLIPBOARD=@0@'.format(have_kde_clipboard.to_int()), '-DHAVE_UKUI_WATERMARK=@0@'.format(have_ukui_watermark.to_int()), '-DHAVE_UKUI_SHELL=@0@'.format(have_ukui_shell.to_int()), '-DHAVE_UKUI_WINDOW_MANAGEMENT=@0@'.format(have_ukui_window_management.to_int()), '-DHAVE_UKUI_OUTPUT=@0@'.format(have_ukui_output.to_int()), '-DHAVE_UKUI_BLUR=@0@'.format(have_ukui_blur.to_int()), '-DHAVE_UKUI_EFFECT=@0@'.format(have_ukui_effect.to_int()), '-DHAVE_UKUI_STARTUP=@0@'.format(have_ukui_startup.to_int()), '-DHAVE_UKUI_VIEW_MODE=@0@'.format(have_ukui_view_mode.to_int()), '-DHAVE_UKUI_OUTPUT_CONFIG=@0@'.format(have_ukui_output_config.to_int()), '-DHAVE_XDG_TOPLEVEL_ICON=@0@'.format(have_xdg_toplevel_icon.to_int()), '-DEXTRA_ICON_PATH="@0@"'.format(extra_icon_path), '-DEXTRA_APPS_PATH="@0@"'.format(extra_apps_path), '-DEXTRA_PIXMAP_PATH="@0@"'.format(extra_pixmap_path), ], language: 'c') msgfmt = find_program('msgfmt', required: get_option('nls')) add_project_arguments('-DHAVE_NLS=@0@'.format(msgfmt.found().to_int()), language: 'c') if msgfmt.found() source_root = meson.current_source_dir() subdir('po') endif if get_option('tracy') tracy = dependency( 'tracy', default_options: ['default_library=static'], version: ['>=0.11.0', '<0.12.0'],) add_project_arguments('-DTRACY_ENABLE', language: 'c') wlcom_deps += tracy endif subdir('data') subdir('protocols') subdir('src') if have_libkywc subdir('libkywc') endif if have_examples subdir('examples') endif if have_tools subdir('tools') endif if have_portal subdir('portal') endif summary({ 'prefix': get_option('prefix'), 'plugin': plugindir, }, section: 'Directories') summary({ 'examples': have_examples, 'tools': have_tools, 'libunwind': unwind.found(), 'nls': msgfmt.found(), 'libkywc': have_libkywc, 'kde_output': have_kde_output, 'kde_virtual_desktop': have_kde_virtual_desktop, 'wlr_output': have_wlr_output, 'wlr_foreign_toplevel': have_wlr_foreign_toplevel, 'wlr_layer_shell': have_wlr_layer_shell, 'kde_global_accel': have_kde_global_accel, 'kde_input': have_kde_input, 'kde_plasma_shell': have_kde_plasma_shell, 'kde_plasma_window_management': have_kde_plasma_window_management, 'kde_blur': have_kde_blur, 'kde_slide': have_kde_slide, 'ukui_screenshot': have_ukui_screenshot, 'ukui_gsettings': have_ukui_gsettings, 'ukui_theme': have_ukui_theme, 'kde_keystate': have_kde_keystate, 'kde_clipboard': have_kde_clipboard, 'ukui_watermark': have_ukui_watermark, 'ukui_shell': have_ukui_shell, 'ukui_window_management': have_ukui_window_management, 'ukui_output': have_ukui_output, 'ukui_blur': have_ukui_blur, 'ukui_effect': have_ukui_effect, 'ukui_startup': have_ukui_startup, 'ukui_view_mode': have_ukui_view_mode, 'ukui_output_config': have_ukui_output_config, 'extra_icon_path': extra_icon_path, 'extra_apps_path': extra_apps_path, 'extra_pixmap_path': extra_pixmap_path, }, section: 'Configuration', bool_yn: true) symbols_file = 'wlcom.syms' symbols_flag = '-Wl,--version-script,@0@/@1@'.format( meson.current_source_dir(), symbols_file) executable( 'kylin-wlcom', wlcom_sources, include_directories: wlcom_inc, dependencies: wlcom_deps, install: true, export_dynamic: true, link_args: symbols_flag, link_depends: symbols_file, ) kylin-wayland-compositor/README.md0000664000175000017500000000605115160460057015744 0ustar fengfeng# kylin-wayland-compositor kylin-wayland-compositor或kylin-wlcom(以下简称kywc)是一个基于wlroots编写的wayland合成器。 目前积极开发中,并作为默认显示服务器随openKylin系统发布。 该项目使用开源协议Expat,项目中来源于其他开源项目的文件或代码片段遵守原开源协议要求。 ## 功能和特点 1. 依赖少,未使用QT或者GTK等图形开发框架。 2. 按需设计应用与合成器之间的协议,目前协议支持情况[PROTOCOLS]。 3. 特效支持,支持常用的窗口动效。 4. 完整的中文输入支持,支持input-method v2和text-input v1/v2/v3。 5. 快捷键和触摸手势支持,支持键盘快捷键,触摸板和触摸屏手势设置 6. 输入设备支持,支持鼠标、键盘、触摸板、触摸屏、数位板 7. 多语言国际化支持 8. 多后端支持,支持x11/wayland嵌套运行,支持drm和fbdev显示后端 ## 编译 运行时需要使用的库或程序: - wlroots, wayland, libinput, xkbcommon - libseat, libdrm, libsystemd, librsvg-2.0 - cairo, pango, pangocairo, pixman-1 - gbm, json-c, libudev - xwayland, xcb (optional) 依赖安装可通过apt进行(配置了deb-src源) apt build-dep kylin-wayland-compositor 编译选项见`meson_options.txt`,简单的编译指令: ``` meson setup build -Dbuildtype=debugoptimized ninja -C build meson install -C build --skip-subprojects ``` 程序参数如下: "Usage: kylin-wlcom [options] [command]" " -h, --help Show help message and quit.\n" " -d, --debug Enables full logging, including debug information.\n" " -D, --debug noxwayland or logtostdout.\n" " -s, --session Run session on startup\n" " -v, --version Show the version number and quit.\n" " -V, --verbose Enables more verbose logging.\n" 通过-D参数可以方便运行时调试, 支持参数如下: ``` -Dnoxwayland 关闭xwayland支持 -Dlogtostdout 将日志打印到stdout -Dloginmtime 使用monotonic time输出日志 ``` 默认情况下,日志打印到文件`$HOME/.log/kylin-wlcom.log`。 ## 多语言支持 在`po`目录中,`LINGUAS`文件中加入支持的语言,`POTFILES.in`加入需要翻译的源文件。 然后运行以下命令,更新`pot`文件: ``` meson compile kylin-wayland-compositor-pot ``` 将重新生成的`pot`文件复制成相应语言的`po`文件,如`zh_CN.po`,并进行翻译。 > 保证:"Content-Type: text/plain; charset=UTF-8\n" ## 已知问题 请参阅[KNOWN_ISSUES]文件,了解已知问题。 ## 参与开发 请参阅[CONTRIBUTING]文件,了解向kywc贡献所需的信息。 ## 致谢 感谢以下代码提供参考: [wlroots] [sway] [wayfire] [labwc] [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [sway]: https://github.com/swaywm [wayfire]: https://github.com/wayfire [labwc]: https://github.com/labwc [PROTOCOLS]: docs/PROTOCOLS.md [KNOWN_ISSUES]: docs/KNOWN_ISSUES.md [CONTRIBUTING]: docs/CONTRIBUTING.md kylin-wayland-compositor/libkywc/0000775000175000017500000000000015160461067016131 5ustar fengfengkylin-wayland-compositor/libkywc/thumbnail.c0000664000175000017500000001322015160460057020254 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include "libkywc_p.h" struct ky_thumbnail *thumbnail_from_kywc_thumbnail(kywc_thumbnail *kywc_thumbnail) { struct ky_thumbnail *thumbnail = wl_container_of(kywc_thumbnail, thumbnail, base); return thumbnail; } void ky_thumbnail_destroy(struct ky_thumbnail *thumbnail) { if (thumbnail->impl && thumbnail->impl->destroy) { thumbnail->impl->destroy(&thumbnail->base, thumbnail->user_data); } if (thumbnail->destroy) { thumbnail->destroy(thumbnail); } wl_list_remove(&thumbnail->link); free((void *)thumbnail->base.source_uuid); free((void *)thumbnail->base.output_uuid); free(thumbnail); } void ky_thumbnail_update_buffer(struct ky_thumbnail *thumbnail, const struct kywc_thumbnail_buffer *buffer, bool *want_buffer) { if (thumbnail->impl && thumbnail->impl->buffer) { *want_buffer = thumbnail->impl->buffer(&thumbnail->base, buffer, thumbnail->user_data); } } struct ky_thumbnail_manager *ky_thumbnail_manager_create(kywc_context *ctx) { struct ky_thumbnail_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return NULL; } manager->ctx = ctx; wl_list_init(&manager->thumbnails); return manager; } void ky_thumbnail_manager_destroy(struct ky_thumbnail_manager *manager) { if (!manager) { return; } // destroy all thumbnails struct ky_thumbnail *thumbnail, *tmp; wl_list_for_each_safe(thumbnail, tmp, &manager->thumbnails, link) { ky_thumbnail_destroy(thumbnail); } if (manager->destroy) { manager->destroy(manager); } free(manager); } kywc_thumbnail *kywc_thumbnail_create(kywc_context *ctx, enum kywc_thumbnail_type type, const char *source_uuid, const char *output_uuid, const struct kywc_thumbnail_interface *impl, void *data) { if (!ctx || !ctx->thumbnail) { return NULL; } struct ky_thumbnail *thumbnail = calloc(1, sizeof(*thumbnail)); if (!thumbnail) { return NULL; } struct ky_thumbnail_manager *manager = ctx->thumbnail; thumbnail->manager = manager; wl_list_insert(&manager->thumbnails, &thumbnail->link); thumbnail->base.type = type; thumbnail->base.source_uuid = strdup(source_uuid); if (output_uuid) { thumbnail->base.output_uuid = strdup(output_uuid); } thumbnail->impl = impl; thumbnail->user_data = data; if (type == KYWC_THUMBNAIL_TYPE_OUTPUT && manager->capture_output) { manager->capture_output(manager, thumbnail, source_uuid); } else if (type == KYWC_THUMBNAIL_TYPE_TOPLEVEL && manager->capture_toplevel) { manager->capture_toplevel(manager, thumbnail, source_uuid, false); } else if (type == KYWC_THUMBNAIL_TYPE_WORKSPACE && manager->capture_workspace) { manager->capture_workspace(manager, thumbnail, source_uuid, output_uuid); } return &thumbnail->base; } kywc_thumbnail *kywc_thumbnail_create_from_output(kywc_context *ctx, const char *source_uuid, const struct kywc_thumbnail_interface *impl, void *data) { return kywc_thumbnail_create(ctx, KYWC_THUMBNAIL_TYPE_OUTPUT, source_uuid, NULL, impl, data); } kywc_thumbnail *kywc_thumbnail_create_from_workspace(kywc_context *ctx, const char *source_uuid, const char *output_uuid, const struct kywc_thumbnail_interface *impl, void *data) { return kywc_thumbnail_create(ctx, KYWC_THUMBNAIL_TYPE_WORKSPACE, source_uuid, output_uuid, impl, data); } kywc_thumbnail *kywc_thumbnail_create_from_toplevel(kywc_context *ctx, const char *source_uuid, bool without_decoration, const struct kywc_thumbnail_interface *impl, void *data) { if (!ctx || !ctx->thumbnail) { return NULL; } struct ky_thumbnail *thumbnail = calloc(1, sizeof(*thumbnail)); if (!thumbnail) { return NULL; } struct ky_thumbnail_manager *manager = ctx->thumbnail; thumbnail->manager = manager; wl_list_insert(&manager->thumbnails, &thumbnail->link); thumbnail->base.type = KYWC_THUMBNAIL_TYPE_TOPLEVEL; thumbnail->base.source_uuid = strdup(source_uuid); thumbnail->impl = impl; thumbnail->user_data = data; if (manager->capture_toplevel) { manager->capture_toplevel(manager, thumbnail, source_uuid, without_decoration); } return &thumbnail->base; } kywc_context *kywc_thumbnail_get_context(kywc_thumbnail *thumbnail) { struct ky_thumbnail *ky_thumbnail = thumbnail_from_kywc_thumbnail(thumbnail); return ky_thumbnail->manager->ctx; } void kywc_thumbnail_set_user_data(kywc_thumbnail *thumbnail, void *data) { struct ky_thumbnail *ky_thumbnail = thumbnail_from_kywc_thumbnail(thumbnail); ky_thumbnail->user_data = data; } void *kywc_thumbnail_get_user_data(kywc_thumbnail *thumbnail) { struct ky_thumbnail *ky_thumbnail = thumbnail_from_kywc_thumbnail(thumbnail); return ky_thumbnail->user_data; } void kywc_thumbnail_destroy(kywc_thumbnail *thumbnail) { struct ky_thumbnail *ky_thumbnail = thumbnail_from_kywc_thumbnail(thumbnail); ky_thumbnail_destroy(ky_thumbnail); } kylin-wayland-compositor/libkywc/kywc_capture.c0000664000175000017500000002432215160460057020776 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "kywc-capture-v1-client-protocol.h" #include "libkywc_p.h" bool _kywc_capture_init(kywc_context *ctx, enum kywc_context_capability capability); static void frame_handle_failed(void *data, struct kywc_capture_frame_v1 *kywc_capture_frame_v1) { struct ky_thumbnail *thumbnail = data; ky_thumbnail_destroy(thumbnail); } static void frame_handle_cancelled(void *data, struct kywc_capture_frame_v1 *kywc_capture_frame_v1) { struct ky_thumbnail *thumbnail = data; ky_thumbnail_destroy(thumbnail); } static void frame_handle_buffer(void *data, struct kywc_capture_frame_v1 *kywc_capture_frame_v1, int32_t fd, uint32_t format, uint32_t width, uint32_t height, uint32_t offset, uint32_t stride, uint32_t modifier_hi, uint32_t modifier_lo, uint32_t flags) { struct ky_thumbnail *thumbnail = data; bool want_buffer = false; thumbnail->buffer = (struct kywc_thumbnail_buffer){ .fd = fd, .format = format, .width = width, .height = height, .offset = offset, .stride = stride, .modifier = (uint64_t)modifier_hi << 32 | modifier_lo, .flags = flags, .n_planes = 1, .planes[0] = { fd, offset, stride }, }; uint32_t version = kywc_capture_frame_v1_get_version(kywc_capture_frame_v1); if (version >= KYWC_CAPTURE_FRAME_V1_BUFFER_DONE_SINCE_VERSION) { return; } ky_thumbnail_update_buffer(thumbnail, &thumbnail->buffer, &want_buffer); kywc_capture_frame_v1_release_buffer(kywc_capture_frame_v1, want_buffer); wl_display_flush(thumbnail->manager->ctx->display); close(fd); if (!want_buffer) { ky_thumbnail_destroy(thumbnail); } } static void frame_handle_buffer_with_plane(void *data, struct kywc_capture_frame_v1 *kywc_capture_frame_v1, uint32_t index, int32_t fd, uint32_t offset, uint32_t stride) { struct ky_thumbnail *thumbnail = data; thumbnail->buffer.planes[index].fd = fd; thumbnail->buffer.planes[index].offset = offset; thumbnail->buffer.planes[index].stride = stride; thumbnail->buffer.n_planes = index + 1; } static void frame_handle_buffer_done(void *data, struct kywc_capture_frame_v1 *kywc_capture_frame_v1) { struct ky_thumbnail *thumbnail = data; bool want_buffer = false; ky_thumbnail_update_buffer(thumbnail, &thumbnail->buffer, &want_buffer); kywc_capture_frame_v1_release_buffer(kywc_capture_frame_v1, want_buffer); wl_display_flush(thumbnail->manager->ctx->display); for (uint32_t i = 0; i < thumbnail->buffer.n_planes; i++) { close(thumbnail->buffer.planes[i].fd); } if (!want_buffer) { ky_thumbnail_destroy(thumbnail); } } static const struct kywc_capture_frame_v1_listener frame_listener = { .failed = frame_handle_failed, .cancelled = frame_handle_cancelled, .buffer = frame_handle_buffer, .buffer_with_plane = frame_handle_buffer_with_plane, .buffer_done = frame_handle_buffer_done, }; static void frame_destroy(struct ky_thumbnail *thumbnail) { struct kywc_capture_frame_v1 *frame = thumbnail->data; kywc_capture_frame_v1_destroy(frame); wl_display_flush(thumbnail->manager->ctx->display); } static void manager_capture_output(struct ky_thumbnail_manager *manager, struct ky_thumbnail *thumbnail, const char *uuid) { struct kywc_capture_manager_v1 *thumbnail_manager = manager->data; struct kywc_capture_frame_v1 *frame = kywc_capture_manager_v1_capture_output(thumbnail_manager, 0, uuid); kywc_capture_frame_v1_add_listener(frame, &frame_listener, thumbnail); wl_display_flush(manager->ctx->display); thumbnail->destroy = frame_destroy; thumbnail->data = frame; } static void manager_capture_workspace(struct ky_thumbnail_manager *manager, struct ky_thumbnail *thumbnail, const char *uuid, const char *output) { struct kywc_capture_manager_v1 *thumbnail_manager = manager->data; struct kywc_capture_frame_v1 *frame = kywc_capture_manager_v1_capture_workspace(thumbnail_manager, uuid, output); kywc_capture_frame_v1_add_listener(frame, &frame_listener, thumbnail); wl_display_flush(manager->ctx->display); thumbnail->destroy = frame_destroy; thumbnail->data = frame; } static void manager_capture_toplevel(struct ky_thumbnail_manager *manager, struct ky_thumbnail *thumbnail, const char *uuid, bool without_decoration) { struct kywc_capture_manager_v1 *thumbnail_manager = manager->data; struct kywc_capture_frame_v1 *frame = kywc_capture_manager_v1_capture_toplevel(thumbnail_manager, uuid, without_decoration); kywc_capture_frame_v1_add_listener(frame, &frame_listener, thumbnail); wl_display_flush(manager->ctx->display); thumbnail->destroy = frame_destroy; thumbnail->data = frame; } static void cursor_handle_enter(void *data, struct kywc_capture_cursor_v1 *kywc_capture_cursor_v1) { struct ky_cursor *cursor = data; ky_cursor_enter(cursor); } static void cursor_handle_leave(void *data, struct kywc_capture_cursor_v1 *kywc_capture_cursor_v1) { struct ky_cursor *cursor = data; ky_cursor_leave(cursor); } static void cursor_handle_position(void *data, struct kywc_capture_cursor_v1 *kywc_capture_cursor_v1, int32_t x, int32_t y) { struct ky_cursor *cursor = data; ky_cursor_update_position(cursor, x, y); } static void cursor_handle_hotspot(void *data, struct kywc_capture_cursor_v1 *kywc_capture_cursor_v1, int32_t x, int32_t y) { struct ky_cursor *cursor = data; ky_cursor_update_hotspot(cursor, x, y); } static const struct kywc_capture_cursor_v1_listener cursor_listener = { .enter = cursor_handle_enter, .leave = cursor_handle_leave, .position = cursor_handle_position, .hotspot = cursor_handle_hotspot, }; static void cursor_destroy(struct ky_cursor *cursor) { struct kywc_capture_cursor_v1 *p = cursor->data; kywc_capture_cursor_v1_destroy(p); wl_display_flush(cursor->manager->ctx->display); } static void manager_create_cursor(struct ky_cursor_manager *manager, struct ky_cursor *cursor, struct wl_seat *seat, struct ky_thumbnail *thumbnail) { struct kywc_capture_manager_v1 *capture_manager = manager->data; struct kywc_capture_cursor_v1 *p = kywc_capture_manager_v1_capture_cursor( capture_manager, seat, thumbnail ? thumbnail->data : NULL); kywc_capture_cursor_v1_add_listener(p, &cursor_listener, cursor); cursor->manager = manager; cursor->destroy = cursor_destroy; cursor->data = p; } static void manager_thumbnail_destroy(struct ky_thumbnail_manager *manager) { struct kywc_capture_manager_v1 *thumbnail_manager = manager->data; kywc_capture_manager_v1_destroy(thumbnail_manager); wl_display_flush(manager->ctx->display); } static void manager_cursor_destroy(struct ky_cursor_manager *manager) { struct kywc_capture_manager_v1 *cursor_manager = manager->data; kywc_capture_manager_v1_destroy(cursor_manager); wl_display_flush(manager->ctx->display); } static bool capture_provider_bind(struct ky_context_provider *provider, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if (strcmp(interface, kywc_capture_manager_v1_interface.name)) { return false; } bool extend = provider->capability == KYWC_CONTEXT_CAPABILITY_THUMBNAIL_EXT; uint32_t version_to_bind = (version > 1 && extend) ? version : 1; struct kywc_capture_manager_v1 *capture_manager = wl_registry_bind(registry, name, &kywc_capture_manager_v1_interface, version_to_bind); void *manager = provider->data; kywc_capture_manager_v1_set_user_data(capture_manager, manager); if (provider->capability == KYWC_CONTEXT_CAPABILITY_CURSOR) { struct ky_cursor_manager *cursor_manager = manager; cursor_manager->create_cursor = manager_create_cursor; cursor_manager->destroy = manager_cursor_destroy; cursor_manager->data = capture_manager; } else { struct ky_thumbnail_manager *thumbnail_manager = manager; thumbnail_manager->capture_output = manager_capture_output; thumbnail_manager->capture_workspace = manager_capture_workspace; thumbnail_manager->capture_toplevel = manager_capture_toplevel; thumbnail_manager->destroy = manager_thumbnail_destroy; thumbnail_manager->data = capture_manager; } return true; } static void capture_provider_destroy(struct ky_context_provider *provider) { if (provider->capability == KYWC_CONTEXT_CAPABILITY_CURSOR) { struct ky_cursor_manager *manager = provider->data; ky_cursor_manager_destroy(manager); } else { struct ky_thumbnail_manager *manager = provider->data; ky_thumbnail_manager_destroy(manager); } free(provider); } bool _kywc_capture_init(kywc_context *ctx, enum kywc_context_capability capability) { struct ky_context_provider *provider = calloc(1, sizeof(*provider)); if (!provider) { return false; } wl_list_init(&provider->link); provider->capability = capability; provider->bind = capture_provider_bind; provider->destroy = capture_provider_destroy; void *manager = NULL; if (provider->capability == KYWC_CONTEXT_CAPABILITY_CURSOR) { manager = ky_cursor_manager_create(ctx); } else { manager = ky_thumbnail_manager_create(ctx); } if (!manager) { free(provider); return false; } provider->data = manager; if (!ky_context_add_provider(ctx, provider, manager)) { free(manager); free(provider); return false; } return true; } kylin-wayland-compositor/libkywc/meson.build0000664000175000017500000000334015160461067020273 0ustar fengfengwayland_client = dependency('wayland-client') files_libkywc = files( 'libkywc.c', 'output.c', 'toplevel.c', 'thumbnail.c', 'workspace.c', 'cursor.c', # providers 'kywc_capture.c', 'kywc_output.c', 'kywc_toplevel.c', 'kywc_workspace.c', ) protos = [ 'kywc-capture-manager-v1', 'kywc-output-manager-v1', 'kywc-toplevel-manager-v1', 'kywc-workspace-manager-v1', ] foreach p : protos files_libkywc += protocols_code[p] files_libkywc += protocols_client_header[p] endforeach egl = dependency('egl') glesv2 = dependency('glesv2') wayland_egl = dependency('wayland-egl') buffer_sources = ['buffer.c', 'buffer.h'] foreach p : ['xdg-shell', 'xdg-decoration'] if p in protocols_code and p in protocols_client_header buffer_sources += [protocols_code[p], protocols_client_header[p]] endif endforeach buffer_static = static_library( 'kywc_buffer', buffer_sources, dependencies: [egl, glesv2, wayland_egl, png], build_by_default : false, ) kywc_buffer = declare_dependency( link_with: buffer_static, ) ld_args = '-Wl,--version-script,@0@/@1@'.format( meson.current_source_dir(), 'kywc.syms') link_depends = files('kywc.syms') kywc = shared_library( 'kywc', files_libkywc, c_args: '-fvisibility=default', link_args : ld_args, link_depends : link_depends, dependencies: [wayland_client, math], install : true, version : meson.project_version(), ) libkywc = declare_dependency( link_with: kywc, dependencies: wayland_client, include_directories: include_directories('.'), ) install_headers('libkywc.h') pkgconfig = import('pkgconfig') pkgconfig.generate( kywc, description: 'Kylin wayland compositor client library', url: 'https://gitee.com/openkylin/kylin-wayland-compositor', ) kylin-wayland-compositor/libkywc/buffer.h0000664000175000017500000000176415160461067017563 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _CLIENT_BUFFER_H_ #define _CLIENT_BUFFER_H_ #include struct kywc_buffer_helper *kywc_buffer_helper_create(kywc_context *ctx, bool window); void kywc_buffer_helper_destroy(struct kywc_buffer_helper *helper); struct kywc_buffer *kywc_buffer_helper_import_thumbnail(struct kywc_buffer_helper *helper, kywc_thumbnail *thumbnail, const struct kywc_thumbnail_buffer *buffer); bool kywc_buffer_write_to_file(struct kywc_buffer *buffer, int32_t x, int32_t y, uint32_t width, uint32_t height, const char *path); uint32_t kywc_buffer_get_pixel(struct kywc_buffer *buffer, int32_t x, int32_t y); bool kywc_buffer_show_in_window(struct kywc_buffer *buffer, const char *title); void kywc_buffer_destroy(struct kywc_buffer *buffer); #endif /* _CLIENT_BUFFER_H_ */ kylin-wayland-compositor/libkywc/toplevel.c0000664000175000017500000003025115160460057020126 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include "libkywc_p.h" static struct ky_toplevel *toplevel_from_kywc_toplevel(kywc_toplevel *kywc_toplevel) { struct ky_toplevel *toplevel = wl_container_of(kywc_toplevel, toplevel, base); return toplevel; } void ky_toplevel_destroy(struct ky_toplevel *toplevel) { if (toplevel->impl && toplevel->impl->destroy) { toplevel->impl->destroy(&toplevel->base); } if (toplevel->destroy) { toplevel->destroy(toplevel); } wl_list_remove(&toplevel->link); for (int i = 0; i < MAX_WORKSPACES; i++) { free((void *)toplevel->base.workspaces[i]); } free((void *)toplevel->base.uuid); free((void *)toplevel->base.title); free((void *)toplevel->base.app_id); free((void *)toplevel->base.icon); free((void *)toplevel->base.primary_output); free(toplevel); } void ky_toplevel_set_capabilities(struct ky_toplevel *toplevel, uint32_t capabilities) { toplevel->base.capabilities = capabilities; } void ky_toplevel_update_title(struct ky_toplevel *toplevel, const char *title) { if (toplevel->base.title && strcmp(toplevel->base.title, title) == 0) { return; } free((void *)toplevel->base.title); toplevel->base.title = strdup(title); toplevel->pending_mask |= KYWC_TOPLEVEL_STATE_TITLE; } void ky_toplevel_update_app_id(struct ky_toplevel *toplevel, const char *app_id) { if (toplevel->base.app_id && strcmp(toplevel->base.app_id, app_id) == 0) { return; } free((void *)toplevel->base.app_id); toplevel->base.app_id = strdup(app_id); toplevel->pending_mask |= KYWC_TOPLEVEL_STATE_APP_ID; } void ky_toplevel_update_primary_output(struct ky_toplevel *toplevel, const char *output_id) { if (toplevel->base.primary_output && strcmp(toplevel->base.primary_output, output_id) == 0) { return; } free((void *)toplevel->base.primary_output); toplevel->base.primary_output = strdup(output_id); toplevel->pending_mask |= KYWC_TOPLEVEL_STATE_PRIMARY_OUTPUT; } void ky_toplevel_update_maximized(struct ky_toplevel *toplevel, bool maximized) { if (toplevel->base.maximized == maximized) { return; } toplevel->base.maximized = maximized; toplevel->pending_mask |= KYWC_TOPLEVEL_STATE_MAXIMIZED; } void ky_toplevel_update_minimized(struct ky_toplevel *toplevel, bool minimized) { if (toplevel->base.minimized == minimized) { return; } toplevel->base.minimized = minimized; toplevel->pending_mask |= KYWC_TOPLEVEL_STATE_MINIMIZED; } void ky_toplevel_update_activated(struct ky_toplevel *toplevel, bool activated) { if (toplevel->base.activated == activated) { return; } toplevel->base.activated = activated; toplevel->pending_mask |= KYWC_TOPLEVEL_STATE_ACTIVATED; } void ky_toplevel_update_fullscreen(struct ky_toplevel *toplevel, bool fullscreen) { if (toplevel->base.fullscreen == fullscreen) { return; } toplevel->base.fullscreen = fullscreen; toplevel->pending_mask |= KYWC_TOPLEVEL_STATE_FULLSCREEN; } void ky_toplevel_update_parent(struct ky_toplevel *toplevel, struct ky_toplevel *parent) { kywc_toplevel *parent_toplevel = parent ? &parent->base : NULL; if (toplevel->base.parent == parent_toplevel) { return; } toplevel->base.parent = parent_toplevel; toplevel->pending_mask |= KYWC_TOPLEVEL_STATE_PARENT; } void ky_toplevel_update_icon(struct ky_toplevel *toplevel, const char *icon) { if (toplevel->base.icon && strcmp(toplevel->base.icon, icon) == 0) { return; } free((void *)toplevel->base.icon); toplevel->base.icon = strdup(icon); toplevel->pending_mask |= KYWC_TOPLEVEL_STATE_ICON; } void ky_toplevel_update_geometry(struct ky_toplevel *toplevel, int32_t x, int32_t y, uint32_t width, uint32_t height) { if (toplevel->base.x != x || toplevel->base.y != y) { toplevel->base.x = x; toplevel->base.y = y; toplevel->pending_mask |= KYWC_TOPLEVEL_STATE_POSITION; } if (toplevel->base.width != width || toplevel->base.height != height) { toplevel->base.width = width; toplevel->base.height = height; toplevel->pending_mask |= KYWC_TOPLEVEL_STATE_SIZE; } } void ky_toplevel_set_pid(struct ky_toplevel *toplevel, uint32_t pid) { toplevel->base.pid = pid; } void ky_toplevel_enter_workspace(struct ky_toplevel *toplevel, const char *workspace) { for (int i = 0; i < MAX_WORKSPACES; i++) { if (toplevel->base.workspaces[i] == NULL) { toplevel->base.workspaces[i] = strdup(workspace); break; } } toplevel->pending_mask |= KYWC_TOPLEVEL_STATE_WORKSPACE; } void ky_toplevel_leave_workspace(struct ky_toplevel *toplevel, const char *workspace) { for (int i = 0; i < MAX_WORKSPACES; i++) { if (toplevel->base.workspaces[i] && strcmp(toplevel->base.workspaces[i], workspace) == 0) { free((void *)toplevel->base.workspaces[i]); toplevel->base.workspaces[i] = NULL; break; } } toplevel->pending_mask |= KYWC_TOPLEVEL_STATE_WORKSPACE; } struct ky_toplevel *ky_toplevel_create(struct ky_toplevel_manager *manager, const char *uuid) { struct ky_toplevel *toplevel = calloc(1, sizeof(*toplevel)); if (!toplevel) { return NULL; } toplevel->manager = manager; toplevel->newly_added = true; toplevel->base.uuid = strdup(uuid); wl_list_insert(&manager->toplevels, &toplevel->link); return toplevel; } void ky_toplevel_update_states(struct ky_toplevel *toplevel) { kywc_context *ctx = toplevel->manager->ctx; if (toplevel->newly_added) { if (ctx->impl && ctx->impl->new_toplevel) { ctx->impl->new_toplevel(ctx, &toplevel->base, ctx->user_data); } toplevel->newly_added = false; toplevel->pending_mask = 0; } else { if (toplevel->pending_mask) { if (toplevel->impl && toplevel->impl->state) { toplevel->impl->state(&toplevel->base, toplevel->pending_mask); } toplevel->pending_mask = 0; } } } struct ky_toplevel_manager *ky_toplevel_manager_create(kywc_context *ctx) { struct ky_toplevel_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return NULL; } manager->ctx = ctx; wl_list_init(&manager->toplevels); return manager; } void ky_toplevel_manager_destroy(struct ky_toplevel_manager *manager) { if (!manager) { return; } // destroy all toplevels struct ky_toplevel *toplevel, *tmp; wl_list_for_each_safe(toplevel, tmp, &manager->toplevels, link) { ky_toplevel_destroy(toplevel); } if (manager->destroy) { manager->destroy(manager); } free(manager); } void kywc_toplevel_set_interface(kywc_toplevel *toplevel, const struct kywc_toplevel_interface *impl) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); ky_toplevel->impl = impl; } void kywc_context_for_each_toplevel(kywc_context *ctx, kywc_toplevel_iterator_func_t iterator, void *data) { if (!ctx->toplevel) { return; } struct ky_toplevel *toplevel; wl_list_for_each_reverse(toplevel, &ctx->toplevel->toplevels, link) { if (iterator(&toplevel->base, data)) { break; } } } kywc_context *kywc_toplevel_get_context(kywc_toplevel *toplevel) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); return ky_toplevel->manager->ctx; } kywc_toplevel *kywc_context_find_toplevel(kywc_context *ctx, const char *uuid) { if (!ctx->toplevel || !uuid) { return NULL; } struct ky_toplevel *toplevel; wl_list_for_each_reverse(toplevel, &ctx->toplevel->toplevels, link) { if (strcmp(toplevel->base.uuid, uuid) == 0) { return &toplevel->base; } } return NULL; } bool kywc_toplevel_has_children(kywc_toplevel *toplevel) { kywc_context *ctx = kywc_toplevel_get_context(toplevel); struct ky_toplevel *ky_toplevel; wl_list_for_each_reverse(ky_toplevel, &ctx->toplevel->toplevels, link) { if (ky_toplevel->base.parent == toplevel) { return true; } } return false; } void kywc_toplevel_set_maximized(kywc_toplevel *toplevel, const char *output) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); if (ky_toplevel->set_maximized) { ky_toplevel->set_maximized(ky_toplevel, output); } } void kywc_toplevel_unset_maximized(kywc_toplevel *toplevel) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); if (ky_toplevel->unset_maximized) { ky_toplevel->unset_maximized(ky_toplevel); } } void kywc_toplevel_set_minimized(kywc_toplevel *toplevel) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); if (ky_toplevel->set_minimized) { ky_toplevel->set_minimized(ky_toplevel); } } void kywc_toplevel_unset_minimized(kywc_toplevel *toplevel) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); if (ky_toplevel->unset_minimized) { ky_toplevel->unset_minimized(ky_toplevel); } } void kywc_toplevel_set_fullscreen(kywc_toplevel *toplevel, const char *output) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); if (ky_toplevel->set_fullscreen) { ky_toplevel->set_fullscreen(ky_toplevel, output); } } void kywc_toplevel_unset_fullscreen(kywc_toplevel *toplevel) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); if (ky_toplevel->unset_fullscreen) { ky_toplevel->unset_fullscreen(ky_toplevel); } } void kywc_toplevel_activate(kywc_toplevel *toplevel) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); if (ky_toplevel->activate) { ky_toplevel->activate(ky_toplevel); } } void kywc_toplevel_close(kywc_toplevel *toplevel) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); if (ky_toplevel->close) { ky_toplevel->close(ky_toplevel); } } void kywc_toplevel_enter_workspace(kywc_toplevel *toplevel, const char *workspace) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); if (ky_toplevel->enter_workspace) { ky_toplevel->enter_workspace(ky_toplevel, workspace); } } void kywc_toplevel_leave_workspace(kywc_toplevel *toplevel, const char *workspace) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); if (ky_toplevel->leave_workspace) { ky_toplevel->leave_workspace(ky_toplevel, workspace); } } void kywc_toplevel_move_to_workspace(kywc_toplevel *toplevel, const char *workspace) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); if (ky_toplevel->move_to_workspace) { ky_toplevel->move_to_workspace(ky_toplevel, workspace); } } void kywc_toplevel_move_to_output(kywc_toplevel *toplevel, const char *output) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); if (ky_toplevel->move_to_output) { ky_toplevel->move_to_output(ky_toplevel, output); } } void kywc_toplevel_set_position(kywc_toplevel *toplevel, int32_t x, int32_t y) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); if (ky_toplevel->set_position) { ky_toplevel->set_position(ky_toplevel, x, y); } } void kywc_toplevel_set_size(kywc_toplevel *toplevel, uint32_t width, uint32_t height) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); if (ky_toplevel->set_size) { ky_toplevel->set_size(ky_toplevel, width, height); } } void kywc_toplevel_set_user_data(kywc_toplevel *toplevel, void *data) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); ky_toplevel->user_data = data; } void *kywc_toplevel_get_user_data(kywc_toplevel *toplevel) { struct ky_toplevel *ky_toplevel = toplevel_from_kywc_toplevel(toplevel); return ky_toplevel->user_data; } kylin-wayland-compositor/libkywc/kywc_workspace.c0000664000175000017500000001525115160460057021332 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "kywc-workspace-v1-client-protocol.h" #include "libkywc_p.h" bool _kywc_workspace_init(kywc_context *ctx, enum kywc_context_capability capability); static void workspace_handle_name(void *data, struct kywc_workspace_v1 *kywc_workspace_v1, const char *name) { struct ky_workspace *workspace = data; ky_workspace_update_name(workspace, name); } static void workspace_handle_position(void *data, struct kywc_workspace_v1 *kywc_workspace_v1, uint32_t position) { struct ky_workspace *workspace = data; ky_workspace_update_position(workspace, position); } static void workspace_handle_activated(void *data, struct kywc_workspace_v1 *kywc_workspace_v1) { struct ky_workspace *workspace = data; ky_workspace_update_activated(workspace, true); } static void workspace_handle_deactivated(void *data, struct kywc_workspace_v1 *kywc_workspace_v1) { struct ky_workspace *workspace = data; ky_workspace_update_activated(workspace, false); } static void workspace_handle_removed(void *data, struct kywc_workspace_v1 *kywc_workspace_v1) { struct ky_workspace *workspace = data; ky_workspace_destroy(workspace); } static const struct kywc_workspace_v1_listener workspace_listener = { .name = workspace_handle_name, .position = workspace_handle_position, .activated = workspace_handle_activated, .deactivated = workspace_handle_deactivated, .removed = workspace_handle_removed, }; static void workspace_destroy(struct ky_workspace *workspace) { struct kywc_workspace_v1 *kywc_workspace_v1 = workspace->data; kywc_workspace_v1_destroy(kywc_workspace_v1); wl_display_flush(workspace->manager->ctx->display); } static void workspace_set_name(struct ky_workspace *workspace, const char *name) { struct kywc_workspace_v1 *kywc_workspace_v1 = workspace->data; kywc_workspace_v1_set_name(kywc_workspace_v1, name); wl_display_flush(workspace->manager->ctx->display); } static void workspace_set_position(struct ky_workspace *workspace, uint32_t position) { struct kywc_workspace_v1 *kywc_workspace_v1 = workspace->data; kywc_workspace_v1_set_position(kywc_workspace_v1, position); wl_display_flush(workspace->manager->ctx->display); } static void workspace_activate(struct ky_workspace *workspace) { struct kywc_workspace_v1 *kywc_workspace_v1 = workspace->data; kywc_workspace_v1_activate(kywc_workspace_v1); wl_display_flush(workspace->manager->ctx->display); } static void workspace_remove(struct ky_workspace *workspace) { struct kywc_workspace_v1 *kywc_workspace_v1 = workspace->data; kywc_workspace_v1_remove(kywc_workspace_v1); wl_display_flush(workspace->manager->ctx->display); } static void manager_handle_workspace(void *data, struct kywc_workspace_manager_v1 *kywc_workspace_manager_v1, struct kywc_workspace_v1 *kywc_workspace_v1, const char *uuid) { struct ky_workspace_manager *manager = data; struct ky_workspace *workspace = ky_workspace_create(manager, uuid); if (!workspace) { return; } workspace->set_name = workspace_set_name; workspace->set_position = workspace_set_position; workspace->activate = workspace_activate; workspace->remove = workspace_remove; workspace->destroy = workspace_destroy; workspace->data = kywc_workspace_v1; kywc_workspace_v1_add_listener(kywc_workspace_v1, &workspace_listener, workspace); } static void manager_handle_done(void *data, struct kywc_workspace_manager_v1 *kywc_workspace_manager_v1) { struct ky_workspace_manager *manager = data; ky_workspace_manager_update_states(manager); } static void manager_handle_finished(void *data, struct kywc_workspace_manager_v1 *kywc_workspace_manager_v1) { kywc_workspace_manager_v1_destroy(kywc_workspace_manager_v1); } static const struct kywc_workspace_manager_v1_listener workspace_manager_listener = { .workspace = manager_handle_workspace, .done = manager_handle_done, .finished = manager_handle_finished, }; static void manager_create_workspace(struct ky_workspace_manager *manager, const char *name, uint32_t position) { struct kywc_workspace_manager_v1 *workspace_manager = manager->data; kywc_workspace_manager_v1_create_workspace(workspace_manager, name, position); wl_display_flush(manager->ctx->display); } static void manager_destroy(struct ky_workspace_manager *manager) { struct kywc_workspace_manager_v1 *workspace_manager = manager->data; kywc_workspace_manager_v1_stop(workspace_manager); wl_display_flush(manager->ctx->display); } static bool workspace_provider_bind(struct ky_context_provider *provider, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if (strcmp(interface, kywc_workspace_manager_v1_interface.name) == 0) { uint32_t version_to_bind = version <= 1 ? version : 1; struct ky_workspace_manager *manager = provider->data; struct kywc_workspace_manager_v1 *workspace_manager = wl_registry_bind(registry, name, &kywc_workspace_manager_v1_interface, version_to_bind); kywc_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_listener, manager); manager->create_workspace = manager_create_workspace; manager->destroy = manager_destroy; manager->data = workspace_manager; return true; } return false; } static void workspace_provider_destroy(struct ky_context_provider *provider) { struct ky_workspace_manager *manager = provider->data; ky_workspace_manager_destroy(manager); free(provider); } bool _kywc_workspace_init(kywc_context *ctx, enum kywc_context_capability capability) { struct ky_context_provider *provider = calloc(1, sizeof(*provider)); if (!provider) { return false; } wl_list_init(&provider->link); provider->capability = capability; provider->bind = workspace_provider_bind; provider->destroy = workspace_provider_destroy; struct ky_workspace_manager *manager = ky_workspace_manager_create(ctx); if (!manager) { free(provider); return false; } provider->data = manager; if (!ky_context_add_provider(ctx, provider, manager)) { free(manager); free(provider); return false; } return true; } kylin-wayland-compositor/libkywc/libkywc_p.h0000664000175000017500000002540415160460057020270 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _LIBKYWC_HEADER_P_H_ #define _LIBKYWC_HEADER_P_H_ #include "libkywc.h" struct ky_context_provider { struct wl_list link; enum kywc_context_capability capability; bool (*bind)(struct ky_context_provider *provider, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version); void (*destroy)(struct ky_context_provider *provider); void *data; }; struct _kywc_context { struct wl_display *display; struct wl_registry *registry; bool own_display; uint32_t capabilities; const struct kywc_context_interface *impl; void *user_data; struct wl_list providers; struct wl_list pending_providers; struct ky_workspace_manager *workspace; struct ky_output_manager *output; struct ky_toplevel_manager *toplevel; struct ky_thumbnail_manager *thumbnail; struct ky_cursor_manager *cursor; }; bool ky_context_add_provider(kywc_context *ctx, struct ky_context_provider *provider, void *manager); /** * workspace */ struct ky_workspace_manager { kywc_context *ctx; struct wl_list workspaces; void (*create_workspace)(struct ky_workspace_manager *manager, const char *name, uint32_t position); void (*destroy)(struct ky_workspace_manager *manager); void *data; }; struct ky_workspace { kywc_workspace base; struct ky_workspace_manager *manager; struct wl_list link; const struct kywc_workspace_interface *impl; void *user_data; void (*set_name)(struct ky_workspace *workspace, const char *name); void (*set_position)(struct ky_workspace *workspace, uint32_t position); void (*activate)(struct ky_workspace *workspace); void (*remove)(struct ky_workspace *workspace); void (*destroy)(struct ky_workspace *workspace); void *data; uint32_t pending_mask; bool newly_added; }; struct ky_workspace_manager *ky_workspace_manager_create(kywc_context *ctx); void ky_workspace_manager_destroy(struct ky_workspace_manager *manager); void ky_workspace_manager_update_states(struct ky_workspace_manager *manager); struct ky_workspace *ky_workspace_create(struct ky_workspace_manager *manager, const char *uuid); void ky_workspace_destroy(struct ky_workspace *workspace); void ky_workspace_update_name(struct ky_workspace *workspace, const char *name); void ky_workspace_update_position(struct ky_workspace *workspace, uint32_t position); void ky_workspace_update_activated(struct ky_workspace *workspace, bool activated); /** * output */ struct ky_output_manager { kywc_context *ctx; struct wl_list outputs; struct ky_output *primary; void (*apply)(struct ky_output_manager *manager); void (*destroy)(struct ky_output_manager *manager); void *data; }; struct ky_output { kywc_output base; struct ky_output_manager *manager; struct wl_list link; const struct kywc_output_interface *impl; void *user_data; void (*destroy)(struct ky_output *output); void *data; uint32_t pending_mask; bool newly_added; }; struct ky_output_mode { struct kywc_output_mode base; struct ky_output *output; void (*destroy)(struct ky_output_mode *mode); void *data; }; struct ky_output_manager *ky_output_manager_create(kywc_context *ctx); void ky_output_manager_destroy(struct ky_output_manager *manager); void ky_output_manager_update_states(struct ky_output_manager *manager); void ky_output_manager_update_primary(struct ky_output_manager *manager, struct ky_output *primary); struct ky_output *ky_output_create(struct ky_output_manager *manager, const char *uuid); void ky_output_destroy(struct ky_output *output); void ky_output_set_name(struct ky_output *output, const char *name); void ky_output_set_make(struct ky_output *output, const char *make); void ky_output_set_model(struct ky_output *output, const char *model); void ky_output_set_serial_number(struct ky_output *output, const char *serial_number); void ky_output_set_description(struct ky_output *output, const char *description); void ky_output_set_physical_size(struct ky_output *output, int width, int height); void ky_output_set_capabilities(struct ky_output *output, uint32_t capabilities); struct ky_output_mode *ky_output_mode_create(struct ky_output *output); void ky_output_mode_set_size(struct ky_output_mode *mode, int width, int height); void ky_output_mode_set_refresh(struct ky_output_mode *mode, int refresh); void ky_output_mode_set_preferred(struct ky_output_mode *mode); void ky_output_mode_destroy(struct ky_output_mode *mode); void ky_output_update_enabled(struct ky_output *output, bool enabled); void ky_output_update_current_mode(struct ky_output *output, struct ky_output_mode *mode); void ky_output_update_position(struct ky_output *output, int x, int y); void ky_output_update_transform(struct ky_output *output, int transform); void ky_output_update_scale(struct ky_output *output, float scale); void ky_output_update_power(struct ky_output *output, bool power); void ky_output_update_primary(struct ky_output *output, bool primary); void ky_output_update_brightness(struct ky_output *output, uint32_t brightness); void ky_output_update_color_temp(struct ky_output *output, uint32_t color_temp); /** * toplevel */ struct ky_toplevel_manager { kywc_context *ctx; struct wl_list toplevels; void (*destroy)(struct ky_toplevel_manager *manager); void *data; }; struct ky_toplevel { kywc_toplevel base; struct ky_toplevel_manager *manager; struct wl_list link; const struct kywc_toplevel_interface *impl; void *user_data; void (*set_maximized)(struct ky_toplevel *toplevel, const char *output); void (*unset_maximized)(struct ky_toplevel *toplevel); void (*set_minimized)(struct ky_toplevel *toplevel); void (*unset_minimized)(struct ky_toplevel *toplevel); void (*set_fullscreen)(struct ky_toplevel *toplevel, const char *output); void (*unset_fullscreen)(struct ky_toplevel *toplevel); void (*activate)(struct ky_toplevel *toplevel); void (*close)(struct ky_toplevel *toplevel); void (*enter_workspace)(struct ky_toplevel *toplevel, const char *workspace); void (*leave_workspace)(struct ky_toplevel *toplevel, const char *workspace); void (*move_to_workspace)(struct ky_toplevel *toplevel, const char *workspace); void (*move_to_output)(struct ky_toplevel *toplevel, const char *output); void (*set_position)(struct ky_toplevel *toplevel, int32_t x, int32_t y); void (*set_size)(struct ky_toplevel *toplevel, uint32_t width, uint32_t height); void (*destroy)(struct ky_toplevel *toplevel); void *data; uint32_t pending_mask; bool newly_added; }; struct ky_toplevel_manager *ky_toplevel_manager_create(kywc_context *ctx); void ky_toplevel_manager_destroy(struct ky_toplevel_manager *manager); struct ky_toplevel *ky_toplevel_create(struct ky_toplevel_manager *manager, const char *uuid); void ky_toplevel_destroy(struct ky_toplevel *toplevel); void ky_toplevel_update_states(struct ky_toplevel *toplevel); void ky_toplevel_set_capabilities(struct ky_toplevel *toplevel, uint32_t capabilities); void ky_toplevel_update_title(struct ky_toplevel *toplevel, const char *title); void ky_toplevel_update_app_id(struct ky_toplevel *toplevel, const char *app_id); void ky_toplevel_update_primary_output(struct ky_toplevel *toplevel, const char *output_id); void ky_toplevel_update_maximized(struct ky_toplevel *toplevel, bool maximized); void ky_toplevel_update_minimized(struct ky_toplevel *toplevel, bool minimized); void ky_toplevel_update_activated(struct ky_toplevel *toplevel, bool activated); void ky_toplevel_update_fullscreen(struct ky_toplevel *toplevel, bool fullscreen); void ky_toplevel_update_parent(struct ky_toplevel *toplevel, struct ky_toplevel *parent); void ky_toplevel_update_icon(struct ky_toplevel *toplevel, const char *icon); void ky_toplevel_update_geometry(struct ky_toplevel *toplevel, int32_t x, int32_t y, uint32_t width, uint32_t height); void ky_toplevel_set_pid(struct ky_toplevel *toplevel, uint32_t pid); void ky_toplevel_enter_workspace(struct ky_toplevel *toplevel, const char *workspace); void ky_toplevel_leave_workspace(struct ky_toplevel *toplevel, const char *workspace); /** * thumbnail */ struct ky_thumbnail; struct ky_cursor; struct ky_thumbnail_manager { kywc_context *ctx; struct wl_list thumbnails; void (*capture_output)(struct ky_thumbnail_manager *manager, struct ky_thumbnail *thumbnail, const char *uuid); void (*capture_workspace)(struct ky_thumbnail_manager *manager, struct ky_thumbnail *thumbnail, const char *uuid, const char *output); void (*capture_toplevel)(struct ky_thumbnail_manager *manager, struct ky_thumbnail *thumbnail, const char *uuid, bool without_decoration); void (*destroy)(struct ky_thumbnail_manager *manager); void *data; }; struct ky_thumbnail { kywc_thumbnail base; struct ky_thumbnail_manager *manager; struct wl_list link; const struct kywc_thumbnail_interface *impl; void *user_data; void (*destroy)(struct ky_thumbnail *thumbnail); void *data; struct kywc_thumbnail_buffer buffer; }; struct ky_thumbnail_manager *ky_thumbnail_manager_create(kywc_context *ctx); void ky_thumbnail_manager_destroy(struct ky_thumbnail_manager *manager); void ky_thumbnail_destroy(struct ky_thumbnail *thumbnail); void ky_thumbnail_update_buffer(struct ky_thumbnail *thumbnail, const struct kywc_thumbnail_buffer *buffer, bool *want_buffer); struct ky_thumbnail *thumbnail_from_kywc_thumbnail(kywc_thumbnail *kywc_thumbnail); struct ky_cursor_manager { kywc_context *ctx; struct wl_list cursors; void (*create_cursor)(struct ky_cursor_manager *manager, struct ky_cursor *cursor, struct wl_seat *seat, struct ky_thumbnail *thumbnail); void (*destroy)(struct ky_cursor_manager *manager); void *data; }; struct ky_cursor_manager *ky_cursor_manager_create(kywc_context *ctx); void ky_cursor_manager_destroy(struct ky_cursor_manager *manager); struct ky_cursor { kywc_cursor base; struct ky_cursor_manager *manager; struct wl_list link; const struct kywc_cursor_interface *impl; void *user_data; void (*destroy)(struct ky_cursor *cursor); void *data; }; void ky_cursor_update_position(struct ky_cursor *cursor, uint32_t x, uint32_t y); void ky_cursor_update_hotspot(struct ky_cursor *cursor, uint32_t x, uint32_t y); void ky_cursor_enter(struct ky_cursor *cursor); void ky_cursor_leave(struct ky_cursor *cursor); void ky_cursor_destroy(struct ky_cursor *cursor); #endif /* _LIBKYWC_HEADER_P_H_ */ kylin-wayland-compositor/libkywc/workspace.c0000664000175000017500000001434715160460057020302 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include "libkywc_p.h" static struct ky_workspace *workspace_from_kywc_workspace(kywc_workspace *kywc_workspace) { struct ky_workspace *workspace = wl_container_of(kywc_workspace, workspace, base); return workspace; } void ky_workspace_destroy(struct ky_workspace *workspace) { if (workspace->impl && workspace->impl->destroy) { workspace->impl->destroy(&workspace->base); } if (workspace->destroy) { workspace->destroy(workspace); } wl_list_remove(&workspace->link); free((void *)workspace->base.uuid); free((void *)workspace->base.name); free(workspace); } void ky_workspace_update_name(struct ky_workspace *workspace, const char *name) { if (workspace->base.name && strcmp(workspace->base.name, name) == 0) { return; } free((void *)workspace->base.name); workspace->base.name = strdup(name); workspace->pending_mask |= KYWC_WORKSPACE_STATE_NAME; } void ky_workspace_update_position(struct ky_workspace *workspace, uint32_t position) { if (workspace->base.position == position) { return; } workspace->base.position = position; workspace->pending_mask |= KYWC_WORKSPACE_STATE_POSITION; } void ky_workspace_update_activated(struct ky_workspace *workspace, bool activated) { if (workspace->base.activated == activated) { return; } workspace->base.activated = activated; workspace->pending_mask |= KYWC_WORKSPACE_STATE_ACTIVATED; } struct ky_workspace *ky_workspace_create(struct ky_workspace_manager *manager, const char *uuid) { struct ky_workspace *workspace = calloc(1, sizeof(*workspace)); if (!workspace) { return NULL; } workspace->manager = manager; workspace->newly_added = true; workspace->base.uuid = strdup(uuid); wl_list_insert(&manager->workspaces, &workspace->link); return workspace; } void ky_workspace_manager_update_states(struct ky_workspace_manager *manager) { kywc_context *ctx = manager->ctx; struct ky_workspace *workspace; wl_list_for_each_reverse(workspace, &manager->workspaces, link) { if (workspace->newly_added) { if (ctx->impl && ctx->impl->new_workspace) { ctx->impl->new_workspace(ctx, &workspace->base, ctx->user_data); } workspace->newly_added = false; workspace->pending_mask = 0; } else { if (workspace->pending_mask) { if (workspace->impl && workspace->impl->state) { workspace->impl->state(&workspace->base, workspace->pending_mask); } workspace->pending_mask = 0; } } } } struct ky_workspace_manager *ky_workspace_manager_create(kywc_context *ctx) { struct ky_workspace_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return NULL; } manager->ctx = ctx; wl_list_init(&manager->workspaces); return manager; } void ky_workspace_manager_destroy(struct ky_workspace_manager *manager) { if (!manager) { return; } // destroy all workspaces struct ky_workspace *workspace, *tmp; wl_list_for_each_safe(workspace, tmp, &manager->workspaces, link) { ky_workspace_destroy(workspace); } if (manager->destroy) { manager->destroy(manager); } free(manager); } void kywc_workspace_set_interface(kywc_workspace *workspace, const struct kywc_workspace_interface *impl) { struct ky_workspace *ky_workspace = workspace_from_kywc_workspace(workspace); ky_workspace->impl = impl; } void kywc_context_for_each_workspace(kywc_context *ctx, kywc_workspace_iterator_func_t iterator, void *data) { if (!ctx->workspace) { return; } struct ky_workspace *workspace; wl_list_for_each_reverse(workspace, &ctx->workspace->workspaces, link) { if (iterator(&workspace->base, data)) { break; } } } kywc_workspace *kywc_context_find_workspace(kywc_context *ctx, const char *uuid) { if (!ctx->workspace || !uuid) { return NULL; } struct ky_workspace *workspace; wl_list_for_each_reverse(workspace, &ctx->workspace->workspaces, link) { if (strcmp(workspace->base.uuid, uuid) == 0) { return &workspace->base; } } return NULL; } kywc_context *kywc_workspace_get_context(kywc_workspace *workspace) { struct ky_workspace *ky_workspace = workspace_from_kywc_workspace(workspace); return ky_workspace->manager->ctx; } void kywc_workspace_create(kywc_context *ctx, const char *name, uint32_t position) { if (ctx && ctx->workspace && ctx->workspace->create_workspace) { ctx->workspace->create_workspace(ctx->workspace, name, position); } } void kywc_workspace_remove(kywc_workspace *workspace) { struct ky_workspace *ky_workspace = workspace_from_kywc_workspace(workspace); if (ky_workspace->remove) { ky_workspace->remove(ky_workspace); } } void kywc_workspace_set_name(kywc_workspace *workspace, const char *name) { struct ky_workspace *ky_workspace = workspace_from_kywc_workspace(workspace); if (ky_workspace->set_name) { ky_workspace->set_name(ky_workspace, name); } } void kywc_workspace_set_position(kywc_workspace *workspace, uint32_t position) { struct ky_workspace *ky_workspace = workspace_from_kywc_workspace(workspace); if (ky_workspace->set_position) { ky_workspace->set_position(ky_workspace, position); } } void kywc_workspace_activate(kywc_workspace *workspace) { struct ky_workspace *ky_workspace = workspace_from_kywc_workspace(workspace); if (ky_workspace->activate) { ky_workspace->activate(ky_workspace); } } void kywc_workspace_set_user_data(kywc_workspace *workspace, void *data) { struct ky_workspace *ky_workspace = workspace_from_kywc_workspace(workspace); ky_workspace->user_data = data; } void *kywc_workspace_get_user_data(kywc_workspace *workspace) { struct ky_workspace *ky_workspace = workspace_from_kywc_workspace(workspace); return ky_workspace->user_data; } kylin-wayland-compositor/libkywc/kywc_toplevel.c0000664000175000017500000002747515160460057021201 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "kywc-toplevel-v1-client-protocol.h" #include "libkywc_p.h" bool _kywc_toplevel_init(kywc_context *ctx, enum kywc_context_capability capability); static void toplevel_handle_closed(void *data, struct kywc_toplevel_v1 *kywc_toplevel_v1) { struct ky_toplevel *toplevel = data; ky_toplevel_destroy(toplevel); } static void toplevel_handle_done(void *data, struct kywc_toplevel_v1 *kywc_toplevel_v1) { struct ky_toplevel *toplevel = data; ky_toplevel_update_states(toplevel); } static void toplevel_handle_title(void *data, struct kywc_toplevel_v1 *kywc_toplevel_v1, const char *title) { struct ky_toplevel *toplevel = data; ky_toplevel_update_title(toplevel, title); } static void toplevel_handle_app_id(void *data, struct kywc_toplevel_v1 *kywc_toplevel_v1, const char *app_id) { struct ky_toplevel *toplevel = data; ky_toplevel_update_app_id(toplevel, app_id); } static void toplevel_handle_primary_output(void *data, struct kywc_toplevel_v1 *kywc_toplevel_v1, const char *output) { struct ky_toplevel *toplevel = data; ky_toplevel_update_primary_output(toplevel, output); } static void toplevel_handle_workspace_enter(void *data, struct kywc_toplevel_v1 *kywc_toplevel_v1, const char *workspace) { struct ky_toplevel *toplevel = data; ky_toplevel_enter_workspace(toplevel, workspace); } static void toplevel_handle_workspace_leave(void *data, struct kywc_toplevel_v1 *kywc_toplevel_v1, const char *workspace) { struct ky_toplevel *toplevel = data; ky_toplevel_leave_workspace(toplevel, workspace); } static void toplevel_handle_capabilities(void *data, struct kywc_toplevel_v1 *kywc_toplevel_v1, uint32_t flags) { struct ky_toplevel *toplevel = data; ky_toplevel_set_capabilities(toplevel, flags); } static void toplevel_handle_state(void *data, struct kywc_toplevel_v1 *kywc_toplevel_v1, uint32_t state) { struct ky_toplevel *toplevel = data; ky_toplevel_update_maximized(toplevel, state & KYWC_TOPLEVEL_V1_STATE_MAXIMIZED); ky_toplevel_update_minimized(toplevel, state & KYWC_TOPLEVEL_V1_STATE_MINIMIZED); ky_toplevel_update_activated(toplevel, state & KYWC_TOPLEVEL_V1_STATE_ACTIVATED); ky_toplevel_update_fullscreen(toplevel, state & KYWC_TOPLEVEL_V1_STATE_FULLSCREEN); } static void toplevel_handle_parent(void *data, struct kywc_toplevel_v1 *kywc_toplevel_v1, struct kywc_toplevel_v1 *parent) { struct ky_toplevel *toplevel = data; struct ky_toplevel *parent_toplevel = parent ? kywc_toplevel_v1_get_user_data(parent) : NULL; ky_toplevel_update_parent(toplevel, parent_toplevel); } static void toplevel_handle_icon(void *data, struct kywc_toplevel_v1 *kywc_toplevel_v1, const char *name) { struct ky_toplevel *toplevel = data; ky_toplevel_update_icon(toplevel, name); } static void toplevel_handle_geometry(void *data, struct kywc_toplevel_v1 *kywc_toplevel_v1, int32_t x, int32_t y, uint32_t width, uint32_t height) { struct ky_toplevel *toplevel = data; ky_toplevel_update_geometry(toplevel, x, y, width, height); } static void toplevel_handle_pid(void *data, struct kywc_toplevel_v1 *kywc_toplevel_v1, uint32_t pid) { struct ky_toplevel *toplevel = data; ky_toplevel_set_pid(toplevel, pid); } static const struct kywc_toplevel_v1_listener toplevel_listener = { .closed = toplevel_handle_closed, .done = toplevel_handle_done, .title = toplevel_handle_title, .app_id = toplevel_handle_app_id, .primary_output = toplevel_handle_primary_output, .workspace_enter = toplevel_handle_workspace_enter, .workspace_leave = toplevel_handle_workspace_leave, .capabilities = toplevel_handle_capabilities, .state = toplevel_handle_state, .parent = toplevel_handle_parent, .icon = toplevel_handle_icon, .geometry = toplevel_handle_geometry, .pid = toplevel_handle_pid, }; static void toplevel_destroy(struct ky_toplevel *toplevel) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_destroy(kywc_toplevel_v1); wl_display_flush(toplevel->manager->ctx->display); } static void toplevel_set_maximized(struct ky_toplevel *toplevel, const char *output) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_set_maximized(kywc_toplevel_v1, output); wl_display_flush(toplevel->manager->ctx->display); } static void toplevel_unset_maximized(struct ky_toplevel *toplevel) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_unset_maximized(kywc_toplevel_v1); wl_display_flush(toplevel->manager->ctx->display); } static void toplevel_set_minimized(struct ky_toplevel *toplevel) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_set_minimized(kywc_toplevel_v1); wl_display_flush(toplevel->manager->ctx->display); } static void toplevel_unset_minimized(struct ky_toplevel *toplevel) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_unset_minimized(kywc_toplevel_v1); wl_display_flush(toplevel->manager->ctx->display); } static void toplevel_set_fullscreen(struct ky_toplevel *toplevel, const char *output) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_set_fullscreen(kywc_toplevel_v1, output); wl_display_flush(toplevel->manager->ctx->display); } static void toplevel_unset_fullscreen(struct ky_toplevel *toplevel) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_unset_fullscreen(kywc_toplevel_v1); wl_display_flush(toplevel->manager->ctx->display); } static void toplevel_activate(struct ky_toplevel *toplevel) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_activate(kywc_toplevel_v1); wl_display_flush(toplevel->manager->ctx->display); } static void toplevel_close(struct ky_toplevel *toplevel) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_close(kywc_toplevel_v1); wl_display_flush(toplevel->manager->ctx->display); } static void toplevel_enter_workspace(struct ky_toplevel *toplevel, const char *workspace) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_enter_workspace(kywc_toplevel_v1, workspace); wl_display_flush(toplevel->manager->ctx->display); } static void toplevel_leave_workspace(struct ky_toplevel *toplevel, const char *workspace) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_leave_workspace(kywc_toplevel_v1, workspace); wl_display_flush(toplevel->manager->ctx->display); } static void toplevel_move_to_workspace(struct ky_toplevel *toplevel, const char *workspace) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_move_to_workspace(kywc_toplevel_v1, workspace); wl_display_flush(toplevel->manager->ctx->display); } static void toplevel_move_to_output(struct ky_toplevel *toplevel, const char *output) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_move_to_output(kywc_toplevel_v1, output); wl_display_flush(toplevel->manager->ctx->display); } static void toplevel_set_position(struct ky_toplevel *toplevel, int32_t x, int32_t y) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_set_position(kywc_toplevel_v1, x, y); wl_display_flush(toplevel->manager->ctx->display); } static void toplevel_set_size(struct ky_toplevel *toplevel, uint32_t width, uint32_t height) { struct kywc_toplevel_v1 *kywc_toplevel_v1 = toplevel->data; kywc_toplevel_v1_set_size(kywc_toplevel_v1, width, height); wl_display_flush(toplevel->manager->ctx->display); } static void manager_handle_toplevel(void *data, struct kywc_toplevel_manager_v1 *kywc_toplevel_manager_v1, struct kywc_toplevel_v1 *kywc_toplevel_v1, const char *uuid) { struct ky_toplevel_manager *manager = data; struct ky_toplevel *toplevel = ky_toplevel_create(manager, uuid); if (!toplevel) { return; } toplevel->set_maximized = toplevel_set_maximized; toplevel->unset_maximized = toplevel_unset_maximized; toplevel->set_minimized = toplevel_set_minimized; toplevel->unset_minimized = toplevel_unset_minimized; toplevel->set_fullscreen = toplevel_set_fullscreen; toplevel->unset_fullscreen = toplevel_unset_fullscreen; toplevel->activate = toplevel_activate; toplevel->close = toplevel_close; toplevel->enter_workspace = toplevel_enter_workspace; toplevel->leave_workspace = toplevel_leave_workspace; toplevel->move_to_workspace = toplevel_move_to_workspace; toplevel->move_to_output = toplevel_move_to_output; toplevel->set_position = toplevel_set_position; toplevel->set_size = toplevel_set_size; toplevel->destroy = toplevel_destroy; toplevel->data = kywc_toplevel_v1; kywc_toplevel_v1_add_listener(kywc_toplevel_v1, &toplevel_listener, toplevel); } static void manager_handle_finished(void *data, struct kywc_toplevel_manager_v1 *kywc_toplevel_manager_v1) { struct ky_toplevel_manager *manager = data; kywc_toplevel_manager_v1_destroy(kywc_toplevel_manager_v1); wl_display_flush(manager->ctx->display); } static const struct kywc_toplevel_manager_v1_listener toplevel_manager_listener = { .toplevel = manager_handle_toplevel, .finished = manager_handle_finished, }; static void manager_destroy(struct ky_toplevel_manager *manager) { struct kywc_toplevel_manager_v1 *toplevel_manager = manager->data; kywc_toplevel_manager_v1_stop(toplevel_manager); wl_display_flush(manager->ctx->display); } static bool toplevel_provider_bind(struct ky_context_provider *provider, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if (strcmp(interface, kywc_toplevel_manager_v1_interface.name) == 0) { uint32_t version_to_bind = version <= 1 ? version : 1; struct ky_toplevel_manager *manager = provider->data; struct kywc_toplevel_manager_v1 *toplevel_manager = wl_registry_bind(registry, name, &kywc_toplevel_manager_v1_interface, version_to_bind); kywc_toplevel_manager_v1_add_listener(toplevel_manager, &toplevel_manager_listener, manager); manager->destroy = manager_destroy; manager->data = toplevel_manager; return true; } return false; } static void toplevel_provider_destroy(struct ky_context_provider *provider) { struct ky_toplevel_manager *manager = provider->data; ky_toplevel_manager_destroy(manager); free(provider); } bool _kywc_toplevel_init(kywc_context *ctx, enum kywc_context_capability capability) { struct ky_context_provider *provider = calloc(1, sizeof(*provider)); if (!provider) { return false; } wl_list_init(&provider->link); provider->capability = capability; provider->bind = toplevel_provider_bind; provider->destroy = toplevel_provider_destroy; struct ky_toplevel_manager *manager = ky_toplevel_manager_create(ctx); if (!manager) { free(provider); return false; } provider->data = manager; if (!ky_context_add_provider(ctx, provider, manager)) { free(manager); free(provider); return false; } return true; } kylin-wayland-compositor/libkywc/output.c0000664000175000017500000002240415160461067017637 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #define _POSIX_C_SOURCE 200809L #include #include #include #include "libkywc_p.h" static struct ky_output *output_from_kywc_output(kywc_output *kywc_output) { struct ky_output *output = wl_container_of(kywc_output, output, base); return output; } void ky_output_destroy(struct ky_output *output) { struct ky_output_mode *mode, *tmp; wl_list_for_each_safe(mode, tmp, &output->base.modes, base.link) { ky_output_mode_destroy(mode); } if (output->impl && output->impl->destroy) { output->impl->destroy(&output->base); } if (output->destroy) { output->destroy(output); } if (output == output->manager->primary) { output->manager->primary = NULL; } wl_list_remove(&output->link); free((void *)output->base.uuid); free((void *)output->base.name); free((void *)output->base.make); free((void *)output->base.model); free((void *)output->base.serial); free((void *)output->base.description); free(output); } void ky_output_set_name(struct ky_output *output, const char *name) { assert(output->base.name == NULL); output->base.name = strdup(name); } void ky_output_set_make(struct ky_output *output, const char *make) { assert(output->base.make == NULL); output->base.make = strdup(make); } void ky_output_set_model(struct ky_output *output, const char *model) { assert(output->base.model == NULL); output->base.model = strdup(model); } void ky_output_set_serial_number(struct ky_output *output, const char *serial_number) { assert(output->base.serial == NULL); output->base.serial = strdup(serial_number); } void ky_output_set_description(struct ky_output *output, const char *description) { assert(output->base.description == NULL); output->base.description = strdup(description); } void ky_output_set_physical_size(struct ky_output *output, int width, int height) { output->base.physical_width = width; output->base.physical_height = height; } void ky_output_set_capabilities(struct ky_output *output, uint32_t capabilities) { output->base.capabilities = capabilities; } struct ky_output_mode *ky_output_mode_create(struct ky_output *output) { struct ky_output_mode *mode = calloc(1, sizeof(*mode)); if (!mode) { return NULL; } mode->output = output; wl_list_insert(&output->base.modes, &mode->base.link); return mode; } void ky_output_mode_set_size(struct ky_output_mode *mode, int width, int height) { mode->base.width = width; mode->base.height = height; } void ky_output_mode_set_refresh(struct ky_output_mode *mode, int refresh) { mode->base.refresh = refresh; } void ky_output_mode_set_preferred(struct ky_output_mode *mode) { mode->base.preferred = true; } void ky_output_mode_destroy(struct ky_output_mode *mode) { if (mode->destroy) { mode->destroy(mode); } wl_list_remove(&mode->base.link); free(mode); } void ky_output_update_enabled(struct ky_output *output, bool enabled) { if (output->base.enabled == enabled) { return; } output->base.enabled = enabled; if (!enabled) { output->base.mode = NULL; } output->pending_mask |= KYWC_OUTPUT_STATE_ENABLED; } void ky_output_update_current_mode(struct ky_output *output, struct ky_output_mode *mode) { if (output->base.mode == &mode->base) { return; } output->base.mode = &mode->base; output->pending_mask |= KYWC_OUTPUT_STATE_MODE; } void ky_output_update_position(struct ky_output *output, int x, int y) { if (output->base.x == x && output->base.y == y) { return; } output->base.x = x; output->base.y = y; output->pending_mask |= KYWC_OUTPUT_STATE_POSITION; } void ky_output_update_transform(struct ky_output *output, int transform) { if (output->base.transform == transform) { return; } output->base.transform = transform; output->pending_mask |= KYWC_OUTPUT_STATE_TRANSFORM; } void ky_output_update_scale(struct ky_output *output, float scale) { if (output->base.scale == scale) { return; } output->base.scale = scale; output->pending_mask |= KYWC_OUTPUT_STATE_SCALE; } void ky_output_update_power(struct ky_output *output, bool power) { if (output->base.power == power) { return; } output->base.power = power; output->pending_mask |= KYWC_OUTPUT_STATE_POWER; } void ky_output_update_brightness(struct ky_output *output, uint32_t brightness) { if (output->base.brightness == brightness) { return; } output->base.brightness = brightness; output->pending_mask |= KYWC_OUTPUT_STATE_BRIGHTNESS; } void ky_output_update_color_temp(struct ky_output *output, uint32_t color_temp) { if (output->base.color_temp == color_temp) { return; } output->base.color_temp = color_temp; output->pending_mask |= KYWC_OUTPUT_STATE_COLOR_TEMP; } struct ky_output *ky_output_create(struct ky_output_manager *manager, const char *uuid) { struct ky_output *output = calloc(1, sizeof(*output)); if (!output) { return NULL; } output->manager = manager; output->base.scale = 1.0; output->newly_added = true; output->base.uuid = strdup(uuid); wl_list_init(&output->base.modes); wl_list_insert(&manager->outputs, &output->link); return output; } static void ky_output_update_logical_size(struct ky_output *output) { if (!output->base.enabled) { output->base.width = 0; output->base.height = 0; return; } int width = output->base.mode->width; int height = output->base.mode->height; if (output->base.transform % 2 != 0) { width = output->base.mode->height; height = output->base.mode->width; } output->base.width = ceil(width / output->base.scale); output->base.height = ceil(height / output->base.scale); } void ky_output_manager_update_states(struct ky_output_manager *manager) { kywc_context *ctx = manager->ctx; struct ky_output *output; wl_list_for_each_reverse(output, &manager->outputs, link) { /* update logical size when states are ready */ ky_output_update_logical_size(output); if (output->newly_added) { if (ctx->impl && ctx->impl->new_output) { ctx->impl->new_output(ctx, &output->base, ctx->user_data); } output->newly_added = false; output->pending_mask = 0; } else { if (output->pending_mask) { if (output->impl && output->impl->state) { output->impl->state(&output->base, output->pending_mask); } output->pending_mask = 0; } } } } void ky_output_manager_update_primary(struct ky_output_manager *manager, struct ky_output *primary) { struct ky_output *old_primary = manager->primary; if (old_primary == primary) { return; } manager->primary = primary; if (old_primary) { old_primary->base.primary = false; old_primary->pending_mask |= KYWC_OUTPUT_STATE_PRIMARY; } if (primary) { primary->base.primary = true; primary->pending_mask |= KYWC_OUTPUT_STATE_PRIMARY; } } struct ky_output_manager *ky_output_manager_create(kywc_context *ctx) { struct ky_output_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return NULL; } manager->ctx = ctx; wl_list_init(&manager->outputs); return manager; } void ky_output_manager_destroy(struct ky_output_manager *manager) { if (!manager) { return; } // destroy all outputs struct ky_output *output, *tmp; wl_list_for_each_safe(output, tmp, &manager->outputs, link) { ky_output_destroy(output); } if (manager->destroy) { manager->destroy(manager); } free(manager); } void kywc_output_set_interface(kywc_output *output, const struct kywc_output_interface *impl) { struct ky_output *ky_output = output_from_kywc_output(output); ky_output->impl = impl; } void kywc_context_for_each_output(kywc_context *ctx, kywc_output_iterator_func_t iterator, void *data) { if (!ctx->output) { return; } struct ky_output *output; wl_list_for_each(output, &ctx->output->outputs, link) { if (iterator(&output->base, data)) { break; } } } kywc_context *kywc_output_get_context(kywc_output *output) { struct ky_output *ky_output = output_from_kywc_output(output); return ky_output->manager->ctx; } kywc_output *kywc_context_find_output(kywc_context *ctx, const char *uuid) { if (!ctx->output || !uuid) { return NULL; } struct ky_output *output; wl_list_for_each_reverse(output, &ctx->output->outputs, link) { if (strcmp(output->base.uuid, uuid) == 0) { return &output->base; } } return NULL; } void kywc_output_set_user_data(kywc_output *output, void *data) { struct ky_output *ky_output = output_from_kywc_output(output); ky_output->user_data = data; } void *kywc_output_get_user_data(kywc_output *output) { struct ky_output *ky_output = output_from_kywc_output(output); return ky_output->user_data; } kylin-wayland-compositor/libkywc/provider.h0000664000175000017500000000241515160460057020134 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _LIBKYWC_PROVIDER_H_ #define _LIBKYWC_PROVIDER_H_ #include "libkywc.h" bool _kywc_workspace_init(kywc_context *ctx, enum kywc_context_capability capability); bool _kywc_output_init(kywc_context *ctx, enum kywc_context_capability capability); bool _kywc_toplevel_init(kywc_context *ctx, enum kywc_context_capability capability); bool _kywc_capture_init(kywc_context *ctx, enum kywc_context_capability capability); static const struct ky_provider { enum kywc_context_capability capability; const char *name; bool (*init)(kywc_context *ctx, enum kywc_context_capability capability); } providers[] = { { KYWC_CONTEXT_CAPABILITY_WORKSPACE, "kywc_workspace_manager_v1", _kywc_workspace_init }, { KYWC_CONTEXT_CAPABILITY_OUTPUT, "kywc_output_manager_v1", _kywc_output_init }, { KYWC_CONTEXT_CAPABILITY_TOPLEVEL, "kywc_toplevel_manager_v1", _kywc_toplevel_init }, { KYWC_CONTEXT_CAPABILITY_THUMBNAIL, "kywc_capture_manager_v1", _kywc_capture_init }, { KYWC_CONTEXT_CAPABILITY_THUMBNAIL_EXT, "kywc_capture_manager_v1", _kywc_capture_init }, { KYWC_CONTEXT_CAPABILITY_CURSOR, "kywc_capture_manager_v1", _kywc_capture_init }, }; #endif /* _LIBKYWC_PROVIDER_H_ */ kylin-wayland-compositor/libkywc/buffer.c0000664000175000017500000007241615160461067017560 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include #include #include #include #include #include #include #include #include "xdg-decoration-unstable-v1-client-protocol.h" #include "xdg-shell-client-protocol.h" #include "buffer.h" struct kywc_buffer_helper { kywc_context *ctx; struct wl_list buffers; EGLDisplay display; EGLConfig config; EGLContext context; struct { bool KHR_image_base; bool EXT_image_dma_buf_import; bool EXT_image_dma_buf_import_modifiers; bool EXT_platform_wayland; bool OES_egl_image_external; } exts; struct { PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT; PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; } procs; struct wl_display *wl_display; struct wl_compositor *wl_compositor; struct xdg_wm_base *xdg_wm_base; struct zxdg_decoration_manager_v1 *xdg_deco_manager; GLuint shader_program; GLint tex; }; struct kywc_window { struct wl_surface *wl_surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct zxdg_toplevel_decoration_v1 *xdg_deco; struct wl_egl_window *native; EGLSurface surface; struct kywc_buffer *buffer; int width, height; }; struct kywc_buffer { struct kywc_buffer_helper *helper; struct wl_list link; void *ptr; size_t size; EGLImageKHR image; GLuint tex, fbo; kywc_thumbnail *thumbnail; struct kywc_thumbnail_buffer buffer; struct kywc_window *window; }; static const char *vertex_source = "\ precision highp float;\ attribute vec4 position;\ varying vec2 texcoord;\ \ void main() {\ gl_Position = vec4(position.xy, 0, 1);\ texcoord = position.zw;\ }\ "; static const char *fragment_source = "\ precision highp float;\ varying vec2 texcoord;\ uniform sampler2D tex;\ \ void main() {\ gl_FragColor = texture2D(tex, texcoord);\ }\ "; // clang-format off static const float vertices[] = { -1.f, 1.f, 0.f, 0.f, -1.f, -1.f, 0.f, 1.f, 1.f, -1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 0.f, }; // clang-format on static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) { xdg_wm_base_pong(xdg_wm_base, serial); } static const struct xdg_wm_base_listener xdg_wm_base_listener = { .ping = xdg_wm_base_ping, }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { struct kywc_buffer_helper *helper = data; if (strcmp(interface, wl_compositor_interface.name) == 0) { helper->wl_compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1); } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { helper->xdg_wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(helper->xdg_wm_base, &xdg_wm_base_listener, helper); } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { helper->xdg_deco_manager = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { // do nothing } static const struct wl_registry_listener registry_listener = { .global = registry_handle_global, .global_remove = registry_handle_global_remove, }; static void window_helper_init(struct kywc_buffer_helper *helper) { struct wl_registry *registry = wl_display_get_registry(helper->wl_display); wl_registry_add_listener(registry, ®istry_listener, helper); wl_display_roundtrip(helper->wl_display); wl_registry_destroy(registry); if (!helper->xdg_wm_base) { return; } /* build shaders for texture */ eglMakeCurrent(helper->display, EGL_NO_SURFACE, EGL_NO_SURFACE, helper->context); GLuint vbo; glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex_shader, 1, &vertex_source, NULL); glCompileShader(vertex_shader); GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment_shader, 1, &fragment_source, NULL); glCompileShader(fragment_shader); GLuint shader_program = glCreateProgram(); glAttachShader(shader_program, vertex_shader); glAttachShader(shader_program, fragment_shader); glLinkProgram(shader_program); glDetachShader(shader_program, vertex_shader); glDetachShader(shader_program, fragment_shader); glDeleteShader(vertex_shader); glDeleteShader(fragment_shader); GLint ok; glGetProgramiv(shader_program, GL_LINK_STATUS, &ok); if (ok == GL_FALSE) { fprintf(stderr, "Failed to link shader\n"); glDeleteProgram(shader_program); return; } glBindAttribLocation(shader_program, 0, "position"); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0); helper->shader_program = shader_program; helper->tex = glGetUniformLocation(shader_program, "tex"); eglMakeCurrent(helper->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } static void kywc_window_draw(struct kywc_window *window) { struct kywc_buffer_helper *helper = window->buffer->helper; eglMakeCurrent(helper->display, window->surface, window->surface, helper->context); glUseProgram(helper->shader_program); glEnableVertexAttribArray(0); glActiveTexture(GL_TEXTURE0); glViewport(0, 0, window->width, window->height); glBindTexture(GL_TEXTURE_2D, window->buffer->tex); glUniform1i(helper->tex, 0); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); eglSwapBuffers(helper->display, window->surface); } static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) { xdg_surface_ack_configure(xdg_surface, serial); } static const struct xdg_surface_listener xdg_surface_listener = { .configure = xdg_surface_configure, }; static void xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) { if (width == 0 || height == 0) { return; } struct kywc_window *window = data; window->width = width; window->height = height; wl_egl_window_resize(window->native, width, height, 0, 0); kywc_window_draw(window); } static void kywc_window_destroy(struct kywc_window *window) { EGLDisplay display = window->buffer->helper->display; eglDestroySurface(display, window->surface); wl_egl_window_destroy(window->native); zxdg_toplevel_decoration_v1_destroy(window->xdg_deco); xdg_toplevel_destroy(window->xdg_toplevel); xdg_surface_destroy(window->xdg_surface); window->buffer->window = NULL; free(window); } static void xdg_toplevel_close(void *data, struct xdg_toplevel *toplevel) { struct kywc_window *window = data; kywc_window_destroy(window); } static const struct xdg_toplevel_listener xdg_toplevel_listener = { .configure = xdg_toplevel_configure, .close = xdg_toplevel_close, }; static void toplevel_decoration_configure( void *data, struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode) { // do nothing } static const struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_listener = { .configure = toplevel_decoration_configure, }; static struct kywc_window *kywc_window_create(struct kywc_buffer *buffer, const char *title) { struct kywc_buffer_helper *helper = buffer->helper; if (!helper->xdg_wm_base) { return NULL; } struct kywc_window *window = calloc(1, sizeof(*window)); if (!window) { return NULL; } window->wl_surface = wl_compositor_create_surface(helper->wl_compositor); window->xdg_surface = xdg_wm_base_get_xdg_surface(helper->xdg_wm_base, window->wl_surface); xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, window); window->xdg_deco = zxdg_decoration_manager_v1_get_toplevel_decoration(helper->xdg_deco_manager, window->xdg_toplevel); zxdg_toplevel_decoration_v1_add_listener(window->xdg_deco, &xdg_toplevel_decoration_listener, window); zxdg_toplevel_decoration_v1_set_mode(window->xdg_deco, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); xdg_toplevel_set_app_id(window->xdg_toplevel, "kywc-thumbnail"); xdg_toplevel_set_title(window->xdg_toplevel, title); wl_surface_commit(window->wl_surface); window->width = buffer->buffer.width / 2; window->height = buffer->buffer.height / 2; window->native = wl_egl_window_create(window->wl_surface, window->width, window->height); window->surface = eglCreateWindowSurface(helper->display, helper->config, (EGLNativeWindowType)window->native, NULL); wl_display_roundtrip(helper->wl_display); window->buffer = buffer; return window; } static bool check_ext(const char *exts, const char *ext) { size_t extlen = strlen(ext); const char *end = exts + strlen(exts); while (exts < end) { if (*exts == ' ') { exts++; continue; } size_t n = strcspn(exts, " "); if (n == extlen && strncmp(ext, exts, n) == 0) { return true; } exts += n; } return false; } static void load_proc(void *proc_ptr, const char *name) { void *proc = (void *)eglGetProcAddress(name); if (proc == NULL) { fprintf(stderr, "eglGetProcAddress(%s) failed\n", name); abort(); } *(void **)proc_ptr = proc; } static void dmabuf_helper_init(struct kywc_buffer_helper *helper) { const char *client_exts_str = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); if (client_exts_str == NULL) { if (eglGetError() == EGL_BAD_DISPLAY) { fprintf(stderr, "EGL_EXT_client_extensions not supported\n"); } else { fprintf(stderr, "Failed to query EGL client extensions\n"); } return; } // fprintf(stdout, "Supported EGL client extensions: %s\n", client_exts_str); if (!check_ext(client_exts_str, "EGL_EXT_platform_base")) { fprintf(stderr, "EGL_EXT_platform_base not supported\n"); return; } if (check_ext(client_exts_str, "EGL_EXT_platform_wayland")) { helper->exts.EXT_platform_wayland = true; load_proc(&helper->procs.eglGetPlatformDisplayEXT, "eglGetPlatformDisplayEXT"); } if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { fprintf(stderr, "Failed to bind to the OpenGL ES API\n"); return; } EGLDisplay egl_display = EGL_NO_DISPLAY; /* get egl display */ if (helper->exts.EXT_platform_wayland) { egl_display = helper->procs.eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, helper->wl_display, NULL); } else { egl_display = eglGetDisplay(helper->wl_display); } if (egl_display == EGL_NO_DISPLAY) { fprintf(stderr, "egl get display failed\n"); return; } EGLint major, minor; if (eglInitialize(egl_display, &major, &minor) == EGL_FALSE) { fprintf(stderr, "Failed to initialize EGL\n"); return; } const char *display_exts_str = eglQueryString(egl_display, EGL_EXTENSIONS); if (display_exts_str == NULL) { fprintf(stderr, "Failed to query EGL display extensions\n"); return; } if (check_ext(display_exts_str, "EGL_KHR_image_base")) { helper->exts.KHR_image_base = true; load_proc(&helper->procs.eglCreateImageKHR, "eglCreateImageKHR"); load_proc(&helper->procs.eglDestroyImageKHR, "eglDestroyImageKHR"); } helper->exts.EXT_image_dma_buf_import = check_ext(display_exts_str, "EGL_EXT_image_dma_buf_import"); if (check_ext(display_exts_str, "EGL_EXT_image_dma_buf_import_modifiers")) { helper->exts.EXT_image_dma_buf_import_modifiers = true; } // fprintf(stdout, "Using EGL %d.%d\n", (int)major, (int)minor); // fprintf(stdout, "Supported EGL display extensions: %s\n", display_exts_str); // fprintf(stdout, "EGL vendor: %s\n", eglQueryString(helper->display, EGL_VENDOR)); // clang-format off EGLint config_attribs[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; // clang-format on EGLint num_configs = 0; if (!eglChooseConfig(egl_display, config_attribs, &helper->config, 1, &num_configs) || !num_configs) { fprintf(stderr, "Failed to choose a config\n"); return; } /* using opengles 2 */ const EGLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; EGLContext egl_context = eglCreateContext(egl_display, helper->config, EGL_NO_CONTEXT, attribs); if (egl_context == EGL_NO_CONTEXT) { fprintf(stderr, "Failed to create EGL context\n"); return; } if (!eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context)) { fprintf(stderr, "eglMakeCurrent failed\n"); eglDestroyContext(egl_display, egl_context); return; } const char *exts_str = (const char *)glGetString(GL_EXTENSIONS); if (exts_str == NULL) { fprintf(stderr, "Failed to get GL_EXTENSIONS\n"); eglDestroyContext(egl_display, egl_context); return; } if (check_ext(exts_str, "GL_OES_EGL_image_external")) { helper->exts.OES_egl_image_external = true; load_proc(&helper->procs.glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES"); } // fprintf(stdout, "Using %s\n", glGetString(GL_VERSION)); // fprintf(stdout, "GL vendor: %s\n", glGetString(GL_VENDOR)); // fprintf(stdout, "GL renderer: %s\n", glGetString(GL_RENDERER)); // fprintf(stdout, "Supported GLES2 extensions: %s\n", exts_str); helper->display = egl_display; helper->context = egl_context; eglMakeCurrent(helper->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } struct kywc_buffer_helper *kywc_buffer_helper_create(kywc_context *ctx, bool window) { struct kywc_buffer_helper *helper = calloc(1, sizeof(*helper)); if (helper == NULL) { fprintf(stderr, "Allocation failed\n"); return NULL; } helper->ctx = ctx; helper->wl_display = kywc_context_get_display(ctx); wl_list_init(&helper->buffers); dmabuf_helper_init(helper); if (helper->display && window) { window_helper_init(helper); } return helper; } void kywc_buffer_helper_destroy(struct kywc_buffer_helper *helper) { if (helper == NULL) { return; } struct kywc_buffer *buffer, *tmp; wl_list_for_each_safe(buffer, tmp, &helper->buffers, link) { kywc_buffer_destroy(buffer); } if (helper->xdg_wm_base) { xdg_wm_base_destroy(helper->xdg_wm_base); wl_compositor_destroy(helper->wl_compositor); wl_display_flush(helper->wl_display); } if (helper->display) { eglMakeCurrent(helper->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(helper->display, helper->context); eglTerminate(helper->display); eglReleaseThread(); } free(helper); } #define ADD_ATTRIB(name, value) \ do { \ attribs[num_attribs++] = (name); \ attribs[num_attribs++] = (value); \ attribs[num_attribs] = EGL_NONE; \ } while (0) static bool kywc_buffer_import_dmabuf(struct kywc_buffer *kywc_buffer, const struct kywc_thumbnail_buffer *buffer, bool can_reuse) { struct kywc_buffer_helper *helper = kywc_buffer->helper; if (!helper->exts.KHR_image_base || !helper->exts.EXT_image_dma_buf_import) { fprintf(stderr, "dma_buf_import is not support\n"); return false; } if (buffer->modifier != DRM_FORMAT_MOD_INVALID && buffer->modifier != DRM_FORMAT_MOD_LINEAR && !helper->exts.EXT_image_dma_buf_import_modifiers) { fprintf(stderr, "buffer has a modifier when dma_buf_import_modifiers is not supported\n"); return false; } if (kywc_buffer->image) { if (can_reuse) { return true; } /* release prev buffer stuff */ eglMakeCurrent(helper->display, EGL_NO_SURFACE, EGL_NO_SURFACE, helper->context); glDeleteFramebuffers(1, &kywc_buffer->fbo); glDeleteTextures(1, &kywc_buffer->tex); helper->procs.eglDestroyImageKHR(helper->display, kywc_buffer->image); eglMakeCurrent(helper->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } EGLint attribs[50] = { EGL_NONE }; int num_attribs = 0; ADD_ATTRIB(EGL_WIDTH, buffer->width); ADD_ATTRIB(EGL_HEIGHT, buffer->height); ADD_ATTRIB(EGL_LINUX_DRM_FOURCC_EXT, buffer->format); struct { EGLint fd, offset, pitch, mod_lo, mod_hi; } attr_names[4] = { { EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT }, { EGL_DMA_BUF_PLANE1_FD_EXT, EGL_DMA_BUF_PLANE1_OFFSET_EXT, EGL_DMA_BUF_PLANE1_PITCH_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT }, { EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT, EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT }, { EGL_DMA_BUF_PLANE3_FD_EXT, EGL_DMA_BUF_PLANE3_OFFSET_EXT, EGL_DMA_BUF_PLANE3_PITCH_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT } }; for (uint32_t i = 0; i < buffer->n_planes; i++) { ADD_ATTRIB(attr_names[i].fd, buffer->planes[i].fd); ADD_ATTRIB(attr_names[i].offset, buffer->planes[i].offset); ADD_ATTRIB(attr_names[i].pitch, buffer->planes[i].stride); if (buffer->modifier != DRM_FORMAT_MOD_INVALID) { ADD_ATTRIB(attr_names[i].mod_hi, buffer->modifier >> 32); ADD_ATTRIB(attr_names[i].mod_lo, buffer->modifier & 0xFFFFFFFF); } } ADD_ATTRIB(EGL_IMAGE_PRESERVED_KHR, EGL_TRUE); kywc_buffer->image = helper->procs.eglCreateImageKHR(helper->display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); if (kywc_buffer->image == EGL_NO_IMAGE_KHR) { fprintf(stderr, "eglCreateImageKHR failed\n"); return false; } eglMakeCurrent(helper->display, EGL_NO_SURFACE, EGL_NO_SURFACE, helper->context); glGenTextures(1, &kywc_buffer->tex); glBindTexture(GL_TEXTURE_2D, kywc_buffer->tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); helper->procs.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, kywc_buffer->image); glBindTexture(GL_TEXTURE_2D, 0); glGenFramebuffers(1, &kywc_buffer->fbo); glBindFramebuffer(GL_FRAMEBUFFER, kywc_buffer->fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, kywc_buffer->tex, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); eglMakeCurrent(helper->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); return true; } static bool kywc_buffer_export_dmabuf(struct kywc_buffer *buffer, int32_t x, int32_t y, uint32_t width, uint32_t height, void *data) { struct kywc_buffer_helper *helper = buffer->helper; if (!eglMakeCurrent(helper->display, EGL_NO_SURFACE, EGL_NO_SURFACE, helper->context)) { fprintf(stderr, "eglMakeCurrent failed\n"); return false; } glBindFramebuffer(GL_FRAMEBUFFER, buffer->fbo); glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(x, y, width, height, GL_BGRA_EXT, GL_UNSIGNED_BYTE, data); glBindFramebuffer(GL_FRAMEBUFFER, 0); eglMakeCurrent(helper->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); return true; } static bool kywc_buffer_import_memfd(struct kywc_buffer *kywc_buffer, const struct kywc_thumbnail_buffer *buffer, bool can_reuse) { if (kywc_buffer->ptr) { if (can_reuse) { return true; } munmap(kywc_buffer->ptr, kywc_buffer->size); } kywc_buffer->size = buffer->height * buffer->stride + buffer->offset; kywc_buffer->ptr = mmap(0, kywc_buffer->size, PROT_READ, MAP_PRIVATE, buffer->fd, 0); if (kywc_buffer->ptr == MAP_FAILED) { kywc_buffer->ptr = NULL; return false; } return true; } static bool kywc_buffer_export_memfd(struct kywc_buffer *buffer, int32_t x, int32_t y, uint32_t width, uint32_t height, void *data) { char *src = (char *)buffer->ptr + buffer->buffer.offset; size_t line = width * 4; /* check if can use one memcpy */ if (buffer->buffer.stride == line) { memcpy(data, src, buffer->size - buffer->buffer.offset); return true; } char *dst = data; for (uint32_t i = 0; i < height; i++) { memcpy(dst, src + (i + y) * buffer->buffer.stride + x * 4, line); dst += line; } return true; } static struct kywc_buffer *helper_get_buffer(struct kywc_buffer_helper *helper, kywc_thumbnail *thumbnail) { struct kywc_buffer *buffer; wl_list_for_each(buffer, &helper->buffers, link) { if (buffer->thumbnail == thumbnail) { return buffer; } } return NULL; } struct kywc_buffer *kywc_buffer_helper_import_thumbnail(struct kywc_buffer_helper *helper, kywc_thumbnail *thumbnail, const struct kywc_thumbnail_buffer *buffer) { if (helper == NULL || buffer == NULL) { return NULL; } struct kywc_buffer *kywc_buffer = helper_get_buffer(helper, thumbnail); if (!kywc_buffer) { kywc_buffer = calloc(1, sizeof(*kywc_buffer)); if (kywc_buffer == NULL) { fprintf(stderr, "Allocation failed\n"); return NULL; } kywc_buffer->helper = helper; kywc_buffer->thumbnail = thumbnail; wl_list_insert(&helper->buffers, &kywc_buffer->link); } bool success = false; bool can_reuse = buffer->flags & KYWC_THUMBNAIL_BUFFER_IS_REUSED; /* try egl dambuf import if support */ if (helper->display && (buffer->flags & KYWC_THUMBNAIL_BUFFER_IS_DMABUF)) { success = kywc_buffer_import_dmabuf(kywc_buffer, buffer, can_reuse); } /* fallback to mmap */ if (!success) { success = kywc_buffer_import_memfd(kywc_buffer, buffer, can_reuse); } if (!success) { free(kywc_buffer); return NULL; } kywc_buffer->buffer = *buffer; return kywc_buffer; } void kywc_buffer_destroy(struct kywc_buffer *buffer) { if (buffer == NULL) { return; } wl_list_remove(&buffer->link); if (buffer->window) { kywc_window_destroy(buffer->window); } if (buffer->image) { struct kywc_buffer_helper *helper = buffer->helper; eglMakeCurrent(helper->display, EGL_NO_SURFACE, EGL_NO_SURFACE, helper->context); glDeleteFramebuffers(1, &buffer->fbo); glDeleteTextures(1, &buffer->tex); helper->procs.eglDestroyImageKHR(helper->display, buffer->image); eglMakeCurrent(helper->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } if (buffer->ptr) { munmap(buffer->ptr, buffer->size); } free(buffer); } typedef struct __attribute__((__packed__)) { uint16_t type; uint32_t file_size; uint16_t reserved1; uint16_t reserved2; uint32_t offset; } bmp_file_header; typedef struct __attribute__((__packed__)) { uint32_t dib_size; int32_t width; int32_t height; uint16_t planes; uint16_t bpp; uint32_t compression; uint32_t img_size; uint32_t hres; uint32_t vres; uint32_t clr_palette; uint32_t clr_important; } bmp_info_header; static bool do_encode_bmp(FILE *file, int width, int height, size_t stride, uint8_t *data) { bmp_info_header info_header = { .dib_size = sizeof(info_header), .width = width, .height = -height, .planes = 1, .bpp = 32, .img_size = stride * height, }; bmp_file_header file_header = { .type = 'B' | ('M' << 8), .file_size = sizeof(file_header) + sizeof(info_header) + info_header.img_size, .offset = sizeof(file_header) + sizeof(info_header), }; fwrite(&file_header, sizeof(file_header), 1, file); fwrite(&info_header, sizeof(info_header), 1, file); fwrite(data, stride, height, file); return true; } static bool do_encode_png(FILE *file, int width, int height, size_t stride, uint8_t *data) { png_struct *png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) { return false; } png_info *info = png_create_info_struct(png); if (!info) { png_destroy_write_struct(&png, NULL); return false; } png_init_io(png, file); // png_set_compression_level(png, 6); png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_set_alpha_mode(png, PNG_ALPHA_PREMULTIPLIED, PNG_GAMMA_LINEAR); png_set_bgr(png); png_write_info(png, info); png_bytep *row_ptrs = malloc(height * sizeof(*row_ptrs)); if (!row_ptrs) { png_destroy_write_struct(&png, &info); return false; } for (int i = 0; i < height; ++i) { row_ptrs[i] = data + i * stride; } png_write_image(png, row_ptrs); png_write_end(png, NULL); png_destroy_write_struct(&png, &info); free(row_ptrs); return true; } bool kywc_buffer_write_to_file(struct kywc_buffer *buffer, int32_t x, int32_t y, uint32_t width, uint32_t height, const char *path) { if (x < 0 || y < 0 || (uint32_t)x >= buffer->buffer.width || (uint32_t)y >= buffer->buffer.height) { return false; } if (x + width > buffer->buffer.width) { width = buffer->buffer.width; } if (y + height > buffer->buffer.height) { height = buffer->buffer.height; } size_t size = width * height * 4; unsigned char *data = malloc(size); if (!data) { return false; } bool success = false; if (buffer->image) { success = kywc_buffer_export_dmabuf(buffer, x, y, width, height, data); } else if (buffer->ptr) { success = kywc_buffer_export_memfd(buffer, x, y, width, height, data); } if (!success) { free(data); return false; } FILE *fp = fopen(path, "w+"); if (!fp) { free(data); fprintf(stderr, "failed to open cache file %s\n", path); return false; } size_t len = strlen(path); const char *suffix = path + len - 3; if (len > 3 && strncmp(suffix, "bmp", 3) == 0) { success = do_encode_bmp(fp, width, height, width * 4, data); } else if (len > 3 && strncmp(suffix, "png", 3) == 0) { success = do_encode_png(fp, width, height, width * 4, data); } else { fwrite(data, 1, size, fp); } fflush(fp); fclose(fp); free(data); return success; } uint32_t kywc_buffer_get_pixel(struct kywc_buffer *buffer, int32_t x, int32_t y) { if (x < 0 || y < 0 || (uint32_t)x >= buffer->buffer.width || (uint32_t)y >= buffer->buffer.height) { return 0; } uint32_t pixel = 0; if (buffer->image) { kywc_buffer_export_dmabuf(buffer, x, y, 1, 1, &pixel); } else if (buffer->ptr) { kywc_buffer_export_memfd(buffer, x, y, 1, 1, &pixel); } return pixel; } bool kywc_buffer_show_in_window(struct kywc_buffer *buffer, const char *title) { struct kywc_buffer_helper *helper = buffer->helper; /* only support dmabuf now */ if (!helper->xdg_wm_base || !buffer->image) { return false; } if (!buffer->window) { buffer->window = kywc_window_create(buffer, title); if (!buffer->window) { return false; } } kywc_window_draw(buffer->window); return true; } kylin-wayland-compositor/libkywc/kywc.syms0000664000175000017500000000004515160460057020020 0ustar fengfeng{ global: kywc_*; local: *; }; kylin-wayland-compositor/libkywc/libkywc.h0000664000175000017500000003164715160460057017757 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #ifndef _LIBKYWC_HEADER_H_ #define _LIBKYWC_HEADER_H_ #include #include #ifdef __cplusplus extern "C" { #endif typedef struct _kywc_context kywc_context; typedef struct _kywc_output kywc_output; typedef struct _kywc_toplevel kywc_toplevel; typedef struct _kywc_workspace kywc_workspace; typedef struct _kywc_thumbnail kywc_thumbnail; enum kywc_context_capability { KYWC_CONTEXT_CAPABILITY_OUTPUT = 1 << 0, KYWC_CONTEXT_CAPABILITY_TOPLEVEL = 1 << 1, KYWC_CONTEXT_CAPABILITY_WORKSPACE = 1 << 2, KYWC_CONTEXT_CAPABILITY_THUMBNAIL = 1 << 3, /* with multi-plane support */ KYWC_CONTEXT_CAPABILITY_THUMBNAIL_EXT = 1 << 4, KYWC_CONTEXT_CAPABILITY_CURSOR = 1 << 5, }; struct kywc_context_interface { /* called when context is created successfully but before wayland roundtrip */ void (*create)(kywc_context *ctx, void *data); void (*destroy)(kywc_context *ctx, void *data); void (*new_output)(kywc_context *ctx, kywc_output *output, void *data); void (*new_toplevel)(kywc_context *ctx, kywc_toplevel *toplevel, void *data); void (*new_workspace)(kywc_context *ctx, kywc_workspace *workspace, void *data); }; /** * Create a kywc context with the wayland display name. */ kywc_context *kywc_context_create(const char *name, uint32_t capabilities, const struct kywc_context_interface *impl, void *data); /** * Create a kywc context with the exist wayland display. */ kywc_context *kywc_context_create_by_display(struct wl_display *display, uint32_t capabilities, const struct kywc_context_interface *impl, void *data); struct wl_display *kywc_context_get_display(kywc_context *ctx); void kywc_context_set_user_data(kywc_context *ctx, void *data); void *kywc_context_get_user_data(kywc_context *ctx); /** * Get the fd, work with kywc_context_process. */ int kywc_context_get_fd(kywc_context *ctx); int kywc_context_process(kywc_context *ctx); /** * Use the internal event loop in kywc context. */ void kywc_context_dispatch(kywc_context *ctx); void kywc_context_destroy(kywc_context *ctx); /** * workspace or virtual desktop */ struct _kywc_workspace { const char *uuid; const char *name; uint32_t position; bool activated; }; enum kywc_workspace_state_mask { KYWC_WORKSPACE_STATE_NAME = 1 << 0, KYWC_WORKSPACE_STATE_POSITION = 1 << 1, KYWC_WORKSPACE_STATE_ACTIVATED = 1 << 2, }; struct kywc_workspace_interface { void (*state)(kywc_workspace *workspace, uint32_t mask); void (*destroy)(kywc_workspace *workspace); }; void kywc_workspace_set_interface(kywc_workspace *workspace, const struct kywc_workspace_interface *impl); /* return true if need to break the loop */ typedef bool (*kywc_workspace_iterator_func_t)(kywc_workspace *workspace, void *data); void kywc_context_for_each_workspace(kywc_context *ctx, kywc_workspace_iterator_func_t iterator, void *data); kywc_workspace *kywc_context_find_workspace(kywc_context *ctx, const char *uuid); kywc_context *kywc_workspace_get_context(kywc_workspace *workspace); void kywc_workspace_create(kywc_context *ctx, const char *name, uint32_t position); void kywc_workspace_remove(kywc_workspace *workspace); void kywc_workspace_set_position(kywc_workspace *workspace, uint32_t position); void kywc_workspace_set_name(kywc_workspace *workspace, const char *name); void kywc_workspace_activate(kywc_workspace *workspace); void kywc_workspace_set_user_data(kywc_workspace *workspace, void *data); void *kywc_workspace_get_user_data(kywc_workspace *workspace); /** * output */ enum kywc_output_capability { KYWC_OUTPUT_CAPABILITY_POWER = 1 << 0, KYWC_OUTPUT_CAPABILITY_BRIGHTNESS = 1 << 1, KYWC_OUTPUT_CAPABILITY_COLOR_TEMP = 1 << 2, }; struct kywc_output_mode { int32_t width, height; int32_t refresh; // mHz bool preferred; struct wl_list link; }; struct _kywc_output { const char *uuid; /* props */ const char *name; const char *make, *model, *serial, *description; int32_t physical_width, physical_height; uint32_t capabilities; struct wl_list modes; /* states */ struct kywc_output_mode *mode; // may be NULL int32_t x, y, width, height; int32_t transform; float scale; bool enabled, power, primary; uint32_t brightness; uint32_t color_temp; }; enum kywc_output_state_mask { KYWC_OUTPUT_STATE_ENABLED = 1 << 0, KYWC_OUTPUT_STATE_MODE = 1 << 1, KYWC_OUTPUT_STATE_POSITION = 1 << 2, KYWC_OUTPUT_STATE_TRANSFORM = 1 << 3, KYWC_OUTPUT_STATE_SCALE = 1 << 4, KYWC_OUTPUT_STATE_POWER = 1 << 5, KYWC_OUTPUT_STATE_PRIMARY = 1 << 6, KYWC_OUTPUT_STATE_BRIGHTNESS = 1 << 7, KYWC_OUTPUT_STATE_COLOR_TEMP = 1 << 8, }; struct kywc_output_interface { void (*state)(kywc_output *output, uint32_t mask); void (*destroy)(kywc_output *output); }; void kywc_output_set_interface(kywc_output *output, const struct kywc_output_interface *impl); typedef bool (*kywc_output_iterator_func_t)(kywc_output *output, void *data); void kywc_context_for_each_output(kywc_context *ctx, kywc_output_iterator_func_t iterator, void *data); kywc_context *kywc_output_get_context(kywc_output *output); kywc_output *kywc_context_find_output(kywc_context *ctx, const char *uuid); void kywc_output_set_user_data(kywc_output *output, void *data); void *kywc_output_get_user_data(kywc_output *output); /** * toplevel or window */ enum kywc_toplevel_capability { KYWC_TOPLEVEL_CAPABILITY_SKIP_TASKBAR = 1 << 0, KYWC_TOPLEVEL_CAPABILITY_SKIP_SWITCHER = 1 << 1, }; #define MAX_WORKSPACES 15 struct _kywc_toplevel { const char *uuid; const char *title, *app_id; const char *icon; /* parent toplevel, NULL if has no parent */ kywc_toplevel *parent; /* output the toplevel most on */ const char *primary_output; /* workspaces the toplevel on, simply use array here */ const char *workspaces[MAX_WORKSPACES]; int32_t x, y; uint32_t width, height; uint32_t capabilities; /* state */ bool activated, minimized, maximized, fullscreen; uint32_t pid; }; enum kywc_toplevel_state_mask { KYWC_TOPLEVEL_STATE_APP_ID = 1 << 0, KYWC_TOPLEVEL_STATE_TITLE = 1 << 1, KYWC_TOPLEVEL_STATE_ACTIVATED = 1 << 2, KYWC_TOPLEVEL_STATE_MINIMIZED = 1 << 3, KYWC_TOPLEVEL_STATE_MAXIMIZED = 1 << 4, KYWC_TOPLEVEL_STATE_FULLSCREEN = 1 << 5, KYWC_TOPLEVEL_STATE_PRIMARY_OUTPUT = 1 << 6, KYWC_TOPLEVEL_STATE_WORKSPACE = 1 << 7, KYWC_TOPLEVEL_STATE_PARENT = 1 << 8, KYWC_TOPLEVEL_STATE_ICON = 1 << 9, KYWC_TOPLEVEL_STATE_POSITION = 1 << 10, KYWC_TOPLEVEL_STATE_SIZE = 1 << 11, }; struct kywc_toplevel_interface { void (*state)(kywc_toplevel *toplevel, uint32_t mask); void (*destroy)(kywc_toplevel *toplevel); }; void kywc_toplevel_set_interface(kywc_toplevel *toplevel, const struct kywc_toplevel_interface *impl); typedef bool (*kywc_toplevel_iterator_func_t)(kywc_toplevel *toplevel, void *data); void kywc_context_for_each_toplevel(kywc_context *ctx, kywc_toplevel_iterator_func_t iterator, void *data); kywc_context *kywc_toplevel_get_context(kywc_toplevel *toplevel); kywc_toplevel *kywc_context_find_toplevel(kywc_context *ctx, const char *uuid); bool kywc_toplevel_has_children(kywc_toplevel *toplevel); void kywc_toplevel_set_maximized(kywc_toplevel *toplevel, const char *output); void kywc_toplevel_unset_maximized(kywc_toplevel *toplevel); void kywc_toplevel_set_minimized(kywc_toplevel *toplevel); void kywc_toplevel_unset_minimized(kywc_toplevel *toplevel); void kywc_toplevel_set_fullscreen(kywc_toplevel *toplevel, const char *output); void kywc_toplevel_unset_fullscreen(kywc_toplevel *toplevel); void kywc_toplevel_activate(kywc_toplevel *toplevel); void kywc_toplevel_close(kywc_toplevel *toplevel); void kywc_toplevel_enter_workspace(kywc_toplevel *toplevel, const char *workspace); void kywc_toplevel_leave_workspace(kywc_toplevel *toplevel, const char *workspace); void kywc_toplevel_move_to_workspace(kywc_toplevel *toplevel, const char *workspace); void kywc_toplevel_move_to_output(kywc_toplevel *toplevel, const char *output); void kywc_toplevel_set_position(kywc_toplevel *toplevel, int32_t x, int32_t y); void kywc_toplevel_set_size(kywc_toplevel *toplevel, uint32_t width, uint32_t height); void kywc_toplevel_set_user_data(kywc_toplevel *toplevel, void *data); void *kywc_toplevel_get_user_data(kywc_toplevel *toplevel); /** * thumbnail for output, toplevel and workspace */ enum kywc_thumbnail_type { KYWC_THUMBNAIL_TYPE_OUTPUT, KYWC_THUMBNAIL_TYPE_TOPLEVEL, KYWC_THUMBNAIL_TYPE_WORKSPACE, }; struct _kywc_thumbnail { enum kywc_thumbnail_type type; const char *source_uuid; const char *output_uuid; // only used when workspace }; enum kywc_thumbnail_buffer_flag { /** * memfd: use mmap and munmap * dmabuf: use egl import dmabuf is better, map can't work when has modifier */ KYWC_THUMBNAIL_BUFFER_IS_DMABUF = 1 << 0, /** * buffer is reused, so we can skip the import sometimes */ KYWC_THUMBNAIL_BUFFER_IS_REUSED = 1 << 1, }; struct kywc_thumbnail_buffer { int32_t fd; // fd is closed in libkywc after buffer callback uint32_t format; // drm fourcc uint32_t width, height; // in pixels uint32_t offset, stride; // in bytes uint64_t modifier; // only used when dmabuf uint32_t flags; // enum kywc_thumbnail_buffer_flag uint32_t n_planes; // planes attributes if multi-planes struct { int32_t fd; uint32_t offset, stride; } planes[4]; }; struct kywc_thumbnail_interface { /** * return true if want buffer callback again when content is changed later, * otherwise destroy callback is called to destroy this thumbnail. */ bool (*buffer)(kywc_thumbnail *thumbnail, const struct kywc_thumbnail_buffer *buffer, void *data); /** * no need to call kywc_thumbnail_destroy */ void (*destroy)(kywc_thumbnail *thumbnail, void *data); }; /** * output_uuid is needed when create a workspace thumbnail. * buffer callback will be called by libkywc after kywc_thumbnail_create. */ kywc_thumbnail *kywc_thumbnail_create(kywc_context *ctx, enum kywc_thumbnail_type type, const char *source_uuid, const char *output_uuid, const struct kywc_thumbnail_interface *impl, void *data); kywc_thumbnail *kywc_thumbnail_create_from_output(kywc_context *ctx, const char *source_uuid, const struct kywc_thumbnail_interface *impl, void *data); kywc_thumbnail *kywc_thumbnail_create_from_toplevel(kywc_context *ctx, const char *source_uuid, bool without_decoration, const struct kywc_thumbnail_interface *impl, void *data); kywc_thumbnail *kywc_thumbnail_create_from_workspace(kywc_context *ctx, const char *source_uuid, const char *output_uuid, const struct kywc_thumbnail_interface *impl, void *data); kywc_context *kywc_thumbnail_get_context(kywc_thumbnail *thumbnail); void kywc_thumbnail_set_user_data(kywc_thumbnail *thumbnail, void *data); void *kywc_thumbnail_get_user_data(kywc_thumbnail *thumbnail); /** * destroy callback will be called in kywc_thumbnail_destroy */ void kywc_thumbnail_destroy(kywc_thumbnail *thumbnail); typedef struct _kywc_cursor { struct wl_seat *seat; kywc_thumbnail *thumbnail; } kywc_cursor; struct kywc_cursor_interface { void (*position)(kywc_cursor *cursor, uint32_t x, uint32_t y); void (*hotspot)(kywc_cursor *cursor, uint32_t x, uint32_t y); void (*enter)(kywc_cursor *cursor); void (*leave)(kywc_cursor *cursor); void (*destroy)(kywc_cursor *cursor); }; kywc_cursor *kywc_cursor_create(kywc_context *ctx, struct wl_seat *seat, const struct kywc_cursor_interface *impl, void *data); kywc_cursor *kywc_cursor_create_from_thumbnail(kywc_context *ctx, struct wl_seat *seat, kywc_thumbnail *thumbnail, const struct kywc_cursor_interface *impl, void *data); void kywc_cursor_set_user_data(kywc_cursor *cursor, void *data); void *kywc_cursor_get_user_data(kywc_cursor *cursor); void kywc_cursor_destroy(kywc_cursor *cursor); #ifdef __cplusplus } #endif #endif /* _LIBKYWC_HEADER_H_ */ kylin-wayland-compositor/libkywc/libkywc.c0000664000175000017500000001352415160461067017746 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include #include "libkywc_p.h" #include "provider.h" static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { kywc_context *ctx = data; struct ky_context_provider *provider, *tmp; wl_list_for_each_safe(provider, tmp, &ctx->pending_providers, link) { if (provider->bind && provider->bind(provider, registry, name, interface, version)) { wl_list_remove(&provider->link); wl_list_insert(&ctx->providers, &provider->link); } } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t id) { // kywc_context *ctx = data; } static struct wl_registry_listener registry_listener = { .global = registry_handle_global, .global_remove = registry_handle_global_remove, }; static void kywc_context_init_providers(kywc_context *ctx) { wl_list_init(&ctx->providers); wl_list_init(&ctx->pending_providers); int num = sizeof(providers) / sizeof(struct ky_provider); const struct ky_provider *provider = NULL; for (int i = 0; i < num; i++) { provider = &providers[i]; if (ctx->capabilities & provider->capability) { provider->init(ctx, provider->capability); } } } kywc_context *kywc_context_create_by_display(struct wl_display *display, uint32_t capabilities, const struct kywc_context_interface *impl, void *data) { if (capabilities & KYWC_CONTEXT_CAPABILITY_THUMBNAIL_EXT) { capabilities &= ~KYWC_CONTEXT_CAPABILITY_THUMBNAIL; } kywc_context *ctx = calloc(1, sizeof(kywc_context)); if (!ctx) { return NULL; } ctx->display = display; ctx->capabilities = capabilities; ctx->impl = impl; ctx->user_data = data; if (impl->create) { impl->create(ctx, data); } // create managers with capabilities by context providers kywc_context_init_providers(ctx); ctx->registry = wl_display_get_registry(ctx->display); wl_registry_add_listener(ctx->registry, ®istry_listener, ctx); /* make sure listener is finished */ wl_display_roundtrip(ctx->display); /* make sure event is received */ wl_display_roundtrip(ctx->display); return ctx; } kywc_context *kywc_context_create(const char *name, uint32_t capabilities, const struct kywc_context_interface *impl, void *data) { struct wl_display *display = wl_display_connect(name); if (!display) { fprintf(stderr, "connect to wayland compositor failed\n"); return NULL; } kywc_context *ctx = kywc_context_create_by_display(display, capabilities, impl, data); if (ctx) { ctx->own_display = true; } return ctx; } struct wl_display *kywc_context_get_display(kywc_context *ctx) { return ctx ? ctx->display : NULL; } void kywc_context_set_user_data(kywc_context *ctx, void *data) { if (ctx) { ctx->user_data = data; } } void *kywc_context_get_user_data(kywc_context *ctx) { return ctx ? ctx->user_data : NULL; } int kywc_context_get_fd(kywc_context *ctx) { return ctx ? wl_display_get_fd(ctx->display) : -1; } void kywc_context_destroy(kywc_context *ctx) { if (!ctx) { return; } struct ky_context_provider *provider, *tmp; wl_list_for_each_safe(provider, tmp, &ctx->providers, link) { wl_list_remove(&provider->link); if (provider->destroy) { provider->destroy(provider); } } wl_list_for_each_safe(provider, tmp, &ctx->pending_providers, link) { wl_list_remove(&provider->link); if (provider->destroy) { provider->destroy(provider); } } if (ctx->impl && ctx->impl->destroy) { ctx->impl->destroy(ctx, ctx->user_data); } wl_registry_destroy(ctx->registry); wl_display_flush(ctx->display); if (ctx->own_display) { wl_display_disconnect(ctx->display); } free(ctx); } int kywc_context_process(kywc_context *ctx) { if (!ctx) { return -1; } wl_display_prepare_read(ctx->display); wl_display_read_events(ctx->display); wl_display_dispatch_pending(ctx->display); int ret = wl_display_flush(ctx->display); if (ret == -1 && errno != EAGAIN) { fprintf(stderr, "failed to write wayland fd: %d\n", errno); return -1; } return 0; } void kywc_context_dispatch(kywc_context *ctx) { if (!ctx) { return; } while (wl_display_dispatch(ctx->display) != -1) { // This space intentionally left blank } } bool ky_context_add_provider(kywc_context *ctx, struct ky_context_provider *provider, void *manager) { if (provider->capability == KYWC_CONTEXT_CAPABILITY_OUTPUT) { if (ctx->output) { return false; } ctx->output = manager; } else if (provider->capability == KYWC_CONTEXT_CAPABILITY_TOPLEVEL) { if (ctx->toplevel) { return false; } ctx->toplevel = manager; } else if (provider->capability == KYWC_CONTEXT_CAPABILITY_WORKSPACE) { if (ctx->workspace) { return false; } ctx->workspace = manager; } else if (provider->capability == KYWC_CONTEXT_CAPABILITY_THUMBNAIL || provider->capability == KYWC_CONTEXT_CAPABILITY_THUMBNAIL_EXT) { if (ctx->thumbnail) { return false; } ctx->thumbnail = manager; } else if (provider->capability == KYWC_CONTEXT_CAPABILITY_CURSOR) { if (ctx->cursor) { return false; } ctx->cursor = manager; } wl_list_insert(&ctx->pending_providers, &provider->link); return true; } kylin-wayland-compositor/libkywc/kywc_output.c0000664000175000017500000002354315160460057020677 0ustar fengfeng// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "kywc-output-v1-client-protocol.h" #include "libkywc_p.h" bool _kywc_output_init(kywc_context *ctx, enum kywc_context_capability capability); static void output_handle_name(void *data, struct kywc_output_v1 *kywc_output_v1, const char *name) { struct ky_output *output = data; ky_output_set_name(output, name); } static void output_handle_make(void *data, struct kywc_output_v1 *kywc_output_v1, const char *make) { struct ky_output *output = data; ky_output_set_make(output, make); } static void output_handle_model(void *data, struct kywc_output_v1 *kywc_output_v1, const char *model) { struct ky_output *output = data; ky_output_set_model(output, model); } static void output_handle_serial_number(void *data, struct kywc_output_v1 *kywc_output_v1, const char *serial_number) { struct ky_output *output = data; ky_output_set_serial_number(output, serial_number); } static void output_handle_description(void *data, struct kywc_output_v1 *kywc_output_v1, const char *description) { struct ky_output *output = data; ky_output_set_description(output, description); } static void output_handle_physical_size(void *data, struct kywc_output_v1 *kywc_output_v1, int32_t width, int32_t height) { struct ky_output *output = data; ky_output_set_physical_size(output, width, height); } static void mode_handle_size(void *data, struct kywc_output_mode_v1 *kywc_output_mode_v1, int32_t width, int32_t height) { struct ky_output_mode *mode = data; ky_output_mode_set_size(mode, width, height); } static void mode_handle_refresh(void *data, struct kywc_output_mode_v1 *kywc_output_mode_v1, int32_t refresh) { struct ky_output_mode *mode = data; ky_output_mode_set_refresh(mode, refresh); } static void mode_handle_preferred(void *data, struct kywc_output_mode_v1 *kywc_output_mode_v1) { struct ky_output_mode *mode = data; ky_output_mode_set_preferred(mode); } static void mode_handle_finished(void *data, struct kywc_output_mode_v1 *kywc_output_mode_v1) { struct ky_output_mode *mode = data; ky_output_mode_destroy(mode); } static const struct kywc_output_mode_v1_listener mode_listener = { .size = mode_handle_size, .refresh = mode_handle_refresh, .preferred = mode_handle_preferred, .finished = mode_handle_finished, }; static void mode_destroy(struct ky_output_mode *ky_mode) { struct kywc_output_mode_v1 *kywc_output_mode_v1 = ky_mode->data; kywc_output_mode_v1_destroy(kywc_output_mode_v1); } static void output_handle_mode(void *data, struct kywc_output_v1 *kywc_output_v1, struct kywc_output_mode_v1 *mode) { struct ky_output *output = data; struct ky_output_mode *ky_mode = ky_output_mode_create(output); if (!ky_mode) { return; } ky_mode->destroy = mode_destroy; ky_mode->data = mode; kywc_output_mode_v1_add_listener(mode, &mode_listener, ky_mode); } static void output_handle_capabilities(void *data, struct kywc_output_v1 *kywc_output_v1, uint32_t capabilities) { struct ky_output *output = data; ky_output_set_capabilities(output, capabilities); } static void output_handle_enabled(void *data, struct kywc_output_v1 *kywc_output_v1, int32_t enabled) { struct ky_output *output = data; ky_output_update_enabled(output, enabled); } static void output_handle_current_mode(void *data, struct kywc_output_v1 *kywc_output_v1, struct kywc_output_mode_v1 *kywc_output_mode_v1) { struct ky_output *output = data; struct ky_output_mode *mode = kywc_output_mode_v1_get_user_data(kywc_output_mode_v1); ky_output_update_current_mode(output, mode); } static void output_handle_position(void *data, struct kywc_output_v1 *kywc_output_v1, int32_t x, int32_t y) { struct ky_output *output = data; ky_output_update_position(output, x, y); } static void output_handle_transform(void *data, struct kywc_output_v1 *kywc_output_v1, int32_t transform) { struct ky_output *output = data; ky_output_update_transform(output, transform); } static void output_handle_scale(void *data, struct kywc_output_v1 *kywc_output_v1, wl_fixed_t scale) { struct ky_output *output = data; ky_output_update_scale(output, wl_fixed_to_double(scale)); } static void output_handle_power(void *data, struct kywc_output_v1 *kywc_output_v1, uint32_t power) { struct ky_output *output = data; ky_output_update_power(output, power); } static void output_handle_brightness(void *data, struct kywc_output_v1 *kywc_output_v1, uint32_t brightness) { struct ky_output *output = data; ky_output_update_brightness(output, brightness); } static void output_handle_color_temp(void *data, struct kywc_output_v1 *kywc_output_v1, uint32_t color_temp) { struct ky_output *output = data; ky_output_update_color_temp(output, color_temp); } static void output_handle_finished(void *data, struct kywc_output_v1 *kywc_output_v1) { struct ky_output *output = data; ky_output_destroy(output); } static const struct kywc_output_v1_listener output_listener = { .name = output_handle_name, .make = output_handle_make, .model = output_handle_model, .serial_number = output_handle_serial_number, .description = output_handle_description, .physical_size = output_handle_physical_size, .mode = output_handle_mode, .capabilities = output_handle_capabilities, .enabled = output_handle_enabled, .current_mode = output_handle_current_mode, .position = output_handle_position, .transform = output_handle_transform, .scale = output_handle_scale, .power = output_handle_power, .brightness = output_handle_brightness, .color_temp = output_handle_color_temp, .finished = output_handle_finished, }; static void output_destroy(struct ky_output *output) { struct kywc_output_v1 *kywc_output_v1 = output->data; kywc_output_v1_destroy(kywc_output_v1); wl_display_flush(output->manager->ctx->display); } static void manager_handle_output(void *data, struct kywc_output_manager_v1 *kywc_output_manager_v1, struct kywc_output_v1 *output, const char *uuid) { struct ky_output_manager *manager = data; struct ky_output *ky_output = ky_output_create(manager, uuid); if (!ky_output) { return; } ky_output->destroy = output_destroy; ky_output->data = output; kywc_output_v1_add_listener(output, &output_listener, ky_output); } static void manager_handle_primary(void *data, struct kywc_output_manager_v1 *kywc_output_manager_v1, struct kywc_output_v1 *output) { struct ky_output_manager *manager = data; struct ky_output *ky_output = kywc_output_v1_get_user_data(output); ky_output_manager_update_primary(manager, ky_output); } static void manager_handle_done(void *data, struct kywc_output_manager_v1 *kywc_output_manager_v1) { struct ky_output_manager *manager = data; ky_output_manager_update_states(manager); } static void manager_handle_finished(void *data, struct kywc_output_manager_v1 *kywc_output_manager_v1) { struct ky_output_manager *manager = data; kywc_output_manager_v1_destroy(kywc_output_manager_v1); wl_display_flush(manager->ctx->display); } static const struct kywc_output_manager_v1_listener output_manager_listener = { .output = manager_handle_output, .primary = manager_handle_primary, .done = manager_handle_done, .finished = manager_handle_finished, }; static void manager_destroy(struct ky_output_manager *manager) { struct kywc_output_manager_v1 *output_manager = manager->data; kywc_output_manager_v1_stop(output_manager); wl_display_flush(manager->ctx->display); } static bool output_provider_bind(struct ky_context_provider *provider, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if (strcmp(interface, kywc_output_manager_v1_interface.name) == 0) { uint32_t version_to_bind = version <= 1 ? version : 1; struct ky_output_manager *manager = provider->data; struct kywc_output_manager_v1 *output_manager = wl_registry_bind(registry, name, &kywc_output_manager_v1_interface, version_to_bind); kywc_output_manager_v1_add_listener(output_manager, &output_manager_listener, manager); manager->destroy = manager_destroy; manager->data = output_manager; return true; } return false; } static void output_provider_destroy(struct ky_context_provider *provider) { struct ky_output_manager *manager = provider->data; ky_output_manager_destroy(manager); free(provider); } bool _kywc_output_init(kywc_context *ctx, enum kywc_context_capability capability) { struct ky_context_provider *provider = calloc(1, sizeof(*provider)); if (!provider) { return false; } wl_list_init(&provider->link); provider->capability = capability; provider->bind = output_provider_bind; provider->destroy = output_provider_destroy; struct ky_output_manager *manager = ky_output_manager_create(ctx); if (!manager) { free(provider); return false; } provider->data = manager; if (!ky_context_add_provider(ctx, provider, manager)) { free(manager); free(provider); return false; } return true; } kylin-wayland-compositor/libkywc/cursor.c0000664000175000017500000000672115160460057017616 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include #include "libkywc_p.h" static struct ky_cursor *cursor_from_kywc_cursor(kywc_cursor *kywc_cursor) { struct ky_cursor *cursor = wl_container_of(kywc_cursor, cursor, base); return cursor; } void ky_cursor_destroy(struct ky_cursor *cursor) { if (cursor->impl && cursor->impl->destroy) { cursor->impl->destroy(&cursor->base); } if (cursor->destroy) { cursor->destroy(cursor); } wl_list_remove(&cursor->link); free(cursor); } struct ky_cursor_manager *ky_cursor_manager_create(kywc_context *ctx) { struct ky_cursor_manager *manager = calloc(1, sizeof(*manager)); if (!manager) { return NULL; } manager->ctx = ctx; wl_list_init(&manager->cursors); return manager; } void ky_cursor_manager_destroy(struct ky_cursor_manager *manager) { if (!manager) { return; } struct ky_cursor *cursor, *c_tmp; wl_list_for_each_safe(cursor, c_tmp, &manager->cursors, link) { ky_cursor_destroy(cursor); } if (manager->destroy) { manager->destroy(manager); } free(manager); } void ky_cursor_enter(struct ky_cursor *cursor) { if (cursor->impl && cursor->impl->enter) { cursor->impl->enter(&cursor->base); } } void ky_cursor_leave(struct ky_cursor *cursor) { if (cursor->impl && cursor->impl->leave) { cursor->impl->leave(&cursor->base); } } void ky_cursor_update_position(struct ky_cursor *cursor, uint32_t x, uint32_t y) { if (cursor->impl && cursor->impl->position) { cursor->impl->position(&cursor->base, x, y); } } void ky_cursor_update_hotspot(struct ky_cursor *cursor, uint32_t x, uint32_t y) { if (cursor->impl && cursor->impl->hotspot) { cursor->impl->hotspot(&cursor->base, x, y); } } kywc_cursor *kywc_cursor_create(kywc_context *ctx, struct wl_seat *seat, const struct kywc_cursor_interface *impl, void *data) { return kywc_cursor_create_from_thumbnail(ctx, seat, NULL, impl, data); } kywc_cursor *kywc_cursor_create_from_thumbnail(kywc_context *ctx, struct wl_seat *seat, kywc_thumbnail *thumbnail, const struct kywc_cursor_interface *impl, void *data) { if (!ctx || !ctx->cursor) { return NULL; } struct ky_cursor *cursor = calloc(1, sizeof(*cursor)); if (!cursor) { return NULL; } struct ky_thumbnail *ky_thumbnail = NULL; struct ky_cursor_manager *manager = ctx->cursor; cursor->manager = manager; wl_list_insert(&manager->cursors, &cursor->link); cursor->impl = impl; cursor->user_data = data; if (thumbnail) { ky_thumbnail = thumbnail_from_kywc_thumbnail(thumbnail); } if (manager->create_cursor) { manager->create_cursor(manager, cursor, seat, ky_thumbnail); } return &cursor->base; } void kywc_cursor_set_user_data(kywc_cursor *cursor, void *data) { struct ky_cursor *ky_cursor = cursor_from_kywc_cursor(cursor); ky_cursor->user_data = data; } void *kywc_cursor_get_user_data(kywc_cursor *cursor) { struct ky_cursor *ky_cursor = cursor_from_kywc_cursor(cursor); return ky_cursor->user_data; } void kywc_cursor_destroy(kywc_cursor *cursor) { struct ky_cursor *ky_cursor = cursor_from_kywc_cursor(cursor); ky_cursor_destroy(ky_cursor); } kylin-wayland-compositor/NOTICE0000664000175000017500000001037415160460057015374 0ustar fengfengOPEN SOURCE SOFTWARE NOTICE Please note we provide an open source software notice for the third party open source software along with this software and/or this software component (in the following just “this SOFTWARE”). The open source software licenses are granted by the respective right holders. Warranty Disclaimer THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. Copyright Notice and License Texts ------------------------------------------------------------------------ Software: redshift Files: src/output/gamma.c Copyright: 2013-2014 Jon Lund Steffensen 2013 Ingo Thies Redshift is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Redshift is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Redshift. If not, see . ------------------------------------------------------------------------ Software: wlroots Files: src/output/xdg_output.c src/scene/xdg_shell.c src/scene/surface.c src/scene/output.c src/scene/subsurface.c src/scene/scene.c src/util/wayland.c src/render/wayland_drm.c src/render/opengl/pass.c src/render/opengl/egl.c src/render/opengl/texture.c src/render/pixel_format.c src/render/shm_buffer.c src/input/keyboard_group.c include/util/wayland.h include/render/pixel_format.h include/render/opengl.h include/render/egl.h include/input/keyboard_group.h Copyright (c) 2017, 2018 Drew DeVault Copyright (c) 2014 Jari Vetoniemi Copyright (c) 2023 The wlroots contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------ Software: sway Files: src/input/text_input.c Copyright (c) 2016-2017 Drew DeVault Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. kylin-wayland-compositor/wlcom.syms0000664000175000017500000000004515160460057016520 0ustar fengfeng{ global: kywc_*; local: *; }; kylin-wayland-compositor/po/0000775000175000017500000000000015160461067015103 5ustar fengfengkylin-wayland-compositor/po/ky.po0000664000175000017500000000546115160461067016074 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2023. # msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2025-11-17 02:33+0000\n" "Last-Translator: KevinDuan \n" "Language-Team: Kyrgyz \n" "Language: ky\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.12.1-dev\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "كەسمە سۉرۅت" #: src/view/action.c:108 msgid "Capture saved to" msgstr "تۇتۇلۇپ ساقلانغان" #: src/view/ssd.c:248 msgid "Minimize" msgstr "كىچىرەيتۉۉ" #: src/view/ssd.c:249 msgid "Maximize" msgstr "چوڭويتۇش" #: src/view/ssd.c:250 msgid "Restore" msgstr "العاچىنا كەلتىرۉۉ" #: src/view/ssd.c:251 msgid "Close" msgstr "ياپ" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "شىرە بەتى" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "ئۈستەلگە يۆتكەش" #: src/view/window_menu.c:248 msgid "Screen" msgstr "ەكىران" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "كەسمە رەسىم ( _T )" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "ئۈستەل ( _D )" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "بارلىق ئۈستەل ( _A )" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "يېڭى ئۈستەل ( _N ) قوشۇلدى ( _N )" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "يېڭى ئۈستەلگە يۆتكەلدى ( _M )" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "ئەڭ چوڭلاشتۇرۇش ( _X )" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "ئېكرانغا يۆتكەلدى ( _S )" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "ئەڭ كىچىكلەش ( _N )" #: src/view/window_menu.c:367 msgid "_More" msgstr "تېخىمۇ كۆپ مەشغۇلات ( _M )" #: src/view/window_menu.c:369 msgid "_Move" msgstr "كۆچمە ( _M )" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "چوڭ - كىچىكلىكىنى تەڭشەش ( _R )" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "يۇقىرى قەۋەتكە قويۇلغان ( _A )" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "تۆۋەن قەۋەتكە قويۇلغان ( _B )" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "پۉتۉن ەكىران(_F)" #: src/view/window_menu.c:378 msgid "_Close" msgstr "ياپ(_C)" kylin-wayland-compositor/po/meson.build0000664000175000017500000000052015160460057017240 0ustar fengfengi18n = import('i18n') add_project_arguments('-DGETTEXT_PACKAGE="' + meson.project_name() + '"', '-DLOCALEDIR="' + get_option('prefix') / get_option('localedir') + '"', language:'c') i18n.gettext(meson.project_name(), args: ['--directory=' + source_root, '--add-comments=TRANSLATORS', '--keyword=tr'], preset: 'glib' ) kylin-wayland-compositor/po/zh_CN.po0000664000175000017500000000437115160460057016447 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2024-08-16 09:56+0800\n" "Last-Translator: Automatically generated\n" "Language-Team: Chinese Simplified\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "截图" #: src/view/action.c:108 msgid "Capture saved to" msgstr "捕获已保存为" #: src/view/ssd.c:248 msgid "Minimize" msgstr "最小化" #: src/view/ssd.c:249 msgid "Maximize" msgstr "最大化" #: src/view/ssd.c:250 msgid "Restore" msgstr "还原" #: src/view/ssd.c:251 msgid "Close" msgstr "关闭" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "桌面" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "移动到桌面" #: src/view/window_menu.c:248 msgid "Screen" msgstr "屏幕" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "截图(_T)" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "桌面(_D)" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "全部桌面(_A)" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "添加到新桌面(_N)" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "移动到新桌面(_M)" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "最大化(_X)" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "移动到屏幕(_S)" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "最小化(_N)" #: src/view/window_menu.c:367 msgid "_More" msgstr "更多操作(_M)" #: src/view/window_menu.c:369 msgid "_Move" msgstr "移动(_M)" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "调整大小(_R)" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "置于顶层(_A)" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "置于底层(_B)" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "全屏(_F)" #: src/view/window_menu.c:378 msgid "_Close" msgstr "关闭(_C)" kylin-wayland-compositor/po/kylin-wayland-compositor.pot0000664000175000017500000000370515160460057022611 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2024-12-12 14:40+0800\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "" #: src/view/action.c:108 msgid "Capture saved to" msgstr "" #: src/view/ssd.c:268 msgid "Minimize" msgstr "" #: src/view/ssd.c:269 msgid "Maximize" msgstr "" #: src/view/ssd.c:270 msgid "Restore" msgstr "" #: src/view/ssd.c:271 msgid "Close" msgstr "" #: src/view/window_menu.c:186 msgid "Desktop" msgstr "" #: src/view/window_menu.c:216 msgid "Move To Desktop" msgstr "" #: src/view/window_menu.c:250 msgid "Screen" msgstr "" #: src/view/window_menu.c:353 msgid "_Take Screenshot" msgstr "" #: src/view/window_menu.c:356 msgid "_Desktop" msgstr "" #: src/view/window_menu.c:359 msgid "_All Desktop" msgstr "" #: src/view/window_menu.c:360 msgid "Add To _New Desktop" msgstr "" #: src/view/window_menu.c:363 msgid "_Move To New Desktop" msgstr "" #: src/view/window_menu.c:367 msgid "Ma_ximize" msgstr "" #: src/view/window_menu.c:371 msgid "Move To _Screen" msgstr "" #: src/view/window_menu.c:376 msgid "Mi_nimize" msgstr "" #: src/view/window_menu.c:380 msgid "_More" msgstr "" #: src/view/window_menu.c:384 msgid "_Move" msgstr "" #: src/view/window_menu.c:386 msgid "_Resize" msgstr "" #: src/view/window_menu.c:388 msgid "Keep-_Above" msgstr "" #: src/view/window_menu.c:390 msgid "Keep-_Below" msgstr "" #: src/view/window_menu.c:392 msgid "_Fullscreen" msgstr "" #: src/view/window_menu.c:395 msgid "_Close" msgstr "" kylin-wayland-compositor/po/zh_TW.po0000664000175000017500000000443515160460057016502 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2024. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2024-08-16 09:56+0800\n" "Last-Translator: yangcanfen \n" "Language-Team: Chinese (traditional) \n" "Language: zh_TW\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "截圖" #: src/view/action.c:108 msgid "Capture saved to" msgstr "捕獲已保存為" #: src/view/ssd.c:248 msgid "Minimize" msgstr "最小化" #: src/view/ssd.c:249 msgid "Maximize" msgstr "最大化" #: src/view/ssd.c:250 msgid "Restore" msgstr "還原" #: src/view/ssd.c:251 msgid "Close" msgstr "關閉" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "桌面" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "移動到桌面" #: src/view/window_menu.c:248 msgid "Screen" msgstr "螢幕" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "截圖(_T)" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "桌面(_D)" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "全部桌面(_A)" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "添加到新桌面(_N)" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "移動到新桌面(_M)" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "最大化(_X)" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "移動到螢幕(_S)" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "最小化(_N)" #: src/view/window_menu.c:367 msgid "_More" msgstr "更多操作(_M)" #: src/view/window_menu.c:369 msgid "_Move" msgstr "移動(_M)" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "調整大小(_R)" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "置於頂層(_A)" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "置於底層(_B)" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "全屏(_F)" #: src/view/window_menu.c:378 msgid "_Close" msgstr "關閉(_C)" kylin-wayland-compositor/po/mn.po0000664000175000017500000000664015160461067016063 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2023. # msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2025-11-17 02:33+0000\n" "Last-Translator: KevinDuan \n" "Language-Team: Mongolian \n" "Language: mn\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.12.1-dev\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "ᠳᠡᠯᠭᠡᠴᠡ ᠬᠠᠢᠴᠢᠯᠠᠬᠤ" #: src/view/action.c:108 msgid "Capture saved to" msgstr "ᠪᠠᠷᠢᠪᠴᠢᠯᠠᠭᠰᠠᠨ ᠤ ᠲᠠᠷᠠᠭ᠎ᠠ ᠨᠢᠭᠡᠨᠲᠡ ᠬᠠᠳᠠᠭᠠᠯᠠᠵᠠᠢ᠃" #: src/view/ssd.c:248 msgid "Minimize" msgstr "ᠬᠠᠮᠤᠭ ᠤ᠋ᠨ ᠪᠠᠭᠠᠴᠢᠯᠠᠯ" #: src/view/ssd.c:249 msgid "Maximize" msgstr "ᠬᠠᠮᠤᠭ ᠤ᠋ᠨ ᠶᠡᠬᠡᠴᠢᠯᠡᠯ" #: src/view/ssd.c:250 msgid "Restore" msgstr "ᠰᠡᠷᠭᠦᠭᠡᠬᠦ" #: src/view/ssd.c:251 msgid "Close" msgstr "ᠬᠠᠭᠠᠬᠤ" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "ᠠᠵᠢᠯᠯᠠᠬᠤ ᠤᠷᠤᠨ" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "ᠰᠢᠷᠡᠭᠡᠨ ᠭᠠᠳᠠᠷᠭᠤ ᠳᠤ ᠰᠢᠯᠵᠢᠨ᠎ᠡ ᠃" #: src/view/window_menu.c:248 msgid "Screen" msgstr "ᠳᠡᠯᠭᠡᠴᠡ" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "ᠵᠢᠷᠤᠭ ( _T )" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "ᠰᠢᠷᠡᠭᠡᠨ ᠭᠠᠳᠠᠷᠭᠤ ( _D )" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "ᠪᠦᠬᠦ ᠰᠢᠷᠡᠭᠡᠨ ᠦ ᠨᠢᠭᠤᠷ ( _A )" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "ᠰᠢᠨ᠎ᠡ ᠰᠢᠷᠡᠭᠡᠨ ᠭᠤᠯᠢᠷ ᠨᠡᠮᠡᠪᠡ  _N " #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "ᠰᠢᠨ᠎ᠡ ᠰᠢᠷᠡᠭᠡᠨ ᠭᠠᠳᠠᠷᠭᠤ ᠳᠤ ᠰᠢᠯᠵᠢᠪᠡ  _M " #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "ᠬᠠᠮᠤᠭ ᠶᠡᠬᠡᠵᠢᠯᠲᠡ ( _X )" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "ᠳᠡᠯᠪᠡᠴᠢᠬᠦ ( _S )" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "ᠬᠠᠮᠤᠭ ᠪᠠᠭ᠎ᠠ ᠪᠣᠯᠪᠠ ( _N )" #: src/view/window_menu.c:367 msgid "_More" msgstr "ᠨᠡᠩ ᠠᠷᠪᠢᠨ ᠠᠵᠢᠯᠯᠠᠭᠤᠯᠬᠤ ( _M )" #: src/view/window_menu.c:369 msgid "_Move" msgstr "ᠰᠢᠯᠵᠢᠮᠡᠯ (_M)" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "ᠶᠡᠬᠡ ᠪᠠᠭ᠎ᠠ ( _R ) ᠳᠤ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠤᠯᠲᠠ ᠬᠢᠬᠦ" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "ᠣᠷᠣᠢ ᠶᠢᠨ ᠳᠠᠪᠬᠤᠷᠭ᠎ᠠ ᠳᠤ ᠲᠠᠯᠪᠢᠨ᠎ᠠ ( _A )" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "ᠳᠣᠣᠷ᠎ᠠ ᠳᠠᠪᠬᠤᠷᠭ᠎ᠠ ᠳᠤ ᠲᠠᠯᠪᠢᠬᠤ ( _B )" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "ᠪᠦᠳᠦᠨ ᠳᠡᠯᠭᠡᠴᠡ (_F )" #: src/view/window_menu.c:378 msgid "_Close" msgstr "ᠬᠠᠭᠠᠬᠤ(_C)" kylin-wayland-compositor/po/LINGUAS0000664000175000017500000000011115160461067016121 0ustar fengfengzh_CN zh_TW zh_HK zh_Hant bo_CN ug kk ky mn ms de_DE es_ES fr_FR th vi arkylin-wayland-compositor/po/th.po0000664000175000017500000000601415160461067016057 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2023. # msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2025-11-17 02:33+0000\n" "Last-Translator: KevinDuan \n" "Language-Team: Thai \n" "Language: th\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.12.1-dev\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "สกรีนช็อต" #: src/view/action.c:108 msgid "Capture saved to" msgstr "การจับภาพจะถูกบันทึกเป็น" #: src/view/ssd.c:248 msgid "Minimize" msgstr "ย่อหน้าต่าง" #: src/view/ssd.c:249 msgid "Maximize" msgstr "ขยายเต็มจอ" #: src/view/ssd.c:250 msgid "Restore" msgstr "กู้คืนข้อมูล" #: src/view/ssd.c:251 msgid "Close" msgstr "ปิด" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "เดสก์ท็อป" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "ย้ายไปยังเดสก์ท็อป" #: src/view/window_menu.c:248 msgid "Screen" msgstr "หน้าจอ" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "ภาพหน้าจอ (_T)" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "เดสก์ท็อป (_D)" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "เดสก์ท็อปทั้งหมด (_A)" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "เพิ่มไปยังเดสก์ท็อปใหม่ (_N)" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "ย้ายไปยังเดสก์ท็อปใหม่ (_M)" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "ขยายใหญ่สุด (_X)" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "ย้ายไปที่หน้าจอ (_S)" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "ย่อเล็กสุด (_N)" #: src/view/window_menu.c:367 msgid "_More" msgstr "การดําเนินการเพิ่มเติม (_M)" #: src/view/window_menu.c:369 msgid "_Move" msgstr "ย้าย (_M)" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "ปรับขนาด (_R)" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "วางไว้ที่ระดับบนสุด (_A)" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "วางที่ด้านล่าง (_B)" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "_เต็มจอ" #: src/view/window_menu.c:378 msgid "_Close" msgstr "_ปิด" kylin-wayland-compositor/po/zh_Hant.po0000664000175000017500000000472415160461067017045 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2023. # msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2025-10-27 06:18+0000\n" "Last-Translator: KevinDuan \n" "Language-Team: Chinese (Traditional) \n" "Language: zh_Hant\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.12.1-dev\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "截圖" #: src/view/action.c:108 msgid "Capture saved to" msgstr "捕獲已保存為" #: src/view/ssd.c:248 msgid "Minimize" msgstr "最小化" #: src/view/ssd.c:249 msgid "Maximize" msgstr "最大化" #: src/view/ssd.c:250 msgid "Restore" msgstr "還原" #: src/view/ssd.c:251 msgid "Close" msgstr "關閉" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "桌面" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "移動到桌面" #: src/view/window_menu.c:248 msgid "Screen" msgstr "螢幕" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "截圖(_T)" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "桌面(_D)" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "所有桌面(_A)" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "新增桌面(_N)" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "移至新桌面(_M)" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "最大化(_X)" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "移動到螢幕(_S)" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "最小化(_N)" #: src/view/window_menu.c:367 msgid "_More" msgstr "更多操作(_M)" #: src/view/window_menu.c:369 msgid "_Move" msgstr "移動(_M)" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "調整大小(_R)" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "置於頂層(_A)" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "置於底層(_B)" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "全屏(_F)" #: src/view/window_menu.c:378 msgid "_Close" msgstr "關閉(_C)" kylin-wayland-compositor/po/kk.po0000664000175000017500000000546415160461067016061 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2023. # msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2025-11-17 02:33+0000\n" "Last-Translator: KevinDuan \n" "Language-Team: Kazakh \n" "Language: kk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.12.1-dev\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "كەسمە سۉرۅت" #: src/view/action.c:108 msgid "Capture saved to" msgstr "تۇتۇلۇپ ساقلانغان" #: src/view/ssd.c:248 msgid "Minimize" msgstr "كىشرەيتۋ" #: src/view/ssd.c:249 msgid "Maximize" msgstr "ۇلكەيتۋ" #: src/view/ssd.c:250 msgid "Restore" msgstr "قالپىنا كەلتىرىلگەن" #: src/view/ssd.c:251 msgid "Close" msgstr "ياپ" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "ۇستەل بەتى" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "ئۈستەلگە يۆتكەش" #: src/view/window_menu.c:248 msgid "Screen" msgstr "ەكٸران" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "كەسمە رەسىم ( _T )" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "ئۈستەل ( _D )" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "بارلىق ئۈستەل ( _A )" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "يېڭى ئۈستەل ( _N ) قوشۇلدى ( _N )" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "يېڭى ئۈستەلگە يۆتكەلدى ( _M )" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "ئەڭ چوڭلاشتۇرۇش ( _X )" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "ئېكرانغا يۆتكەلدى ( _S )" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "ئەڭ كىچىكلەش ( _N )" #: src/view/window_menu.c:367 msgid "_More" msgstr "تېخىمۇ كۆپ مەشغۇلات ( _M )" #: src/view/window_menu.c:369 msgid "_Move" msgstr "كۆچمە ( _M )" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "چوڭ - كىچىكلىكىنى تەڭشەش ( _R )" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "يۇقىرى قەۋەتكە قويۇلغان ( _A )" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "تۆۋەن قەۋەتكە قويۇلغان ( _B )" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "پۇتٸن ەكٸران(_F)" #: src/view/window_menu.c:378 msgid "_Close" msgstr "تاقاۋ(_C)" kylin-wayland-compositor/po/zh_HK.po0000664000175000017500000000442015160460057016444 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2024. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2024-08-16 09:56+0800\n" "Last-Translator: yangcanfen \n" "Language-Team: Chinese (Hong Kong) <(nothing)>\n" "Language: zh_HK\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "截圖" #: src/view/action.c:108 msgid "Capture saved to" msgstr "捕獲已保存為" #: src/view/ssd.c:248 msgid "Minimize" msgstr "最小化" #: src/view/ssd.c:249 msgid "Maximize" msgstr "最大化" #: src/view/ssd.c:250 msgid "Restore" msgstr "還原" #: src/view/ssd.c:251 msgid "Close" msgstr "關閉" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "桌面" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "移動到桌面" #: src/view/window_menu.c:248 msgid "Screen" msgstr "螢幕" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "截圖(_T)" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "桌面(_D)" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "全部桌面(_A)" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "添加到新桌面(_N)" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "移動到新桌面(_M)" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "最大化(_X)" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "移動到螢幕(_S)" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "最小化(_N)" #: src/view/window_menu.c:367 msgid "_More" msgstr "更多操作(_M)" #: src/view/window_menu.c:369 msgid "_Move" msgstr "移動(_M)" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "調整大小(_R)" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "置於頂層(_A)" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "置於底層(_B)" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "全屏(_F)" #: src/view/window_menu.c:378 msgid "_Close" msgstr "關閉(_C)" kylin-wayland-compositor/po/es_ES.po0000664000175000017500000000457415160460057016451 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2024. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2024-08-16 09:56+0800\n" "Last-Translator: yangcanfen \n" "Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "Captura de pantalla" #: src/view/action.c:108 msgid "Capture saved to" msgstr "Captura guardada en" #: src/view/ssd.c:248 msgid "Minimize" msgstr "Minimizar" #: src/view/ssd.c:249 msgid "Maximize" msgstr "Maximizar" #: src/view/ssd.c:250 msgid "Restore" msgstr "Restaurar" #: src/view/ssd.c:251 msgid "Close" msgstr "Cerrar" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "Escritorio" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "Mover al escritorio" #: src/view/window_menu.c:248 msgid "Screen" msgstr "Pantalla" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "_Tomar captura de pantalla" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "Escritorio(_D)" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "Todos los escritorios(_A)" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "Añadir a _Nuevo escritorio" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "_Mover a Nuevo escritorio" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "Ma_ximizar" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "Mover a la pantalla(_S)" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "Mi_nimizar" #: src/view/window_menu.c:367 msgid "_More" msgstr "_Más acciones" #: src/view/window_menu.c:369 msgid "_Move" msgstr "_Mover" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "_Redimensionar la ventana" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "M_antener encima" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "Mantener de_bajo" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "Pantalla completa(_F)" #: src/view/window_menu.c:378 msgid "_Close" msgstr "_Cerrar" kylin-wayland-compositor/po/de_DE.po0000664000175000017500000000475615160460057016415 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2024. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2024-08-16 09:56+0800\n" "Last-Translator: yangcanfen \n" "Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "Bildschirmfoto" #: src/view/action.c:108 msgid "Capture saved to" msgstr "Bildschirmfoto wurde gespeichert unter" #: src/view/ssd.c:248 msgid "Minimize" msgstr "Minimieren" #: src/view/ssd.c:249 msgid "Maximize" msgstr "Maximieren" #: src/view/ssd.c:250 msgid "Restore" msgstr "Wiederherstellen" #: src/view/ssd.c:251 msgid "Close" msgstr "Schließen" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "Arbeitsfläche" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "Auf Arbeitsfläche verschieben" #: src/view/window_menu.c:248 msgid "Screen" msgstr "Bildschirm" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "Bildschirmfo_to" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "Arbeitsfläche(_D)" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "_Alle Arbeitsflächen" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "Zur _neuen Arbeitsfläche hinzufügen" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "Auf neue Arbeitsfläche verschieben(_M)" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "Ma_ximieren" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "Auf Bild_schirm verschieben" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "Mi_nimieren" #: src/view/window_menu.c:367 msgid "_More" msgstr "Weitere Aktionen(_M)" #: src/view/window_menu.c:369 msgid "_Move" msgstr "Verschieben(_M)" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "Fenste_rgröße ändern" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "Im Vordergrund h_alten" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "Im Hintergrund halten(_B)" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "Vollbild(_F)" #: src/view/window_menu.c:378 msgid "_Close" msgstr "S_chließen" kylin-wayland-compositor/po/POTFILES.in0000664000175000017500000000007015160460057016653 0ustar fengfengsrc/view/action.c src/view/ssd.c src/view/window_menu.c kylin-wayland-compositor/po/vi.po0000664000175000017500000000522615160461067016066 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2023. # msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2025-11-17 02:33+0000\n" "Last-Translator: KevinDuan \n" "Language-Team: Vietnamese \n" "Language: vi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.12.1-dev\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "Cắt màn hình" #: src/view/action.c:108 msgid "Capture saved to" msgstr "Ảnh chụp được lưu dưới dạng" #: src/view/ssd.c:248 msgid "Minimize" msgstr "Tối thiểu hóa" #: src/view/ssd.c:249 msgid "Maximize" msgstr "cực đại hoá" #: src/view/ssd.c:250 msgid "Restore" msgstr "phục hồi" #: src/view/ssd.c:251 msgid "Close" msgstr "Tắt" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "Màn hình nền" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "Di chuyển sang máy tính để bàn" #: src/view/window_menu.c:248 msgid "Screen" msgstr "Màn hình" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "Ảnh chụp màn hình (_T)" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "Máy tính để bàn (_D)" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "Tất cả máy tính để bàn (_A)" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "Thêm vào màn hình mới (_N)" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "Di chuyển sang màn hình nền mới (_M)" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "Tối đa hóa (_X)" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "Di chuyển đến màn hình (_S)" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "Thu nhỏ (_N)" #: src/view/window_menu.c:367 msgid "_More" msgstr "Hành động khác (_M)" #: src/view/window_menu.c:369 msgid "_Move" msgstr "Di chuyển (_M)" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "Thay đổi kích thước (_R)" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "Đặt ở tầng cao nhất (_A)" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "Đặt dưới cùng (_B)" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "Toàn màn hình(_F)" #: src/view/window_menu.c:378 msgid "_Close" msgstr "Đóng(_C)" kylin-wayland-compositor/po/ar.po0000664000175000017500000000445715160461067016057 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2023. # msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2025-10-27 12:19+0000\n" "Last-Translator: 3616346829@qq.com <3616346829@qq.com>\n" "Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " "&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" "X-Generator: Weblate 4.12.1-dev\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "لقطة شاشة" #: src/view/action.c:108 msgid "Capture saved to" msgstr "" #: src/view/ssd.c:248 msgid "Minimize" msgstr "تصغير" #: src/view/ssd.c:249 msgid "Maximize" msgstr "تكبير" #: src/view/ssd.c:250 msgid "Restore" msgstr "استعادة" #: src/view/ssd.c:251 msgid "Close" msgstr "إغلاق" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "سطح المكتب" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "" #: src/view/window_menu.c:248 msgid "Screen" msgstr "شاشة" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "" #: src/view/window_menu.c:367 msgid "_More" msgstr "" #: src/view/window_menu.c:369 msgid "_Move" msgstr "" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "ملء الشاشة (_F)" #: src/view/window_menu.c:378 msgid "_Close" msgstr "إغلاق (_C)" kylin-wayland-compositor/po/bo_CN.po0000664000175000017500000000665715160461067016441 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2023. # msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2025-11-17 02:33+0000\n" "Last-Translator: KevinDuan \n" "Language-Team: Tibetan (China) \n" "Language: bo_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.12.1-dev\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "བརྙན་ཡོལ་འདྲ་ཕབ།" #: src/view/action.c:108 msgid "Capture saved to" msgstr "འཛིན་བཟུང་བྱས་ནས་ཉར་ཚགས་བྱས་ཡོད།" #: src/view/ssd.c:248 msgid "Minimize" msgstr "ཉུང་དུ་གཏོང་གང་ཐུབ་བྱ་དགོས།" #: src/view/ssd.c:249 msgid "Maximize" msgstr "ཆེས་ཆེར་སྒྱུར།" #: src/view/ssd.c:250 msgid "Restore" msgstr "སླར་གསོ་བྱེད་པ།" #: src/view/ssd.c:251 msgid "Close" msgstr "སྒོ་རྒྱག་པ།" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "ཅོག་ཙེའི་སྟེང་གི" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "ཅོག་ཙེའི་ངོས་སུ་སྤོ་བ།" #: src/view/window_menu.c:248 msgid "Screen" msgstr "བརྙན་ཤེལ།" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "བཅད་ཁྲ། (_T། )" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "ཅོག་ཙེའི་ངོས། (_D)" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "ཅོག་ཙེའི་ངོས་ཡོངས་རྫོགས། (_A)" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "ཅོག་ཙེའི་ངོས་གསར་པར་ཁ་སྣོན་རྒྱག་དགོས། (_N)" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "ཅོག་ཙེའི་ངོས་གསར་པར་སྤོས་པ།(_M)" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "ཆེས་ཆེ་བ། (_X)" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "བརྙན་ཤེལ་དུ་སྤོ་འགུལ་བྱས་པ། (_S)" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "ཆེས་ཆུང་བ།(_N)" #: src/view/window_menu.c:367 msgid "_More" msgstr "བཀོལ་སྤྱོད་སྔར་ལས་མང་བ།(_M)" #: src/view/window_menu.c:369 msgid "_Move" msgstr "གནས་སྤོ་(_M། )" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "ཆེ་ཆུང་(_R་)ལེགས་སྒྲིག་བྱ་དགོས།" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "རྩེ་རིམ་(_A)ལ་བཞག་ཡོད།" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "འོག་རིམ་(_B)ལ་བཞག་ཡོད།" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "ཡོལ་ཡོངས།(_F)" #: src/view/window_menu.c:378 msgid "_Close" msgstr "ཁ་རྒྱག(_C)" kylin-wayland-compositor/po/ug.po0000664000175000017500000000546615160461067016071 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2023. # msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2025-11-17 02:33+0000\n" "Last-Translator: KevinDuan \n" "Language-Team: Uyghur \n" "Language: ug\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.12.1-dev\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "كەسمە رەسىم" #: src/view/action.c:108 msgid "Capture saved to" msgstr "تۇتۇلۇپ ساقلانغان" #: src/view/ssd.c:248 msgid "Minimize" msgstr "كىچىكلىتىش" #: src/view/ssd.c:249 msgid "Maximize" msgstr "چوڭايتىش" #: src/view/ssd.c:250 msgid "Restore" msgstr "ئەسلىگە كەلتۈرۈش" #: src/view/ssd.c:251 msgid "Close" msgstr "تاقاش" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "ئۈستەل يۈزى" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "ئۈستەلگە يۆتكەش" #: src/view/window_menu.c:248 msgid "Screen" msgstr "ئېكران" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "كەسمە رەسىم ( _T )" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "ئۈستەل ( _D )" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "بارلىق ئۈستەل ( _A )" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "يېڭى ئۈستەل ( _N ) قوشۇلدى ( _N )" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "يېڭى ئۈستەلگە يۆتكەلدى ( _M )" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "ئەڭ چوڭلاشتۇرۇش ( _X )" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "ئېكرانغا يۆتكەلدى ( _S )" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "ئەڭ كىچىكلەش ( _N )" #: src/view/window_menu.c:367 msgid "_More" msgstr "تېخىمۇ كۆپ مەشغۇلات ( _M )" #: src/view/window_menu.c:369 msgid "_Move" msgstr "كۆچمە ( _M )" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "چوڭ - كىچىكلىكىنى تەڭشەش ( _R )" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "يۇقىرى قەۋەتكە قويۇلغان ( _A )" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "تۆۋەن قەۋەتكە قويۇلغان ( _B )" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "پۈتۈن ئېكران(_F)" #: src/view/window_menu.c:378 msgid "_Close" msgstr "ياپ(_C)" kylin-wayland-compositor/po/fr_FR.po0000664000175000017500000000462115160460057016442 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2024. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2024-08-16 09:56+0800\n" "Last-Translator: yangcanfen \n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "Capture d'écran" #: src/view/action.c:108 msgid "Capture saved to" msgstr "Capture enregistrée à" #: src/view/ssd.c:248 msgid "Minimize" msgstr "Minimiser" #: src/view/ssd.c:249 msgid "Maximize" msgstr "Maximiser" #: src/view/ssd.c:250 msgid "Restore" msgstr "Restaurer" #: src/view/ssd.c:251 msgid "Close" msgstr "Fermer" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "Bureau" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "Déplacer vers le bureau" #: src/view/window_menu.c:248 msgid "Screen" msgstr "Écran" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "Faire une cap_ture d'écran" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "Bureau(_D)" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "Tous les bure_aux" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "Ajouter au _nouveau bureau" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "Déplacer vers le nouveau bureau(_M)" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "Ma_ximiser" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "Déplacer ver_s l'écran" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "Mi_nimiser" #: src/view/window_menu.c:367 msgid "_More" msgstr "Actions supplé_mentaires" #: src/view/window_menu.c:369 msgid "_Move" msgstr "Déplacer(_M)" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "_Re-dimensionner" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "Conserver _au-dessu" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "Conserver au-dessous(_B)" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "Plein écran(_F)" #: src/view/window_menu.c:378 msgid "_Close" msgstr "Fermer(_C)" kylin-wayland-compositor/po/ms.po0000664000175000017500000000475615160461067016076 0ustar fengfeng# kylin-wayland-compositor pot file # Copyright (C) 2023 # This file is distributed under the same license as the kylin-wayland-compositor package. # yangcanfen , 2023. # msgid "" msgstr "" "Project-Id-Version: kylin-wayland-compositor\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-20 13:39+0800\n" "PO-Revision-Date: 2025-11-17 02:33+0000\n" "Last-Translator: KevinDuan \n" "Language-Team: Malay \n" "Language: ms\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.12.1-dev\n" #: src/view/action.c:108 msgid "Screenshot" msgstr "Tangkapan skrin" #: src/view/action.c:108 msgid "Capture saved to" msgstr "Tangkapan disimpan sebagai" #: src/view/ssd.c:248 msgid "Minimize" msgstr "Minimumkan" #: src/view/ssd.c:249 msgid "Maximize" msgstr "Maksimumkan" #: src/view/ssd.c:250 msgid "Restore" msgstr "Pulihkan" #: src/view/ssd.c:251 msgid "Close" msgstr "Tutup" #: src/view/window_menu.c:184 msgid "Desktop" msgstr "Desktop" #: src/view/window_menu.c:214 msgid "Move To Desktop" msgstr "Beralih ke desktop" #: src/view/window_menu.c:248 msgid "Screen" msgstr "Skrin" #: src/view/window_menu.c:342 msgid "_Take Screenshot" msgstr "Tangkapan skrin (_T)" #: src/view/window_menu.c:346 msgid "_Desktop" msgstr "Desktop (_D)" #: src/view/window_menu.c:348 msgid "_All Desktop" msgstr "Semua Desktop (_A)" #: src/view/window_menu.c:350 msgid "Add To _New Desktop" msgstr "Tambah ke Desktop Baharu (_N)" #: src/view/window_menu.c:353 msgid "_Move To New Desktop" msgstr "Beralih ke desktop baharu (_M)" #: src/view/window_menu.c:356 msgid "Ma_ximize" msgstr "Maksimumkan (_X)" #: src/view/window_menu.c:360 msgid "Move To _Screen" msgstr "Pindah ke Skrin (_S)" #: src/view/window_menu.c:363 msgid "Mi_nimize" msgstr "Diminimumkan (_N)" #: src/view/window_menu.c:367 msgid "_More" msgstr "Lebih Banyak Tindakan (_M)" #: src/view/window_menu.c:369 msgid "_Move" msgstr "Bergerak (_M)" #: src/view/window_menu.c:370 msgid "_Resize" msgstr "Ubah saiz (_R)" #: src/view/window_menu.c:371 msgid "Keep-_Above" msgstr "Diletakkan di tingkat atas (_A)" #: src/view/window_menu.c:373 msgid "Keep-_Below" msgstr "Letakkan di bahagian bawah (_B)" #: src/view/window_menu.c:375 msgid "_Fullscreen" msgstr "_Skrin penuh" #: src/view/window_menu.c:378 msgid "_Close" msgstr "Tutup(_C)" kylin-wayland-compositor/docs/0000775000175000017500000000000015160461067015415 5ustar fengfengkylin-wayland-compositor/docs/PROTOCOLS.md0000664000175000017500000002060415160461067017325 0ustar fengfeng# protocol wayland协议支持情况 > https://wayland.app/protocols/ ## Wayland core > https://gitlab.freedesktop.org/wayland/wayland.git | 协议名称 | 支持情况 | 说明 | | :--------------------- | :------: | :--------- | | wl_compositor | 6 | | | wl_shm | 1 | | | wl_data_device_manager | 3 | | | wl_shell | - | deprecated | | wl_seat | 8 | 9 | | wl_output | 4 | | | wl_subcompositor | 1 | | ## Wayland protocols > https://gitlab.freedesktop.org/wayland/wayland-protocols.git ### stable | 协议名称 | 支持情况 | 说明 | | :-------------- | :------: | :---- | | wp_presentation | 1 | | | wp_viewporter | 1 | | | xdg_wm_base | 5 | | ### staging | 协议名称 | 支持情况 | 说明 | | :-------------------------------- | :------: | :---- | | wp_content_type_manager_v1 | no | 1 | | wp_cursor_shape_manager_v1 | 1 | | | wp_drm_lease_device_v1 | 1 | | | ext_foreign_toplevel_list_v1 | no | 1 | | ext_idle_notifier_v1 | 1 | | | ext_session_lock_manager_v1 | no | 1 | | ext_transient_seat_manager_v1 | 1 | | | wp_fractional_scale_manager_v1 | 1 | | | wp_security_context_manager_v1 | no | 1 | | wp_single_pixel_buffer_manager_v1 | 1 | | | wp_tearing_control_manager_v1 | 1 | | | xdg_activation_v1 | 1 | | | xdg_wm_dialog_v1 | 1 | | | xwayland_shell_v1 | 1 | | | xdg_toplevel_drag_manager_v1 | 1 | | | xdg_toplevel_icon_manager_v1 | 1 | | | wp_alpha_modifier_v1 | 1 | | ### unstable | 协议名称 | 支持情况 | 说明 | | :---------------------------------------- | :------: | :---------- | | zwp_fullscreen_shell_v1 | no | 1 | | zwp_idle_inhibit_manager_v1 | 1 | | | zwp_input_method_context_v1 | no | 1 | | zwp_input_timestamps_manager_v1 | 1 | | | zwp_keyboard_shortcuts_inhibit_manager_v1 | 1 | | | zwp_linux_dmabuf_v1 | 4 | | | zwp_linux_explicit_synchronization_v1 | no | deprecated | | zwp_pointer_constraints_v1 | 1 | | | zwp_pointer_gestures_v1 | 3 | | | zwp_primary_selection_device_manager_v1 | 1 | | | zwp_relative_pointer_manager_v1 | 1 | | | zwp_tablet_manager_v1 | - | v2 | | zwp_tablet_manager_v2 | 1 | | | zwp_text_input_manager_v1 | 1 | | | zwp_text_input_manager_v3 | 1 | | | zxdg_decoration_manager_v1 | 1 | | | zxdg_exporter_v1 | 1 | | | zxdg_exporter_v2 | 1 | | | zxdg_importer_v1 | 1 | | | zxdg_importer_v2 | 1 | | | zxdg_output_manager_v1 | 3 | | | xdg_shell | - | xdg_wm_base | | zxdg_shell_v6 | - | xdg_wm_base | | zwp_xwayland_keyboard_grab_manager_v1 | no | 1 | ## wlr > https://gitlab.freedesktop.org/wlroots/wlroots.git | 协议名称 | 支持情况 | 说明 | | :------------------------------- | :------: | :---- | | zwlr_data_control_manager_v1 | 2 | | | zwlr_export_dmabuf_manager_v1 | 1 | | | zwlr_foreign_toplevel_manager_v1 | 3 | | | zwlr_gamma_control_manager_v1 | no | 1 | | zwlr_input_inhibit_manager_v1 | no | deprecated | | zwlr_layer_shell_v1 | 4 | | | zwlr_output_manager_v1 | 4 | | | zwlr_output_power_manager_v1 | 1 | | | zwlr_screencopy_manager_v1 | 3 | | | zwlr_virtual_pointer_manager_v1 | 2 | | ## kde > https://invent.kde.org/libraries/plasma-wayland-protocols.git | 协议名称 | 支持情况 | 说明 | | :--------------------------------------------- | :------: | :---- | | org_kde_kwin_appmenu_manager | no | 1 | | org_kde_kwin_blur_manager | 1 | ukui | | org_kde_kwin_contrast_manager | no | 2 | | org_kde_kwin_dpms_manager | 1 | | | org_kde_kwin_fake_input | no | 5 | | org_kde_kwin_idle | 1 | | | kde_lockscreen_overlay_v1 | no | 1 | | kde_output_device_v2 | 8 | 12 | | kde_output_management_v2 | 9 | 12 | | kde_output_order_v1 | no | 1 | | kde_primary_output_v1 | 2 | | | kde_screen_edge_manager_v1 | no | 1 | | org_kde_kwin_keystate | 5 | | | org_kde_plasma_virtual_desktop_management | 2 | | | org_kde_kwin_outputmanagement | - | 4 v2 | | org_kde_kwin_outputdevice | - | 4 v2 | | org_kde_plasma_shell | 8 | ukui | | org_kde_plasma_window_management | 17 | | | org_kde_kwin_remote_access_manager | no | 1 | | org_kde_kwin_server_decoration_palette_manager | no | 1 | | org_kde_kwin_server_decoration_manager | 1 | | | org_kde_kwin_shadow_manager | no | 2 | | org_kde_kwin_slide_manager | 1 | | | zkde_screencast_unstable_v1 | no | 5 | ## weston > https://gitlab.freedesktop.org/wayland/weston.git | 协议名称 | 支持情况 | 说明 | | :------------------------ | :------: | :--- | | ivi_surface | no | 1 | | ivi_hmi_controller | no | 1 | | text_cursor_position | no | 1 | | weston_content_protection | no | 1 | | weston_debug_v1 | no | 1 | | weston_desktop_shell | no | 1 | | weston_direct_display_v1 | no | 1 | | weston_capture_v1 | no | 1 | | weston_test | no | 1 | | weston_touch_calibration | no | 1 | ## external | 协议名称 | 支持情况 | 说明 | | :------------------------------ | :------: | :--- | | wl_drm | 2 | | | wl_eglstream | no | 1 | | wl_eglstream_controller | no | 2 | | zwp_input_method_manager_v2 | 1 | | | zwp_virtual_keyboard_manager_v1 | 1 | | | zwp_text_input_manager_v2 | 1 | | | qt_surface_extension | no | 1 | | gtk_shell1 | no | 5 | ## ukui > https://gitee.com/openkylin/kylin-wayland-protocols | 协议名称 | 支持情况 | 说明 | | :------------------------- | :------: | :--- | | ukui_blur_manager_v1 | 1 | | | ukui_output_management_v1 | 1 | | | ukui_shell_v1 | 4 | | | ukui_window_management | 1 | | | ukui_startup_management_v2 | 1 | | | ukui_effect_v1 | 1 | | ## kywc | 协议名称 | 支持情况 | 说明 | | :------------------------ | :------: | :--- | | kywc_capture_manager_v1 | 2 | | | kywc_output_manager_v1 | 1 | | | kywc_security_manager_v1 | 1 | | | kywc_toplevel_manager_v1 | 1 | | | kywc_workspace_manager_v1 | 1 | | kylin-wayland-compositor/docs/CONTRIBUTING.md0000664000175000017500000000533515160460057017652 0ustar fengfeng# 如何贡献 1. 将问题提交issues。提交issues暂时没有固定格式,只需要提供一些合理的信息,例如当前的软硬件信息, 执行了什么操作,你预计会发生什么,实际的现象以及复现问题的步骤。 如果可以的话,试着做一些[调试](#调试),并附上日志输入,方便问题定位。 2. 提交补丁完善代码。如果您希望引入重大更改或新功能,请首先在issues中进行讨论。 # 调试 ## 手动运行 进入tty界面,确保原有的桌面环境管已经关闭。 sudo systemctl stop lightdm 执行以下命令手动启动合成器 kylin-wlcom -s ukui-session 此时将启动ukui桌面。 ## 运程调试 需要安装tmux,在调试机器tty上启动tmux。 在运程机器上通过ssh连接调试机后,通过`tmux a`命令挂接上调试机。 ## 嵌套运行 在已有的图形服务器上,终端启动kylin-wlcom, 此时为嵌入式运行状态,要在此合成器上运行客户端,需加上参数: WAYLAND_DISPLAY=[display] [display]可通过日志查看。 ## Backtraces 如果合成器崩溃,可在构建时使用ASAN/UBSAN来回溯 meson setup build -Dbuildtype=debug -Db_sanitize=address,undefined ninja -C build 如果合成器多次崩溃且崩溃场景存在随机性,可使用valgrind工具进行测试 valgrind /path/kylin-wlcom ... ## Debug日志 查看客户端debug打印信息,可在启动客户端时添加如下指令打印debug信息 WAYLAND_DEBUG=1 服务端日志默认路径 $HOME/.log/kylin-wlcom.log 可通过入参`-Dlogtostdout`直接打印日志到stdout "Usage: kylin-wlcom [options] [command]" " -d, --debug Enables full logging, including debug information.\n" " -D, --debug noxwayland or logtostdout.\n" 设置debug信息输出到屏幕: ./kylin-wlcom -d -Dlogtostdout ## xwayland 不开启xwayland: ./kylin-wlcom -Dnoxwayland ## ukui程序 在终端启动应用前面加入参数: QT_QPA_PLATFORM=wayland QT_QPA_PLATFORMTHEME=ukui ## 输入 使用如下指令来显示输入事件 sudo libinput debug-events 在终端上,可以使用如下指令来分析键盘事件 xev -event keyboard wev -f wl_keyboard:key # 代码风格 代码风格由项目附带的`.clang-format`文件控制,可使用clang-format进行格式化,例如 clang-format -i src/view/workspace.c # Commit信息 解释commit的原因和commit本身一样重要。像这样来提交commit信息, view: mark mapped before configure 第一行应该: - 简要描述提交的原因 - 大多数情况下,前缀为被修改的模块或文件名 - 不使用句号 commit信息标题请控制在合理长度内。 kylin-wayland-compositor/docs/ENV.md0000664000175000017500000000363215160461067016373 0ustar fengfeng# 环境变量 kylin-wlcom合成器支持多个环境变量,可对内部流程进行修改。 ## 日志 * *KYWC_LOG_LEVEL*:设置日志等级,可选值为:DEBUG|INFO|WARN|ERROR|FATAL|SILENT ## 显示 * *KYWC_BACKEND*:设置显示后端,可选值为:fbdev * *KYWC_FB_DEVICES*:设置FBDEV后端输出节点,如/dev/fb0:/dev/fb1 * *WLR_DRM_DEVICES*:设置DRM后端输出节点,如/dev/dri/card1:/dev/dri/card0,第一个设备为默认渲染设备 * *KYWC_DRM_NO_ATOMIC*:设置DRM后端禁用原子提交,默认关闭 * *KYWC_DRM_NO_MODIFIERS*:设置DRM后端禁用modifiers,默认关闭 * *KYWC_SOFTWARE_COLOR*:设置DRM后端使用软件色彩(亮度、色温、色彩管理),默认关闭 * *KYWC_OUTPUT_FILL_WALLPAPER*:在合成器启动和模式切换的第一帧填充壁纸,默认关闭 ## 渲染 * *KYWC_RENDERER*:设置渲染后端,可选值为:gl,gles2,pixman,vulkan * *KYWC_EGL_NO_MODIFIERS*:关闭egl中的modifiers支持 * *KYWC_RENDERER_ALLOW_SOFTWARE*:允许使用软件渲染器,如llvmpipe ## 输出 * *KYWC_DISABLE_DIRECT_SCANOUT*:强制去使能全屏应用直接输出,默认关闭 * *KYWC_USE_LAYOUT_MANAGER*:使用合成器内部的屏幕配置管理,默认关闭 ## 应用 这部分环境变量供应用程序使用,例如vkcube,glmark2,glxgears,glxinfo等 * *DRI_PRIME*:为应用指定渲染设备,值为pci-area_bus_dev_func,如pci-0000_01_00_0 * *__VK_LAYER_NV_optimus*:VULKAN的加速卡限制为NVIDIA或者非NVIDIA,可选值为:NVIDIA_only|non_NVIDIA_only * *__NV_PRIME_RENDER_OFFLOAD*:VULKAN或者EGL的加速卡是否首选NVIDIA GPU,可选值为:0|1 * *__GLX_VENDOR_LIBRARY_NAME*:指定GLX的渲染库,如nvidia ## 其他 * *XCURSOR_THEME*:设置光标主题 * *XCURSOR_SIZE*:设置光标大小 * *KYWC_FORCE_QUIRKS*:设置wlcom定制行为,输入要求十进制,如2,表示强制软件鼠标 kylin-wayland-compositor/docs/KNOWN_ISSUES.md0000664000175000017500000000015115160460057017721 0ustar fengfeng# ukui应用显示双标题栏 - 部分应用未使用sdk接口进行设置,需要应用进行修改 kylin-wayland-compositor/tools/0000775000175000017500000000000015160461067015625 5ustar fengfengkylin-wayland-compositor/tools/meson.build0000664000175000017500000000002215160461067017761 0ustar fengfengsubdir('wlcctrl') kylin-wayland-compositor/tools/wlcctrl/0000775000175000017500000000000015160461067017277 5ustar fengfengkylin-wayland-compositor/tools/wlcctrl/wlcctrl.h0000664000175000017500000000133515160460057021122 0ustar fengfeng// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd. // // SPDX-License-Identifier: Expat #include #include "seat.h" typedef struct _keyboard keyboard; typedef struct _cursor cursor; struct wlcom_ctrl_manager { struct wl_display *wl_display; kywc_context *kywc_ctx; struct wl_registry *registry; struct zwp_virtual_keyboard_manager_v1 *k_manager; struct zwlr_virtual_pointer_manager_v1 *p_manager; keyboard *keyboard; cursor *cursor; seat *seat; char *primary_output; struct wl_list outputs; }; cursor *wlcctrl_cursor_init(void); keyboard *wlcctrl_keyboard_init(void); struct wlcom_ctrl_manager *wayland_init(void); void wayland_deinit(struct wlcom_ctrl_manager *manager); kylin-wayland-compositor/tools/wlcctrl/meson.build0000664000175000017500000000146415160460057021444 0ustar fengfengwlcctrl_sources = [] wlcctrl_sources += files( 'main.c', 'seat.c', 'output.c', 'wlcctrl.c', 'cursor.c', 'keyboard.c', ) protos = [ 'virtual-keyboard-unstable-v1', 'wlr-virtual-pointer-unstable-v1', ] foreach p : protos wlcctrl_sources += protocols_code[p] wlcctrl_sources += protocols_client_header[p] endforeach executable( 'wlcctrl', wlcctrl_sources, include_directories: wlcom_inc, dependencies: [wayland_client, libkywc, xkbcommon, kywc_buffer, util], build_by_default: true, install: true, ) scdoc = find_program('scdoc') if scdoc.found() mandir = get_option('mandir') custom_target( 'wlcctrl.1', input: 'wlcctrl.1.scd', output: 'wlcctrl.1', command: scdoc, feed: true, capture: true, install: true, install_dir: '@0@/man1'.format(mandir) ) endif kylin-wayland-compositor/tools/wlcctrl/README.md0000664000175000017500000001176015160460057020561 0ustar fengfeng# wlcctrl `wlcctrl` 是一个用于 kylin-wlcom 合成器的命令行工具。 ## 概述 ```bash wlcctrl [command] [options...] ``` ## 选项 ### 常用选项 - `--help` - 显示帮助信息并退出。 - `-v, --version` - 显示wlrcctrl版本并退出。 - `--exec` - 执行一个shell命令。 - `--sleep` - 设置暂停时长。此命令可以与其他任何命令组合使用。`_int_ms_` 是延迟时间,单位是毫秒。 示例: ```bash wlcctrl --keydown a --sleep 1000 --keyup a wlcctrl --mousepress 1 --sleep 200 --mouserelease 1 ``` - `--path` - 设置截图保存路径,图像格式为PNG。默认情况下保存在当前工作目录中。 - 与 `--windowcapture` 一起使用:设置窗口截图保存路径。 - 与 `--workspacecapture` 一起使用:设置工作区截图保存路径。 - 与 `--fullscreencapture`一起使用: 设置全屏截图保存路径。 示例: ```bash wlcctrl --windowcapture uuid --path "/home/kylin/xxx.png" ``` - `-o` - 指定输出设备。`_o_uuid_` 是输出设备的UUID。 ## 键盘选项 - `--keystring ` - 模拟字符串输入。 - `--key` - 发送指定的按键事件。按键可以是单个键或给定的组合键。 字符名称映射: ```plaintext "PrintScreen" 映射为 "sys_req", "esc" 映射为 "escape", "ctrl" 映射为 "control_l", "shift" 映射为 "shift_l", "alt" 映射为 "alt_l", "winleft" 映射为 "super_l", "enter" 映射为 "return". ``` 示例: ```bash wlcctrl --key control_l+alt_l+t wlcctrl --key super_l ``` - `--keyup` - 释放单个键。 - `--keydown` - 按下单个键。 ## 鼠标选项 - `--mousebutton` [button] - 点击鼠标按钮。 按钮代码:BTN_LEFT->1 | BTN_RIGHT->2 | BTN_MIDDLE->3 | BTN_EXTRA->4 | BTN_SIDE->5 | BTN_FORWARD->6 | BTN_BACK->7 示例: ```bash wlcctrl --mousebutton 2 ``` - `--mousepress` [button] - 按下鼠标按钮。 示例: ```bash wlcctrl --mousepress 2 --mouserelease 2 ``` - `--mouserelease` [button] - 释放鼠标按钮。 - `--mousemove` - 移动光标。`_dx_` 是向右方向的位移,`_dy_` 是向下方向的位移。 示例: ```bash wlcctrl --mousemove 100,500 ``` - `--scroll` [options...] - 滚动轴。 - `-x `: dx 是水平滚动的量。 - `-y `: dy 是垂直滚动的量。 允许为负数。 示例: ```bash wlcctrl --scroll -y 10 wlcctrl --scroll -x 10 -y 20 ``` - `--getmouselocation` - 获取当前鼠标位置。 ## 窗口选项 - `-l, --list` - 列出所有窗口名称。 - `--windowmove` [options...] - 将窗口移动到指定位置。 示例: ```bash wlcctrl --windowmove 182fcd8f-6136-413b-aa1b-c77b066ebe23 -x 200 -y 400 ``` - `--windowsize` [options...] - 调整窗口大小。 - `-w `: width 是期望的窗口宽度。 - `-h `: height 是期望的窗口高度。 示例: ```bash wlcctrl --windowsize 182fcd8f-6136-413b-aa1b-c77b066ebe23 -w 800 -h 600 ``` - `--windowminimize` - 最小化窗口。 - `--windowmaximize` - 最大化窗口。 - `--windowfullscreen` - 全屏显示窗口。 - `--windowenter` - 将窗口添加到指定工作区。 - `--windowleave` - 将窗口从指定的工作区移除。 - `--movewindowtoworkspace` - 将窗口移动到指定的工作区。 - `--movewindowtooutput` - 将窗口移动到指定的输出设备。 - `--windowmap` - 恢复窗口。 - `--windowclose` - 关闭窗口。 - `--windowkill` - 强制关闭指定名称的所有窗口。 - `--windowactivate` - 激活指定的窗口。 - `--windowcapture` - 窗口截图。 - `--getactivewindow` - 获取当前活动的窗口。 - `--getwindowname` - 查询特定窗口的应用ID、标题、图标和进程ID。 - `--getwindowgeometry` - 查询特定窗口的几何信息。 - `--search` - 通过检查窗口应用ID、图标和标题是否与正则表达式模式匹配来获取窗口UUID列表。 ## 输出设备选项 - `--outputs` - 列出所有已知输出设备的名称。 - `--getdisplaygeometry` - 获取指定输出设备的几何信息。 - `--fullscreencapture` - 截取整个屏幕的截图。 - `--setarea` - 区域截图。设置矩形区域的起点和大小。 示例: ```bash wlcctrl --setarea 10,40,500,100 --path "/home/kylin/xxx.png" ``` - `--getpixelcolor` - 获取指定位置像素的RGBA值。 ## 工作区选项 - `--workspace` - 列出所有已知工作区的名称。 - `--workspacecapture` - 工作区截图。 - `--workspaceadd` - 添加新的工作区。默认添加一个新工作区。 - `--workspaceremove` - 移除指定的工作区。 - `--workspaceactive` - 激活指定的工作区。 kylin-wayland-compositor/tools/wlcctrl/wlcctrl.1.scd0000664000175000017500000002056415160460057021610 0ustar fengfengwlcctrl(1) # NAME wlcctrl - A command line utility for kylin-wayland-compositor # SYNOPSIS wlcctrl [command] [options...] # GLOBAL OPTIONS *--help* Show a help message and quit. *-v, --version* Show a wlcctrl version and quit. *--exec* Execute a shell command. *--sleep* Set sleep time in milliseconds. Can be combined with other commands. example: wlcctrl --keydown a --sleep 1000 --keyup a wlcctrl --mousepress 1 --sleep 200 --mouserelease 1 *--path* Set screenshot save path (PNG format). Uses last value when set multiple times. with *--windowcapture*: Set the window screenshot saved path. with *--workspacecapture*: Set the workspace screenshot saved path. with *--fullscreencapture*: Set the screen/output screenshot saved path. example: wlcctrl --windowcapture uuid --path "/home/kylin/xxx.png" *-o* Specify output device by UUID. Uses last value when set multiple times. # KEYBOARD OPTIONS *--keystring* Send a string to be typed into the focused client. No commands can chain after 'keystring'. *--key* Send key events. Single key or key combination. Note: CapsLock(58), NumLock(69), ScrollLock(70) may not switch states normally. For reliable lock key functionality, use ydotool instead. Key mappings: "PrintScreen" -> "sys_req" "esc" -> "escape" "ctrl" -> "control_l" "shift" -> "shift_l" "alt" -> "alt_l" "winleft" -> "super_l" "enter" -> "return" Valid keys: escape, return, shift_l, shift_r, control_l, control_r, meta_l, meta_r, alt_l, alt_r, page_up, page_down, menu, caps_lock, pause, break, print, sys_req, scroll_lock, num_lock, etc. Multiple keys separated by '+'. examples: wlcctrl --key control_l+alt_l+t wlcctrl --key ctrl+alt+t wlcctrl --key super_l *--keyup* Release a single key. *--keydown* Press a single key. with *--keyup*: Indicate pressing and releasing a key. otherwise: Indicate the state of a key being pressed individually. The command defaults to releasing the key upon completion. # POINTER OPTIONS *--mousebutton*