Compare commits
5 Commits
1.0
...
export-dma
Author | SHA1 | Date | |
---|---|---|---|
3ac3f6cab2 | |||
dd7e1e22ee | |||
0ed52013dd | |||
f3f2b9e9e2 | |||
71ce9741d1 |
@ -8,7 +8,7 @@ Copyright: 2020 Jason Francis <jason@cycles.network>
|
||||
License: CC0-1.0
|
||||
|
||||
Files: resources/style.css resources/wdisplays.desktop.in resources/*.ui
|
||||
resources/resources.xml
|
||||
resources/resources.xml.in
|
||||
Copyright: 2020 Jason Francis <jason@cycles.network>
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
|
38
README.md
38
README.md
@ -4,19 +4,34 @@
|
||||
|
||||
wdisplays is a graphical application for configuring displays in Wayland
|
||||
compositors. It borrows some code from [kanshi]. It should work in any
|
||||
compositor that implements the wlr-output-management-unstable-v1 protocol,
|
||||
including [sway]. The goal of this project is to allow precise adjustment of
|
||||
display settings in kiosks, digital signage, and other elaborate multi-monitor
|
||||
setups.
|
||||
compositor that implements the wlr-output-management-unstable-v1 protocol.
|
||||
Compositors that are known to support the protocol are [Sway] and [Wayfire].
|
||||
The goal of this project is to allow precise adjustment of display settings in
|
||||
kiosks, digital signage, and other elaborate multi-monitor setups.
|
||||
|
||||

|
||||
|
||||
# Installation
|
||||
|
||||
[![Repology][repology-img]][repology-pkg]
|
||||
|
||||
Check your distro for a `wdisplays` package. Known distro packages:
|
||||
|
||||
- [Alpine](https://pkgs.alpinelinux.org/package/edge/testing/x86_64/wdisplays)
|
||||
- [Arch](https://aur.archlinux.org/packages/wdisplays-git/)
|
||||
- [Debian](https://packages.debian.org/sid/wdisplays)
|
||||
- [Fedora](https://copr.fedorainfracloud.org/coprs/wef/wdisplays/)
|
||||
- [FreeBSD](https://svnweb.freebsd.org/ports/head/x11/wdisplays/)
|
||||
- [Nix](https://github.com/NixOS/nixpkgs/tree/master/pkgs/tools/graphics/wdisplays)
|
||||
- [OpenSUSE](https://build.opensuse.org/package/show/home%3AMWh3/wdisplays)
|
||||
|
||||
# Building
|
||||
|
||||
Build requirements are:
|
||||
|
||||
- meson
|
||||
- GTK+3
|
||||
- libdrm
|
||||
- epoxy
|
||||
- wayland-client
|
||||
|
||||
@ -26,8 +41,6 @@ ninja -C build
|
||||
sudo ninja -C build install
|
||||
```
|
||||
|
||||
Binaries are not available. Only building from source is supported.
|
||||
|
||||
# Usage
|
||||
|
||||
Displays can be moved around the virtual screen space by clicking and dragging
|
||||
@ -60,10 +73,19 @@ file. See man `sway-output`. If you want to have multiple configurations
|
||||
depending on the monitors connected, you'll need to use an external program
|
||||
like [kanshi]. Integration with that and other external daemons is planned.
|
||||
|
||||
### How do I add support to my compositor?
|
||||
|
||||
A minimal amount of code (approximately 150-200 LOC) is currently required to
|
||||
get support for this in wlroots compositors. See the diff here for a sample
|
||||
implementation on top of tinywl: [tinywl-output-management].
|
||||
|
||||
[kanshi]: https://github.com/emersion/kanshi
|
||||
[sway]: https://github.com/swaywm/sway
|
||||
[Sway]: https://swaywm.org
|
||||
[Wayfire]: https://wayfire.org
|
||||
[ARandR]: https://christian.amsuess.com/tools/arandr/
|
||||
[tinywl-output-management]: https://git.sr.ht/~jf/tinywl-output-management/commit/87a45d89ae0e7975e2a59f84e960380dd2f5ac08
|
||||
|
||||
[license-img]: https://img.shields.io/badge/License-GPL%203.0%20or%20later-blue.svg?logo=gnu
|
||||
[license-spdx]: https://spdx.org/licenses/GPL-3.0-or-later.html
|
||||
|
||||
[repology-img]: https://repology.org/badge/tiny-repos/wdisplays.svg
|
||||
[repology-pkg]: https://repology.org/project/wdisplays/versions
|
||||
|
@ -3,9 +3,11 @@
|
||||
|
||||
project('network.cycles.wdisplays', 'c', license: 'MIT', version: '1.0')
|
||||
|
||||
conf = configuration_data()
|
||||
conf.set('app_id', meson.project_name())
|
||||
conf.set('version', meson.project_version())
|
||||
conf = configuration_data({
|
||||
'app_id': meson.project_name(),
|
||||
'version': meson.project_version(),
|
||||
'resource_prefix': '/' / '/'.join(meson.project_name().split('.')),
|
||||
})
|
||||
|
||||
subdir('protocol')
|
||||
subdir('resources')
|
||||
|
@ -21,9 +21,11 @@ wayland_scanner_client = generator(
|
||||
|
||||
client_protocols = [
|
||||
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'],
|
||||
[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
|
||||
['wlr-output-management-unstable-v1.xml'],
|
||||
['wlr-screencopy-unstable-v1.xml'],
|
||||
['wlr-export-dmabuf-unstable-v1.xml'],
|
||||
['wlr-layer-shell-unstable-v1.xml']
|
||||
]
|
||||
|
||||
|
203
protocol/wlr-export-dmabuf-unstable-v1.xml
Normal file
203
protocol/wlr-export-dmabuf-unstable-v1.xml
Normal file
@ -0,0 +1,203 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_export_dmabuf_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2018 Rostislav Pehlivanov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<description summary="a protocol for low overhead screen content capturing">
|
||||
An interface to capture surfaces in an efficient way by exporting DMA-BUFs.
|
||||
|
||||
Warning! The protocol described in this file is experimental and
|
||||
backward incompatible changes may be made. Backward compatible changes
|
||||
may be added together with the corresponding interface version bump.
|
||||
Backward incompatible changes are done by bumping the version number in
|
||||
the protocol and interface names and resetting the interface version.
|
||||
Once the protocol is to be declared stable, the 'z' prefix and the
|
||||
version number in the protocol and interface names are removed and the
|
||||
interface version number is reset.
|
||||
</description>
|
||||
|
||||
<interface name="zwlr_export_dmabuf_manager_v1" version="1">
|
||||
<description summary="manager to inform clients and begin capturing">
|
||||
This object is a manager with which to start capturing from sources.
|
||||
</description>
|
||||
|
||||
<request name="capture_output">
|
||||
<description summary="capture a frame from an output">
|
||||
Capture the next frame of a an entire output.
|
||||
</description>
|
||||
<arg name="frame" type="new_id" interface="zwlr_export_dmabuf_frame_v1"/>
|
||||
<arg name="overlay_cursor" type="int"
|
||||
summary="include custom client hardware cursor on top of the frame"/>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the manager">
|
||||
All objects created by the manager will still remain valid, until their
|
||||
appropriate destroy request has been called.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_export_dmabuf_frame_v1" version="1">
|
||||
<description summary="a DMA-BUF frame">
|
||||
This object represents a single DMA-BUF frame.
|
||||
|
||||
If the capture is successful, the compositor will first send a "frame"
|
||||
event, followed by one or several "object". When the frame is available
|
||||
for readout, the "ready" event is sent.
|
||||
|
||||
If the capture failed, the "cancel" event is sent. This can happen anytime
|
||||
before the "ready" event.
|
||||
|
||||
Once either a "ready" or a "cancel" event is received, the client should
|
||||
destroy the frame. Once an "object" event is received, the client is
|
||||
responsible for closing the associated file descriptor.
|
||||
|
||||
All frames are read-only and may not be written into or altered.
|
||||
</description>
|
||||
|
||||
<enum name="flags">
|
||||
<description summary="frame flags">
|
||||
Special flags that should be respected by the client.
|
||||
</description>
|
||||
<entry name="transient" value="0x1"
|
||||
summary="clients should copy frame before processing"/>
|
||||
</enum>
|
||||
|
||||
<event name="frame">
|
||||
<description summary="a frame description">
|
||||
Main event supplying the client with information about the frame. If the
|
||||
capture didn't fail, this event is always emitted first before any other
|
||||
events.
|
||||
|
||||
This event is followed by a number of "object" as specified by the
|
||||
"num_objects" argument.
|
||||
</description>
|
||||
<arg name="width" type="uint"
|
||||
summary="frame width in pixels"/>
|
||||
<arg name="height" type="uint"
|
||||
summary="frame height in pixels"/>
|
||||
<arg name="offset_x" type="uint"
|
||||
summary="crop offset for the x axis"/>
|
||||
<arg name="offset_y" type="uint"
|
||||
summary="crop offset for the y axis"/>
|
||||
<arg name="buffer_flags" type="uint"
|
||||
summary="flags which indicate properties (invert, interlacing),
|
||||
has the same values as zwp_linux_buffer_params_v1:flags"/>
|
||||
<arg name="flags" type="uint" enum="flags"
|
||||
summary="indicates special frame features"/>
|
||||
<arg name="format" type="uint"
|
||||
summary="format of the frame (DRM_FORMAT_*)"/>
|
||||
<arg name="mod_high" type="uint"
|
||||
summary="drm format modifier, high"/>
|
||||
<arg name="mod_low" type="uint"
|
||||
summary="drm format modifier, low"/>
|
||||
<arg name="num_objects" type="uint"
|
||||
summary="indicates how many objects (FDs) the frame has (max 4)"/>
|
||||
</event>
|
||||
|
||||
<event name="object">
|
||||
<description summary="an object description">
|
||||
Event which serves to supply the client with the file descriptors
|
||||
containing the data for each object.
|
||||
|
||||
After receiving this event, the client must always close the file
|
||||
descriptor as soon as they're done with it and even if the frame fails.
|
||||
</description>
|
||||
<arg name="index" type="uint"
|
||||
summary="index of the current object"/>
|
||||
<arg name="fd" type="fd"
|
||||
summary="fd of the current object"/>
|
||||
<arg name="size" type="uint"
|
||||
summary="size in bytes for the current object"/>
|
||||
<arg name="offset" type="uint"
|
||||
summary="starting point for the data in the object's fd"/>
|
||||
<arg name="stride" type="uint"
|
||||
summary="line size in bytes"/>
|
||||
<arg name="plane_index" type="uint"
|
||||
summary="index of the the plane the data in the object applies to"/>
|
||||
</event>
|
||||
|
||||
<event name="ready">
|
||||
<description summary="indicates frame is available for reading">
|
||||
This event is sent as soon as the frame is presented, 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 this object.
|
||||
</description>
|
||||
<arg name="tv_sec_hi" type="uint"
|
||||
summary="high 32 bits of the seconds part of the timestamp"/>
|
||||
<arg name="tv_sec_lo" type="uint"
|
||||
summary="low 32 bits of the seconds part of the timestamp"/>
|
||||
<arg name="tv_nsec" type="uint"
|
||||
summary="nanoseconds part of the timestamp"/>
|
||||
</event>
|
||||
|
||||
<enum name="cancel_reason">
|
||||
<description summary="cancel reason">
|
||||
Indicates reason for cancelling the frame.
|
||||
</description>
|
||||
<entry name="temporary" value="0"
|
||||
summary="temporary error, source will produce more frames"/>
|
||||
<entry name="permanent" value="1"
|
||||
summary="fatal error, source will not produce frames"/>
|
||||
<entry name="resizing" value="2"
|
||||
summary="temporary error, source will produce more frames"/>
|
||||
</enum>
|
||||
|
||||
<event name="cancel">
|
||||
<description summary="indicates the frame is no longer valid">
|
||||
If the capture failed or if the frame is no longer valid after the
|
||||
"frame" event has been emitted, this event will be used to inform the
|
||||
client to scrap the frame.
|
||||
|
||||
If the failure is temporary, the client may capture again the same
|
||||
source. If the failure is permanent, any further attempts to capture the
|
||||
same source will fail again.
|
||||
|
||||
After receiving this event, the client should destroy this object.
|
||||
</description>
|
||||
<arg name="reason" type="uint" enum="cancel_reason"
|
||||
summary="indicates a reason for cancelling this frame capture"/>
|
||||
</event>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="delete this object, used or not">
|
||||
Unreferences the frame. This request must be called as soon as its no
|
||||
longer used.
|
||||
|
||||
It can be called at any time by the client. The client will still have
|
||||
to close any FDs it has been given.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.0 -->
|
||||
<!-- Generated with glade 3.22.2 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<object class="GtkAdjustment" id="height_adjustment">
|
||||
@ -7,23 +7,6 @@
|
||||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="GtkPopover" id="modes">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="mode_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_start">10</property>
|
||||
<property name="margin_end">10</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="pos_x_adjustment">
|
||||
<property name="upper">16383</property>
|
||||
<property name="step_increment">1</property>
|
||||
@ -50,7 +33,7 @@
|
||||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="GtkGrid" id="form">
|
||||
<template class="WdHeadForm" parent="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_start">8</property>
|
||||
@ -69,7 +52,7 @@
|
||||
<property name="halign">start</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="enabled" swapped="no"/>
|
||||
<signal name="toggled" handler="enabled_toggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
@ -99,7 +82,7 @@
|
||||
<property name="adjustment">scale_adjustment</property>
|
||||
<property name="digits">2</property>
|
||||
<property name="value">1</property>
|
||||
<signal name="change-value" handler="scale" swapped="no"/>
|
||||
<signal name="value-changed" handler="position_spin_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
@ -194,6 +177,7 @@
|
||||
<property name="spacing">8</property>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="refresh">
|
||||
<property name="name">refresh</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="width_chars">9</property>
|
||||
@ -202,6 +186,7 @@
|
||||
<property name="digits">3</property>
|
||||
<property name="numeric">True</property>
|
||||
<property name="update_policy">if-valid</property>
|
||||
<signal name="value-changed" handler="mode_spin_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@ -245,7 +230,6 @@
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="popover">transforms</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
@ -277,7 +261,7 @@
|
||||
<property name="halign">start</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="flipped" swapped="no"/>
|
||||
<signal name="toggled" handler="flipped_toggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
@ -299,6 +283,7 @@
|
||||
<property name="adjustment">pos_x_adjustment</property>
|
||||
<property name="numeric">True</property>
|
||||
<property name="update_policy">if-valid</property>
|
||||
<signal name="value-changed" handler="position_spin_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
@ -315,6 +300,7 @@
|
||||
<property name="adjustment">pos_y_adjustment</property>
|
||||
<property name="numeric">True</property>
|
||||
<property name="update_policy">if-valid</property>
|
||||
<signal name="value-changed" handler="position_spin_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
@ -323,6 +309,7 @@
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="width">
|
||||
<property name="name">width</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="width_chars">4</property>
|
||||
@ -331,6 +318,7 @@
|
||||
<property name="adjustment">width_adjustment</property>
|
||||
<property name="numeric">True</property>
|
||||
<property name="update_policy">if-valid</property>
|
||||
<signal name="value-changed" handler="mode_spin_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
@ -351,6 +339,7 @@
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="height">
|
||||
<property name="name">height</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="width_chars">4</property>
|
||||
@ -359,6 +348,7 @@
|
||||
<property name="adjustment">height_adjustment</property>
|
||||
<property name="numeric">True</property>
|
||||
<property name="update_policy">if-valid</property>
|
||||
<signal name="value-changed" handler="mode_spin_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
@ -371,9 +361,7 @@
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Select Mode Preset</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_start">8</property>
|
||||
<property name="popover">modes</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
@ -406,76 +394,5 @@
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkPopover" id="transforms">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="relative_to">rotate_button</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_start">10</property>
|
||||
<property name="margin_end">10</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="rotate_0">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">transform.rotate_0</property>
|
||||
<property name="text" translatable="yes">Don't Rotate</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="rotate_90">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">transform.rotate_90</property>
|
||||
<property name="text" translatable="yes">Rotate 90°</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="rotate_180">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">transform.rotate_180</property>
|
||||
<property name="text" translatable="yes">Rotate 180°</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="rotate_270">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">transform.rotate_270</property>
|
||||
<property name="text" translatable="yes">Rotate 270°</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</template>
|
||||
</interface>
|
||||
|
@ -1,11 +1,17 @@
|
||||
# SPDX-FileCopyrightText: 2020 Jason Francis <jason@cycles.network>
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
resources_xml = configure_file(
|
||||
input: 'resources.xml.in',
|
||||
output: 'resources.xml',
|
||||
configuration: conf
|
||||
)
|
||||
|
||||
gnome = import('gnome')
|
||||
resources = gnome.compile_resources(
|
||||
'waydisplay-resources', 'resources.xml',
|
||||
'wdisplays-resources', resources_xml,
|
||||
source_dir : '.',
|
||||
c_name : 'waydisplay_resources')
|
||||
c_name : 'wdisplays_resources')
|
||||
|
||||
scour = find_program('scour', required: false)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/">
|
||||
<gresource prefix="@resource_prefix@">
|
||||
<file compressed="true" preprocess="xml-stripblanks">wdisplays.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">head.ui</file>
|
||||
<file compressed="true">style.css</file>
|
@ -11,62 +11,6 @@
|
||||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="GtkPopover" id="main_menu">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_start">10</property>
|
||||
<property name="margin_end">10</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">app.auto-apply</property>
|
||||
<property name="text" translatable="yes">_Automatically Apply Changes</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">app.capture-screens</property>
|
||||
<property name="text" translatable="yes">Show Screen Contents</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">app.show-overlay</property>
|
||||
<property name="text" translatable="yes">Overlay Screen Names</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkWindow" id="heads_window">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">wdisplays</property>
|
||||
@ -171,8 +115,6 @@
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="margin_start">8</property>
|
||||
<property name="margin_end">8</property>
|
||||
<property name="margin_top">8</property>
|
||||
@ -248,14 +190,14 @@
|
||||
<object class="GtkButtonBox" id="zoom_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="zoom_out">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Zoom Out</property>
|
||||
<signal name="clicked" handler="zoom_out" swapped="yes"/>
|
||||
<property name="action_name">app.zoom-out</property>
|
||||
<accelerator key="minus" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
@ -263,7 +205,6 @@
|
||||
<property name="icon_name">zoom-out-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="minus" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
@ -278,7 +219,7 @@
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Zoom Reset</property>
|
||||
<signal name="clicked" handler="zoom_reset" swapped="yes"/>
|
||||
<property name="action_name">app.zoom-reset</property>
|
||||
<accelerator key="0" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
@ -294,7 +235,8 @@
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Zoom In</property>
|
||||
<signal name="clicked" handler="zoom_in" swapped="yes"/>
|
||||
<property name="action_name">app.zoom-in</property>
|
||||
<accelerator key="equal" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
@ -302,7 +244,6 @@
|
||||
<property name="icon_name">zoom-in-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="equal" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
@ -311,6 +252,9 @@
|
||||
<property name="non_homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
@ -318,7 +262,6 @@
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="popover">main_menu</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
@ -355,7 +298,7 @@
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="clicked" handler="apply_changes" swapped="no"/>
|
||||
<property name="action_name">app.apply-changes</property>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
@ -371,7 +314,7 @@
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="clicked" handler="cancel_changes" swapped="no"/>
|
||||
<property name="action_name">app.cancel-changes</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
|
@ -6,5 +6,6 @@
|
||||
|
||||
#define WDISPLAYS_APP_ID "@app_id@"
|
||||
#define WDISPLAYS_VERSION "@version@"
|
||||
#define WDISPLAYS_RESOURCE_PREFIX "@resource_prefix@"
|
||||
|
||||
#endif
|
||||
|
393
src/headform.c
Normal file
393
src/headform.c
Normal file
@ -0,0 +1,393 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Jason Francis <jason@cycles.network>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
|
||||
#include "headform.h"
|
||||
#include "wdisplays.h"
|
||||
|
||||
typedef struct _WdHeadFormPrivate {
|
||||
GtkWidget *enabled;
|
||||
GtkWidget *description;
|
||||
GtkWidget *physical_size;
|
||||
GtkWidget *scale;
|
||||
GtkWidget *pos_x;
|
||||
GtkWidget *pos_y;
|
||||
GtkWidget *width;
|
||||
GtkWidget *height;
|
||||
GtkWidget *refresh;
|
||||
GtkWidget *mode_button;
|
||||
GtkWidget *rotate_button;
|
||||
GtkWidget *flipped;
|
||||
|
||||
GAction *mode_action;
|
||||
GAction *rotate_action;
|
||||
} WdHeadFormPrivate;
|
||||
|
||||
enum {
|
||||
CHANGED,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
static guint signals[LAST_SIGNAL];
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE(WdHeadForm, wd_head_form, GTK_TYPE_GRID,
|
||||
G_ADD_PRIVATE(WdHeadForm))
|
||||
|
||||
static const char *HEAD_PREFIX = "head";
|
||||
static const char *MODE_PREFIX = "mode";
|
||||
static const char *ROTATE_PREFIX = "rotate";
|
||||
|
||||
static void head_form_update_sensitivity(WdHeadForm *form) {
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
|
||||
bool enabled_toggled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->enabled));
|
||||
|
||||
g_autoptr(GList) children = gtk_container_get_children(GTK_CONTAINER(form));
|
||||
for (GList *child = children; child != NULL; child = child->next) {
|
||||
GtkWidget *widget = GTK_WIDGET(child->data);
|
||||
if (widget != priv->enabled) {
|
||||
gtk_widget_set_sensitive(widget, enabled_toggled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static GVariant *create_mode_variant(int32_t w, int32_t h, int32_t r) {
|
||||
GVariant * const children[] = {
|
||||
g_variant_new_int32(w),
|
||||
g_variant_new_int32(h),
|
||||
g_variant_new_int32(r),
|
||||
};
|
||||
return g_variant_new_tuple(children, G_N_ELEMENTS(children));
|
||||
}
|
||||
|
||||
struct vid_mode {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
int32_t refresh;
|
||||
};
|
||||
|
||||
static void unpack_mode_variant(GVariant *value, struct vid_mode *mode) {
|
||||
g_autoptr(GVariant) width = g_variant_get_child_value(value, 0);
|
||||
mode->width = g_variant_get_int32(width);
|
||||
g_autoptr(GVariant) height = g_variant_get_child_value(value, 1);
|
||||
mode->height = g_variant_get_int32(height);
|
||||
g_autoptr(GVariant) refresh = g_variant_get_child_value(value, 2);
|
||||
mode->refresh = g_variant_get_int32(refresh);
|
||||
}
|
||||
|
||||
static void enabled_toggled(GtkToggleButton *toggle, gpointer data) {
|
||||
WdHeadForm *form = WD_HEAD_FORM(data);
|
||||
head_form_update_sensitivity(form);
|
||||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_ENABLED);
|
||||
}
|
||||
|
||||
static void mode_spin_changed(GtkSpinButton *spin_button, gpointer data) {
|
||||
WdHeadForm *form = WD_HEAD_FORM(data);
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
struct vid_mode mode;
|
||||
GVariant *value = g_action_get_state(priv->mode_action);
|
||||
unpack_mode_variant(value, &mode);
|
||||
if (strcmp(gtk_widget_get_name(GTK_WIDGET(spin_button)), "width") == 0) {
|
||||
mode.width = gtk_spin_button_get_value(spin_button);
|
||||
} else if (strcmp(gtk_widget_get_name(GTK_WIDGET(spin_button)), "height") == 0) {
|
||||
mode.height = gtk_spin_button_get_value(spin_button);
|
||||
} else if (strcmp(gtk_widget_get_name(GTK_WIDGET(spin_button)), "refresh") == 0) {
|
||||
mode.refresh = gtk_spin_button_get_value(spin_button) * 1000.;
|
||||
}
|
||||
g_action_activate(priv->mode_action, create_mode_variant(mode.width, mode.height, mode.refresh));
|
||||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_MODE);
|
||||
}
|
||||
|
||||
static void position_spin_changed(GtkSpinButton *spin_button, gpointer data) {
|
||||
WdHeadForm *form = WD_HEAD_FORM(data);
|
||||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_POSITION);
|
||||
}
|
||||
|
||||
static void flipped_toggled(GtkToggleButton *toggle, gpointer data) {
|
||||
WdHeadForm *form = WD_HEAD_FORM(data);
|
||||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_TRANSFORM);
|
||||
}
|
||||
|
||||
static void wd_head_form_class_init(WdHeadFormClass *class) {
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(class);
|
||||
|
||||
signals[CHANGED] = g_signal_new("changed",
|
||||
G_OBJECT_CLASS_TYPE(class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
G_STRUCT_OFFSET(WdHeadFormClass, changed),
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 1, G_TYPE_INT);
|
||||
|
||||
gtk_widget_class_set_template_from_resource(widget_class,
|
||||
WDISPLAYS_RESOURCE_PREFIX "/head.ui");
|
||||
|
||||
gtk_widget_class_bind_template_callback(widget_class, enabled_toggled);
|
||||
gtk_widget_class_bind_template_callback(widget_class, mode_spin_changed);
|
||||
gtk_widget_class_bind_template_callback(widget_class, position_spin_changed);
|
||||
gtk_widget_class_bind_template_callback(widget_class, flipped_toggled);
|
||||
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, enabled);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, description);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, physical_size);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, scale);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, pos_x);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, pos_y);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, width);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, height);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, refresh);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, mode_button);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, rotate_button);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, flipped);
|
||||
gtk_widget_class_set_css_name(widget_class, "wd-head-form");
|
||||
}
|
||||
|
||||
static int32_t get_rotate_value(enum wl_output_transform transform) {
|
||||
if (transform == WL_OUTPUT_TRANSFORM_90 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_90) {
|
||||
return 90;
|
||||
} else if (transform == WL_OUTPUT_TRANSFORM_180 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_180) {
|
||||
return 180;
|
||||
} else if (transform == WL_OUTPUT_TRANSFORM_270 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_270) {
|
||||
return 270;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rotate_selected(GSimpleAction *action, GVariant *param, gpointer data) {
|
||||
WdHeadForm *form = data;
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
GMenuModel *menu = gtk_menu_button_get_menu_model(GTK_MENU_BUTTON(priv->rotate_button));
|
||||
int items = g_menu_model_get_n_items(menu);
|
||||
for (int i = 0; i < items; i++) {
|
||||
g_autoptr(GVariant) target = g_menu_model_get_item_attribute_value(menu, i, G_MENU_ATTRIBUTE_TARGET, NULL);
|
||||
g_autoptr(GVariant) label = g_menu_model_get_item_attribute_value(menu, i, G_MENU_ATTRIBUTE_LABEL, NULL);
|
||||
if (g_variant_get_int32(target) == g_variant_get_int32(param)) {
|
||||
gtk_button_set_label(GTK_BUTTON(priv->rotate_button), g_variant_get_string(label, NULL));
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_simple_action_set_state(action, param);
|
||||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_TRANSFORM);
|
||||
}
|
||||
|
||||
static void mode_selected(GSimpleAction *action, GVariant *param, gpointer data) {
|
||||
WdHeadForm *form = data;
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
struct vid_mode mode;
|
||||
unpack_mode_variant(param, &mode);
|
||||
|
||||
g_simple_action_set_state(action, param);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->width), mode.width);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->height), mode.height);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->refresh), mode.refresh / 1000.);
|
||||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_MODE);
|
||||
}
|
||||
|
||||
static void wd_head_form_init(WdHeadForm *form) {
|
||||
gtk_widget_init_template(GTK_WIDGET(form));
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
|
||||
GSimpleActionGroup *head_actions = g_simple_action_group_new();
|
||||
gtk_widget_insert_action_group(priv->mode_button, HEAD_PREFIX, G_ACTION_GROUP(head_actions));
|
||||
gtk_widget_insert_action_group(priv->rotate_button, HEAD_PREFIX, G_ACTION_GROUP(head_actions));
|
||||
|
||||
GMenu *rotate_menu = g_menu_new();
|
||||
g_menu_append(rotate_menu, "Don't Rotate", "head.rotate(0)");
|
||||
g_menu_append(rotate_menu, "Rotate 90°", "head.rotate(90)");
|
||||
g_menu_append(rotate_menu, "Rotate 180°", "head.rotate(180)");
|
||||
g_menu_append(rotate_menu, "Rotate 270°", "head.rotate(270)");
|
||||
gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->rotate_button), G_MENU_MODEL(rotate_menu));
|
||||
|
||||
static const GVariantType * const mode_types[] = {
|
||||
G_VARIANT_TYPE_INT32,
|
||||
G_VARIANT_TYPE_INT32,
|
||||
G_VARIANT_TYPE_INT32
|
||||
};
|
||||
GSimpleAction *action = g_simple_action_new_stateful("mode",
|
||||
g_variant_type_new_tuple(mode_types, G_N_ELEMENTS(mode_types)),
|
||||
create_mode_variant(0, 0, 0));
|
||||
g_action_map_add_action(G_ACTION_MAP(head_actions), G_ACTION(action));
|
||||
g_signal_connect(action, "change-state", G_CALLBACK(mode_selected), form);
|
||||
g_object_unref(action);
|
||||
priv->mode_action = G_ACTION(action);
|
||||
|
||||
action = g_simple_action_new_stateful(ROTATE_PREFIX, G_VARIANT_TYPE_INT32,
|
||||
g_variant_new_int32(0));
|
||||
g_action_map_add_action(G_ACTION_MAP(head_actions), G_ACTION(action));
|
||||
g_signal_connect(action, "change-state", G_CALLBACK(rotate_selected), form);
|
||||
g_object_unref(action);
|
||||
priv->rotate_action = G_ACTION(action);
|
||||
|
||||
g_object_unref(head_actions);
|
||||
}
|
||||
|
||||
void wd_head_form_update(WdHeadForm *form, const struct wd_head *head,
|
||||
enum wd_head_fields fields) {
|
||||
g_return_if_fail(form);
|
||||
g_return_if_fail(head);
|
||||
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
if (!fields)
|
||||
return;
|
||||
|
||||
if (fields & WD_FIELD_DESCRIPTION)
|
||||
gtk_label_set_text(GTK_LABEL(priv->description), head->description);
|
||||
if (fields & WD_FIELD_PHYSICAL_SIZE) {
|
||||
g_autofree gchar *physical_str = g_strdup_printf("%dmm × %dmm", head->phys_width, head->phys_height);
|
||||
gtk_label_set_text(GTK_LABEL(priv->physical_size), physical_str);
|
||||
}
|
||||
if (fields & WD_FIELD_ENABLED)
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->enabled), head->enabled);
|
||||
if (fields & WD_FIELD_SCALE)
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->scale), head->scale);
|
||||
if (fields & WD_FIELD_POSITION) {
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->pos_x), head->x);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->pos_y), head->y);
|
||||
}
|
||||
|
||||
if (fields & WD_FIELD_MODE) {
|
||||
GMenu *mode_menu = g_menu_new();
|
||||
struct wd_mode *mode;
|
||||
g_autofree gchar *action = g_strdup_printf("%s.%s", HEAD_PREFIX, MODE_PREFIX);
|
||||
wl_list_for_each(mode, &head->modes, link) {
|
||||
g_autofree gchar *name = g_strdup_printf("%d×%d@%0.3fHz", mode->width, mode->height, mode->refresh / 1000.);
|
||||
GMenuItem *item = g_menu_item_new(name, action);
|
||||
g_menu_item_set_attribute_value(item, G_MENU_ATTRIBUTE_TARGET,
|
||||
create_mode_variant(mode->width, mode->height, mode->refresh));
|
||||
g_menu_append_item(mode_menu, item);
|
||||
}
|
||||
gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->mode_button), G_MENU_MODEL(mode_menu));
|
||||
// Mode entries
|
||||
int w = head->custom_mode.width;
|
||||
int h = head->custom_mode.height;
|
||||
int r = head->custom_mode.refresh;
|
||||
if (head->enabled && head->mode != NULL) {
|
||||
w = head->mode->width;
|
||||
h = head->mode->height;
|
||||
r = head->mode->refresh;
|
||||
} else if (!head->enabled && w == 0 && h == 0) {
|
||||
struct wd_mode *mode;
|
||||
wl_list_for_each(mode, &head->modes, link) {
|
||||
if (mode->preferred) {
|
||||
w = mode->width;
|
||||
h = mode->height;
|
||||
r = mode->refresh;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_action_change_state(priv->mode_action, create_mode_variant(w, h, r));
|
||||
}
|
||||
|
||||
if (fields & WD_FIELD_TRANSFORM) {
|
||||
int active_rotate = get_rotate_value(head->transform);
|
||||
g_action_change_state(priv->rotate_action, g_variant_new_int32(active_rotate));
|
||||
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->flipped),
|
||||
head->transform == WL_OUTPUT_TRANSFORM_FLIPPED
|
||||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_90
|
||||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_180
|
||||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_270);
|
||||
}
|
||||
|
||||
// Sync state
|
||||
if (fields & WD_FIELD_ENABLED) {
|
||||
head_form_update_sensitivity(form);
|
||||
}
|
||||
g_signal_emit(form, signals[CHANGED], 0);
|
||||
}
|
||||
|
||||
GtkWidget *wd_head_form_new(void) {
|
||||
return gtk_widget_new(WD_TYPE_HEAD_FORM, NULL);
|
||||
}
|
||||
|
||||
gboolean wd_head_form_get_enabled(WdHeadForm *form) {
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->enabled));
|
||||
}
|
||||
|
||||
gboolean wd_head_form_has_changes(WdHeadForm *form, const struct wd_head *head) {
|
||||
g_return_val_if_fail(form, FALSE);
|
||||
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
if (head->enabled != gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->enabled))) {
|
||||
return TRUE;
|
||||
}
|
||||
double old_scale = round(head->scale * 100.) / 100.;
|
||||
double new_scale = round(gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->scale)) * 100.) / 100.;
|
||||
if (old_scale != new_scale) {
|
||||
return TRUE;
|
||||
}
|
||||
if (head->x != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_x))) {
|
||||
return TRUE;
|
||||
}
|
||||
if (head->y != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_y))) {
|
||||
return TRUE;
|
||||
}
|
||||
int w = head->mode != NULL ? head->mode->width : head->custom_mode.width;
|
||||
if (w != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->width))) {
|
||||
return TRUE;
|
||||
}
|
||||
int h = head->mode != NULL ? head->mode->height : head->custom_mode.height;
|
||||
if (h != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->height))) {
|
||||
return TRUE;
|
||||
}
|
||||
int r = head->mode != NULL ? head->mode->refresh : head->custom_mode.refresh;
|
||||
if (r / 1000. != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->refresh))) {
|
||||
return TRUE;
|
||||
}
|
||||
if (g_variant_get_int32(g_action_get_state(priv->rotate_action)) != get_rotate_value(head->transform)) {
|
||||
return TRUE;
|
||||
}
|
||||
bool flipped = head->transform == WL_OUTPUT_TRANSFORM_FLIPPED
|
||||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_90
|
||||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_180
|
||||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_270;
|
||||
if (flipped != gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->flipped))) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void wd_head_form_fill_config(WdHeadForm *form, struct wd_head_config *output) {
|
||||
g_return_if_fail(form);
|
||||
g_return_if_fail(output);
|
||||
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
output->enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->enabled));
|
||||
output->scale = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->scale));
|
||||
output->x = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_x));
|
||||
output->y = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_y));
|
||||
output->width = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->width));
|
||||
output->height = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->height));
|
||||
output->refresh = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->refresh)) * 1000.;
|
||||
int32_t rotate = g_variant_get_int32(g_action_get_state(priv->rotate_action));
|
||||
gboolean flipped = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->flipped));
|
||||
switch (rotate) {
|
||||
case 0: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED : WL_OUTPUT_TRANSFORM_NORMAL; break;
|
||||
case 90: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_90 : WL_OUTPUT_TRANSFORM_90; break;
|
||||
case 180: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_180 : WL_OUTPUT_TRANSFORM_180; break;
|
||||
case 270: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_270 : WL_OUTPUT_TRANSFORM_270; break;
|
||||
}
|
||||
}
|
||||
|
||||
void wd_head_form_get_dimensions(WdHeadForm *form, WdHeadDimensions *dimensions) {
|
||||
g_return_if_fail(form);
|
||||
g_return_if_fail(dimensions);
|
||||
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
|
||||
dimensions->x = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_x));
|
||||
dimensions->y = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_y));
|
||||
dimensions->w = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->width));
|
||||
dimensions->h = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->height));
|
||||
dimensions->scale = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->scale));
|
||||
dimensions->rotation_id = g_variant_get_int32(g_action_get_state(priv->rotate_action)) / 90;
|
||||
dimensions->flipped = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->flipped));
|
||||
}
|
||||
|
||||
void wd_head_form_set_position(WdHeadForm *form, double x, double y) {
|
||||
g_return_if_fail(form);
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->pos_x), x);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->pos_y), y);
|
||||
}
|
59
src/headform.h
Normal file
59
src/headform.h
Normal file
@ -0,0 +1,59 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Jason Francis <jason@cycles.network>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
|
||||
#ifndef WDISPLAY_HEADFORM_H
|
||||
#define WDISPLAY_HEADFORM_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
enum wd_head_fields {
|
||||
WD_FIELD_NAME = 1 << 0,
|
||||
WD_FIELD_ENABLED = 1 << 1,
|
||||
WD_FIELD_DESCRIPTION = 1 << 2,
|
||||
WD_FIELD_PHYSICAL_SIZE = 1 << 3,
|
||||
WD_FIELD_SCALE = 1 << 4,
|
||||
WD_FIELD_POSITION = 1 << 5,
|
||||
WD_FIELD_MODE = 1 << 6,
|
||||
WD_FIELD_TRANSFORM = 1 << 7,
|
||||
WD_FIELDS_ALL = (1 << 8) - 1
|
||||
};
|
||||
|
||||
#define WD_TYPE_HEAD_FORM (wd_head_form_get_type())
|
||||
G_DECLARE_DERIVABLE_TYPE(
|
||||
WdHeadForm, wd_head_form, WD, HEAD_FORM, GtkGrid)
|
||||
|
||||
struct _WdHeadFormClass {
|
||||
GtkGridClass parent_class;
|
||||
|
||||
void (*changed)(WdHeadForm *form, enum wd_head_fields fields);
|
||||
};
|
||||
|
||||
struct wd_head;
|
||||
struct wd_head_config;
|
||||
|
||||
typedef struct _WdHeadDimensions {
|
||||
gdouble x;
|
||||
gdouble y;
|
||||
gdouble w;
|
||||
gdouble h;
|
||||
gdouble scale;
|
||||
int rotation_id;
|
||||
gboolean flipped;
|
||||
} WdHeadDimensions;
|
||||
|
||||
GtkWidget *wd_head_form_new(void);
|
||||
|
||||
gboolean wd_head_form_get_enabled(WdHeadForm *form);
|
||||
gboolean wd_head_form_has_changes(WdHeadForm *form, const struct wd_head *head);
|
||||
void wd_head_form_update(WdHeadForm *form, const struct wd_head *head,
|
||||
enum wd_head_fields fields);
|
||||
void wd_head_form_fill_config(WdHeadForm *form, struct wd_head_config *output);
|
||||
void wd_head_form_get_dimensions(WdHeadForm *form, WdHeadDimensions *dimensions);
|
||||
void wd_head_form_set_position(WdHeadForm *form, double x, double y);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
960
src/main.c
960
src/main.c
File diff suppressed because it is too large
Load Diff
@ -4,10 +4,11 @@
|
||||
cc = meson.get_compiler('c')
|
||||
m_dep = cc.find_library('m', required : false)
|
||||
rt_dep = cc.find_library('rt', required : false)
|
||||
gdk = dependency('gdk-3.0')
|
||||
gtk = dependency('gtk+-3.0')
|
||||
gdk = dependency('gdk-3.0', version: '>= 3.24')
|
||||
gtk = dependency('gtk+-3.0', version: '>= 3.24')
|
||||
assert(gdk.get_pkgconfig_variable('targets').split().contains('wayland'), 'Wayland GDK backend not present')
|
||||
epoxy = dependency('epoxy')
|
||||
libdrm = dependency('libdrm')
|
||||
|
||||
configure_file(input: 'config.h.in', output: 'config.h', configuration: conf)
|
||||
|
||||
@ -15,10 +16,11 @@ executable(
|
||||
'wdisplays',
|
||||
[
|
||||
'main.c',
|
||||
'outputs.c',
|
||||
'render.c',
|
||||
'glviewport.c',
|
||||
'headform.c',
|
||||
'outputs.c',
|
||||
'overlay.c',
|
||||
'render.c',
|
||||
resources,
|
||||
],
|
||||
dependencies : [
|
||||
@ -27,6 +29,7 @@ executable(
|
||||
wayland_client,
|
||||
client_protos,
|
||||
epoxy,
|
||||
libdrm,
|
||||
gtk
|
||||
],
|
||||
install: true
|
||||
|
194
src/outputs.c
194
src/outputs.c
@ -25,6 +25,8 @@
|
||||
|
||||
#include "wlr-output-management-unstable-v1-client-protocol.h"
|
||||
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||
#include "linux-dmabuf-unstable-v1-client-protocol.h"
|
||||
#include "wlr-export-dmabuf-unstable-v1-client-protocol.h"
|
||||
#include "wlr-screencopy-unstable-v1-client-protocol.h"
|
||||
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
|
||||
|
||||
@ -143,15 +145,18 @@ void wd_apply_state(struct wd_state *state, struct wl_list *new_outputs,
|
||||
|
||||
static void wd_frame_destroy(struct wd_frame *frame) {
|
||||
if (frame->pixels != NULL)
|
||||
munmap(frame->pixels, frame->height * frame->stride);
|
||||
munmap(frame->pixels, frame->height * frame->strides[0]);
|
||||
if (frame->buffer != NULL)
|
||||
wl_buffer_destroy(frame->buffer);
|
||||
if (frame->pool != NULL)
|
||||
wl_shm_pool_destroy(frame->pool);
|
||||
if (frame->capture_fd != -1)
|
||||
close(frame->capture_fd);
|
||||
if (frame->wlr_frame != NULL)
|
||||
zwlr_screencopy_frame_v1_destroy(frame->wlr_frame);
|
||||
for (int i = 0; i < frame->n_planes; i++)
|
||||
if (frame->fds[i])
|
||||
close(frame->fds[i]);
|
||||
if (frame->copy_frame != NULL)
|
||||
zwlr_screencopy_frame_v1_destroy(frame->copy_frame);
|
||||
if (frame->export_frame != NULL)
|
||||
zwlr_export_dmabuf_frame_v1_destroy(frame->export_frame);
|
||||
|
||||
wl_list_remove(&frame->link);
|
||||
free(frame);
|
||||
@ -189,6 +194,112 @@ static int create_shm_file(size_t size, const char *fmt, ...) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
static void make_frame_current(struct wd_frame *frame,
|
||||
uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
|
||||
uint64_t tv_sec = (uint64_t) tv_sec_hi << 32 | tv_sec_lo;
|
||||
frame->tick = (tv_sec * 1000000) + (tv_nsec / 1000);
|
||||
|
||||
struct wd_frame *frame_iter, *frame_tmp;
|
||||
wl_list_for_each_safe(frame_iter, frame_tmp, &frame->output->frames, link) {
|
||||
if (frame != frame_iter) {
|
||||
wd_frame_destroy(frame_iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define WD_FRAME_COPY 0x100
|
||||
|
||||
static void export_frame(void *data,
|
||||
struct zwlr_export_dmabuf_frame_v1 *wlr_frame,
|
||||
uint32_t width, uint32_t height, uint32_t offset_x, uint32_t offset_y,
|
||||
uint32_t buffer_flags, uint32_t flags, uint32_t format,
|
||||
uint32_t mod_high, uint32_t mod_low, uint32_t num_objects) {
|
||||
struct wd_frame *frame = data;
|
||||
frame->width = width;
|
||||
frame->height = height;
|
||||
frame->format = format;
|
||||
frame->flags = buffer_flags;
|
||||
/*
|
||||
if (flags & ZWLR_EXPORT_DMABUF_FRAME_V1_FLAGS_TRANSIENT) {
|
||||
frame->flags |= WD_FRAME_COPY;
|
||||
}
|
||||
*/
|
||||
frame->modifier = ((uint64_t) mod_high << 32) | mod_low;
|
||||
frame->n_planes = num_objects;
|
||||
}
|
||||
|
||||
static void export_object(void *data,
|
||||
struct zwlr_export_dmabuf_frame_v1 *wlr_frame,
|
||||
uint32_t index, int32_t fd, uint32_t size, uint32_t offset,
|
||||
uint32_t stride, uint32_t plane_index) {
|
||||
struct wd_frame *frame = data;
|
||||
|
||||
if (frame->flags & WD_FRAME_COPY) {
|
||||
void *src = NULL;
|
||||
void *dest = NULL;
|
||||
int wfd = create_shm_file(size, "/wd-%s", frame->output->name);
|
||||
if (wfd == -1) {
|
||||
goto done;
|
||||
}
|
||||
src = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, offset);
|
||||
if (src == MAP_FAILED) {
|
||||
fprintf(stderr, "mmap: %d: %s\n", fd, strerror(errno));
|
||||
close(wfd);
|
||||
wfd = -1;
|
||||
goto done;
|
||||
}
|
||||
dest = mmap(NULL, size - offset, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE, wfd, 0);
|
||||
if (dest == MAP_FAILED) {
|
||||
fprintf(stderr, "mmap: %d: %s\n", wfd, strerror(errno));
|
||||
close(wfd);
|
||||
wfd = -1;
|
||||
goto done;
|
||||
}
|
||||
memcpy(dest, src, size - offset);
|
||||
done:
|
||||
if (src)
|
||||
munmap(src, size);
|
||||
if (dest)
|
||||
munmap(dest, size - offset);
|
||||
close(fd);
|
||||
frame->fds[plane_index] = wfd;
|
||||
} else {
|
||||
frame->fds[plane_index] = fd;
|
||||
frame->offsets[plane_index] = offset;
|
||||
}
|
||||
frame->strides[plane_index] = stride;
|
||||
}
|
||||
|
||||
static void export_ready(void *data,
|
||||
struct zwlr_export_dmabuf_frame_v1 *wlr_frame,
|
||||
uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
|
||||
struct wd_frame *frame = data;
|
||||
|
||||
make_frame_current(frame, tv_sec_hi, tv_sec_lo, tv_nsec);
|
||||
|
||||
if (frame->flags & WD_FRAME_COPY) {
|
||||
frame->flags ^= WD_FRAME_COPY;
|
||||
zwlr_export_dmabuf_frame_v1_destroy(frame->export_frame);
|
||||
frame->export_frame = NULL;
|
||||
}
|
||||
frame->ready = true;
|
||||
}
|
||||
|
||||
static void export_cancel(void *data,
|
||||
struct zwlr_export_dmabuf_frame_v1 *wlr_frame,
|
||||
uint32_t reason) {
|
||||
struct wd_frame *frame = data;
|
||||
wd_frame_destroy(frame);
|
||||
}
|
||||
|
||||
static struct zwlr_export_dmabuf_frame_v1_listener export_listener = {
|
||||
.frame = export_frame,
|
||||
.object = export_object,
|
||||
.ready = export_ready,
|
||||
.cancel = export_cancel
|
||||
};
|
||||
|
||||
static void capture_buffer(void *data,
|
||||
struct zwlr_screencopy_frame_v1 *copy_frame,
|
||||
uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
|
||||
@ -199,22 +310,23 @@ static void capture_buffer(void *data,
|
||||
goto err;
|
||||
}
|
||||
|
||||
frame->n_planes = 1;
|
||||
size_t size = stride * height;
|
||||
frame->capture_fd = create_shm_file(size, "/wd-%s", frame->output->name);
|
||||
if (frame->capture_fd == -1) {
|
||||
frame->fds[0] = create_shm_file(size, "/wd-%s", frame->output->name);
|
||||
if (frame->fds[0] == -1) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
frame->pool = wl_shm_create_pool(frame->output->state->shm,
|
||||
frame->capture_fd, size);
|
||||
frame->fds[0], size);
|
||||
frame->buffer = wl_shm_pool_create_buffer(frame->pool, 0,
|
||||
width, height, stride, format);
|
||||
zwlr_screencopy_frame_v1_copy(copy_frame, frame->buffer);
|
||||
frame->stride = stride;
|
||||
frame->offsets[0] = 0;
|
||||
frame->strides[0] = stride;
|
||||
frame->width = width;
|
||||
frame->height = height;
|
||||
frame->swap_rgb = format == WL_SHM_FORMAT_ABGR8888
|
||||
|| format == WL_SHM_FORMAT_XBGR8888;
|
||||
frame->format = format;
|
||||
|
||||
return;
|
||||
err:
|
||||
@ -225,7 +337,9 @@ static void capture_flags(void *data,
|
||||
struct zwlr_screencopy_frame_v1 *wlr_frame,
|
||||
uint32_t flags) {
|
||||
struct wd_frame *frame = data;
|
||||
frame->y_invert = !!(flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT);
|
||||
if (flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT) {
|
||||
frame->flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT;
|
||||
}
|
||||
}
|
||||
|
||||
static void capture_ready(void *data,
|
||||
@ -233,27 +347,20 @@ static void capture_ready(void *data,
|
||||
uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
|
||||
struct wd_frame *frame = data;
|
||||
|
||||
frame->pixels = mmap(NULL, frame->stride * frame->height,
|
||||
PROT_READ, MAP_SHARED, frame->capture_fd, 0);
|
||||
frame->pixels = mmap(NULL, frame->strides[0] * frame->height,
|
||||
PROT_READ, MAP_SHARED, frame->fds[0], 0);
|
||||
if (frame->pixels == MAP_FAILED) {
|
||||
frame->pixels = NULL;
|
||||
fprintf(stderr, "mmap: %d: %s\n", frame->capture_fd, strerror(errno));
|
||||
fprintf(stderr, "mmap: %d: %s\n", frame->fds[0], strerror(errno));
|
||||
wd_frame_destroy(frame);
|
||||
return;
|
||||
} else {
|
||||
uint64_t tv_sec = (uint64_t) tv_sec_hi << 32 | tv_sec_lo;
|
||||
frame->tick = (tv_sec * 1000000) + (tv_nsec / 1000);
|
||||
}
|
||||
|
||||
make_frame_current(frame, tv_sec_hi, tv_sec_lo, tv_nsec);
|
||||
|
||||
zwlr_screencopy_frame_v1_destroy(frame->wlr_frame);
|
||||
frame->wlr_frame = NULL;
|
||||
|
||||
struct wd_frame *frame_iter, *frame_tmp;
|
||||
wl_list_for_each_safe(frame_iter, frame_tmp, &frame->output->frames, link) {
|
||||
if (frame != frame_iter) {
|
||||
wd_frame_destroy(frame_iter);
|
||||
}
|
||||
}
|
||||
zwlr_screencopy_frame_v1_destroy(frame->copy_frame);
|
||||
frame->copy_frame = NULL;
|
||||
frame->ready = true;
|
||||
}
|
||||
|
||||
static void capture_failed(void *data,
|
||||
@ -274,7 +381,7 @@ static bool has_pending_captures(struct wd_state *state) {
|
||||
wl_list_for_each(output, &state->outputs, link) {
|
||||
struct wd_frame *frame;
|
||||
wl_list_for_each(frame, &output->frames, link) {
|
||||
if (frame->pixels == NULL) {
|
||||
if (!frame->ready) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -283,8 +390,8 @@ static bool has_pending_captures(struct wd_state *state) {
|
||||
}
|
||||
|
||||
void wd_capture_frame(struct wd_state *state) {
|
||||
if (state->copy_manager == NULL || has_pending_captures(state)
|
||||
|| !state->capture) {
|
||||
if ((state->export_manager == NULL && state->copy_manager == NULL)
|
||||
|| has_pending_captures(state) || !state->capture) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -292,12 +399,21 @@ void wd_capture_frame(struct wd_state *state) {
|
||||
wl_list_for_each(output, &state->outputs, link) {
|
||||
struct wd_frame *frame = calloc(1, sizeof(*frame));
|
||||
frame->output = output;
|
||||
frame->capture_fd = -1;
|
||||
frame->wlr_frame =
|
||||
zwlr_screencopy_manager_v1_capture_output(state->copy_manager, 1,
|
||||
output->wl_output);
|
||||
zwlr_screencopy_frame_v1_add_listener(frame->wlr_frame, &capture_listener,
|
||||
frame);
|
||||
for (int i = 0; i < WD_MAX_PLANES; i++)
|
||||
frame->fds[i] = -1;
|
||||
if (state->export_manager) {
|
||||
frame->export_frame =
|
||||
zwlr_export_dmabuf_manager_v1_capture_output(state->export_manager, 1,
|
||||
output->wl_output);
|
||||
zwlr_export_dmabuf_frame_v1_add_listener(frame->export_frame,
|
||||
&export_listener, frame);
|
||||
} else if (state->copy_manager) {
|
||||
frame->copy_frame =
|
||||
zwlr_screencopy_manager_v1_capture_output(state->copy_manager, 1,
|
||||
output->wl_output);
|
||||
zwlr_screencopy_frame_v1_add_listener(frame->copy_frame,
|
||||
&capture_listener, frame);
|
||||
}
|
||||
wl_list_insert(&output->frames, &frame->link);
|
||||
}
|
||||
}
|
||||
@ -540,6 +656,9 @@ static void registry_handle_global(void *data, struct wl_registry *registry,
|
||||
} else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
|
||||
state->xdg_output_manager = wl_registry_bind(registry, name,
|
||||
&zxdg_output_manager_v1_interface, version);
|
||||
} else if(strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name) == 0) {
|
||||
state->export_manager = wl_registry_bind(registry, name,
|
||||
&zwlr_export_dmabuf_manager_v1_interface, version);
|
||||
} else if(strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) {
|
||||
state->copy_manager = wl_registry_bind(registry, name,
|
||||
&zwlr_screencopy_manager_v1_interface, version);
|
||||
@ -689,6 +808,9 @@ void wd_state_destroy(struct wd_state *state) {
|
||||
if (state->layer_shell != NULL) {
|
||||
zwlr_layer_shell_v1_destroy(state->layer_shell);
|
||||
}
|
||||
if (state->export_manager != NULL) {
|
||||
zwlr_export_dmabuf_manager_v1_destroy(state->export_manager);
|
||||
}
|
||||
if (state->copy_manager != NULL) {
|
||||
zwlr_screencopy_manager_v1_destroy(state->copy_manager);
|
||||
}
|
||||
|
233
src/render.c
233
src/render.c
@ -1,13 +1,24 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Jason Francis <jason@cycles.network>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
/* SPDX-FileCopyrightText: 2018 Simon Ser
|
||||
* SPDX-FileCopyrightText: 2018 Guido Günther
|
||||
* SPDX-License-Identifier: MIT */
|
||||
/* Parts of this file are taken from swaywm/wlroots:
|
||||
* https://github.com/swaywm/wlroots/blob/e97c2c3639119831ced4f6b9f704b096c2075973/render/egl.c
|
||||
*/
|
||||
|
||||
#include "wdisplays.h"
|
||||
|
||||
#define WL_EGL_PLATFORM 1
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <epoxy/gl.h>
|
||||
#include <wayland-util.h>
|
||||
#include <epoxy/gl.h>
|
||||
#include <epoxy/egl.h>
|
||||
#include <drm_fourcc.h>
|
||||
|
||||
#define BT_UV_VERT_SIZE (2 + 2)
|
||||
#define BT_UV_QUAD_SIZE (6 * BT_UV_VERT_SIZE)
|
||||
@ -29,22 +40,25 @@ enum gl_buffers {
|
||||
NUM_BUFFERS
|
||||
};
|
||||
|
||||
struct wd_gl_texture_program {
|
||||
GLuint id;
|
||||
GLuint position_attribute;
|
||||
GLuint uv_attribute;
|
||||
GLuint screen_size_uniform;
|
||||
GLuint texture_uniform;
|
||||
GLuint color_transform_uniform;
|
||||
GLuint color_add_uniform;
|
||||
};
|
||||
|
||||
struct wd_gl_data {
|
||||
EGLDisplay display;
|
||||
|
||||
GLuint color_program;
|
||||
GLuint color_vertex_shader;
|
||||
GLuint color_fragment_shader;
|
||||
GLuint color_position_attribute;
|
||||
GLuint color_color_attribute;
|
||||
GLuint color_screen_size_uniform;
|
||||
|
||||
GLuint texture_program;
|
||||
GLuint texture_vertex_shader;
|
||||
GLuint texture_fragment_shader;
|
||||
GLuint texture_position_attribute;
|
||||
GLuint texture_uv_attribute;
|
||||
GLuint texture_screen_size_uniform;
|
||||
GLuint texture_texture_uniform;
|
||||
GLuint texture_color_transform_uniform;
|
||||
struct wd_gl_texture_program rgb;
|
||||
|
||||
GLuint buffers[NUM_BUFFERS];
|
||||
|
||||
@ -90,8 +104,9 @@ precision mediump float;\n\
|
||||
varying vec2 uv_out;\n\
|
||||
uniform sampler2D texture;\n\
|
||||
uniform mat4 color_transform;\n\
|
||||
uniform vec4 color_add;\n\
|
||||
void main(void) {\n\
|
||||
gl_FragColor = texture2D(texture, uv_out) * color_transform;\n\
|
||||
gl_FragColor = texture2D(texture, uv_out) * color_transform + color_add;\n\
|
||||
}";
|
||||
|
||||
static GLuint gl_make_shader(GLenum type, const char *src) {
|
||||
@ -142,18 +157,62 @@ static void gl_link_and_validate(GLint program) {
|
||||
}
|
||||
}
|
||||
|
||||
struct wd_gl_data *wd_gl_setup(void) {
|
||||
static void setup_texture_program(struct wd_gl_texture_program *prog,
|
||||
const char *fragment_src) {
|
||||
prog->id = glCreateProgram();
|
||||
|
||||
GLuint vertex_shader = gl_make_shader(GL_VERTEX_SHADER,
|
||||
texture_vertex_shader_src);
|
||||
glAttachShader(prog->id, vertex_shader);
|
||||
GLuint fragment_shader = gl_make_shader(GL_FRAGMENT_SHADER, fragment_src);
|
||||
glAttachShader(prog->id, fragment_shader);
|
||||
gl_link_and_validate(prog->id);
|
||||
|
||||
glDeleteShader(fragment_shader);
|
||||
glDeleteShader(vertex_shader);
|
||||
|
||||
prog->position_attribute = glGetAttribLocation(prog->id, "position");
|
||||
prog->uv_attribute = glGetAttribLocation(prog->id, "uv");
|
||||
prog->screen_size_uniform = glGetUniformLocation(prog->id, "screen_size");
|
||||
prog->texture_uniform = glGetUniformLocation(prog->id, "texture");
|
||||
prog->color_transform_uniform = glGetUniformLocation(prog->id,
|
||||
"color_transform");
|
||||
prog->color_add_uniform = glGetUniformLocation(prog->id, "color_add");
|
||||
}
|
||||
|
||||
#define assert_ext(_name, _expr) do { \
|
||||
static bool _tested_##_name = false; \
|
||||
if (!_tested_##_name) { \
|
||||
_tested_##_name = true; \
|
||||
if (!(_expr)) \
|
||||
wd_fatal_error(1, "Extension " #_name " not found\n"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define assert_gl_ext(_name) \
|
||||
assert_ext(_name, epoxy_has_gl_extension(#_name))
|
||||
|
||||
#define assert_egl_ext(_res, _name) \
|
||||
assert_ext(_name, epoxy_has_egl_extension((_res)->display, #_name))
|
||||
|
||||
struct wd_gl_data *wd_gl_setup(struct wl_display *display) {
|
||||
struct wd_gl_data *res = calloc(1, sizeof(struct wd_gl_data));
|
||||
res->display = eglGetDisplay(display);
|
||||
res->color_program = glCreateProgram();
|
||||
|
||||
res->color_vertex_shader = gl_make_shader(GL_VERTEX_SHADER,
|
||||
GLuint vertex_shader = gl_make_shader(GL_VERTEX_SHADER,
|
||||
color_vertex_shader_src);
|
||||
glAttachShader(res->color_program, res->color_vertex_shader);
|
||||
res->color_fragment_shader = gl_make_shader(GL_FRAGMENT_SHADER,
|
||||
glAttachShader(res->color_program, vertex_shader);
|
||||
GLuint fragment_shader = gl_make_shader(GL_FRAGMENT_SHADER,
|
||||
color_fragment_shader_src);
|
||||
glAttachShader(res->color_program, res->color_fragment_shader);
|
||||
glAttachShader(res->color_program, fragment_shader);
|
||||
gl_link_and_validate(res->color_program);
|
||||
|
||||
glDeleteShader(fragment_shader);
|
||||
glDeleteShader(vertex_shader);
|
||||
|
||||
setup_texture_program(&res->rgb, texture_fragment_shader_src);
|
||||
|
||||
res->color_position_attribute = glGetAttribLocation(res->color_program,
|
||||
"position");
|
||||
res->color_color_attribute = glGetAttribLocation(res->color_program,
|
||||
@ -161,27 +220,6 @@ struct wd_gl_data *wd_gl_setup(void) {
|
||||
res->color_screen_size_uniform = glGetUniformLocation(res->color_program,
|
||||
"screen_size");
|
||||
|
||||
res->texture_program = glCreateProgram();
|
||||
|
||||
res->texture_vertex_shader = gl_make_shader(GL_VERTEX_SHADER,
|
||||
texture_vertex_shader_src);
|
||||
glAttachShader(res->texture_program, res->texture_vertex_shader);
|
||||
res->texture_fragment_shader = gl_make_shader(GL_FRAGMENT_SHADER,
|
||||
texture_fragment_shader_src);
|
||||
glAttachShader(res->texture_program, res->texture_fragment_shader);
|
||||
gl_link_and_validate(res->texture_program);
|
||||
|
||||
res->texture_position_attribute = glGetAttribLocation(res->texture_program,
|
||||
"position");
|
||||
res->texture_uv_attribute = glGetAttribLocation(res->texture_program,
|
||||
"uv");
|
||||
res->texture_screen_size_uniform = glGetUniformLocation(res->texture_program,
|
||||
"screen_size");
|
||||
res->texture_texture_uniform = glGetUniformLocation(res->texture_program,
|
||||
"texture");
|
||||
res->texture_color_transform_uniform = glGetUniformLocation(
|
||||
res->texture_program, "color_transform");
|
||||
|
||||
glGenBuffers(NUM_BUFFERS, res->buffers);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, res->buffers[TEXTURE_BUFFER]);
|
||||
glBufferData(GL_ARRAY_BUFFER, BT_UV_MAX * sizeof(float),
|
||||
@ -198,6 +236,83 @@ struct wd_gl_data *wd_gl_setup(void) {
|
||||
return res;
|
||||
}
|
||||
|
||||
EGLImageKHR wd_gl_create_dmabuf_texture(struct wd_gl_data *res,
|
||||
struct wd_frame *frame) {
|
||||
assert(!frame->pixels);
|
||||
assert_egl_ext(res, EGL_KHR_image_base);
|
||||
assert_egl_ext(res, EGL_EXT_image_dma_buf_import);
|
||||
|
||||
bool has_modifier = frame->modifier != DRM_FORMAT_MOD_INVALID
|
||||
&& frame->modifier != DRM_FORMAT_MOD_LINEAR;
|
||||
|
||||
if (has_modifier) {
|
||||
assert_egl_ext(res, EGL_EXT_image_dma_buf_import_modifiers);
|
||||
}
|
||||
|
||||
unsigned int atti = 0;
|
||||
EGLint attribs[50];
|
||||
attribs[atti++] = EGL_WIDTH;
|
||||
attribs[atti++] = frame->width;
|
||||
attribs[atti++] = EGL_HEIGHT;
|
||||
attribs[atti++] = frame->height;
|
||||
attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT;
|
||||
attribs[atti++] = frame->format;
|
||||
|
||||
struct {
|
||||
EGLint fd;
|
||||
EGLint offset;
|
||||
EGLint pitch;
|
||||
EGLint mod_lo;
|
||||
EGLint mod_hi;
|
||||
} attr_names[WD_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 < frame->n_planes; i++) {
|
||||
attribs[atti++] = attr_names[i].fd;
|
||||
attribs[atti++] = frame->fds[i];
|
||||
attribs[atti++] = attr_names[i].offset;
|
||||
attribs[atti++] = frame->offsets[i];
|
||||
attribs[atti++] = attr_names[i].pitch;
|
||||
attribs[atti++] = frame->strides[i];
|
||||
if (has_modifier) {
|
||||
attribs[atti++] = attr_names[i].mod_lo;
|
||||
attribs[atti++] = frame->modifier & 0xFFFFFFFF;
|
||||
attribs[atti++] = attr_names[i].mod_hi;
|
||||
attribs[atti++] = frame->modifier >> 32;
|
||||
}
|
||||
}
|
||||
attribs[atti++] = EGL_NONE;
|
||||
assert(atti < sizeof(attribs)/sizeof(attribs[0]));
|
||||
|
||||
return eglCreateImageKHR(res->display, EGL_NO_CONTEXT,
|
||||
EGL_LINUX_DMA_BUF_EXT, NULL, attribs);
|
||||
}
|
||||
|
||||
static const GLfloat TRANSFORM_RGB[16] = {
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
@ -320,34 +435,42 @@ void wd_gl_render(struct wd_gl_data *res, struct wd_render_data *info,
|
||||
float screen_size[2] = { info->viewport_width, info->viewport_height };
|
||||
|
||||
if (tri_verts > 0) {
|
||||
glUseProgram(res->texture_program);
|
||||
glUseProgram(res->rgb.id);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, res->buffers[TEXTURE_BUFFER]);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0,
|
||||
tri_verts * BT_UV_VERT_SIZE * sizeof(float), res->verts);
|
||||
glEnableVertexAttribArray(res->texture_position_attribute);
|
||||
glEnableVertexAttribArray(res->texture_uv_attribute);
|
||||
glVertexAttribPointer(res->texture_position_attribute,
|
||||
glEnableVertexAttribArray(res->rgb.position_attribute);
|
||||
glEnableVertexAttribArray(res->rgb.uv_attribute);
|
||||
glVertexAttribPointer(res->rgb.position_attribute,
|
||||
2, GL_FLOAT, GL_FALSE,
|
||||
BT_UV_VERT_SIZE * sizeof(float), (void *) (0 * sizeof(float)));
|
||||
glVertexAttribPointer(res->texture_uv_attribute, 2, GL_FLOAT, GL_FALSE,
|
||||
glVertexAttribPointer(res->rgb.uv_attribute, 2, GL_FLOAT, GL_FALSE,
|
||||
BT_UV_VERT_SIZE * sizeof(float), (void *) (2 * sizeof(float)));
|
||||
glUniform2fv(res->texture_screen_size_uniform, 1, screen_size);
|
||||
glUniform1i(res->texture_texture_uniform, 0);
|
||||
glUniform2fv(res->rgb.screen_size_uniform, 1, screen_size);
|
||||
glUniform1i(res->rgb.texture_uniform, 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
i = 0;
|
||||
wl_list_for_each_reverse(head, &info->heads, link) {
|
||||
glBindTexture(GL_TEXTURE_2D, res->textures[i]);
|
||||
if (head->updated_at == tick) {
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, head->tex_stride / 4);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
|
||||
head->tex_width, head->tex_height,
|
||||
0, GL_RGBA, GL_UNSIGNED_BYTE, head->pixels);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
|
||||
if (head->image) {
|
||||
assert_gl_ext(GL_OES_EGL_image);
|
||||
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, head->image);
|
||||
} else if (head->pixels) {
|
||||
assert_gl_ext(GL_EXT_unpack_subimage);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, head->tex_stride / 4);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
|
||||
head->tex_width, head->tex_height,
|
||||
0, GL_RGBA, GL_UNSIGNED_BYTE, head->pixels);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
|
||||
}
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
glUniformMatrix4fv(res->texture_color_transform_uniform, 1, GL_FALSE,
|
||||
glUniformMatrix4fv(res->rgb.color_transform_uniform, 1, GL_FALSE,
|
||||
head->swap_rgb ? TRANSFORM_RGB : TRANSFORM_BGR);
|
||||
float color_add[4] = { 0.f, 0.f, 0.f, head->has_alpha ? 0.f : 1.f };
|
||||
glUniform4fv(res->rgb.color_add_uniform, 1, color_add);
|
||||
glDrawArrays(GL_TRIANGLES, i * 6, 6);
|
||||
i++;
|
||||
if (i >= HEADS_MAX)
|
||||
@ -514,13 +637,9 @@ void wd_gl_render(struct wd_gl_data *res, struct wd_render_data *info,
|
||||
|
||||
void wd_gl_cleanup(struct wd_gl_data *res) {
|
||||
glDeleteBuffers(NUM_BUFFERS, res->buffers);
|
||||
glDeleteShader(res->texture_fragment_shader);
|
||||
glDeleteShader(res->texture_vertex_shader);
|
||||
glDeleteProgram(res->texture_program);
|
||||
|
||||
glDeleteShader(res->color_fragment_shader);
|
||||
glDeleteShader(res->color_vertex_shader);
|
||||
glDeleteProgram(res->color_program);
|
||||
|
||||
glDeleteProgram(res->rgb.id);
|
||||
|
||||
free(res);
|
||||
}
|
||||
|
@ -20,11 +20,15 @@
|
||||
#include <stdbool.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "headform.h"
|
||||
|
||||
struct zxdg_output_v1;
|
||||
struct zxdg_output_manager_v1;
|
||||
struct zwlr_output_mode_v1;
|
||||
struct zwlr_output_head_v1;
|
||||
struct zwlr_output_manager_v1;
|
||||
struct zwlr_export_dmabuf_manager_v1;
|
||||
struct zwlr_export_dmabuf_frame_v1;
|
||||
struct zwlr_screencopy_manager_v1;
|
||||
struct zwlr_screencopy_frame_v1;
|
||||
struct zwlr_layer_shell_v1;
|
||||
@ -39,17 +43,7 @@ typedef struct _GdkCursor GdkCursor;
|
||||
struct _cairo_surface;
|
||||
typedef struct _cairo_surface cairo_surface_t;
|
||||
|
||||
enum wd_head_fields {
|
||||
WD_FIELD_NAME = 1 << 0,
|
||||
WD_FIELD_ENABLED = 1 << 1,
|
||||
WD_FIELD_DESCRIPTION = 1 << 2,
|
||||
WD_FIELD_PHYSICAL_SIZE = 1 << 3,
|
||||
WD_FIELD_SCALE = 1 << 4,
|
||||
WD_FIELD_POSITION = 1 << 5,
|
||||
WD_FIELD_MODE = 1 << 6,
|
||||
WD_FIELD_TRANSFORM = 1 << 7,
|
||||
WD_FIELDS_ALL = (1 << 8) - 1
|
||||
};
|
||||
typedef void *EGLImageKHR;
|
||||
|
||||
struct wd_output {
|
||||
struct wd_state *state;
|
||||
@ -63,21 +57,30 @@ struct wd_output {
|
||||
struct zwlr_layer_surface_v1 *overlay_layer_surface;
|
||||
};
|
||||
|
||||
#define WD_MAX_PLANES 4
|
||||
|
||||
struct wd_frame {
|
||||
struct wd_output *output;
|
||||
struct zwlr_screencopy_frame_v1 *wlr_frame;
|
||||
struct zwlr_export_dmabuf_frame_v1 *export_frame;
|
||||
struct zwlr_screencopy_frame_v1 *copy_frame;
|
||||
|
||||
struct wl_list link;
|
||||
int capture_fd;
|
||||
unsigned stride;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
uint64_t tick;
|
||||
|
||||
uint32_t format;
|
||||
uint32_t flags;
|
||||
uint64_t modifier;
|
||||
uint32_t n_planes;
|
||||
int fds[WD_MAX_PLANES];
|
||||
uint32_t offsets[WD_MAX_PLANES];
|
||||
uint32_t strides[WD_MAX_PLANES];
|
||||
struct wl_shm_pool *pool;
|
||||
struct wl_buffer *buffer;
|
||||
uint8_t *pixels;
|
||||
uint64_t tick;
|
||||
bool y_invert;
|
||||
bool swap_rgb;
|
||||
bool ready;
|
||||
};
|
||||
|
||||
struct wd_head_config {
|
||||
@ -151,15 +154,17 @@ struct wd_render_head_data {
|
||||
struct wd_render_head_flags active;
|
||||
|
||||
uint8_t *pixels;
|
||||
EGLImageKHR image;
|
||||
unsigned tex_stride;
|
||||
unsigned tex_width;
|
||||
unsigned tex_height;
|
||||
|
||||
bool preview;
|
||||
bool y_invert;
|
||||
bool swap_rgb;
|
||||
bool hovered;
|
||||
bool clicked;
|
||||
uint32_t preview : 1,
|
||||
y_invert : 1,
|
||||
swap_rgb : 1,
|
||||
has_alpha : 1,
|
||||
hovered : 1,
|
||||
clicked : 1;
|
||||
};
|
||||
|
||||
struct wd_render_data {
|
||||
@ -176,6 +181,7 @@ struct wd_render_data {
|
||||
int x_origin;
|
||||
int y_origin;
|
||||
uint64_t updated_at;
|
||||
bool external_images;
|
||||
|
||||
struct wl_list heads;
|
||||
};
|
||||
@ -188,6 +194,7 @@ struct wd_point {
|
||||
struct wd_state {
|
||||
struct zxdg_output_manager_v1 *xdg_output_manager;
|
||||
struct zwlr_output_manager_v1 *output_manager;
|
||||
struct zwlr_export_dmabuf_manager_v1 *export_manager;
|
||||
struct zwlr_screencopy_manager_v1 *copy_manager;
|
||||
struct zwlr_layer_shell_v1 *layer_shell;
|
||||
struct wl_shm *shm;
|
||||
@ -205,10 +212,10 @@ struct wd_state {
|
||||
unsigned int reset_idle;
|
||||
|
||||
struct wd_render_head_data *clicked;
|
||||
/* top left, bottom right */
|
||||
struct wd_point click_offset;
|
||||
struct wd_point drag_start;
|
||||
struct wd_point head_drag_start; /* 0-1 range in head rect */
|
||||
bool panning;
|
||||
struct wd_point pan_last;
|
||||
struct wd_point pan_start;
|
||||
|
||||
GtkWidget *main_box;
|
||||
GtkWidget *header_stack;
|
||||
@ -301,7 +308,7 @@ void wd_ui_reset_heads(struct wd_state *state);
|
||||
* Updates the UI form for a single head. Useful for when the compositor
|
||||
* notifies us of updated configuration caused by another program.
|
||||
*/
|
||||
void wd_ui_reset_head(const struct wd_head *head, unsigned int fields);
|
||||
void wd_ui_reset_head(const struct wd_head *head, enum wd_head_fields fields);
|
||||
|
||||
/*
|
||||
* Updates the stack and all forms to the last known server state.
|
||||
@ -321,8 +328,14 @@ void wd_ui_show_error(struct wd_state *state, const char *message);
|
||||
/*
|
||||
* Compiles the GL shaders.
|
||||
*/
|
||||
struct wd_gl_data *wd_gl_setup(void);
|
||||
struct wd_gl_data *wd_gl_setup(struct wl_display *display);
|
||||
|
||||
/*
|
||||
* Creates an EGLImage from a given output frame. The frame must have a dmabuf
|
||||
* received from a wlr_export_dmabuf_frame.
|
||||
*/
|
||||
EGLImageKHR wd_gl_create_dmabuf_texture(struct wd_gl_data *res,
|
||||
struct wd_frame *frame);
|
||||
/*
|
||||
* Renders the GL scene.
|
||||
*/
|
||||
|
Reference in New Issue
Block a user