Compare commits
4 Commits
Author | SHA1 | Date |
---|---|---|
Jason Francis | dd7e1e22ee | |
Jason Francis | 0ed52013dd | |
Jason Francis | f3f2b9e9e2 | |
Jason Francis | 71ce9741d1 |
|
@ -8,7 +8,7 @@ Copyright: 2020 Jason Francis <jason@cycles.network>
|
|||
License: CC0-1.0
|
||||
|
||||
Files: resources/style.css resources/wdisplays.desktop.in resources/*.ui
|
||||
resources/resources.xml
|
||||
resources/resources.xml.in
|
||||
Copyright: 2020 Jason Francis <jason@cycles.network>
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
|
|
37
README.md
37
README.md
|
@ -4,13 +4,27 @@
|
|||
|
||||
wdisplays is a graphical application for configuring displays in Wayland
|
||||
compositors. It borrows some code from [kanshi]. It should work in any
|
||||
compositor that implements the wlr-output-management-unstable-v1 protocol,
|
||||
including [sway]. The goal of this project is to allow precise adjustment of
|
||||
display settings in kiosks, digital signage, and other elaborate multi-monitor
|
||||
setups.
|
||||
compositor that implements the wlr-output-management-unstable-v1 protocol.
|
||||
Compositors that are known to support the protocol are [Sway] and [Wayfire].
|
||||
The goal of this project is to allow precise adjustment of display settings in
|
||||
kiosks, digital signage, and other elaborate multi-monitor setups.
|
||||
|
||||
![Screenshot](wdisplays.png)
|
||||
|
||||
# Installation
|
||||
|
||||
[![Repology][repology-img]][repology-pkg]
|
||||
|
||||
Check your distro for a `wdisplays` package. Known distro packages:
|
||||
|
||||
- [Alpine](https://pkgs.alpinelinux.org/package/edge/testing/x86_64/wdisplays)
|
||||
- [Arch](https://aur.archlinux.org/packages/wdisplays-git/)
|
||||
- [Debian](https://packages.debian.org/sid/wdisplays)
|
||||
- [Fedora](https://copr.fedorainfracloud.org/coprs/wef/wdisplays/)
|
||||
- [FreeBSD](https://svnweb.freebsd.org/ports/head/x11/wdisplays/)
|
||||
- [Nix](https://github.com/NixOS/nixpkgs/tree/master/pkgs/tools/graphics/wdisplays)
|
||||
- [OpenSUSE](https://build.opensuse.org/package/show/home%3AMWh3/wdisplays)
|
||||
|
||||
# Building
|
||||
|
||||
Build requirements are:
|
||||
|
@ -26,8 +40,6 @@ ninja -C build
|
|||
sudo ninja -C build install
|
||||
```
|
||||
|
||||
Binaries are not available. Only building from source is supported.
|
||||
|
||||
# Usage
|
||||
|
||||
Displays can be moved around the virtual screen space by clicking and dragging
|
||||
|
@ -60,10 +72,19 @@ file. See man `sway-output`. If you want to have multiple configurations
|
|||
depending on the monitors connected, you'll need to use an external program
|
||||
like [kanshi]. Integration with that and other external daemons is planned.
|
||||
|
||||
### How do I add support to my compositor?
|
||||
|
||||
A minimal amount of code (approximately 150-200 LOC) is currently required to
|
||||
get support for this in wlroots compositors. See the diff here for a sample
|
||||
implementation on top of tinywl: [tinywl-output-management].
|
||||
|
||||
[kanshi]: https://github.com/emersion/kanshi
|
||||
[sway]: https://github.com/swaywm/sway
|
||||
[Sway]: https://swaywm.org
|
||||
[Wayfire]: https://wayfire.org
|
||||
[ARandR]: https://christian.amsuess.com/tools/arandr/
|
||||
[tinywl-output-management]: https://git.sr.ht/~jf/tinywl-output-management/commit/87a45d89ae0e7975e2a59f84e960380dd2f5ac08
|
||||
|
||||
[license-img]: https://img.shields.io/badge/License-GPL%203.0%20or%20later-blue.svg?logo=gnu
|
||||
[license-spdx]: https://spdx.org/licenses/GPL-3.0-or-later.html
|
||||
|
||||
[repology-img]: https://repology.org/badge/tiny-repos/wdisplays.svg
|
||||
[repology-pkg]: https://repology.org/project/wdisplays/versions
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
|
||||
project('network.cycles.wdisplays', 'c', license: 'MIT', version: '1.0')
|
||||
|
||||
conf = configuration_data()
|
||||
conf.set('app_id', meson.project_name())
|
||||
conf.set('version', meson.project_version())
|
||||
conf = configuration_data({
|
||||
'app_id': meson.project_name(),
|
||||
'version': meson.project_version(),
|
||||
'resource_prefix': '/' / '/'.join(meson.project_name().split('.')),
|
||||
})
|
||||
|
||||
subdir('protocol')
|
||||
subdir('resources')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.0 -->
|
||||
<!-- Generated with glade 3.22.2 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<object class="GtkAdjustment" id="height_adjustment">
|
||||
|
@ -7,23 +7,6 @@
|
|||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="GtkPopover" id="modes">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="mode_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_start">10</property>
|
||||
<property name="margin_end">10</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="pos_x_adjustment">
|
||||
<property name="upper">16383</property>
|
||||
<property name="step_increment">1</property>
|
||||
|
@ -50,7 +33,7 @@
|
|||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="GtkGrid" id="form">
|
||||
<template class="WdHeadForm" parent="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_start">8</property>
|
||||
|
@ -69,7 +52,7 @@
|
|||
<property name="halign">start</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="enabled" swapped="no"/>
|
||||
<signal name="toggled" handler="enabled_toggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
|
@ -99,7 +82,7 @@
|
|||
<property name="adjustment">scale_adjustment</property>
|
||||
<property name="digits">2</property>
|
||||
<property name="value">1</property>
|
||||
<signal name="change-value" handler="scale" swapped="no"/>
|
||||
<signal name="value-changed" handler="position_spin_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
|
@ -194,6 +177,7 @@
|
|||
<property name="spacing">8</property>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="refresh">
|
||||
<property name="name">refresh</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="width_chars">9</property>
|
||||
|
@ -202,6 +186,7 @@
|
|||
<property name="digits">3</property>
|
||||
<property name="numeric">True</property>
|
||||
<property name="update_policy">if-valid</property>
|
||||
<signal name="value-changed" handler="mode_spin_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
@ -245,7 +230,6 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="popover">transforms</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
|
@ -277,7 +261,7 @@
|
|||
<property name="halign">start</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="flipped" swapped="no"/>
|
||||
<signal name="toggled" handler="flipped_toggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
|
@ -299,6 +283,7 @@
|
|||
<property name="adjustment">pos_x_adjustment</property>
|
||||
<property name="numeric">True</property>
|
||||
<property name="update_policy">if-valid</property>
|
||||
<signal name="value-changed" handler="position_spin_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
|
@ -315,6 +300,7 @@
|
|||
<property name="adjustment">pos_y_adjustment</property>
|
||||
<property name="numeric">True</property>
|
||||
<property name="update_policy">if-valid</property>
|
||||
<signal name="value-changed" handler="position_spin_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
|
@ -323,6 +309,7 @@
|
|||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="width">
|
||||
<property name="name">width</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="width_chars">4</property>
|
||||
|
@ -331,6 +318,7 @@
|
|||
<property name="adjustment">width_adjustment</property>
|
||||
<property name="numeric">True</property>
|
||||
<property name="update_policy">if-valid</property>
|
||||
<signal name="value-changed" handler="mode_spin_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
|
@ -351,6 +339,7 @@
|
|||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="height">
|
||||
<property name="name">height</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="width_chars">4</property>
|
||||
|
@ -359,6 +348,7 @@
|
|||
<property name="adjustment">height_adjustment</property>
|
||||
<property name="numeric">True</property>
|
||||
<property name="update_policy">if-valid</property>
|
||||
<signal name="value-changed" handler="mode_spin_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
|
@ -371,9 +361,7 @@
|
|||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Select Mode Preset</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_start">8</property>
|
||||
<property name="popover">modes</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
|
@ -406,76 +394,5 @@
|
|||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkPopover" id="transforms">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="relative_to">rotate_button</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_start">10</property>
|
||||
<property name="margin_end">10</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="rotate_0">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">transform.rotate_0</property>
|
||||
<property name="text" translatable="yes">Don't Rotate</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="rotate_90">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">transform.rotate_90</property>
|
||||
<property name="text" translatable="yes">Rotate 90°</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="rotate_180">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">transform.rotate_180</property>
|
||||
<property name="text" translatable="yes">Rotate 180°</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="rotate_270">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">transform.rotate_270</property>
|
||||
<property name="text" translatable="yes">Rotate 270°</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</template>
|
||||
</interface>
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
# SPDX-FileCopyrightText: 2020 Jason Francis <jason@cycles.network>
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
resources_xml = configure_file(
|
||||
input: 'resources.xml.in',
|
||||
output: 'resources.xml',
|
||||
configuration: conf
|
||||
)
|
||||
|
||||
gnome = import('gnome')
|
||||
resources = gnome.compile_resources(
|
||||
'waydisplay-resources', 'resources.xml',
|
||||
'wdisplays-resources', resources_xml,
|
||||
source_dir : '.',
|
||||
c_name : 'waydisplay_resources')
|
||||
c_name : 'wdisplays_resources')
|
||||
|
||||
scour = find_program('scour', required: false)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/">
|
||||
<gresource prefix="@resource_prefix@">
|
||||
<file compressed="true" preprocess="xml-stripblanks">wdisplays.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">head.ui</file>
|
||||
<file compressed="true">style.css</file>
|
|
@ -11,62 +11,6 @@
|
|||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="GtkPopover" id="main_menu">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_start">10</property>
|
||||
<property name="margin_end">10</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">app.auto-apply</property>
|
||||
<property name="text" translatable="yes">_Automatically Apply Changes</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">app.capture-screens</property>
|
||||
<property name="text" translatable="yes">Show Screen Contents</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">app.show-overlay</property>
|
||||
<property name="text" translatable="yes">Overlay Screen Names</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkWindow" id="heads_window">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">wdisplays</property>
|
||||
|
@ -171,8 +115,6 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="margin_start">8</property>
|
||||
<property name="margin_end">8</property>
|
||||
<property name="margin_top">8</property>
|
||||
|
@ -248,14 +190,14 @@
|
|||
<object class="GtkButtonBox" id="zoom_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="zoom_out">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Zoom Out</property>
|
||||
<signal name="clicked" handler="zoom_out" swapped="yes"/>
|
||||
<property name="action_name">app.zoom-out</property>
|
||||
<accelerator key="minus" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
|
@ -263,7 +205,6 @@
|
|||
<property name="icon_name">zoom-out-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="minus" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
|
@ -278,7 +219,7 @@
|
|||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Zoom Reset</property>
|
||||
<signal name="clicked" handler="zoom_reset" swapped="yes"/>
|
||||
<property name="action_name">app.zoom-reset</property>
|
||||
<accelerator key="0" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -294,7 +235,8 @@
|
|||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Zoom In</property>
|
||||
<signal name="clicked" handler="zoom_in" swapped="yes"/>
|
||||
<property name="action_name">app.zoom-in</property>
|
||||
<accelerator key="equal" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
|
@ -302,7 +244,6 @@
|
|||
<property name="icon_name">zoom-in-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="equal" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
|
@ -311,6 +252,9 @@
|
|||
<property name="non_homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
@ -318,7 +262,6 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="popover">main_menu</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
|
@ -355,7 +298,7 @@
|
|||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="clicked" handler="apply_changes" swapped="no"/>
|
||||
<property name="action_name">app.apply-changes</property>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
|
@ -371,7 +314,7 @@
|
|||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="clicked" handler="cancel_changes" swapped="no"/>
|
||||
<property name="action_name">app.cancel-changes</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
|
|
|
@ -6,5 +6,6 @@
|
|||
|
||||
#define WDISPLAYS_APP_ID "@app_id@"
|
||||
#define WDISPLAYS_VERSION "@version@"
|
||||
#define WDISPLAYS_RESOURCE_PREFIX "@resource_prefix@"
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,393 @@
|
|||
/* SPDX-FileCopyrightText: 2020 Jason Francis <jason@cycles.network>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
|
||||
#include "headform.h"
|
||||
#include "wdisplays.h"
|
||||
|
||||
typedef struct _WdHeadFormPrivate {
|
||||
GtkWidget *enabled;
|
||||
GtkWidget *description;
|
||||
GtkWidget *physical_size;
|
||||
GtkWidget *scale;
|
||||
GtkWidget *pos_x;
|
||||
GtkWidget *pos_y;
|
||||
GtkWidget *width;
|
||||
GtkWidget *height;
|
||||
GtkWidget *refresh;
|
||||
GtkWidget *mode_button;
|
||||
GtkWidget *rotate_button;
|
||||
GtkWidget *flipped;
|
||||
|
||||
GAction *mode_action;
|
||||
GAction *rotate_action;
|
||||
} WdHeadFormPrivate;
|
||||
|
||||
enum {
|
||||
CHANGED,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
static guint signals[LAST_SIGNAL];
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE(WdHeadForm, wd_head_form, GTK_TYPE_GRID,
|
||||
G_ADD_PRIVATE(WdHeadForm))
|
||||
|
||||
static const char *HEAD_PREFIX = "head";
|
||||
static const char *MODE_PREFIX = "mode";
|
||||
static const char *ROTATE_PREFIX = "rotate";
|
||||
|
||||
static void head_form_update_sensitivity(WdHeadForm *form) {
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
|
||||
bool enabled_toggled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->enabled));
|
||||
|
||||
g_autoptr(GList) children = gtk_container_get_children(GTK_CONTAINER(form));
|
||||
for (GList *child = children; child != NULL; child = child->next) {
|
||||
GtkWidget *widget = GTK_WIDGET(child->data);
|
||||
if (widget != priv->enabled) {
|
||||
gtk_widget_set_sensitive(widget, enabled_toggled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static GVariant *create_mode_variant(int32_t w, int32_t h, int32_t r) {
|
||||
GVariant * const children[] = {
|
||||
g_variant_new_int32(w),
|
||||
g_variant_new_int32(h),
|
||||
g_variant_new_int32(r),
|
||||
};
|
||||
return g_variant_new_tuple(children, G_N_ELEMENTS(children));
|
||||
}
|
||||
|
||||
struct vid_mode {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
int32_t refresh;
|
||||
};
|
||||
|
||||
static void unpack_mode_variant(GVariant *value, struct vid_mode *mode) {
|
||||
g_autoptr(GVariant) width = g_variant_get_child_value(value, 0);
|
||||
mode->width = g_variant_get_int32(width);
|
||||
g_autoptr(GVariant) height = g_variant_get_child_value(value, 1);
|
||||
mode->height = g_variant_get_int32(height);
|
||||
g_autoptr(GVariant) refresh = g_variant_get_child_value(value, 2);
|
||||
mode->refresh = g_variant_get_int32(refresh);
|
||||
}
|
||||
|
||||
static void enabled_toggled(GtkToggleButton *toggle, gpointer data) {
|
||||
WdHeadForm *form = WD_HEAD_FORM(data);
|
||||
head_form_update_sensitivity(form);
|
||||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_ENABLED);
|
||||
}
|
||||
|
||||
static void mode_spin_changed(GtkSpinButton *spin_button, gpointer data) {
|
||||
WdHeadForm *form = WD_HEAD_FORM(data);
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
struct vid_mode mode;
|
||||
GVariant *value = g_action_get_state(priv->mode_action);
|
||||
unpack_mode_variant(value, &mode);
|
||||
if (strcmp(gtk_widget_get_name(GTK_WIDGET(spin_button)), "width") == 0) {
|
||||
mode.width = gtk_spin_button_get_value(spin_button);
|
||||
} else if (strcmp(gtk_widget_get_name(GTK_WIDGET(spin_button)), "height") == 0) {
|
||||
mode.height = gtk_spin_button_get_value(spin_button);
|
||||
} else if (strcmp(gtk_widget_get_name(GTK_WIDGET(spin_button)), "refresh") == 0) {
|
||||
mode.refresh = gtk_spin_button_get_value(spin_button) * 1000.;
|
||||
}
|
||||
g_action_activate(priv->mode_action, create_mode_variant(mode.width, mode.height, mode.refresh));
|
||||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_MODE);
|
||||
}
|
||||
|
||||
static void position_spin_changed(GtkSpinButton *spin_button, gpointer data) {
|
||||
WdHeadForm *form = WD_HEAD_FORM(data);
|
||||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_POSITION);
|
||||
}
|
||||
|
||||
static void flipped_toggled(GtkToggleButton *toggle, gpointer data) {
|
||||
WdHeadForm *form = WD_HEAD_FORM(data);
|
||||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_TRANSFORM);
|
||||
}
|
||||
|
||||
static void wd_head_form_class_init(WdHeadFormClass *class) {
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(class);
|
||||
|
||||
signals[CHANGED] = g_signal_new("changed",
|
||||
G_OBJECT_CLASS_TYPE(class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
G_STRUCT_OFFSET(WdHeadFormClass, changed),
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 1, G_TYPE_INT);
|
||||
|
||||
gtk_widget_class_set_template_from_resource(widget_class,
|
||||
WDISPLAYS_RESOURCE_PREFIX "/head.ui");
|
||||
|
||||
gtk_widget_class_bind_template_callback(widget_class, enabled_toggled);
|
||||
gtk_widget_class_bind_template_callback(widget_class, mode_spin_changed);
|
||||
gtk_widget_class_bind_template_callback(widget_class, position_spin_changed);
|
||||
gtk_widget_class_bind_template_callback(widget_class, flipped_toggled);
|
||||
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, enabled);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, description);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, physical_size);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, scale);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, pos_x);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, pos_y);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, width);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, height);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, refresh);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, mode_button);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, rotate_button);
|
||||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, flipped);
|
||||
gtk_widget_class_set_css_name(widget_class, "wd-head-form");
|
||||
}
|
||||
|
||||
static int32_t get_rotate_value(enum wl_output_transform transform) {
|
||||
if (transform == WL_OUTPUT_TRANSFORM_90 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_90) {
|
||||
return 90;
|
||||
} else if (transform == WL_OUTPUT_TRANSFORM_180 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_180) {
|
||||
return 180;
|
||||
} else if (transform == WL_OUTPUT_TRANSFORM_270 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_270) {
|
||||
return 270;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rotate_selected(GSimpleAction *action, GVariant *param, gpointer data) {
|
||||
WdHeadForm *form = data;
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
GMenuModel *menu = gtk_menu_button_get_menu_model(GTK_MENU_BUTTON(priv->rotate_button));
|
||||
int items = g_menu_model_get_n_items(menu);
|
||||
for (int i = 0; i < items; i++) {
|
||||
g_autoptr(GVariant) target = g_menu_model_get_item_attribute_value(menu, i, G_MENU_ATTRIBUTE_TARGET, NULL);
|
||||
g_autoptr(GVariant) label = g_menu_model_get_item_attribute_value(menu, i, G_MENU_ATTRIBUTE_LABEL, NULL);
|
||||
if (g_variant_get_int32(target) == g_variant_get_int32(param)) {
|
||||
gtk_button_set_label(GTK_BUTTON(priv->rotate_button), g_variant_get_string(label, NULL));
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_simple_action_set_state(action, param);
|
||||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_TRANSFORM);
|
||||
}
|
||||
|
||||
static void mode_selected(GSimpleAction *action, GVariant *param, gpointer data) {
|
||||
WdHeadForm *form = data;
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
struct vid_mode mode;
|
||||
unpack_mode_variant(param, &mode);
|
||||
|
||||
g_simple_action_set_state(action, param);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->width), mode.width);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->height), mode.height);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->refresh), mode.refresh / 1000.);
|
||||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_MODE);
|
||||
}
|
||||
|
||||
static void wd_head_form_init(WdHeadForm *form) {
|
||||
gtk_widget_init_template(GTK_WIDGET(form));
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
|
||||
GSimpleActionGroup *head_actions = g_simple_action_group_new();
|
||||
gtk_widget_insert_action_group(priv->mode_button, HEAD_PREFIX, G_ACTION_GROUP(head_actions));
|
||||
gtk_widget_insert_action_group(priv->rotate_button, HEAD_PREFIX, G_ACTION_GROUP(head_actions));
|
||||
|
||||
GMenu *rotate_menu = g_menu_new();
|
||||
g_menu_append(rotate_menu, "Don't Rotate", "head.rotate(0)");
|
||||
g_menu_append(rotate_menu, "Rotate 90°", "head.rotate(90)");
|
||||
g_menu_append(rotate_menu, "Rotate 180°", "head.rotate(180)");
|
||||
g_menu_append(rotate_menu, "Rotate 270°", "head.rotate(270)");
|
||||
gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->rotate_button), G_MENU_MODEL(rotate_menu));
|
||||
|
||||
static const GVariantType * const mode_types[] = {
|
||||
G_VARIANT_TYPE_INT32,
|
||||
G_VARIANT_TYPE_INT32,
|
||||
G_VARIANT_TYPE_INT32
|
||||
};
|
||||
GSimpleAction *action = g_simple_action_new_stateful("mode",
|
||||
g_variant_type_new_tuple(mode_types, G_N_ELEMENTS(mode_types)),
|
||||
create_mode_variant(0, 0, 0));
|
||||
g_action_map_add_action(G_ACTION_MAP(head_actions), G_ACTION(action));
|
||||
g_signal_connect(action, "change-state", G_CALLBACK(mode_selected), form);
|
||||
g_object_unref(action);
|
||||
priv->mode_action = G_ACTION(action);
|
||||
|
||||
action = g_simple_action_new_stateful(ROTATE_PREFIX, G_VARIANT_TYPE_INT32,
|
||||
g_variant_new_int32(0));
|
||||
g_action_map_add_action(G_ACTION_MAP(head_actions), G_ACTION(action));
|
||||
g_signal_connect(action, "change-state", G_CALLBACK(rotate_selected), form);
|
||||
g_object_unref(action);
|
||||
priv->rotate_action = G_ACTION(action);
|
||||
|
||||
g_object_unref(head_actions);
|
||||
}
|
||||
|
||||
void wd_head_form_update(WdHeadForm *form, const struct wd_head *head,
|
||||
enum wd_head_fields fields) {
|
||||
g_return_if_fail(form);
|
||||
g_return_if_fail(head);
|
||||
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
if (!fields)
|
||||
return;
|
||||
|
||||
if (fields & WD_FIELD_DESCRIPTION)
|
||||
gtk_label_set_text(GTK_LABEL(priv->description), head->description);
|
||||
if (fields & WD_FIELD_PHYSICAL_SIZE) {
|
||||
g_autofree gchar *physical_str = g_strdup_printf("%dmm × %dmm", head->phys_width, head->phys_height);
|
||||
gtk_label_set_text(GTK_LABEL(priv->physical_size), physical_str);
|
||||
}
|
||||
if (fields & WD_FIELD_ENABLED)
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->enabled), head->enabled);
|
||||
if (fields & WD_FIELD_SCALE)
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->scale), head->scale);
|
||||
if (fields & WD_FIELD_POSITION) {
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->pos_x), head->x);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->pos_y), head->y);
|
||||
}
|
||||
|
||||
if (fields & WD_FIELD_MODE) {
|
||||
GMenu *mode_menu = g_menu_new();
|
||||
struct wd_mode *mode;
|
||||
g_autofree gchar *action = g_strdup_printf("%s.%s", HEAD_PREFIX, MODE_PREFIX);
|
||||
wl_list_for_each(mode, &head->modes, link) {
|
||||
g_autofree gchar *name = g_strdup_printf("%d×%d@%0.3fHz", mode->width, mode->height, mode->refresh / 1000.);
|
||||
GMenuItem *item = g_menu_item_new(name, action);
|
||||
g_menu_item_set_attribute_value(item, G_MENU_ATTRIBUTE_TARGET,
|
||||
create_mode_variant(mode->width, mode->height, mode->refresh));
|
||||
g_menu_append_item(mode_menu, item);
|
||||
}
|
||||
gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->mode_button), G_MENU_MODEL(mode_menu));
|
||||
// Mode entries
|
||||
int w = head->custom_mode.width;
|
||||
int h = head->custom_mode.height;
|
||||
int r = head->custom_mode.refresh;
|
||||
if (head->enabled && head->mode != NULL) {
|
||||
w = head->mode->width;
|
||||
h = head->mode->height;
|
||||
r = head->mode->refresh;
|
||||
} else if (!head->enabled && w == 0 && h == 0) {
|
||||
struct wd_mode *mode;
|
||||
wl_list_for_each(mode, &head->modes, link) {
|
||||
if (mode->preferred) {
|
||||
w = mode->width;
|
||||
h = mode->height;
|
||||
r = mode->refresh;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_action_change_state(priv->mode_action, create_mode_variant(w, h, r));
|
||||
}
|
||||
|
||||
if (fields & WD_FIELD_TRANSFORM) {
|
||||
int active_rotate = get_rotate_value(head->transform);
|
||||
g_action_change_state(priv->rotate_action, g_variant_new_int32(active_rotate));
|
||||
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->flipped),
|
||||
head->transform == WL_OUTPUT_TRANSFORM_FLIPPED
|
||||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_90
|
||||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_180
|
||||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_270);
|
||||
}
|
||||
|
||||
// Sync state
|
||||
if (fields & WD_FIELD_ENABLED) {
|
||||
head_form_update_sensitivity(form);
|
||||
}
|
||||
g_signal_emit(form, signals[CHANGED], 0);
|
||||
}
|
||||
|
||||
GtkWidget *wd_head_form_new(void) {
|
||||
return gtk_widget_new(WD_TYPE_HEAD_FORM, NULL);
|
||||
}
|
||||
|
||||
gboolean wd_head_form_get_enabled(WdHeadForm *form) {
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->enabled));
|
||||
}
|
||||
|
||||
gboolean wd_head_form_has_changes(WdHeadForm *form, const struct wd_head *head) {
|
||||
g_return_val_if_fail(form, FALSE);
|
||||
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
if (head->enabled != gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->enabled))) {
|
||||
return TRUE;
|
||||
}
|
||||
double old_scale = round(head->scale * 100.) / 100.;
|
||||
double new_scale = round(gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->scale)) * 100.) / 100.;
|
||||
if (old_scale != new_scale) {
|
||||
return TRUE;
|
||||
}
|
||||
if (head->x != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_x))) {
|
||||
return TRUE;
|
||||
}
|
||||
if (head->y != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_y))) {
|
||||
return TRUE;
|
||||
}
|
||||
int w = head->mode != NULL ? head->mode->width : head->custom_mode.width;
|
||||
if (w != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->width))) {
|
||||
return TRUE;
|
||||
}
|
||||
int h = head->mode != NULL ? head->mode->height : head->custom_mode.height;
|
||||
if (h != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->height))) {
|
||||
return TRUE;
|
||||
}
|
||||
int r = head->mode != NULL ? head->mode->refresh : head->custom_mode.refresh;
|
||||
if (r / 1000. != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->refresh))) {
|
||||
return TRUE;
|
||||
}
|
||||
if (g_variant_get_int32(g_action_get_state(priv->rotate_action)) != get_rotate_value(head->transform)) {
|
||||
return TRUE;
|
||||
}
|
||||
bool flipped = head->transform == WL_OUTPUT_TRANSFORM_FLIPPED
|
||||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_90
|
||||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_180
|
||||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_270;
|
||||
if (flipped != gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->flipped))) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void wd_head_form_fill_config(WdHeadForm *form, struct wd_head_config *output) {
|
||||
g_return_if_fail(form);
|
||||
g_return_if_fail(output);
|
||||
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
output->enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->enabled));
|
||||
output->scale = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->scale));
|
||||
output->x = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_x));
|
||||
output->y = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_y));
|
||||
output->width = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->width));
|
||||
output->height = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->height));
|
||||
output->refresh = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->refresh)) * 1000.;
|
||||
int32_t rotate = g_variant_get_int32(g_action_get_state(priv->rotate_action));
|
||||
gboolean flipped = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->flipped));
|
||||
switch (rotate) {
|
||||
case 0: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED : WL_OUTPUT_TRANSFORM_NORMAL; break;
|
||||
case 90: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_90 : WL_OUTPUT_TRANSFORM_90; break;
|
||||
case 180: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_180 : WL_OUTPUT_TRANSFORM_180; break;
|
||||
case 270: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_270 : WL_OUTPUT_TRANSFORM_270; break;
|
||||
}
|
||||
}
|
||||
|
||||
void wd_head_form_get_dimensions(WdHeadForm *form, WdHeadDimensions *dimensions) {
|
||||
g_return_if_fail(form);
|
||||
g_return_if_fail(dimensions);
|
||||
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
|
||||
dimensions->x = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_x));
|
||||
dimensions->y = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_y));
|
||||
dimensions->w = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->width));
|
||||
dimensions->h = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->height));
|
||||
dimensions->scale = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->scale));
|
||||
dimensions->rotation_id = g_variant_get_int32(g_action_get_state(priv->rotate_action)) / 90;
|
||||
dimensions->flipped = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->flipped));
|
||||
}
|
||||
|
||||
void wd_head_form_set_position(WdHeadForm *form, double x, double y) {
|
||||
g_return_if_fail(form);
|
||||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->pos_x), x);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->pos_y), y);
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/* SPDX-FileCopyrightText: 2020 Jason Francis <jason@cycles.network>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
|
||||
#ifndef WDISPLAY_HEADFORM_H
|
||||
#define WDISPLAY_HEADFORM_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
enum wd_head_fields {
|
||||
WD_FIELD_NAME = 1 << 0,
|
||||
WD_FIELD_ENABLED = 1 << 1,
|
||||
WD_FIELD_DESCRIPTION = 1 << 2,
|
||||
WD_FIELD_PHYSICAL_SIZE = 1 << 3,
|
||||
WD_FIELD_SCALE = 1 << 4,
|
||||
WD_FIELD_POSITION = 1 << 5,
|
||||
WD_FIELD_MODE = 1 << 6,
|
||||
WD_FIELD_TRANSFORM = 1 << 7,
|
||||
WD_FIELDS_ALL = (1 << 8) - 1
|
||||
};
|
||||
|
||||
#define WD_TYPE_HEAD_FORM (wd_head_form_get_type())
|
||||
G_DECLARE_DERIVABLE_TYPE(
|
||||
WdHeadForm, wd_head_form, WD, HEAD_FORM, GtkGrid)
|
||||
|
||||
struct _WdHeadFormClass {
|
||||
GtkGridClass parent_class;
|
||||
|
||||
void (*changed)(WdHeadForm *form, enum wd_head_fields fields);
|
||||
};
|
||||
|
||||
struct wd_head;
|
||||
struct wd_head_config;
|
||||
|
||||
typedef struct _WdHeadDimensions {
|
||||
gdouble x;
|
||||
gdouble y;
|
||||
gdouble w;
|
||||
gdouble h;
|
||||
gdouble scale;
|
||||
int rotation_id;
|
||||
gboolean flipped;
|
||||
} WdHeadDimensions;
|
||||
|
||||
GtkWidget *wd_head_form_new(void);
|
||||
|
||||
gboolean wd_head_form_get_enabled(WdHeadForm *form);
|
||||
gboolean wd_head_form_has_changes(WdHeadForm *form, const struct wd_head *head);
|
||||
void wd_head_form_update(WdHeadForm *form, const struct wd_head *head,
|
||||
enum wd_head_fields fields);
|
||||
void wd_head_form_fill_config(WdHeadForm *form, struct wd_head_config *output);
|
||||
void wd_head_form_get_dimensions(WdHeadForm *form, WdHeadDimensions *dimensions);
|
||||
void wd_head_form_set_position(WdHeadForm *form, double x, double y);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
924
src/main.c
924
src/main.c
File diff suppressed because it is too large
Load Diff
|
@ -4,8 +4,8 @@
|
|||
cc = meson.get_compiler('c')
|
||||
m_dep = cc.find_library('m', required : false)
|
||||
rt_dep = cc.find_library('rt', required : false)
|
||||
gdk = dependency('gdk-3.0')
|
||||
gtk = dependency('gtk+-3.0')
|
||||
gdk = dependency('gdk-3.0', version: '>= 3.24')
|
||||
gtk = dependency('gtk+-3.0', version: '>= 3.24')
|
||||
assert(gdk.get_pkgconfig_variable('targets').split().contains('wayland'), 'Wayland GDK backend not present')
|
||||
epoxy = dependency('epoxy')
|
||||
|
||||
|
@ -15,10 +15,11 @@ executable(
|
|||
'wdisplays',
|
||||
[
|
||||
'main.c',
|
||||
'outputs.c',
|
||||
'render.c',
|
||||
'glviewport.c',
|
||||
'headform.c',
|
||||
'outputs.c',
|
||||
'overlay.c',
|
||||
'render.c',
|
||||
resources,
|
||||
],
|
||||
dependencies : [
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
#include <stdbool.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "headform.h"
|
||||
|
||||
struct zxdg_output_v1;
|
||||
struct zxdg_output_manager_v1;
|
||||
struct zwlr_output_mode_v1;
|
||||
|
@ -39,18 +41,6 @@ typedef struct _GdkCursor GdkCursor;
|
|||
struct _cairo_surface;
|
||||
typedef struct _cairo_surface cairo_surface_t;
|
||||
|
||||
enum wd_head_fields {
|
||||
WD_FIELD_NAME = 1 << 0,
|
||||
WD_FIELD_ENABLED = 1 << 1,
|
||||
WD_FIELD_DESCRIPTION = 1 << 2,
|
||||
WD_FIELD_PHYSICAL_SIZE = 1 << 3,
|
||||
WD_FIELD_SCALE = 1 << 4,
|
||||
WD_FIELD_POSITION = 1 << 5,
|
||||
WD_FIELD_MODE = 1 << 6,
|
||||
WD_FIELD_TRANSFORM = 1 << 7,
|
||||
WD_FIELDS_ALL = (1 << 8) - 1
|
||||
};
|
||||
|
||||
struct wd_output {
|
||||
struct wd_state *state;
|
||||
struct zxdg_output_v1 *xdg_output;
|
||||
|
@ -205,10 +195,10 @@ struct wd_state {
|
|||
unsigned int reset_idle;
|
||||
|
||||
struct wd_render_head_data *clicked;
|
||||
/* top left, bottom right */
|
||||
struct wd_point click_offset;
|
||||
struct wd_point drag_start;
|
||||
struct wd_point head_drag_start; /* 0-1 range in head rect */
|
||||
bool panning;
|
||||
struct wd_point pan_last;
|
||||
struct wd_point pan_start;
|
||||
|
||||
GtkWidget *main_box;
|
||||
GtkWidget *header_stack;
|
||||
|
@ -301,7 +291,7 @@ void wd_ui_reset_heads(struct wd_state *state);
|
|||
* Updates the UI form for a single head. Useful for when the compositor
|
||||
* notifies us of updated configuration caused by another program.
|
||||
*/
|
||||
void wd_ui_reset_head(const struct wd_head *head, unsigned int fields);
|
||||
void wd_ui_reset_head(const struct wd_head *head, enum wd_head_fields fields);
|
||||
|
||||
/*
|
||||
* Updates the stack and all forms to the last known server state.
|
||||
|
|
Loading…
Reference in New Issue