mirror of
https://github.com/artizirk/wdisplays.git
synced 2024-11-21 16:30:59 +02:00
finish up viewport widget with screen previews
This commit is contained in:
parent
dcf130616f
commit
43a2d18075
@ -1,5 +1,8 @@
|
|||||||
wayland_scanner = find_program('wayland-scanner')
|
wayland_scanner = find_program('wayland-scanner')
|
||||||
wayland_client = dependency('wayland-client')
|
wayland_client = dependency('wayland-client')
|
||||||
|
wayland_protos = dependency('wayland-protocols', version: '>=1.17')
|
||||||
|
|
||||||
|
wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir')
|
||||||
|
|
||||||
wayland_scanner_code = generator(
|
wayland_scanner_code = generator(
|
||||||
wayland_scanner,
|
wayland_scanner,
|
||||||
@ -14,7 +17,9 @@ wayland_scanner_client = generator(
|
|||||||
)
|
)
|
||||||
|
|
||||||
client_protocols = [
|
client_protocols = [
|
||||||
|
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
|
||||||
['wlr-output-management-unstable-v1.xml'],
|
['wlr-output-management-unstable-v1.xml'],
|
||||||
|
['wlr-screencopy-unstable-v1.xml']
|
||||||
]
|
]
|
||||||
|
|
||||||
client_protos_src = []
|
client_protos_src = []
|
||||||
|
179
protocol/wlr-screencopy-unstable-v1.xml
Normal file
179
protocol/wlr-screencopy-unstable-v1.xml
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="wlr_screencopy_unstable_v1">
|
||||||
|
<copyright>
|
||||||
|
Copyright © 2018 Simon Ser
|
||||||
|
|
||||||
|
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="screen content capturing on client buffers">
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<interface name="zwlr_screencopy_manager_v1" version="1">
|
||||||
|
<description summary="manager to inform clients and begin capturing">
|
||||||
|
This object is a manager which offers requests to start capturing from a
|
||||||
|
source.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="capture_output">
|
||||||
|
<description summary="capture an output">
|
||||||
|
Capture the next frame of an entire output.
|
||||||
|
</description>
|
||||||
|
<arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
|
||||||
|
<arg name="overlay_cursor" type="int"
|
||||||
|
summary="composite cursor onto the frame"/>
|
||||||
|
<arg name="output" type="object" interface="wl_output"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="capture_output_region">
|
||||||
|
<description summary="capture an output's region">
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
<arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
|
||||||
|
<arg name="overlay_cursor" type="int"
|
||||||
|
summary="composite cursor onto the frame"/>
|
||||||
|
<arg name="output" type="object" interface="wl_output"/>
|
||||||
|
<arg name="x" type="int"/>
|
||||||
|
<arg name="y" type="int"/>
|
||||||
|
<arg name="width" type="int"/>
|
||||||
|
<arg name="height" type="int"/>
|
||||||
|
</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_screencopy_frame_v1" version="1">
|
||||||
|
<description summary="a frame ready for copy">
|
||||||
|
This object represents a single frame.
|
||||||
|
|
||||||
|
When created, a "buffer" event will be sent. 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<event name="buffer">
|
||||||
|
<description summary="buffer information">
|
||||||
|
Provides information about the frame's buffer. This event is sent once
|
||||||
|
as soon as the frame is created.
|
||||||
|
|
||||||
|
The client should then create a buffer with the provided attributes, and
|
||||||
|
send a "copy" request.
|
||||||
|
</description>
|
||||||
|
<arg name="format" type="uint" summary="buffer format"/>
|
||||||
|
<arg name="width" type="uint" summary="buffer width"/>
|
||||||
|
<arg name="height" type="uint" summary="buffer height"/>
|
||||||
|
<arg name="stride" type="uint" summary="buffer stride"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="copy">
|
||||||
|
<description summary="copy the frame">
|
||||||
|
Copy the frame to the supplied buffer. The buffer must have a the
|
||||||
|
correct size, see zwlr_screencopy_frame_v1.buffer. 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.
|
||||||
|
</description>
|
||||||
|
<arg name="buffer" type="object" interface="wl_buffer"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="already_used" value="0"
|
||||||
|
summary="the object has already been used to copy a wl_buffer"/>
|
||||||
|
<entry name="invalid_buffer" value="1"
|
||||||
|
summary="buffer attributes are invalid"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<enum name="flags" bitfield="true">
|
||||||
|
<entry name="y_invert" value="1" summary="contents are y-inverted"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<event name="flags">
|
||||||
|
<description summary="frame flags">
|
||||||
|
Provides flags about the frame. This event is sent once before the
|
||||||
|
"ready" event.
|
||||||
|
</description>
|
||||||
|
<arg name="flags" type="uint" enum="flags" summary="frame flags"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="ready">
|
||||||
|
<description summary="indicates frame is available for reading">
|
||||||
|
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.
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<event name="failed">
|
||||||
|
<description summary="frame copy failed">
|
||||||
|
This event indicates that the attempted frame copy has failed.
|
||||||
|
|
||||||
|
After receiving this event, the client should destroy the object.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="delete this object, used or not">
|
||||||
|
Destroys the frame. This request can be sent at any time by the client.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
@ -25,13 +25,11 @@
|
|||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkAdjustment" id="pos_x_adjustment">
|
<object class="GtkAdjustment" id="pos_x_adjustment">
|
||||||
<property name="lower">-16384</property>
|
|
||||||
<property name="upper">16383</property>
|
<property name="upper">16383</property>
|
||||||
<property name="step_increment">1</property>
|
<property name="step_increment">1</property>
|
||||||
<property name="page_increment">10</property>
|
<property name="page_increment">10</property>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkAdjustment" id="pos_y_adjustment">
|
<object class="GtkAdjustment" id="pos_y_adjustment">
|
||||||
<property name="lower">-16384</property>
|
|
||||||
<property name="upper">16383</property>
|
<property name="upper">16383</property>
|
||||||
<property name="step_increment">1</property>
|
<property name="step_increment">1</property>
|
||||||
<property name="page_increment">10</property>
|
<property name="page_increment">10</property>
|
||||||
|
@ -3,6 +3,14 @@
|
|||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk+" version="3.22"/>
|
<requires lib="gtk+" version="3.22"/>
|
||||||
<!-- interface-css-provider-path style.css -->
|
<!-- interface-css-provider-path style.css -->
|
||||||
|
<object class="GtkAdjustment" id="canvas_horiz">
|
||||||
|
<property name="step_increment">1</property>
|
||||||
|
<property name="page_increment">10</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkAdjustment" id="canvas_vert">
|
||||||
|
<property name="step_increment">1</property>
|
||||||
|
<property name="page_increment">10</property>
|
||||||
|
</object>
|
||||||
<object class="GtkPopover" id="main_menu">
|
<object class="GtkPopover" id="main_menu">
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<child>
|
<child>
|
||||||
@ -28,6 +36,20 @@
|
|||||||
<property name="position">0</property>
|
<property name="position">0</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</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>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
@ -112,13 +134,12 @@
|
|||||||
<object class="GtkScrolledWindow" id="heads_scroll">
|
<object class="GtkScrolledWindow" id="heads_scroll">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
|
<property name="hadjustment">canvas_horiz</property>
|
||||||
|
<property name="vadjustment">canvas_vert</property>
|
||||||
|
<property name="min_content_width">300</property>
|
||||||
|
<property name="min_content_height">300</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLayout" id="heads_layout">
|
<placeholder/>
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="width">400</property>
|
|
||||||
<signal name="draw" handler="heads_draw" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
@ -221,7 +242,7 @@
|
|||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="receives_default">True</property>
|
<property name="receives_default">True</property>
|
||||||
<property name="tooltip_text" translatable="yes">Zoom Out</property>
|
<property name="tooltip_text" translatable="yes">Zoom Out</property>
|
||||||
<signal name="clicked" handler="zoom_out" swapped="no"/>
|
<signal name="clicked" handler="zoom_out" swapped="yes"/>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImage">
|
<object class="GtkImage">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
@ -244,7 +265,7 @@
|
|||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="receives_default">True</property>
|
<property name="receives_default">True</property>
|
||||||
<property name="tooltip_text" translatable="yes">Zoom Reset</property>
|
<property name="tooltip_text" translatable="yes">Zoom Reset</property>
|
||||||
<signal name="clicked" handler="zoom_reset" swapped="no"/>
|
<signal name="clicked" handler="zoom_reset" swapped="yes"/>
|
||||||
<accelerator key="0" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
<accelerator key="0" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
@ -260,7 +281,7 @@
|
|||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="receives_default">True</property>
|
<property name="receives_default">True</property>
|
||||||
<property name="tooltip_text" translatable="yes">Zoom In</property>
|
<property name="tooltip_text" translatable="yes">Zoom In</property>
|
||||||
<signal name="clicked" handler="zoom_in" swapped="no"/>
|
<signal name="clicked" handler="zoom_in" swapped="yes"/>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImage">
|
<object class="GtkImage">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
135
src/glviewport.c
Normal file
135
src/glviewport.c
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 cyclopsian
|
||||||
|
|
||||||
|
* 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 X CONSORTIUM BE LIABLE FOR ANY
|
||||||
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "glviewport.h"
|
||||||
|
|
||||||
|
typedef struct _WdGLViewportPrivate {
|
||||||
|
GtkAdjustment *hadjustment;
|
||||||
|
GtkAdjustment *vadjustment;
|
||||||
|
guint hscroll_policy : 1;
|
||||||
|
guint vscroll_policy : 1;
|
||||||
|
} WdGLViewportPrivate;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_HADJUSTMENT,
|
||||||
|
PROP_VADJUSTMENT,
|
||||||
|
PROP_HSCROLL_POLICY,
|
||||||
|
PROP_VSCROLL_POLICY
|
||||||
|
};
|
||||||
|
|
||||||
|
static void wd_gl_viewport_set_property(
|
||||||
|
GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
|
||||||
|
static void wd_gl_viewport_get_property(
|
||||||
|
GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
|
||||||
|
|
||||||
|
G_DEFINE_TYPE_WITH_CODE(WdGLViewport, wd_gl_viewport, GTK_TYPE_GL_AREA,
|
||||||
|
G_ADD_PRIVATE(WdGLViewport)
|
||||||
|
G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, NULL))
|
||||||
|
|
||||||
|
static void wd_gl_viewport_class_init(WdGLViewportClass *class) {
|
||||||
|
GObjectClass *gobject_class = G_OBJECT_CLASS(class);
|
||||||
|
|
||||||
|
gobject_class->set_property = wd_gl_viewport_set_property;
|
||||||
|
gobject_class->get_property = wd_gl_viewport_get_property;
|
||||||
|
|
||||||
|
g_object_class_override_property(gobject_class, PROP_HADJUSTMENT, "hadjustment");
|
||||||
|
g_object_class_override_property(gobject_class, PROP_VADJUSTMENT, "vadjustment");
|
||||||
|
g_object_class_override_property(gobject_class, PROP_HSCROLL_POLICY, "hscroll-policy");
|
||||||
|
g_object_class_override_property(gobject_class, PROP_VSCROLL_POLICY, "vscroll-policy");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void viewport_set_adjustment(GtkAdjustment *adjustment,
|
||||||
|
GtkAdjustment **store) {
|
||||||
|
if (!adjustment) {
|
||||||
|
adjustment = gtk_adjustment_new(0., 0., 0., 0., 0., 0.);
|
||||||
|
}
|
||||||
|
if (adjustment != *store) {
|
||||||
|
if (*store != NULL) {
|
||||||
|
g_object_unref(*store);
|
||||||
|
}
|
||||||
|
*store = adjustment;
|
||||||
|
g_object_ref_sink(adjustment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wd_gl_viewport_set_property(
|
||||||
|
GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
|
||||||
|
WdGLViewport *viewport = WD_GL_VIEWPORT(object);
|
||||||
|
WdGLViewportPrivate *priv = wd_gl_viewport_get_instance_private(viewport);
|
||||||
|
|
||||||
|
switch (prop_id) {
|
||||||
|
case PROP_HADJUSTMENT:
|
||||||
|
viewport_set_adjustment(g_value_get_object(value), &priv->hadjustment);
|
||||||
|
break;
|
||||||
|
case PROP_VADJUSTMENT:
|
||||||
|
viewport_set_adjustment(g_value_get_object(value), &priv->vadjustment);
|
||||||
|
break;
|
||||||
|
case PROP_HSCROLL_POLICY:
|
||||||
|
if (priv->hscroll_policy != g_value_get_enum(value)) {
|
||||||
|
priv->hscroll_policy = g_value_get_enum(value);
|
||||||
|
g_object_notify_by_pspec(object, pspec);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PROP_VSCROLL_POLICY:
|
||||||
|
if (priv->vscroll_policy != g_value_get_enum(value)) {
|
||||||
|
priv->vscroll_policy = g_value_get_enum(value);
|
||||||
|
g_object_notify_by_pspec (object, pspec);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wd_gl_viewport_get_property(
|
||||||
|
GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
|
||||||
|
WdGLViewport *viewport = WD_GL_VIEWPORT(object);
|
||||||
|
WdGLViewportPrivate *priv = wd_gl_viewport_get_instance_private(viewport);
|
||||||
|
|
||||||
|
switch (prop_id) {
|
||||||
|
case PROP_HADJUSTMENT:
|
||||||
|
g_value_set_object(value, priv->hadjustment);
|
||||||
|
break;
|
||||||
|
case PROP_VADJUSTMENT:
|
||||||
|
g_value_set_object(value, priv->vadjustment);
|
||||||
|
break;
|
||||||
|
case PROP_HSCROLL_POLICY:
|
||||||
|
g_value_set_enum(value, priv->hscroll_policy);
|
||||||
|
break;
|
||||||
|
case PROP_VSCROLL_POLICY:
|
||||||
|
g_value_set_enum(value, priv->vscroll_policy);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wd_gl_viewport_init(WdGLViewport *viewport) {
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *wd_gl_viewport_new(void) {
|
||||||
|
return gtk_widget_new(WD_TYPE_GL_VIEWPORT, NULL);
|
||||||
|
}
|
43
src/glviewport.h
Normal file
43
src/glviewport.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 cyclopsian
|
||||||
|
|
||||||
|
* 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 X CONSORTIUM BE LIABLE FOR ANY
|
||||||
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WDISPLAY_GLVIEWPORT_H
|
||||||
|
#define WDISPLAY_GLVIEWPORT_H
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define WD_TYPE_GL_VIEWPORT (wd_gl_viewport_get_type())
|
||||||
|
G_DECLARE_DERIVABLE_TYPE(
|
||||||
|
WdGLViewport, wd_gl_viewport, WD, GL_VIEWPORT,GtkGLArea)
|
||||||
|
|
||||||
|
struct _WdGLViewportClass {
|
||||||
|
GtkGLAreaClass parent_class;
|
||||||
|
};
|
||||||
|
|
||||||
|
GtkWidget *wd_gl_viewport_new(void);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
803
src/main.c
803
src/main.c
@ -25,6 +25,7 @@
|
|||||||
#include <gdk/gdkwayland.h>
|
#include <gdk/gdkwayland.h>
|
||||||
|
|
||||||
#include "wdisplay.h"
|
#include "wdisplay.h"
|
||||||
|
#include "glviewport.h"
|
||||||
|
|
||||||
__attribute__((noreturn)) void wd_fatal_error(int status, const char *message) {
|
__attribute__((noreturn)) void wd_fatal_error(int status, const char *message) {
|
||||||
GtkWindow *parent = gtk_application_get_active_window(GTK_APPLICATION(g_application_get_default()));
|
GtkWindow *parent = gtk_application_get_active_window(GTK_APPLICATION(g_application_get_default()));
|
||||||
@ -180,6 +181,208 @@ static gboolean apply_done_reset(gpointer data) {
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void update_scroll_size(struct wd_state *state) {
|
||||||
|
state->render.viewport_width = gtk_widget_get_allocated_width(state->canvas);
|
||||||
|
state->render.viewport_height = gtk_widget_get_allocated_height(state->canvas);
|
||||||
|
|
||||||
|
GtkAdjustment *scroll_x_adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
||||||
|
GtkAdjustment *scroll_y_adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
||||||
|
int scroll_x_upper = state->render.width;
|
||||||
|
int scroll_y_upper = state->render.height;
|
||||||
|
gtk_adjustment_set_upper(scroll_x_adj, MAX(0, scroll_x_upper));
|
||||||
|
gtk_adjustment_set_upper(scroll_y_adj, MAX(0, scroll_y_upper));
|
||||||
|
gtk_adjustment_set_page_size(scroll_x_adj, state->render.viewport_width);
|
||||||
|
gtk_adjustment_set_page_size(scroll_y_adj, state->render.viewport_height);
|
||||||
|
gtk_adjustment_set_page_increment(scroll_x_adj, state->render.viewport_width);
|
||||||
|
gtk_adjustment_set_page_increment(scroll_y_adj, state->render.viewport_height);
|
||||||
|
gtk_adjustment_set_step_increment(scroll_x_adj, state->render.viewport_width / 10);
|
||||||
|
gtk_adjustment_set_step_increment(scroll_y_adj, state->render.viewport_height / 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Recalculates the desired canvas size, accounting for zoom + margins.
|
||||||
|
*/
|
||||||
|
static void update_canvas_size(struct wd_state *state) {
|
||||||
|
int xmin = 0;
|
||||||
|
int xmax = 0;
|
||||||
|
int ymin = 0;
|
||||||
|
int ymax = 0;
|
||||||
|
|
||||||
|
struct wd_head *head;
|
||||||
|
wl_list_for_each(head, &state->heads, link) {
|
||||||
|
int w = head->custom_mode.width;
|
||||||
|
int h = head->custom_mode.height;
|
||||||
|
if (head->enabled && head->mode != NULL) {
|
||||||
|
w = head->mode->width;
|
||||||
|
h = head->mode->height;
|
||||||
|
}
|
||||||
|
if (head->scale > 0.) {
|
||||||
|
w /= head->scale;
|
||||||
|
h /= head->scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x2 = head->x + w;
|
||||||
|
int y2 = head->y + h;
|
||||||
|
xmin = MIN(xmin, head->x);
|
||||||
|
xmax = MAX(xmax, x2);
|
||||||
|
ymin = MIN(ymin, head->y);
|
||||||
|
ymax = MAX(ymax, y2);
|
||||||
|
}
|
||||||
|
// update canvas sizings
|
||||||
|
state->render.x_origin = floor(xmin * state->zoom) - CANVAS_MARGIN;
|
||||||
|
state->render.y_origin = floor(ymin * state->zoom) - CANVAS_MARGIN;
|
||||||
|
state->render.width = ceil((xmax - xmin) * state->zoom) + CANVAS_MARGIN * 2;
|
||||||
|
state->render.height = ceil((ymax - ymin) * state->zoom) + CANVAS_MARGIN * 2;
|
||||||
|
|
||||||
|
update_scroll_size(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cache_scroll(struct wd_state *state) {
|
||||||
|
GtkAdjustment *scroll_x_adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
||||||
|
GtkAdjustment *scroll_y_adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
||||||
|
state->render.scroll_x = gtk_adjustment_get_value(scroll_x_adj);
|
||||||
|
state->render.scroll_y = gtk_adjustment_get_value(scroll_y_adj);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean redraw_canvas(GtkWidget *widget, GdkFrameClock *frame_clock, gpointer data);
|
||||||
|
|
||||||
|
static void update_tick_callback(struct wd_state *state) {
|
||||||
|
bool any_animate = false;
|
||||||
|
for (int i = 0; i < state->render.head_count; i++) {
|
||||||
|
struct wd_render_head_data *head = &state->render.heads[i];
|
||||||
|
if (state->render.updated_at < head->transition_begin + HOVER_USECS) {
|
||||||
|
any_animate = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!any_animate && !state->capture) {
|
||||||
|
if (state->canvas_tick != -1) {
|
||||||
|
gtk_widget_remove_tick_callback(state->canvas, state->canvas_tick);
|
||||||
|
state->canvas_tick = -1;
|
||||||
|
}
|
||||||
|
} else if (state->canvas_tick == -1) {
|
||||||
|
state->canvas_tick =
|
||||||
|
gtk_widget_add_tick_callback(state->canvas, redraw_canvas, state, NULL);
|
||||||
|
}
|
||||||
|
gtk_gl_area_queue_render(GTK_GL_AREA(state->canvas));
|
||||||
|
gtk_gl_area_set_auto_render(GTK_GL_AREA(state->canvas), state->capture);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_cursor(struct wd_state *state) {
|
||||||
|
bool any_hovered = false;
|
||||||
|
struct wd_head *head;
|
||||||
|
wl_list_for_each(head, &state->heads, link) {
|
||||||
|
struct wd_render_head_data *render = head->render;
|
||||||
|
if (render != NULL && render->hovered) {
|
||||||
|
any_hovered = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GdkWindow *window = gtk_widget_get_window(state->canvas);
|
||||||
|
if (any_hovered) {
|
||||||
|
gdk_window_set_cursor(window, state->grab_cursor);
|
||||||
|
} else if (state->clicked != NULL) {
|
||||||
|
gdk_window_set_cursor(window, state->grabbing_cursor);
|
||||||
|
} else if (state->panning) {
|
||||||
|
gdk_window_set_cursor(window, state->move_cursor);
|
||||||
|
} else {
|
||||||
|
gdk_window_set_cursor(window, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_hovered(struct wd_state *state) {
|
||||||
|
GdkDisplay *display = gdk_display_get_default();
|
||||||
|
GdkWindow *window = gtk_widget_get_window(state->canvas);
|
||||||
|
if (!gtk_widget_get_realized(state->canvas)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GdkFrameClock *clock = gtk_widget_get_frame_clock(state->canvas);
|
||||||
|
uint64_t tick = gdk_frame_clock_get_frame_time(clock);
|
||||||
|
g_autoptr(GList) seats = gdk_display_list_seats(display);
|
||||||
|
struct wd_head *head;
|
||||||
|
wl_list_for_each(head, &state->heads, link) {
|
||||||
|
struct wd_render_head_data *render = head->render;
|
||||||
|
if (render != NULL) {
|
||||||
|
bool init_hovered = render->hovered;
|
||||||
|
render->hovered = false;
|
||||||
|
if (state->clicked == head) {
|
||||||
|
render->hovered = true;
|
||||||
|
} else if (state->clicked == NULL) {
|
||||||
|
for (GList *iter = seats; iter != NULL; iter = iter->next) {
|
||||||
|
double mouse_x;
|
||||||
|
double mouse_y;
|
||||||
|
|
||||||
|
GdkDevice *pointer = gdk_seat_get_pointer(GDK_SEAT(iter->data));
|
||||||
|
gdk_window_get_device_position_double(window, pointer, &mouse_x, &mouse_y, NULL);
|
||||||
|
if (mouse_x >= render->x1 && mouse_x < render->x2 &&
|
||||||
|
mouse_y >= render->y1 && mouse_y < render->y2) {
|
||||||
|
render->hovered = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (init_hovered != render->hovered) {
|
||||||
|
render->transition_begin = tick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update_cursor(state);
|
||||||
|
update_tick_callback(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void color_to_float_array(GtkStyleContext *ctx,
|
||||||
|
const char *color_name, float out[4]) {
|
||||||
|
GdkRGBA color;
|
||||||
|
gtk_style_context_lookup_color(ctx, color_name, &color);
|
||||||
|
out[0] = color.red;
|
||||||
|
out[1] = color.green;
|
||||||
|
out[2] = color.blue;
|
||||||
|
out[3] = color.alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void queue_canvas_draw(struct wd_state *state) {
|
||||||
|
GtkStyleContext *style_ctx = gtk_widget_get_style_context(state->canvas);
|
||||||
|
color_to_float_array(style_ctx,
|
||||||
|
"theme_fg_color", state->render.fg_color);
|
||||||
|
color_to_float_array(style_ctx,
|
||||||
|
"theme_bg_color", state->render.bg_color);
|
||||||
|
color_to_float_array(style_ctx,
|
||||||
|
"borders", state->render.border_color);
|
||||||
|
color_to_float_array(style_ctx,
|
||||||
|
"theme_selected_bg_color", state->render.selection_color);
|
||||||
|
|
||||||
|
cache_scroll(state);
|
||||||
|
|
||||||
|
state->render.head_count = 0;
|
||||||
|
g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack));
|
||||||
|
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
||||||
|
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form_iter->data), "builder"));
|
||||||
|
gboolean enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "enabled")));
|
||||||
|
if (enabled) {
|
||||||
|
int x = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "pos_x")));
|
||||||
|
int y = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "pos_y")));
|
||||||
|
int w = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "width")));
|
||||||
|
int h = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "height")));
|
||||||
|
double scale = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "scale")));
|
||||||
|
if (scale <= 0.)
|
||||||
|
scale = 1.;
|
||||||
|
|
||||||
|
struct wd_head *head = g_object_get_data(G_OBJECT(form_iter->data), "head");
|
||||||
|
struct wd_render_head_data *render = &state->render.heads[state->render.head_count];
|
||||||
|
render->x1 = floor(x * state->zoom - state->render.scroll_x - state->render.x_origin);
|
||||||
|
render->y1 = floor(y * state->zoom - state->render.scroll_y - state->render.y_origin);
|
||||||
|
render->x2 = floor(render->x1 + w * state->zoom / scale);
|
||||||
|
render->y2 = floor(render->y1 + h * state->zoom / scale);
|
||||||
|
head->render = render;
|
||||||
|
|
||||||
|
state->render.head_count++;
|
||||||
|
if (state->render.head_count >= HEADS_MAX)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gtk_gl_area_queue_render(GTK_GL_AREA(state->canvas));
|
||||||
|
}
|
||||||
|
|
||||||
// BEGIN FORM CALLBACKS
|
// BEGIN FORM CALLBACKS
|
||||||
static void show_apply(struct wd_state *state) {
|
static void show_apply(struct wd_state *state) {
|
||||||
const gchar *page = "title";
|
const gchar *page = "title";
|
||||||
@ -191,7 +394,12 @@ static void show_apply(struct wd_state *state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
gtk_stack_set_visible_child_name(GTK_STACK(state->header_stack), page);
|
gtk_stack_set_visible_child_name(GTK_STACK(state->header_stack), page);
|
||||||
gtk_widget_queue_draw(state->canvas);
|
}
|
||||||
|
|
||||||
|
static void update_ui(struct wd_state *state) {
|
||||||
|
show_apply(state);
|
||||||
|
update_canvas_size(state);
|
||||||
|
queue_canvas_draw(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_sensitivity(GtkWidget *form) {
|
static void update_sensitivity(GtkWidget *form) {
|
||||||
@ -226,7 +434,7 @@ static void select_rotate_option(GtkWidget *form, GtkWidget *model_button) {
|
|||||||
static void rotate_selected(GSimpleAction *action, GVariant *param, gpointer data) {
|
static void rotate_selected(GSimpleAction *action, GVariant *param, gpointer data) {
|
||||||
select_rotate_option(GTK_WIDGET(data), g_object_get_data(G_OBJECT(action), "widget"));
|
select_rotate_option(GTK_WIDGET(data), g_object_get_data(G_OBJECT(action), "widget"));
|
||||||
const struct wd_head *head = g_object_get_data(G_OBJECT(data), "head");
|
const struct wd_head *head = g_object_get_data(G_OBJECT(data), "head");
|
||||||
show_apply(head->state);
|
update_ui(head->state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void select_mode_option(GtkWidget *form, int32_t w, int32_t h, int32_t r) {
|
static void select_mode_option(GtkWidget *form, int32_t w, int32_t h, int32_t r) {
|
||||||
@ -257,47 +465,10 @@ static void mode_selected(GSimpleAction *action, GVariant *param, gpointer data)
|
|||||||
|
|
||||||
update_mode_entries(form, mode->width, mode->height, mode->refresh);
|
update_mode_entries(form, mode->width, mode->height, mode->refresh);
|
||||||
select_mode_option(form, mode->width, mode->height, mode->refresh);
|
select_mode_option(form, mode->width, mode->height, mode->refresh);
|
||||||
show_apply(head->state);
|
update_ui(head->state);
|
||||||
}
|
}
|
||||||
// END FORM CALLBACKS
|
// END FORM CALLBACKS
|
||||||
|
|
||||||
/*
|
|
||||||
* Recalculates the desired canvas size, accounting for zoom + margins.
|
|
||||||
*/
|
|
||||||
static void update_canvas_size(struct wd_state *state) {
|
|
||||||
int xmin = 0;
|
|
||||||
int xmax = 0;
|
|
||||||
int ymin = 0;
|
|
||||||
int ymax = 0;
|
|
||||||
|
|
||||||
struct wd_head *head;
|
|
||||||
wl_list_for_each(head, &state->heads, link) {
|
|
||||||
int w = head->custom_mode.width;
|
|
||||||
int h = head->custom_mode.height;
|
|
||||||
if (head->enabled && head->mode != NULL) {
|
|
||||||
w = head->mode->width;
|
|
||||||
h = head->mode->height;
|
|
||||||
}
|
|
||||||
if (head->scale > 0.) {
|
|
||||||
w /= head->scale;
|
|
||||||
h /= head->scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
int x2 = head->x + w;
|
|
||||||
int y2 = head->y + h;
|
|
||||||
xmin = MIN(xmin, head->x);
|
|
||||||
xmax = MAX(xmax, x2);
|
|
||||||
ymin = MIN(ymin, head->y);
|
|
||||||
ymax = MAX(ymax, y2);
|
|
||||||
}
|
|
||||||
// update canvas sizings
|
|
||||||
state->xorigin = floor(xmin * state->zoom) - CANVAS_MARGIN;
|
|
||||||
state->yorigin = floor(ymin * state->zoom) - CANVAS_MARGIN;
|
|
||||||
int heads_width = ceil((xmax - xmin) * state->zoom) + CANVAS_MARGIN * 2;
|
|
||||||
int heads_height = ceil((ymax - ymin) * state->zoom) + CANVAS_MARGIN * 2;
|
|
||||||
gtk_layout_set_size(GTK_LAYOUT(state->canvas), heads_width, heads_height);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void clear_menu(GtkWidget *box, GActionMap *action_map) {
|
static void clear_menu(GtkWidget *box, GActionMap *action_map) {
|
||||||
g_autoptr(GList) children = gtk_container_get_children(GTK_CONTAINER(box));
|
g_autoptr(GList) children = gtk_container_get_children(GTK_CONTAINER(box));
|
||||||
for (GList *child = children; child != NULL; child = child->next) {
|
for (GList *child = children; child != NULL; child = child->next) {
|
||||||
@ -305,6 +476,7 @@ static void clear_menu(GtkWidget *box, GActionMap *action_map) {
|
|||||||
gtk_container_remove(GTK_CONTAINER(box), GTK_WIDGET(child->data));
|
gtk_container_remove(GTK_CONTAINER(box), GTK_WIDGET(child->data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_head_form(GtkWidget *form, unsigned int fields) {
|
static void update_head_form(GtkWidget *form, unsigned int fields) {
|
||||||
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder"));
|
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder"));
|
||||||
GtkWidget *description = GTK_WIDGET(gtk_builder_get_object(builder, "description"));
|
GtkWidget *description = GTK_WIDGET(gtk_builder_get_object(builder, "description"));
|
||||||
@ -368,7 +540,18 @@ static void update_head_form(GtkWidget *form, unsigned int fields) {
|
|||||||
w = head->mode->width;
|
w = head->mode->width;
|
||||||
h = head->mode->height;
|
h = head->mode->height;
|
||||||
r = head->mode->refresh;
|
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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
update_mode_entries(form, w, h, r);
|
update_mode_entries(form, w, h, r);
|
||||||
select_mode_option(form, w, h, r);
|
select_mode_option(form, w, h, r);
|
||||||
gtk_widget_show_all(mode_box);
|
gtk_widget_show_all(mode_box);
|
||||||
@ -389,8 +572,7 @@ static void update_head_form(GtkWidget *form, unsigned int fields) {
|
|||||||
if (fields & WD_FIELD_ENABLED) {
|
if (fields & WD_FIELD_ENABLED) {
|
||||||
update_sensitivity(form);
|
update_sensitivity(form);
|
||||||
}
|
}
|
||||||
show_apply(head->state);
|
update_ui(head->state);
|
||||||
gtk_widget_queue_draw(head->state->canvas);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void wd_ui_reset_heads(struct wd_state *state) {
|
void wd_ui_reset_heads(struct wd_state *state) {
|
||||||
@ -437,14 +619,14 @@ void wd_ui_reset_heads(struct wd_state *state) {
|
|||||||
gtk_widget_show_all(form);
|
gtk_widget_show_all(form);
|
||||||
|
|
||||||
g_signal_connect_swapped(gtk_builder_get_object(builder, "enabled"), "toggled", G_CALLBACK(update_sensitivity), form);
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "enabled"), "toggled", G_CALLBACK(update_sensitivity), form);
|
||||||
g_signal_connect_swapped(gtk_builder_get_object(builder, "enabled"), "toggled", G_CALLBACK(show_apply), state);
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "enabled"), "toggled", G_CALLBACK(update_ui), state);
|
||||||
g_signal_connect_swapped(gtk_builder_get_object(builder, "scale"), "value-changed", G_CALLBACK(show_apply), state);
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "scale"), "value-changed", G_CALLBACK(update_ui), state);
|
||||||
g_signal_connect_swapped(gtk_builder_get_object(builder, "pos_x"), "value-changed", G_CALLBACK(show_apply), state);
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "pos_x"), "value-changed", G_CALLBACK(update_ui), state);
|
||||||
g_signal_connect_swapped(gtk_builder_get_object(builder, "pos_y"), "value-changed", G_CALLBACK(show_apply), state);
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "pos_y"), "value-changed", G_CALLBACK(update_ui), state);
|
||||||
g_signal_connect_swapped(gtk_builder_get_object(builder, "width"), "value-changed", G_CALLBACK(show_apply), state);
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "width"), "value-changed", G_CALLBACK(update_ui), state);
|
||||||
g_signal_connect_swapped(gtk_builder_get_object(builder, "height"), "value-changed", G_CALLBACK(show_apply), state);
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "height"), "value-changed", G_CALLBACK(update_ui), state);
|
||||||
g_signal_connect_swapped(gtk_builder_get_object(builder, "refresh"), "value-changed", G_CALLBACK(show_apply), state);
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "refresh"), "value-changed", G_CALLBACK(update_ui), state);
|
||||||
g_signal_connect_swapped(gtk_builder_get_object(builder, "flipped"), "toggled", G_CALLBACK(show_apply), state);
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "flipped"), "toggled", G_CALLBACK(update_ui), state);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
form = form_iter->data;
|
form = form_iter->data;
|
||||||
@ -458,13 +640,10 @@ void wd_ui_reset_heads(struct wd_state *state) {
|
|||||||
g_object_unref(builder);
|
g_object_unref(builder);
|
||||||
gtk_container_remove(GTK_CONTAINER(state->stack), GTK_WIDGET(form_iter->data));
|
gtk_container_remove(GTK_CONTAINER(state->stack), GTK_WIDGET(form_iter->data));
|
||||||
}
|
}
|
||||||
gtk_widget_queue_draw(state->canvas);
|
update_canvas_size(state);
|
||||||
|
queue_canvas_draw(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, unsigned int fields) {
|
||||||
if (head->state->stack == NULL) {
|
if (head->state->stack == NULL) {
|
||||||
return;
|
return;
|
||||||
@ -474,8 +653,11 @@ void wd_ui_reset_head(const struct wd_head *head, unsigned int fields) {
|
|||||||
const struct wd_head *other = g_object_get_data(G_OBJECT(form_iter->data), "head");
|
const struct wd_head *other = g_object_get_data(G_OBJECT(form_iter->data), "head");
|
||||||
if (head == other) {
|
if (head == other) {
|
||||||
update_head_form(GTK_WIDGET(form_iter->data), fields);
|
update_head_form(GTK_WIDGET(form_iter->data), fields);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
update_canvas_size(head->state);
|
||||||
|
queue_canvas_draw(head->state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void wd_ui_reset_all(struct wd_state *state) {
|
void wd_ui_reset_all(struct wd_state *state) {
|
||||||
@ -484,6 +666,8 @@ void wd_ui_reset_all(struct wd_state *state) {
|
|||||||
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
||||||
update_head_form(GTK_WIDGET(form_iter->data), WD_FIELDS_ALL);
|
update_head_form(GTK_WIDGET(form_iter->data), WD_FIELDS_ALL);
|
||||||
}
|
}
|
||||||
|
update_canvas_size(state);
|
||||||
|
queue_canvas_draw(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void wd_ui_apply_done(struct wd_state *state, struct wl_list *outputs) {
|
void wd_ui_apply_done(struct wd_state *state, struct wl_list *outputs) {
|
||||||
@ -509,51 +693,398 @@ void wd_ui_show_error(struct wd_state *state, const char *message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BEGIN GLOBAL CALLBACKS
|
// BEGIN GLOBAL CALLBACKS
|
||||||
static void cleanup(GtkWidget *window, gpointer state) {
|
static void cleanup(GtkWidget *window, gpointer data) {
|
||||||
g_free(state);
|
struct wd_state *state = data;
|
||||||
|
g_object_unref(state->grab_cursor);
|
||||||
|
g_object_unref(state->grabbing_cursor);
|
||||||
|
g_object_unref(state->move_cursor);
|
||||||
|
wd_state_destroy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void monitor_added(GdkDisplay *display, GdkMonitor *monitor, gpointer data) {
|
||||||
|
wd_add_output(data, gdk_wayland_monitor_get_wl_output(monitor));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void monitor_removed(GdkDisplay *display, GdkMonitor *monitor, gpointer data) {
|
||||||
|
struct wl_display *wl_display = gdk_wayland_display_get_wl_display(display);
|
||||||
|
wd_remove_output(data, gdk_wayland_monitor_get_wl_output(monitor), wl_display);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void canvas_realize(GtkWidget *widget, gpointer data) {
|
||||||
|
gtk_gl_area_make_current(GTK_GL_AREA(widget));
|
||||||
|
if (gtk_gl_area_get_error(GTK_GL_AREA(widget)) != NULL) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean draw(GtkWidget *widget, cairo_t *cr, gpointer data) {
|
|
||||||
struct wd_state *state = data;
|
struct wd_state *state = data;
|
||||||
|
state->gl_data = wd_gl_setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool size_changed(const struct wd_render_head_data *render) {
|
||||||
|
return render->x2 - render->x1 != render->tex_width ||
|
||||||
|
render->y2 - render->y1 != render->tex_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void cairo_set_source_color(cairo_t *cr, float color[4]) {
|
||||||
|
cairo_set_source_rgba(cr, color[0], color[1], color[2], color[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_zoom(struct wd_state *state) {
|
||||||
|
g_autofree gchar *zoom_percent = g_strdup_printf("%.f%%", state->zoom * 100.);
|
||||||
|
gtk_button_set_label(GTK_BUTTON(state->zoom_reset), zoom_percent);
|
||||||
|
gtk_widget_set_sensitive(state->zoom_in, state->zoom < MAX_ZOOM);
|
||||||
|
gtk_widget_set_sensitive(state->zoom_out, state->zoom > MIN_ZOOM);
|
||||||
|
|
||||||
update_canvas_size(state);
|
update_canvas_size(state);
|
||||||
GtkStyleContext *style_ctx = gtk_widget_get_style_context(widget);
|
queue_canvas_draw(state);
|
||||||
GtkAdjustment *scroll_x_adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
}
|
||||||
GtkAdjustment *scroll_y_adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
|
||||||
double scroll_x = gtk_adjustment_get_value(scroll_x_adj);
|
|
||||||
double scroll_y = gtk_adjustment_get_value(scroll_y_adj);
|
|
||||||
int width = gtk_widget_get_allocated_width(widget);
|
|
||||||
int height = gtk_widget_get_allocated_height(widget);
|
|
||||||
|
|
||||||
GdkRGBA border;
|
static void zoom_to(struct wd_state *state, double zoom) {
|
||||||
gtk_style_context_lookup_color(style_ctx, "borders", &border);
|
state->zoom = zoom;
|
||||||
|
state->zoom = MAX(state->zoom, MIN_ZOOM);
|
||||||
|
state->zoom = MIN(state->zoom, MAX_ZOOM);
|
||||||
|
update_zoom(state);
|
||||||
|
}
|
||||||
|
|
||||||
gdk_cairo_set_source_rgba(cr, &border);
|
static void zoom_out(struct wd_state *state) {
|
||||||
cairo_set_line_width(cr, .5);
|
zoom_to(state, state->zoom * 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
gtk_render_background(style_ctx, cr, 0, 0, width, height);
|
static void zoom_reset(struct wd_state *state) {
|
||||||
|
zoom_to(state, DEFAULT_ZOOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void zoom_in(struct wd_state *state) {
|
||||||
|
zoom_to(state, state->zoom / 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define TEXT_MARGIN 5
|
||||||
|
|
||||||
|
static cairo_surface_t *draw_head(PangoContext *pango,
|
||||||
|
struct wd_render_data *info, const char *name,
|
||||||
|
unsigned width, unsigned height) {
|
||||||
|
cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
|
||||||
|
width, height);
|
||||||
|
cairo_t *cr = cairo_create(surface);
|
||||||
|
|
||||||
|
cairo_rectangle(cr, 0., 0., width, height);
|
||||||
|
cairo_set_source_color(cr, info->border_color);
|
||||||
|
cairo_fill(cr);
|
||||||
|
|
||||||
|
cairo_set_line_width(cr, 1.);
|
||||||
|
cairo_rectangle(cr, 0, 0, width, height);
|
||||||
|
cairo_set_source_color(cr, info->fg_color);
|
||||||
|
cairo_stroke(cr);
|
||||||
|
|
||||||
|
PangoLayout *layout = pango_layout_new(pango);
|
||||||
|
pango_layout_set_text(layout, name, -1);
|
||||||
|
int text_width = pango_units_from_double(width - TEXT_MARGIN * 2);
|
||||||
|
int text_height = pango_units_from_double(height - TEXT_MARGIN * 2);
|
||||||
|
pango_layout_set_width(layout, MAX(text_width, 0));
|
||||||
|
pango_layout_set_height(layout, MAX(text_height, 0));
|
||||||
|
pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
|
||||||
|
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
|
||||||
|
pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
|
||||||
|
|
||||||
|
cairo_set_source_color(cr, info->fg_color);
|
||||||
|
pango_layout_get_size(layout, &text_width, &text_height);
|
||||||
|
cairo_move_to(cr, TEXT_MARGIN, (height - PANGO_PIXELS(text_height)) / 2);
|
||||||
|
pango_cairo_show_layout(cr, layout);
|
||||||
|
g_object_unref(layout);
|
||||||
|
|
||||||
|
cairo_destroy(cr);
|
||||||
|
cairo_surface_flush(surface);
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void canvas_render(GtkGLArea *area, GdkGLContext *context, gpointer data) {
|
||||||
|
struct wd_state *state = data;
|
||||||
|
|
||||||
|
PangoContext *pango = gtk_widget_get_pango_context(state->canvas);
|
||||||
|
GdkFrameClock *clock = gtk_widget_get_frame_clock(state->canvas);
|
||||||
|
uint64_t tick = gdk_frame_clock_get_frame_time(clock);
|
||||||
|
|
||||||
|
wd_capture_frame(state);
|
||||||
|
|
||||||
|
struct wd_head *head;
|
||||||
|
wl_list_for_each(head, &state->heads, link) {
|
||||||
|
struct wd_render_head_data *render = head->render;
|
||||||
|
struct wd_output *output = wd_find_output(state, head);
|
||||||
|
struct wd_frame *frame = NULL;
|
||||||
|
if (output != NULL && !wl_list_empty(&output->frames)) {
|
||||||
|
frame = wl_container_of(output->frames.prev, frame, link);
|
||||||
|
}
|
||||||
|
if (render != NULL) {
|
||||||
|
if (state->capture && frame != NULL && frame->pixels != NULL) {
|
||||||
|
if (frame->tick > render->updated_at) {
|
||||||
|
render->tex_stride = frame->stride;
|
||||||
|
render->tex_width = frame->width;
|
||||||
|
render->tex_height = frame->height;
|
||||||
|
render->pixels = frame->pixels;
|
||||||
|
render->preview = true;
|
||||||
|
render->updated_at = tick;
|
||||||
|
render->y_invert = frame->y_invert;
|
||||||
|
}
|
||||||
|
} else if (render->preview
|
||||||
|
|| render->pixels == NULL || size_changed(render)) {
|
||||||
|
render->tex_width = render->x2 - render->x1;
|
||||||
|
render->tex_height = render->y2 - render->y1;
|
||||||
|
render->preview = false;
|
||||||
|
if (head->surface != NULL) {
|
||||||
|
cairo_surface_destroy(head->surface);
|
||||||
|
}
|
||||||
|
head->surface = draw_head(pango, &state->render, head->name,
|
||||||
|
render->tex_width, render->tex_height);
|
||||||
|
render->pixels = cairo_image_surface_get_data(head->surface);
|
||||||
|
render->tex_stride = cairo_image_surface_get_stride(head->surface);
|
||||||
|
render->updated_at = tick;
|
||||||
|
render->y_invert = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wd_gl_render(state->gl_data, &state->render, tick);
|
||||||
|
state->render.updated_at = tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void canvas_unrealize(GtkWidget *widget, gpointer data) {
|
||||||
|
gtk_gl_area_make_current(GTK_GL_AREA(widget));
|
||||||
|
if (gtk_gl_area_get_error(GTK_GL_AREA(widget)) != NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
struct wd_state *state = data;
|
||||||
|
|
||||||
|
GdkDisplay *gdk_display = gdk_display_get_default();
|
||||||
|
struct wl_display *display = gdk_wayland_display_get_wl_display(gdk_display);
|
||||||
|
wd_capture_wait(state, display);
|
||||||
|
|
||||||
|
wd_gl_cleanup(state->gl_data);
|
||||||
|
state->gl_data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean canvas_click(GtkWidget *widget, GdkEvent *event,
|
||||||
|
gpointer data) {
|
||||||
|
struct wd_state *state = data;
|
||||||
|
if (event->button.type == GDK_BUTTON_PRESS) {
|
||||||
|
if (event->button.button == 1) {
|
||||||
|
int i = 0;
|
||||||
|
struct wd_head *head;
|
||||||
|
wl_list_for_each(head, &state->heads, link) {
|
||||||
|
struct wd_render_head_data *render = head->render;
|
||||||
|
if (render != NULL) {
|
||||||
|
double mouse_x = event->button.x;
|
||||||
|
double mouse_y = event->button.y;
|
||||||
|
if (mouse_x >= render->x1 && mouse_x < render->x2 &&
|
||||||
|
mouse_y >= render->y1 && mouse_y < render->y2) {
|
||||||
|
state->clicked = head;
|
||||||
|
state->click_offset.x = event->button.x - render->x1;
|
||||||
|
state->click_offset.y = event->button.y - render->y1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (state->clicked != NULL) {
|
||||||
g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack));
|
g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack));
|
||||||
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
||||||
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form_iter->data), "builder"));
|
const struct wd_head *other = g_object_get_data(G_OBJECT(form_iter->data), "head");
|
||||||
gboolean enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "enabled")));
|
if (state->clicked == other) {
|
||||||
if (enabled) {
|
gtk_stack_set_visible_child(GTK_STACK(state->stack), form_iter->data);
|
||||||
int x = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "pos_x")));
|
break;
|
||||||
int y = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "pos_y")));
|
|
||||||
int w = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "width")));
|
|
||||||
int h = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "height")));
|
|
||||||
double scale = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "scale")));
|
|
||||||
if (scale <= 0.) scale = 1.;
|
|
||||||
cairo_rectangle(cr,
|
|
||||||
x * state->zoom + .5 - scroll_x - state->xorigin,
|
|
||||||
y * state->zoom + .5 - scroll_y - state->yorigin,
|
|
||||||
w * state->zoom / scale,
|
|
||||||
h * state->zoom / scale);
|
|
||||||
cairo_stroke(cr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else if (event->button.button == 2) {
|
||||||
|
state->panning = true;
|
||||||
|
state->pan_last.x = event->button.x;
|
||||||
|
state->pan_last.y = event->button.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean canvas_release(GtkWidget *widget, GdkEvent *event,
|
||||||
|
gpointer data) {
|
||||||
|
struct wd_state *state = data;
|
||||||
|
if (event->button.button == 1) {
|
||||||
|
state->clicked = NULL;
|
||||||
|
}
|
||||||
|
if (event->button.button == 2) {
|
||||||
|
state->panning = false;
|
||||||
|
}
|
||||||
|
update_cursor(state);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define SNAP_DIST 6.
|
||||||
|
|
||||||
|
static gboolean canvas_motion(GtkWidget *widget, GdkEvent *event,
|
||||||
|
gpointer data) {
|
||||||
|
struct wd_state *state = data;
|
||||||
|
if (event->motion.state & GDK_BUTTON2_MASK) {
|
||||||
|
GtkAdjustment *xadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
||||||
|
GtkAdjustment *yadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
||||||
|
double delta_x = event->motion.x - state->pan_last.x;
|
||||||
|
double delta_y = event->motion.y - state->pan_last.y;
|
||||||
|
gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) + delta_x);
|
||||||
|
gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) + delta_y);
|
||||||
|
state->pan_last.x = event->motion.x;
|
||||||
|
state->pan_last.y = event->motion.y;
|
||||||
|
queue_canvas_draw(state);
|
||||||
|
}
|
||||||
|
if ((event->motion.state & GDK_BUTTON1_MASK) && state->clicked != NULL) {
|
||||||
|
GtkWidget *form = NULL;
|
||||||
|
g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack));
|
||||||
|
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
||||||
|
const struct wd_head *other = g_object_get_data(G_OBJECT(form_iter->data), "head");
|
||||||
|
if (state->clicked == other) {
|
||||||
|
form = form_iter->data;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (form != NULL) {
|
||||||
|
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder"));
|
||||||
|
struct wd_point size = {
|
||||||
|
.x = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "width"))),
|
||||||
|
.y = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "height"))),
|
||||||
|
};
|
||||||
|
struct wd_point tl = {
|
||||||
|
.x = (event->motion.x - state->click_offset.x
|
||||||
|
+ state->render.x_origin + state->render.scroll_x) / state->zoom,
|
||||||
|
.y = (event->motion.y - state->click_offset.y
|
||||||
|
+ state->render.y_origin + state->render.scroll_y) / state->zoom
|
||||||
|
};
|
||||||
|
const struct wd_point br = {
|
||||||
|
.x = tl.x + size.x,
|
||||||
|
.y = tl.y + size.y
|
||||||
|
};
|
||||||
|
struct wd_point new_pos = tl;
|
||||||
|
float snap = SNAP_DIST / state->zoom;
|
||||||
|
|
||||||
|
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
||||||
|
const struct wd_head *other = g_object_get_data(G_OBJECT(form_iter->data), "head");
|
||||||
|
if (other != state->clicked && !(event->motion.state & GDK_SHIFT_MASK)) {
|
||||||
|
GtkBuilder *other_builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form_iter->data), "builder"));
|
||||||
|
double x1 = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(other_builder, "pos_x")));
|
||||||
|
double y1 = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(other_builder, "pos_y")));
|
||||||
|
double x2 = x1 + gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(other_builder, "width")));
|
||||||
|
double y2 = y1 + gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(other_builder, "height")));
|
||||||
|
if (fabs(br.x) <= snap)
|
||||||
|
new_pos.x = -size.x;
|
||||||
|
if (fabs(br.y) <= snap)
|
||||||
|
new_pos.y = -size.y;
|
||||||
|
if (fabs(br.x - x1) <= snap)
|
||||||
|
new_pos.x = x1 - size.x;
|
||||||
|
if (fabs(br.x - x2) <= snap)
|
||||||
|
new_pos.x = x2 - size.x;
|
||||||
|
if (fabs(br.y - y1) <= snap)
|
||||||
|
new_pos.y = y1 - size.y;
|
||||||
|
if (fabs(br.y - y2) <= snap)
|
||||||
|
new_pos.y = y2 - size.y;
|
||||||
|
|
||||||
|
if (fabs(tl.x) <= snap)
|
||||||
|
new_pos.x = 0.;
|
||||||
|
if (fabs(tl.y) <= snap)
|
||||||
|
new_pos.y = 0.;
|
||||||
|
if (fabs(tl.x - x1) <= snap)
|
||||||
|
new_pos.x = x1;
|
||||||
|
if (fabs(tl.x - x2) <= snap)
|
||||||
|
new_pos.x = x2;
|
||||||
|
if (fabs(tl.y - y1) <= snap)
|
||||||
|
new_pos.y = y1;
|
||||||
|
if (fabs(tl.y - y2) <= snap)
|
||||||
|
new_pos.y = y2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GtkWidget *pos_x = GTK_WIDGET(gtk_builder_get_object(builder, "pos_x"));
|
||||||
|
GtkWidget *pos_y = GTK_WIDGET(gtk_builder_get_object(builder, "pos_y"));
|
||||||
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(pos_x), new_pos.x);
|
||||||
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(pos_y), new_pos.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update_hovered(state);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean canvas_enter(GtkWidget *widget, GdkEvent *event,
|
||||||
|
gpointer data) {
|
||||||
|
struct wd_state *state = data;
|
||||||
|
if (!(event->crossing.state & GDK_BUTTON1_MASK)) {
|
||||||
|
state->clicked = NULL;
|
||||||
|
}
|
||||||
|
if (!(event->crossing.state & GDK_BUTTON2_MASK)) {
|
||||||
|
state->panning = false;
|
||||||
|
}
|
||||||
|
update_cursor(state);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean canvas_leave(GtkWidget *widget, GdkEvent *event,
|
||||||
|
gpointer data) {
|
||||||
|
struct wd_state *state = data;
|
||||||
|
for (int i = 0; i < state->render.head_count; i++) {
|
||||||
|
struct wd_render_head_data *head = &state->render.heads[i];
|
||||||
|
head->hovered = false;
|
||||||
|
}
|
||||||
|
update_tick_callback(state);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean canvas_scroll(GtkWidget *widget, GdkEvent *event,
|
||||||
|
gpointer data) {
|
||||||
|
struct wd_state *state = data;
|
||||||
|
if (event->scroll.state & GDK_CONTROL_MASK) {
|
||||||
|
switch (event->scroll.direction) {
|
||||||
|
case GDK_SCROLL_UP:
|
||||||
|
zoom_in(state);
|
||||||
|
break;
|
||||||
|
case GDK_SCROLL_DOWN:
|
||||||
|
zoom_out(state);
|
||||||
|
break;
|
||||||
|
case GDK_SCROLL_SMOOTH:
|
||||||
|
if (event->scroll.delta_y)
|
||||||
|
zoom_to(state, state->zoom * pow(0.75, event->scroll.delta_y));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GtkAdjustment *xadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
||||||
|
GtkAdjustment *yadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
||||||
|
double xstep = gtk_adjustment_get_step_increment(xadj);
|
||||||
|
double ystep = gtk_adjustment_get_step_increment(yadj);
|
||||||
|
switch (event->scroll.direction) {
|
||||||
|
case GDK_SCROLL_UP:
|
||||||
|
gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) - ystep);
|
||||||
|
break;
|
||||||
|
case GDK_SCROLL_DOWN:
|
||||||
|
gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) + ystep);
|
||||||
|
break;
|
||||||
|
case GDK_SCROLL_LEFT:
|
||||||
|
gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) - xstep);
|
||||||
|
break;
|
||||||
|
case GDK_SCROLL_RIGHT:
|
||||||
|
gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) + xstep);
|
||||||
|
break;
|
||||||
|
case GDK_SCROLL_SMOOTH:
|
||||||
|
if (event->scroll.delta_x)
|
||||||
|
gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) + xstep * event->scroll.delta_x);
|
||||||
|
if (event->scroll.delta_y)
|
||||||
|
gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) + ystep * event->scroll.delta_y);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void canvas_resize(GtkWidget *widget, GdkRectangle *allocation,
|
||||||
|
gpointer data) {
|
||||||
|
struct wd_state *state = data;
|
||||||
|
update_scroll_size(state);
|
||||||
|
}
|
||||||
|
|
||||||
static void cancel_changes(GtkButton *button, gpointer data) {
|
static void cancel_changes(GtkButton *button, gpointer data) {
|
||||||
struct wd_state *state = data;
|
struct wd_state *state = data;
|
||||||
gtk_stack_set_visible_child_name(GTK_STACK(state->header_stack), "title");
|
gtk_stack_set_visible_child_name(GTK_STACK(state->header_stack), "title");
|
||||||
@ -564,34 +1095,6 @@ static void apply_changes(GtkButton *button, gpointer data) {
|
|||||||
apply_state(data);
|
apply_state(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_zoom(struct wd_state *state) {
|
|
||||||
g_autofree gchar *zoom_percent = g_strdup_printf("%.f%%", state->zoom * 100.);
|
|
||||||
gtk_button_set_label(GTK_BUTTON(state->zoom_reset), zoom_percent);
|
|
||||||
gtk_widget_set_sensitive(state->zoom_in, state->zoom < MAX_ZOOM);
|
|
||||||
gtk_widget_set_sensitive(state->zoom_out, state->zoom > MIN_ZOOM);
|
|
||||||
gtk_widget_queue_draw(state->canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void zoom_out(GtkButton *button, gpointer data) {
|
|
||||||
struct wd_state *state = data;
|
|
||||||
state->zoom *= 0.75;
|
|
||||||
state->zoom = MAX(state->zoom, MIN_ZOOM);
|
|
||||||
update_zoom(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void zoom_reset(GtkButton *button, gpointer data) {
|
|
||||||
struct wd_state *state = data;
|
|
||||||
state->zoom = DEFAULT_ZOOM;
|
|
||||||
update_zoom(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void zoom_in(GtkButton *button, gpointer data) {
|
|
||||||
struct wd_state *state = data;
|
|
||||||
state->zoom /= 0.75;
|
|
||||||
state->zoom = MIN(state->zoom, MAX_ZOOM);
|
|
||||||
update_zoom(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void info_response(GtkInfoBar *info_bar, gint response_id, gpointer data) {
|
static void info_response(GtkInfoBar *info_bar, gint response_id, gpointer data) {
|
||||||
gtk_info_bar_set_revealed(info_bar, FALSE);
|
gtk_info_bar_set_revealed(info_bar, FALSE);
|
||||||
}
|
}
|
||||||
@ -610,28 +1113,47 @@ static void auto_apply_selected(GSimpleAction *action, GVariant *param, gpointer
|
|||||||
g_simple_action_set_state(action, g_variant_new_boolean(state->autoapply));
|
g_simple_action_set_state(action, g_variant_new_boolean(state->autoapply));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean redraw_canvas(GtkWidget *widget, GdkFrameClock *frame_clock, gpointer data) {
|
||||||
|
struct wd_state *state = data;
|
||||||
|
if (state->capture) {
|
||||||
|
wd_capture_frame(state);
|
||||||
|
}
|
||||||
|
queue_canvas_draw(state);
|
||||||
|
return G_SOURCE_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void capture_selected(GSimpleAction *action, GVariant *param, gpointer data) {
|
||||||
|
struct wd_state *state = data;
|
||||||
|
state->capture = !state->capture;
|
||||||
|
g_simple_action_set_state(action, g_variant_new_boolean(state->capture));
|
||||||
|
update_tick_callback(state);
|
||||||
|
}
|
||||||
|
|
||||||
static void activate(GtkApplication* app, gpointer user_data) {
|
static void activate(GtkApplication* app, gpointer user_data) {
|
||||||
GdkDisplay *gdk_display = gdk_display_get_default();
|
GdkDisplay *gdk_display = gdk_display_get_default();
|
||||||
if (!GDK_IS_WAYLAND_DISPLAY(gdk_display)) {
|
if (!GDK_IS_WAYLAND_DISPLAY(gdk_display)) {
|
||||||
wd_fatal_error(1, "This program is only usable on Wayland sessions.");
|
wd_fatal_error(1, "This program is only usable on Wayland sessions.");
|
||||||
}
|
}
|
||||||
|
|
||||||
struct wd_state *state = g_new0(struct wd_state, 1);
|
struct wd_state *state = wd_state_create();
|
||||||
state->zoom = DEFAULT_ZOOM;
|
state->zoom = DEFAULT_ZOOM;
|
||||||
wl_list_init(&state->heads);
|
state->canvas_tick = -1;
|
||||||
|
|
||||||
GtkCssProvider *css_provider = gtk_css_provider_new();
|
GtkCssProvider *css_provider = gtk_css_provider_new();
|
||||||
gtk_css_provider_load_from_resource(css_provider, "/style.css");
|
gtk_css_provider_load_from_resource(css_provider, "/style.css");
|
||||||
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(css_provider),
|
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(css_provider),
|
||||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||||
|
|
||||||
|
state->grab_cursor = gdk_cursor_new_from_name(gdk_display, "grab");
|
||||||
|
state->grabbing_cursor = gdk_cursor_new_from_name(gdk_display, "grabbing");
|
||||||
|
state->move_cursor = gdk_cursor_new_from_name(gdk_display, "move");
|
||||||
|
|
||||||
GtkBuilder *builder = gtk_builder_new_from_resource("/wdisplay.ui");
|
GtkBuilder *builder = gtk_builder_new_from_resource("/wdisplay.ui");
|
||||||
GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "heads_window"));
|
GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "heads_window"));
|
||||||
state->header_stack = GTK_WIDGET(gtk_builder_get_object(builder, "header_stack"));
|
state->header_stack = GTK_WIDGET(gtk_builder_get_object(builder, "header_stack"));
|
||||||
state->stack_switcher = GTK_WIDGET(gtk_builder_get_object(builder, "heads_stack_switcher"));
|
state->stack_switcher = GTK_WIDGET(gtk_builder_get_object(builder, "heads_stack_switcher"));
|
||||||
state->stack = GTK_WIDGET(gtk_builder_get_object(builder, "heads_stack"));
|
state->stack = GTK_WIDGET(gtk_builder_get_object(builder, "heads_stack"));
|
||||||
state->scroller = GTK_WIDGET(gtk_builder_get_object(builder, "heads_scroll"));
|
state->scroller = GTK_WIDGET(gtk_builder_get_object(builder, "heads_scroll"));
|
||||||
state->canvas = GTK_WIDGET(gtk_builder_get_object(builder, "heads_layout"));
|
|
||||||
state->spinner = GTK_WIDGET(gtk_builder_get_object(builder, "spinner"));
|
state->spinner = GTK_WIDGET(gtk_builder_get_object(builder, "spinner"));
|
||||||
state->zoom_out = GTK_WIDGET(gtk_builder_get_object(builder, "zoom_out"));
|
state->zoom_out = GTK_WIDGET(gtk_builder_get_object(builder, "zoom_out"));
|
||||||
state->zoom_reset = GTK_WIDGET(gtk_builder_get_object(builder, "zoom_reset"));
|
state->zoom_reset = GTK_WIDGET(gtk_builder_get_object(builder, "zoom_reset"));
|
||||||
@ -640,7 +1162,7 @@ static void activate(GtkApplication* app, gpointer user_data) {
|
|||||||
state->info_bar = GTK_WIDGET(gtk_builder_get_object(builder, "heads_info"));
|
state->info_bar = GTK_WIDGET(gtk_builder_get_object(builder, "heads_info"));
|
||||||
state->info_label = GTK_WIDGET(gtk_builder_get_object(builder, "heads_info_label"));
|
state->info_label = GTK_WIDGET(gtk_builder_get_object(builder, "heads_info_label"));
|
||||||
state->menu_button = GTK_WIDGET(gtk_builder_get_object(builder, "menu_button"));
|
state->menu_button = GTK_WIDGET(gtk_builder_get_object(builder, "menu_button"));
|
||||||
gtk_builder_add_callback_symbol(builder, "heads_draw", G_CALLBACK(draw));
|
|
||||||
gtk_builder_add_callback_symbol(builder, "apply_changes", G_CALLBACK(apply_changes));
|
gtk_builder_add_callback_symbol(builder, "apply_changes", G_CALLBACK(apply_changes));
|
||||||
gtk_builder_add_callback_symbol(builder, "cancel_changes", G_CALLBACK(cancel_changes));
|
gtk_builder_add_callback_symbol(builder, "cancel_changes", G_CALLBACK(cancel_changes));
|
||||||
gtk_builder_add_callback_symbol(builder, "zoom_out", G_CALLBACK(zoom_out));
|
gtk_builder_add_callback_symbol(builder, "zoom_out", G_CALLBACK(zoom_out));
|
||||||
@ -650,6 +1172,31 @@ static void activate(GtkApplication* app, gpointer user_data) {
|
|||||||
gtk_builder_add_callback_symbol(builder, "destroy", G_CALLBACK(cleanup));
|
gtk_builder_add_callback_symbol(builder, "destroy", G_CALLBACK(cleanup));
|
||||||
gtk_builder_connect_signals(builder, state);
|
gtk_builder_connect_signals(builder, state);
|
||||||
gtk_box_set_homogeneous(GTK_BOX(gtk_builder_get_object(builder, "zoom_box")), FALSE);
|
gtk_box_set_homogeneous(GTK_BOX(gtk_builder_get_object(builder, "zoom_box")), FALSE);
|
||||||
|
|
||||||
|
state->canvas = wd_gl_viewport_new();
|
||||||
|
gtk_container_add(GTK_CONTAINER(state->scroller), state->canvas);
|
||||||
|
gtk_widget_add_events(state->canvas, GDK_POINTER_MOTION_MASK
|
||||||
|
| GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK
|
||||||
|
| GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
|
||||||
|
g_signal_connect(state->canvas, "realize", G_CALLBACK(canvas_realize), state);
|
||||||
|
g_signal_connect(state->canvas, "render", G_CALLBACK(canvas_render), state);
|
||||||
|
g_signal_connect(state->canvas, "unrealize", G_CALLBACK(canvas_unrealize), state);
|
||||||
|
g_signal_connect(state->canvas, "button-press-event", G_CALLBACK(canvas_click), state);
|
||||||
|
g_signal_connect(state->canvas, "button-release-event", G_CALLBACK(canvas_release), state);
|
||||||
|
g_signal_connect(state->canvas, "enter-notify-event", G_CALLBACK(canvas_enter), state);
|
||||||
|
g_signal_connect(state->canvas, "leave-notify-event", G_CALLBACK(canvas_leave), state);
|
||||||
|
g_signal_connect(state->canvas, "motion-notify-event", G_CALLBACK(canvas_motion), state);
|
||||||
|
g_signal_connect(state->canvas, "scroll-event", G_CALLBACK(canvas_scroll), state);
|
||||||
|
g_signal_connect(state->canvas, "size-allocate", G_CALLBACK(canvas_resize), state);
|
||||||
|
gtk_gl_area_set_use_es(GTK_GL_AREA(state->canvas), TRUE);
|
||||||
|
gtk_gl_area_set_has_alpha(GTK_GL_AREA(state->canvas), TRUE);
|
||||||
|
gtk_gl_area_set_auto_render(GTK_GL_AREA(state->canvas), state->capture);
|
||||||
|
|
||||||
|
GtkAdjustment *scroll_x_adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
||||||
|
GtkAdjustment *scroll_y_adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
||||||
|
g_signal_connect_swapped(scroll_x_adj, "value-changed", G_CALLBACK(queue_canvas_draw), state);
|
||||||
|
g_signal_connect_swapped(scroll_y_adj, "value-changed", G_CALLBACK(queue_canvas_draw), state);
|
||||||
|
|
||||||
update_zoom(state);
|
update_zoom(state);
|
||||||
|
|
||||||
GSimpleActionGroup *main_actions = g_simple_action_group_new();
|
GSimpleActionGroup *main_actions = g_simple_action_group_new();
|
||||||
@ -661,6 +1208,11 @@ static void activate(GtkApplication* app, gpointer user_data) {
|
|||||||
g_signal_connect(autoapply_action, "activate", G_CALLBACK(auto_apply_selected), state);
|
g_signal_connect(autoapply_action, "activate", G_CALLBACK(auto_apply_selected), state);
|
||||||
g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(autoapply_action));
|
g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(autoapply_action));
|
||||||
|
|
||||||
|
GSimpleAction *capture_action = g_simple_action_new_stateful("capture-screens", NULL,
|
||||||
|
g_variant_new_boolean(state->capture));
|
||||||
|
g_signal_connect(capture_action, "activate", G_CALLBACK(capture_selected), state);
|
||||||
|
g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(capture_action));
|
||||||
|
|
||||||
/* first child of GtkInfoBar is always GtkRevealer */
|
/* first child of GtkInfoBar is always GtkRevealer */
|
||||||
g_autoptr(GList) info_children = gtk_container_get_children(GTK_CONTAINER(state->info_bar));
|
g_autoptr(GList) info_children = gtk_container_get_children(GTK_CONTAINER(state->info_bar));
|
||||||
g_signal_connect(info_children->data, "notify::child-revealed", G_CALLBACK(info_bar_animation_done), state);
|
g_signal_connect(info_children->data, "notify::child-revealed", G_CALLBACK(info_bar_animation_done), state);
|
||||||
@ -671,6 +1223,23 @@ static void activate(GtkApplication* app, gpointer user_data) {
|
|||||||
if (state->output_manager == NULL) {
|
if (state->output_manager == NULL) {
|
||||||
wd_fatal_error(1, "Compositor doesn't support wlr-output-management-unstable-v1");
|
wd_fatal_error(1, "Compositor doesn't support wlr-output-management-unstable-v1");
|
||||||
}
|
}
|
||||||
|
if (state->xdg_output_manager == NULL) {
|
||||||
|
wd_fatal_error(1, "Compositor doesn't support xdg-output-unstable-v1");
|
||||||
|
}
|
||||||
|
if (state->copy_manager == NULL) {
|
||||||
|
state->capture = false;
|
||||||
|
g_simple_action_set_state(capture_action, g_variant_new_boolean(state->capture));
|
||||||
|
g_simple_action_set_enabled(capture_action, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int n_monitors = gdk_display_get_n_monitors(gdk_display);
|
||||||
|
for (int i = 0; i < n_monitors; i++) {
|
||||||
|
GdkMonitor *monitor = gdk_display_get_monitor(gdk_display, i);
|
||||||
|
wd_add_output(state, gdk_wayland_monitor_get_wl_output(monitor));
|
||||||
|
}
|
||||||
|
|
||||||
|
g_signal_connect(gdk_display, "monitor-added", G_CALLBACK(monitor_added), state);
|
||||||
|
g_signal_connect(gdk_display, "monitor-removed", G_CALLBACK(monitor_removed), state);
|
||||||
|
|
||||||
gtk_application_add_window(app, GTK_WINDOW(window));
|
gtk_application_add_window(app, GTK_WINDOW(window));
|
||||||
gtk_widget_show_all(window);
|
gtk_widget_show_all(window);
|
||||||
|
@ -1,21 +1,27 @@
|
|||||||
|
|
||||||
cc = meson.get_compiler('c')
|
cc = meson.get_compiler('c')
|
||||||
m_dep = cc.find_library('m', required : false)
|
m_dep = cc.find_library('m', required : false)
|
||||||
|
rt_dep = cc.find_library('rt', required : false)
|
||||||
gdk = dependency('gdk-3.0')
|
gdk = dependency('gdk-3.0')
|
||||||
gtk = dependency('gtk+-3.0')
|
gtk = dependency('gtk+-3.0')
|
||||||
assert(gdk.get_pkgconfig_variable('targets').split().contains('wayland'), 'Wayland GDK backend not present')
|
assert(gdk.get_pkgconfig_variable('targets').split().contains('wayland'), 'Wayland GDK backend not present')
|
||||||
|
epoxy = dependency('epoxy')
|
||||||
|
|
||||||
executable(
|
executable(
|
||||||
'wdisplay',
|
'wdisplay',
|
||||||
[
|
[
|
||||||
'main.c',
|
'main.c',
|
||||||
'outputs.c',
|
'outputs.c',
|
||||||
|
'render.c',
|
||||||
|
'glviewport.c',
|
||||||
resources,
|
resources,
|
||||||
],
|
],
|
||||||
dependencies : [
|
dependencies : [
|
||||||
m_dep,
|
m_dep,
|
||||||
|
rt_dep,
|
||||||
wayland_client,
|
wayland_client,
|
||||||
client_protos,
|
client_protos,
|
||||||
|
epoxy,
|
||||||
gtk
|
gtk
|
||||||
],
|
],
|
||||||
install: true
|
install: true
|
||||||
|
349
src/outputs.c
349
src/outputs.c
@ -28,15 +28,26 @@
|
|||||||
* https://github.com/emersion/kanshi/blob/38d27474b686fcc8324cc5e454741a49577c0988/main.c
|
* https://github.com/emersion/kanshi/blob/38d27474b686fcc8324cc5e454741a49577c0988/main.c
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "wdisplay.h"
|
#include "wdisplay.h"
|
||||||
#include "wlr-output-management-unstable-v1-client-protocol.h"
|
|
||||||
|
|
||||||
#define HEADS_MAX 64
|
#include "wlr-output-management-unstable-v1-client-protocol.h"
|
||||||
|
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||||
|
#include "wlr-screencopy-unstable-v1-client-protocol.h"
|
||||||
|
|
||||||
|
static void noop() {
|
||||||
|
// This space is intentionally left blank
|
||||||
|
}
|
||||||
|
|
||||||
struct wd_pending_config {
|
struct wd_pending_config {
|
||||||
struct wd_state *state;
|
struct wd_state *state;
|
||||||
@ -143,6 +154,171 @@ void wd_apply_state(struct wd_state *state, struct wl_list *new_outputs) {
|
|||||||
zwlr_output_configuration_v1_apply(config);
|
zwlr_output_configuration_v1_apply(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void wd_frame_destroy(struct wd_frame *frame) {
|
||||||
|
if (frame->pixels != NULL)
|
||||||
|
munmap(frame->pixels, frame->height * frame->stride);
|
||||||
|
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);
|
||||||
|
|
||||||
|
wl_list_remove(&frame->link);
|
||||||
|
free(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
struct wd_frame *frame = data;
|
||||||
|
|
||||||
|
char *shm_name = NULL;
|
||||||
|
if (asprintf(&shm_name, "/wd-%s", frame->output->name) == -1) {
|
||||||
|
fprintf(stderr, "asprintf: %s\n", strerror(errno));
|
||||||
|
shm_name = NULL;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
frame->capture_fd = shm_open(shm_name, O_CREAT | O_RDWR, 0);
|
||||||
|
if (frame->capture_fd == -1) {
|
||||||
|
fprintf(stderr, "shm_open: %s\n", strerror(errno));
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
shm_unlink(shm_name);
|
||||||
|
free(shm_name);
|
||||||
|
|
||||||
|
size_t size = stride * height;
|
||||||
|
ftruncate(frame->capture_fd, size);
|
||||||
|
frame->pool = wl_shm_create_pool(frame->output->state->shm,
|
||||||
|
frame->capture_fd, 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->width = width;
|
||||||
|
frame->height = height;
|
||||||
|
|
||||||
|
return;
|
||||||
|
err:
|
||||||
|
if (shm_name != NULL) {
|
||||||
|
free(shm_name);
|
||||||
|
}
|
||||||
|
wd_frame_destroy(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void capture_ready(void *data,
|
||||||
|
struct zwlr_screencopy_frame_v1 *wlr_frame,
|
||||||
|
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);
|
||||||
|
if (frame->pixels == MAP_FAILED) {
|
||||||
|
frame->pixels = NULL;
|
||||||
|
fprintf(stderr, "mmap: %d: %s\n", frame->capture_fd, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void capture_failed(void *data,
|
||||||
|
struct zwlr_screencopy_frame_v1 *wlr_frame) {
|
||||||
|
struct wd_frame *frame = data;
|
||||||
|
wd_frame_destroy(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct zwlr_screencopy_frame_v1_listener capture_listener = {
|
||||||
|
.buffer = capture_buffer,
|
||||||
|
.flags = capture_flags,
|
||||||
|
.ready = capture_ready,
|
||||||
|
.failed = capture_failed
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool has_pending_captures(struct wd_state *state) {
|
||||||
|
struct wd_output *output;
|
||||||
|
wl_list_for_each(output, &state->outputs, link) {
|
||||||
|
struct wd_frame *frame;
|
||||||
|
wl_list_for_each(frame, &output->frames, link) {
|
||||||
|
if (frame->pixels == NULL) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wd_capture_frame(struct wd_state *state) {
|
||||||
|
if (state->copy_manager == NULL || has_pending_captures(state)
|
||||||
|
|| !state->capture) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wd_output *output;
|
||||||
|
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);
|
||||||
|
wl_list_insert(&output->frames, &frame->link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wd_output_destroy(struct wd_output *output) {
|
||||||
|
struct wd_frame *frame, *frame_tmp;
|
||||||
|
wl_list_for_each_safe(frame, frame_tmp, &output->frames, link) {
|
||||||
|
wd_frame_destroy(frame);
|
||||||
|
}
|
||||||
|
zxdg_output_v1_destroy(output->xdg_output);
|
||||||
|
free(output->name);
|
||||||
|
free(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wd_mode_destroy(struct wd_mode* mode) {
|
||||||
|
zwlr_output_mode_v1_destroy(mode->wlr_mode);
|
||||||
|
free(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wd_head_destroy(struct wd_head *head) {
|
||||||
|
struct wd_mode *mode, *mode_tmp;
|
||||||
|
if (head->state->clicked == head) {
|
||||||
|
head->state->clicked = NULL;
|
||||||
|
}
|
||||||
|
wl_list_for_each_safe(mode, mode_tmp, &head->modes, link) {
|
||||||
|
zwlr_output_mode_v1_destroy(mode->wlr_mode);
|
||||||
|
free(mode);
|
||||||
|
}
|
||||||
|
zwlr_output_head_v1_destroy(head->wlr_head);
|
||||||
|
free(head->name);
|
||||||
|
free(head->description);
|
||||||
|
free(head);
|
||||||
|
}
|
||||||
|
|
||||||
static void mode_handle_size(void *data, struct zwlr_output_mode_v1 *wlr_mode,
|
static void mode_handle_size(void *data, struct zwlr_output_mode_v1 *wlr_mode,
|
||||||
int32_t width, int32_t height) {
|
int32_t width, int32_t height) {
|
||||||
struct wd_mode *mode = data;
|
struct wd_mode *mode = data;
|
||||||
@ -166,8 +342,7 @@ static void mode_handle_finished(void *data,
|
|||||||
struct zwlr_output_mode_v1 *wlr_mode) {
|
struct zwlr_output_mode_v1 *wlr_mode) {
|
||||||
struct wd_mode *mode = data;
|
struct wd_mode *mode = data;
|
||||||
wl_list_remove(&mode->link);
|
wl_list_remove(&mode->link);
|
||||||
zwlr_output_mode_v1_destroy(mode->wlr_mode);
|
wd_mode_destroy(mode);
|
||||||
free(mode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct zwlr_output_mode_v1_listener mode_listener = {
|
static const struct zwlr_output_mode_v1_listener mode_listener = {
|
||||||
@ -217,6 +392,7 @@ static void head_handle_enabled(void *data,
|
|||||||
struct wd_head *head = data;
|
struct wd_head *head = data;
|
||||||
head->enabled = !!enabled;
|
head->enabled = !!enabled;
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
|
head->output = NULL;
|
||||||
head->mode = NULL;
|
head->mode = NULL;
|
||||||
}
|
}
|
||||||
wd_ui_reset_head(head, WD_FIELD_ENABLED);
|
wd_ui_reset_head(head, WD_FIELD_ENABLED);
|
||||||
@ -264,10 +440,7 @@ static void head_handle_finished(void *data,
|
|||||||
struct zwlr_output_head_v1 *wlr_head) {
|
struct zwlr_output_head_v1 *wlr_head) {
|
||||||
struct wd_head *head = data;
|
struct wd_head *head = data;
|
||||||
wl_list_remove(&head->link);
|
wl_list_remove(&head->link);
|
||||||
zwlr_output_head_v1_destroy(head->wlr_head);
|
wd_head_destroy(head);
|
||||||
free(head->name);
|
|
||||||
free(head->description);
|
|
||||||
free(head);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct zwlr_output_head_v1_listener head_listener = {
|
static const struct zwlr_output_head_v1_listener head_listener = {
|
||||||
@ -307,41 +480,169 @@ static void output_manager_handle_done(void *data,
|
|||||||
wd_ui_reset_heads(state);
|
wd_ui_reset_heads(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void output_manager_handle_finished(void *data,
|
|
||||||
struct zwlr_output_manager_v1 *manager) {
|
|
||||||
// This space is intentionally left blank
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct zwlr_output_manager_v1_listener output_manager_listener = {
|
static const struct zwlr_output_manager_v1_listener output_manager_listener = {
|
||||||
.head = output_manager_handle_head,
|
.head = output_manager_handle_head,
|
||||||
.done = output_manager_handle_done,
|
.done = output_manager_handle_done,
|
||||||
.finished = output_manager_handle_finished,
|
.finished = noop,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void registry_handle_global(void *data, struct wl_registry *registry,
|
static void registry_handle_global(void *data, struct wl_registry *registry,
|
||||||
uint32_t name, const char *interface, uint32_t version) {
|
uint32_t name, const char *interface, uint32_t version) {
|
||||||
struct wd_state *state = data;
|
struct wd_state *state = data;
|
||||||
|
|
||||||
if (strcmp(interface, zwlr_output_manager_v1_interface.name) == 0) {
|
if (strcmp(interface, zwlr_output_manager_v1_interface.name) == 0) {
|
||||||
state->output_manager = wl_registry_bind(registry, name, &zwlr_output_manager_v1_interface, 1);
|
state->output_manager = wl_registry_bind(registry, name,
|
||||||
zwlr_output_manager_v1_add_listener(state->output_manager, &output_manager_listener, state);
|
&zwlr_output_manager_v1_interface, version);
|
||||||
|
zwlr_output_manager_v1_add_listener(state->output_manager,
|
||||||
|
&output_manager_listener, state);
|
||||||
|
} 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_screencopy_manager_v1_interface.name) == 0) {
|
||||||
|
state->copy_manager = wl_registry_bind(registry, name,
|
||||||
|
&zwlr_screencopy_manager_v1_interface, version);
|
||||||
|
} else if(strcmp(interface, wl_shm_interface.name) == 0) {
|
||||||
|
state->shm = wl_registry_bind(registry, name, &wl_shm_interface, version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registry_handle_global_remove(void *data,
|
|
||||||
struct wl_registry *registry, uint32_t name) {
|
|
||||||
// This space is intentionally left blank
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct wl_registry_listener registry_listener = {
|
static const struct wl_registry_listener registry_listener = {
|
||||||
.global = registry_handle_global,
|
.global = registry_handle_global,
|
||||||
.global_remove = registry_handle_global_remove,
|
.global_remove = noop,
|
||||||
};
|
};
|
||||||
|
|
||||||
void wd_add_output_management_listener(struct wd_state *state, struct wl_display *display) {
|
void wd_add_output_management_listener(struct wd_state *state, struct
|
||||||
|
wl_display *display) {
|
||||||
struct wl_registry *registry = wl_display_get_registry(display);
|
struct wl_registry *registry = wl_display_get_registry(display);
|
||||||
wl_registry_add_listener(registry, ®istry_listener, state);
|
wl_registry_add_listener(registry, ®istry_listener, state);
|
||||||
|
|
||||||
wl_display_dispatch(display);
|
wl_display_dispatch(display);
|
||||||
wl_display_roundtrip(display);
|
wl_display_roundtrip(display);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct wd_head *wd_find_head(struct wd_state *state,
|
||||||
|
struct wd_output *output) {
|
||||||
|
struct wd_head *head;
|
||||||
|
wl_list_for_each(head, &state->heads, link) {
|
||||||
|
if (output->name != NULL && strcmp(output->name, head->name) == 0) {
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void output_logical_position(void *data, struct zxdg_output_v1 *zxdg_output_v1,
|
||||||
|
int32_t x, int32_t y) {
|
||||||
|
struct wd_output *output = data;
|
||||||
|
struct wd_head *head = wd_find_head(output->state, output);
|
||||||
|
if (head != NULL) {
|
||||||
|
head->x = x;
|
||||||
|
head->y = y;
|
||||||
|
wd_ui_reset_head(head, WD_FIELD_POSITION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void output_logical_size(void *data, struct zxdg_output_v1 *zxdg_output_v1,
|
||||||
|
int32_t width, int32_t height) {
|
||||||
|
struct wd_output *output = data;
|
||||||
|
struct wd_head *head = wd_find_head(output->state, output);
|
||||||
|
if (head != NULL) {
|
||||||
|
struct wd_mode *mode;
|
||||||
|
head->custom_mode.width = width;
|
||||||
|
head->custom_mode.height = height;
|
||||||
|
head->mode = NULL;
|
||||||
|
wl_list_for_each(mode, &head->modes, link) {
|
||||||
|
if (mode->width == width && mode->height == height) {
|
||||||
|
head->mode = mode;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wd_ui_reset_head(head, WD_FIELD_MODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void output_name(void *data, struct zxdg_output_v1 *zxdg_output_v1,
|
||||||
|
const char *name) {
|
||||||
|
struct wd_output *output = data;
|
||||||
|
output->name = strdup(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zxdg_output_v1_listener output_listener = {
|
||||||
|
.logical_position = output_logical_position,
|
||||||
|
.logical_size = output_logical_size,
|
||||||
|
.done = noop,
|
||||||
|
.name = output_name,
|
||||||
|
.description = noop
|
||||||
|
};
|
||||||
|
|
||||||
|
void wd_add_output(struct wd_state *state, struct wl_output *wl_output) {
|
||||||
|
struct wd_output *output = calloc(1, sizeof(*output));
|
||||||
|
output->state = state;
|
||||||
|
output->wl_output = wl_output;
|
||||||
|
output->xdg_output = zxdg_output_manager_v1_get_xdg_output(
|
||||||
|
state->xdg_output_manager, wl_output);
|
||||||
|
wl_list_init(&output->frames);
|
||||||
|
zxdg_output_v1_add_listener(output->xdg_output, &output_listener, output);
|
||||||
|
wl_list_insert(&output->state->outputs, &output->link);
|
||||||
|
}
|
||||||
|
|
||||||
|
void wd_remove_output(struct wd_state *state, struct wl_output *wl_output,
|
||||||
|
struct wl_display *display) {
|
||||||
|
struct wd_output *output, *output_tmp;
|
||||||
|
wl_list_for_each_safe(output, output_tmp, &state->outputs, link) {
|
||||||
|
if (output->wl_output == wl_output) {
|
||||||
|
wl_list_remove(&output->link);
|
||||||
|
wd_output_destroy(output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wd_capture_wait(state, display);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wd_output *wd_find_output(struct wd_state *state, struct wd_head
|
||||||
|
*head) {
|
||||||
|
if (!head->enabled) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (head->output != NULL) {
|
||||||
|
return head->output;
|
||||||
|
}
|
||||||
|
struct wd_output *output;
|
||||||
|
wl_list_for_each(output, &state->outputs, link) {
|
||||||
|
if (output->name != NULL && strcmp(output->name, head->name) == 0) {
|
||||||
|
head->output = output;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
head->output = NULL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wd_state *wd_state_create(void) {
|
||||||
|
struct wd_state *state = calloc(1, sizeof(*state));
|
||||||
|
state->zoom = 1.;
|
||||||
|
state->capture = true;
|
||||||
|
wl_list_init(&state->heads);
|
||||||
|
wl_list_init(&state->outputs);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wd_capture_wait(struct wd_state *state, struct wl_display *display) {
|
||||||
|
wl_display_flush(display);
|
||||||
|
while (has_pending_captures(state)) {
|
||||||
|
if (wl_display_dispatch(display) == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void wd_state_destroy(struct wd_state *state) {
|
||||||
|
struct wd_head *head, *head_tmp;
|
||||||
|
wl_list_for_each_safe(head, head_tmp, &state->heads, link) {
|
||||||
|
wd_head_destroy(head);
|
||||||
|
}
|
||||||
|
struct wd_output *output, *output_tmp;
|
||||||
|
wl_list_for_each_safe(output, output_tmp, &state->outputs, link) {
|
||||||
|
wd_output_destroy(output);
|
||||||
|
}
|
||||||
|
free(state);
|
||||||
|
}
|
||||||
|
360
src/render.c
Normal file
360
src/render.c
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 cyclopsian
|
||||||
|
|
||||||
|
* 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 X CONSORTIUM BE LIABLE FOR ANY
|
||||||
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "wdisplay.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <epoxy/gl.h>
|
||||||
|
|
||||||
|
#define CANVAS_MARGIN 100
|
||||||
|
|
||||||
|
#define BT_UV_VERT_SIZE (2 + 2)
|
||||||
|
#define BT_UV_QUAD_SIZE (6 * BT_UV_VERT_SIZE)
|
||||||
|
#define BT_UV_MAX (BT_COLOR_QUAD_SIZE * HEADS_MAX)
|
||||||
|
|
||||||
|
#define BT_COLOR_VERT_SIZE (2 + 4)
|
||||||
|
#define BT_COLOR_QUAD_SIZE (6 * BT_COLOR_VERT_SIZE)
|
||||||
|
#define BT_COLOR_MAX (BT_COLOR_QUAD_SIZE * HEADS_MAX)
|
||||||
|
|
||||||
|
struct wd_gl_data {
|
||||||
|
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 buffers[2];
|
||||||
|
|
||||||
|
unsigned texture_count;
|
||||||
|
GLuint textures[HEADS_MAX];
|
||||||
|
|
||||||
|
float tris[BT_COLOR_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *color_vertex_shader_src = "\
|
||||||
|
attribute vec2 position;\n\
|
||||||
|
attribute vec4 color;\n\
|
||||||
|
varying vec4 color_out;\n\
|
||||||
|
uniform vec2 screen_size;\n\
|
||||||
|
void main(void) {\n\
|
||||||
|
vec2 screen_pos = (position / screen_size * 2. - 1.) * vec2(1., -1.);\n\
|
||||||
|
gl_Position = vec4(screen_pos, 0., 1.);\n\
|
||||||
|
color_out = color;\n\
|
||||||
|
}";
|
||||||
|
|
||||||
|
static const char *color_fragment_shader_src = "\
|
||||||
|
varying vec4 color_out;\n\
|
||||||
|
void main(void) {\n\
|
||||||
|
gl_FragColor = color_out;\n\
|
||||||
|
}";
|
||||||
|
|
||||||
|
static const char *texture_vertex_shader_src = "\
|
||||||
|
attribute vec2 position;\n\
|
||||||
|
attribute vec2 uv;\n\
|
||||||
|
varying vec2 uv_out;\n\
|
||||||
|
uniform vec2 screen_size;\n\
|
||||||
|
void main(void) {\n\
|
||||||
|
vec2 screen_pos = (position / screen_size * 2. - 1.) * vec2(1., -1.);\n\
|
||||||
|
gl_Position = vec4(screen_pos, 0., 1.);\n\
|
||||||
|
uv_out = uv;\n\
|
||||||
|
}";
|
||||||
|
|
||||||
|
static const char *texture_fragment_shader_src = "\
|
||||||
|
varying vec2 uv_out;\n\
|
||||||
|
uniform sampler2D texture;\n\
|
||||||
|
void main(void) {\n\
|
||||||
|
gl_FragColor = texture2D(texture, uv_out);\n\
|
||||||
|
}";
|
||||||
|
|
||||||
|
static GLuint gl_make_shader(GLenum type, const char *src) {
|
||||||
|
GLuint shader = glCreateShader(type);
|
||||||
|
glShaderSource(shader, 1, &src, NULL);
|
||||||
|
glCompileShader(shader);
|
||||||
|
GLint status;
|
||||||
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||||
|
if (status == GL_FALSE) {
|
||||||
|
GLsizei length;
|
||||||
|
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
|
||||||
|
GLchar *log = "Failed";
|
||||||
|
if (length > 0) {
|
||||||
|
log = malloc(length);
|
||||||
|
glGetShaderInfoLog(shader, length, NULL, log);
|
||||||
|
}
|
||||||
|
fprintf(stderr, "glCompileShader: %s\n", log);
|
||||||
|
if (length > 0) {
|
||||||
|
free(log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gl_link_and_validate(GLint program) {
|
||||||
|
GLint status;
|
||||||
|
|
||||||
|
glLinkProgram(program);
|
||||||
|
glGetProgramiv(program, GL_LINK_STATUS, &status);
|
||||||
|
if (status == GL_FALSE) {
|
||||||
|
GLsizei length;
|
||||||
|
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
|
||||||
|
GLchar *log = malloc(length);
|
||||||
|
glGetProgramInfoLog(program, length, NULL, log);
|
||||||
|
fprintf(stderr, "glLinkProgram: %s\n", log);
|
||||||
|
free(log);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
glValidateProgram(program);
|
||||||
|
glGetProgramiv(program, GL_VALIDATE_STATUS, &status);
|
||||||
|
if (status == GL_FALSE) {
|
||||||
|
GLsizei length;
|
||||||
|
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
|
||||||
|
GLchar *log = malloc(length);
|
||||||
|
glGetProgramInfoLog(program, length, NULL, log);
|
||||||
|
fprintf(stderr, "glValidateProgram: %s\n", log);
|
||||||
|
free(log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wd_gl_data *wd_gl_setup(void) {
|
||||||
|
struct wd_gl_data *res = calloc(1, sizeof(struct wd_gl_data));
|
||||||
|
res->color_program = glCreateProgram();
|
||||||
|
|
||||||
|
res->color_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,
|
||||||
|
color_fragment_shader_src);
|
||||||
|
glAttachShader(res->color_program, res->color_fragment_shader);
|
||||||
|
gl_link_and_validate(res->color_program);
|
||||||
|
|
||||||
|
res->color_position_attribute = glGetAttribLocation(res->color_program,
|
||||||
|
"position");
|
||||||
|
res->color_color_attribute = glGetAttribLocation(res->color_program,
|
||||||
|
"color");
|
||||||
|
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");
|
||||||
|
|
||||||
|
glGenBuffers(2, res->buffers);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, res->buffers[0]);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, BT_UV_MAX * sizeof(float),
|
||||||
|
NULL, GL_DYNAMIC_DRAW);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, res->buffers[1]);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, BT_COLOR_MAX * sizeof(float),
|
||||||
|
NULL, GL_DYNAMIC_DRAW);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define PUSH_POINT(_start, _a, _b) \
|
||||||
|
*((_start)++) = (_a);\
|
||||||
|
*((_start)++) = (_b);
|
||||||
|
|
||||||
|
#define PUSH_COLOR(_start, _a, _b, _c, _d) \
|
||||||
|
*((_start)++) = (_a);\
|
||||||
|
*((_start)++) = (_b);\
|
||||||
|
*((_start)++) = (_c);\
|
||||||
|
*((_start)++) = (_d);
|
||||||
|
|
||||||
|
#define PUSH_POINT_UV(_start, _a, _b, _c, _d) \
|
||||||
|
PUSH_COLOR(_start, _a, _b, _c, _d)
|
||||||
|
|
||||||
|
void wd_gl_render(struct wd_gl_data *res, struct wd_render_data *info,
|
||||||
|
uint64_t tick) {
|
||||||
|
unsigned int tris = 0;
|
||||||
|
|
||||||
|
if (info->head_count > res->texture_count) {
|
||||||
|
glGenTextures(info->head_count - res->texture_count,
|
||||||
|
res->textures + res->texture_count);
|
||||||
|
for (int i = res->texture_count; i < info->head_count; i++) {
|
||||||
|
glBindTexture(GL_TEXTURE_2D, res->textures[i]);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
res->texture_count = info->head_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < info->head_count; i++) {
|
||||||
|
struct wd_render_head_data *head = &info->heads[i];
|
||||||
|
float *tri_ptr = res->tris + i * BT_UV_QUAD_SIZE;
|
||||||
|
float x1 = head->x1;
|
||||||
|
float y1 = head->y1;
|
||||||
|
float x2 = head->x2;
|
||||||
|
float y2 = head->y2;
|
||||||
|
|
||||||
|
float t1 = head->y_invert ? 1.f : 0.f;
|
||||||
|
float t2 = head->y_invert ? 0.f : 1.f;
|
||||||
|
|
||||||
|
PUSH_POINT_UV(tri_ptr, x1, y1, 0.f, t1)
|
||||||
|
PUSH_POINT_UV(tri_ptr, x2, y1, 1.f, t1)
|
||||||
|
PUSH_POINT_UV(tri_ptr, x1, y2, 0.f, t2)
|
||||||
|
PUSH_POINT_UV(tri_ptr, x1, y2, 0.f, t2)
|
||||||
|
PUSH_POINT_UV(tri_ptr, x2, y1, 1.f, t1)
|
||||||
|
PUSH_POINT_UV(tri_ptr, x2, y2, 1.f, t2)
|
||||||
|
|
||||||
|
tris += 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
glClearColor(info->bg_color[0], info->bg_color[1], info->bg_color[2], 1.f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
float screen_size[2] = { info->viewport_width, info->viewport_height };
|
||||||
|
|
||||||
|
if (tris > 0) {
|
||||||
|
glUseProgram(res->texture_program);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, res->buffers[0]);
|
||||||
|
glBufferSubData(GL_ARRAY_BUFFER, 0,
|
||||||
|
tris * BT_UV_VERT_SIZE * sizeof(float), res->tris);
|
||||||
|
glEnableVertexAttribArray(res->texture_position_attribute);
|
||||||
|
glEnableVertexAttribArray(res->texture_uv_attribute);
|
||||||
|
glVertexAttribPointer(res->texture_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,
|
||||||
|
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);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
|
||||||
|
for (int i = 0; i < info->head_count; i++) {
|
||||||
|
struct wd_render_head_data *head = &info->heads[i];
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
glDrawArrays(GL_TRIANGLES, i * 6, 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tris = 0;
|
||||||
|
|
||||||
|
int j = 0;
|
||||||
|
for (int i = 0; i < info->head_count; i++) {
|
||||||
|
struct wd_render_head_data *head = &info->heads[i];
|
||||||
|
if (head->hovered || tick < head->transition_begin + HOVER_USECS) {
|
||||||
|
float *tri_ptr = res->tris + j++ * BT_COLOR_QUAD_SIZE;
|
||||||
|
float x1 = head->x1;
|
||||||
|
float y1 = head->y1;
|
||||||
|
float x2 = head->x2;
|
||||||
|
float y2 = head->y2;
|
||||||
|
|
||||||
|
float *color = info->selection_color;
|
||||||
|
float d = fminf(
|
||||||
|
(tick - head->transition_begin) / (double) HOVER_USECS, 1.f);
|
||||||
|
if (!head->hovered) {
|
||||||
|
d = 1.f - d;
|
||||||
|
}
|
||||||
|
d *= 2.f;
|
||||||
|
if (d <= 1.f) {
|
||||||
|
d = d * d;
|
||||||
|
} else {
|
||||||
|
d -= 1.f;
|
||||||
|
d = d * (2.f - d) + 1.f;
|
||||||
|
}
|
||||||
|
d /= 2.f;
|
||||||
|
float alpha = color[3] * d * .5f;
|
||||||
|
|
||||||
|
PUSH_POINT(tri_ptr, x1, y1)
|
||||||
|
PUSH_COLOR(tri_ptr, color[0], color[1], color[2], alpha)
|
||||||
|
PUSH_POINT(tri_ptr, x2, y1)
|
||||||
|
PUSH_COLOR(tri_ptr, color[0], color[1], color[2], alpha)
|
||||||
|
PUSH_POINT(tri_ptr, x1, y2)
|
||||||
|
PUSH_COLOR(tri_ptr, color[0], color[1], color[2], alpha)
|
||||||
|
PUSH_POINT(tri_ptr, x1, y2)
|
||||||
|
PUSH_COLOR(tri_ptr, color[0], color[1], color[2], alpha)
|
||||||
|
PUSH_POINT(tri_ptr, x2, y1)
|
||||||
|
PUSH_COLOR(tri_ptr, color[0], color[1], color[2], alpha)
|
||||||
|
PUSH_POINT(tri_ptr, x2, y2)
|
||||||
|
PUSH_COLOR(tri_ptr, color[0], color[1], color[2], alpha)
|
||||||
|
|
||||||
|
tris += 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tris > 0) {
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
glUseProgram(res->color_program);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, res->buffers[1]);
|
||||||
|
glBufferSubData(GL_ARRAY_BUFFER, 0,
|
||||||
|
tris * BT_COLOR_VERT_SIZE * sizeof(float), res->tris);
|
||||||
|
glEnableVertexAttribArray(res->color_position_attribute);
|
||||||
|
glEnableVertexAttribArray(res->color_color_attribute);
|
||||||
|
glVertexAttribPointer(res->color_position_attribute, 2, GL_FLOAT, GL_FALSE,
|
||||||
|
BT_COLOR_VERT_SIZE * sizeof(float), (void *) (0 * sizeof(float)));
|
||||||
|
glVertexAttribPointer(res->color_color_attribute, 4, GL_FLOAT, GL_FALSE,
|
||||||
|
BT_COLOR_VERT_SIZE * sizeof(float), (void *) (2 * sizeof(float)));
|
||||||
|
glUniform2fv(res->color_screen_size_uniform, 1, screen_size);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, tris);
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void wd_gl_cleanup(struct wd_gl_data *res) {
|
||||||
|
glDeleteBuffers(2, 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);
|
||||||
|
|
||||||
|
free(res);
|
||||||
|
}
|
159
src/wdisplay.h
159
src/wdisplay.h
@ -31,16 +31,28 @@
|
|||||||
#ifndef WDISPLAY_WDISPLAY_H
|
#ifndef WDISPLAY_WDISPLAY_H
|
||||||
#define WDISPLAY_WDISPLAY_H
|
#define WDISPLAY_WDISPLAY_H
|
||||||
|
|
||||||
|
#define HEADS_MAX 64
|
||||||
|
#define HOVER_USECS (100 * 1000)
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <wayland-client.h>
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
struct zxdg_output_v1;
|
||||||
|
struct zxdg_output_manager_v1;
|
||||||
struct zwlr_output_mode_v1;
|
struct zwlr_output_mode_v1;
|
||||||
struct zwlr_output_head_v1;
|
struct zwlr_output_head_v1;
|
||||||
struct zwlr_output_manager_v1;
|
struct zwlr_output_manager_v1;
|
||||||
|
struct zwlr_screencopy_manager_v1;
|
||||||
|
struct zwlr_screencopy_frame_v1;
|
||||||
|
|
||||||
struct _GtkWidget;
|
struct _GtkWidget;
|
||||||
typedef struct _GtkWidget GtkWidget;
|
typedef struct _GtkWidget GtkWidget;
|
||||||
struct _GtkBuilder;
|
struct _GtkBuilder;
|
||||||
typedef struct _GtkBuilder GtkBuilder;
|
typedef struct _GtkBuilder GtkBuilder;
|
||||||
|
struct _GdkCursor;
|
||||||
|
typedef struct _GdkCursor GdkCursor;
|
||||||
|
struct _cairo_surface;
|
||||||
|
typedef struct _cairo_surface cairo_surface_t;
|
||||||
|
|
||||||
enum wd_head_fields {
|
enum wd_head_fields {
|
||||||
WD_FIELD_NAME = 1 << 0,
|
WD_FIELD_NAME = 1 << 0,
|
||||||
@ -54,6 +66,32 @@ enum wd_head_fields {
|
|||||||
WD_FIELDS_ALL = (1 << 8) - 1
|
WD_FIELDS_ALL = (1 << 8) - 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct wd_output {
|
||||||
|
struct wd_state *state;
|
||||||
|
struct zxdg_output_v1 *xdg_output;
|
||||||
|
struct wl_output *wl_output;
|
||||||
|
struct wl_list link;
|
||||||
|
|
||||||
|
char *name;
|
||||||
|
struct wl_list frames;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wd_frame {
|
||||||
|
struct wd_output *output;
|
||||||
|
struct zwlr_screencopy_frame_v1 *wlr_frame;
|
||||||
|
|
||||||
|
struct wl_list link;
|
||||||
|
int capture_fd;
|
||||||
|
unsigned stride;
|
||||||
|
unsigned width;
|
||||||
|
unsigned height;
|
||||||
|
struct wl_shm_pool *pool;
|
||||||
|
struct wl_buffer *buffer;
|
||||||
|
uint8_t *pixels;
|
||||||
|
uint64_t tick;
|
||||||
|
bool y_invert;
|
||||||
|
};
|
||||||
|
|
||||||
struct wd_head_config {
|
struct wd_head_config {
|
||||||
struct wl_list link;
|
struct wl_list link;
|
||||||
|
|
||||||
@ -83,6 +121,10 @@ struct wd_head {
|
|||||||
struct zwlr_output_head_v1 *wlr_head;
|
struct zwlr_output_head_v1 *wlr_head;
|
||||||
struct wl_list link;
|
struct wl_list link;
|
||||||
|
|
||||||
|
struct wd_output *output;
|
||||||
|
struct wd_render_head_data *render;
|
||||||
|
cairo_surface_t *surface;
|
||||||
|
|
||||||
char *name, *description;
|
char *name, *description;
|
||||||
int32_t phys_width, phys_height; // mm
|
int32_t phys_width, phys_height; // mm
|
||||||
struct wl_list modes;
|
struct wl_list modes;
|
||||||
@ -98,16 +140,69 @@ struct wd_head {
|
|||||||
double scale;
|
double scale;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct wd_gl_data;
|
||||||
|
|
||||||
|
struct wd_render_head_data {
|
||||||
|
float x1;
|
||||||
|
float y1;
|
||||||
|
float x2;
|
||||||
|
float y2;
|
||||||
|
|
||||||
|
uint8_t *pixels;
|
||||||
|
unsigned tex_stride;
|
||||||
|
unsigned tex_width;
|
||||||
|
unsigned tex_height;
|
||||||
|
bool preview;
|
||||||
|
bool y_invert;
|
||||||
|
uint64_t updated_at;
|
||||||
|
|
||||||
|
bool hovered;
|
||||||
|
uint64_t transition_begin;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wd_render_data {
|
||||||
|
float fg_color[4];
|
||||||
|
float bg_color[4];
|
||||||
|
float border_color[4];
|
||||||
|
float selection_color[4];
|
||||||
|
unsigned int viewport_width;
|
||||||
|
unsigned int viewport_height;
|
||||||
|
unsigned int width;
|
||||||
|
unsigned int height;
|
||||||
|
int scroll_x;
|
||||||
|
int scroll_y;
|
||||||
|
int x_origin;
|
||||||
|
int y_origin;
|
||||||
|
uint64_t updated_at;
|
||||||
|
|
||||||
|
unsigned int head_count;
|
||||||
|
struct wd_render_head_data heads[HEADS_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wd_point {
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
};
|
||||||
|
|
||||||
struct wd_state {
|
struct wd_state {
|
||||||
|
struct zxdg_output_manager_v1 *xdg_output_manager;
|
||||||
struct zwlr_output_manager_v1 *output_manager;
|
struct zwlr_output_manager_v1 *output_manager;
|
||||||
|
struct zwlr_screencopy_manager_v1 *copy_manager;
|
||||||
|
struct wl_shm *shm;
|
||||||
struct wl_list heads;
|
struct wl_list heads;
|
||||||
|
struct wl_list outputs;
|
||||||
uint32_t serial;
|
uint32_t serial;
|
||||||
|
|
||||||
bool apply_pending;
|
bool apply_pending;
|
||||||
bool autoapply;
|
bool autoapply;
|
||||||
|
bool capture;
|
||||||
double zoom;
|
double zoom;
|
||||||
int xorigin;
|
|
||||||
int yorigin;
|
struct wd_head *clicked;
|
||||||
|
/* top left, bottom right */
|
||||||
|
struct wd_point click_offset;
|
||||||
|
bool panning;
|
||||||
|
struct wd_point pan_last;
|
||||||
|
|
||||||
GtkWidget *header_stack;
|
GtkWidget *header_stack;
|
||||||
GtkWidget *stack_switcher;
|
GtkWidget *stack_switcher;
|
||||||
@ -122,13 +217,47 @@ struct wd_state {
|
|||||||
GtkWidget *info_bar;
|
GtkWidget *info_bar;
|
||||||
GtkWidget *info_label;
|
GtkWidget *info_label;
|
||||||
GtkWidget *menu_button;
|
GtkWidget *menu_button;
|
||||||
|
|
||||||
|
GdkCursor *grab_cursor;
|
||||||
|
GdkCursor *grabbing_cursor;
|
||||||
|
GdkCursor *move_cursor;
|
||||||
|
|
||||||
|
unsigned int canvas_tick;
|
||||||
|
struct wd_gl_data *gl_data;
|
||||||
|
struct wd_render_data render;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Creates the application state structure.
|
||||||
|
*/
|
||||||
|
struct wd_state *wd_state_create(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Frees the application state structure.
|
||||||
|
*/
|
||||||
|
void wd_state_destroy(struct wd_state *state);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Displays an error message and then exits the program.
|
* Displays an error message and then exits the program.
|
||||||
*/
|
*/
|
||||||
void wd_fatal_error(int status, const char *message);
|
void wd_fatal_error(int status, const char *message);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add an output to the list of screen captured outputs.
|
||||||
|
*/
|
||||||
|
void wd_add_output(struct wd_state *state, struct wl_output *wl_output);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove an output from the list of screen captured outputs.
|
||||||
|
*/
|
||||||
|
void wd_remove_output(struct wd_state *state, struct wl_output *wl_output, struct wl_display *display);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Finds the output associated with a given head. Can return NULL if the head's
|
||||||
|
* output is disabled.
|
||||||
|
*/
|
||||||
|
struct wd_output *wd_find_output(struct wd_state *state, struct wd_head *head);
|
||||||
/*
|
/*
|
||||||
* Starts listening for output management events from the compositor.
|
* Starts listening for output management events from the compositor.
|
||||||
*/
|
*/
|
||||||
@ -139,6 +268,16 @@ void wd_add_output_management_listener(struct wd_state *state, struct wl_display
|
|||||||
*/
|
*/
|
||||||
void wd_apply_state(struct wd_state *state, struct wl_list *new_outputs);
|
void wd_apply_state(struct wd_state *state, struct wl_list *new_outputs);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Queues capture of the next frame of all screens.
|
||||||
|
*/
|
||||||
|
void wd_capture_frame(struct wd_state *state);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Blocks until all captures are finished.
|
||||||
|
*/
|
||||||
|
void wd_capture_wait(struct wd_state *state, struct wl_display *display);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Updates the UI stack of all heads. Does not update individual head forms.
|
* Updates the UI stack of all heads. Does not update individual head forms.
|
||||||
* Useful for when a display is plugged/unplugged and we want to add/remove
|
* Useful for when a display is plugged/unplugged and we want to add/remove
|
||||||
@ -147,7 +286,8 @@ void wd_apply_state(struct wd_state *state, struct wl_list *new_outputs);
|
|||||||
void wd_ui_reset_heads(struct wd_state *state);
|
void wd_ui_reset_heads(struct wd_state *state);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Updates a form with head configuration from the server. Only updates specified fields.
|
* 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, unsigned int fields);
|
||||||
|
|
||||||
@ -166,4 +306,17 @@ void wd_ui_apply_done(struct wd_state *state, struct wl_list *outputs);
|
|||||||
*/
|
*/
|
||||||
void wd_ui_show_error(struct wd_state *state, const char *message);
|
void wd_ui_show_error(struct wd_state *state, const char *message);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compiles the GL shaders.
|
||||||
|
*/
|
||||||
|
struct wd_gl_data *wd_gl_setup(void);
|
||||||
|
/*
|
||||||
|
* Renders the GL scene.
|
||||||
|
*/
|
||||||
|
void wd_gl_render(struct wd_gl_data *res, struct wd_render_data *info, uint64_t tick);
|
||||||
|
/*
|
||||||
|
* Destroys the GL shaders.
|
||||||
|
*/
|
||||||
|
void wd_gl_cleanup(struct wd_gl_data *res);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user