Port to GTK4 (WIP)

This commit is contained in:
Jason Francis 2020-03-07 10:37:19 -05:00
parent ba331cab53
commit 58dec1391f
8 changed files with 543 additions and 905 deletions

View File

@ -16,7 +16,7 @@ setups.
Build requirements are: Build requirements are:
- meson - meson
- GTK+3 - GTK4
- epoxy - epoxy
- wayland-client - wayland-client

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.0 -->
<interface> <interface>
<requires lib="gtk+" version="3.22"/> <requires lib="gtk+" version="3.22"/>
<object class="GtkAdjustment" id="height_adjustment"> <object class="GtkAdjustment" id="height_adjustment">
@ -7,23 +6,6 @@
<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="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"> <object class="GtkAdjustment" id="pos_x_adjustment">
<property name="upper">16383</property> <property name="upper">16383</property>
<property name="step_increment">1</property> <property name="step_increment">1</property>
@ -51,430 +33,278 @@
<property name="page_increment">10</property> <property name="page_increment">10</property>
</object> </object>
<object class="GtkGrid" id="form"> <object class="GtkGrid" id="form">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_start">8</property> <property name="margin_start">8</property>
<property name="margin_end">8</property> <property name="margin_end">8</property>
<property name="margin_top">8</property> <property name="margin_top">8</property>
<property name="margin_bottom">8</property> <property name="margin_bottom">8</property>
<property name="row_spacing">8</property> <property name="row_spacing">8</property>
<property name="column_spacing">16</property> <property name="column_spacing">16</property>
<property name="row_homogeneous">True</property> <property name="row_homogeneous">1</property>
<child> <child>
<object class="GtkCheckButton" id="enabled"> <object class="GtkCheckButton" id="enabled">
<property name="label" translatable="yes">_Enabled</property> <property name="label" translatable="yes">_Enabled</property>
<property name="visible">True</property> <property name="can_focus">1</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="halign">start</property> <property name="halign">start</property>
<property name="use_underline">True</property> <property name="use_underline">1</property>
<property name="draw_indicator">True</property> <layout>
<signal name="toggled" handler="enabled" swapped="no"/> <property name="left_attach">1</property>
<property name="top_attach">0</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="description"> <object class="GtkLabel" id="description">
<property name="visible">True</property> <property name="wrap">1</property>
<property name="can_focus">False</property>
<property name="wrap">True</property>
<property name="wrap_mode">word-char</property> <property name="wrap_mode">word-char</property>
<property name="ellipsize">end</property> <property name="ellipsize">end</property>
<property name="xalign">0</property> <property name="xalign">0</property>
<layout>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkSpinButton" id="scale"> <object class="GtkSpinButton" id="scale">
<property name="visible">True</property> <property name="can_focus">1</property>
<property name="can_focus">True</property>
<property name="halign">start</property> <property name="halign">start</property>
<property name="width_chars">9</property> <property name="width_chars">9</property>
<property name="adjustment">scale_adjustment</property> <property name="adjustment">scale_adjustment</property>
<property name="digits">2</property> <property name="digits">2</property>
<property name="value">1</property> <property name="value">1</property>
<signal name="change-value" handler="scale" swapped="no"/> <layout>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">DPI _Scale</property> <property name="label" translatable="yes">DPI _Scale</property>
<property name="use_underline">True</property> <property name="use_underline">1</property>
<property name="mnemonic_widget">scale</property> <property name="mnemonic_widget">scale</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<layout>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Position</property> <property name="label" translatable="yes">_Position</property>
<property name="use_underline">True</property> <property name="use_underline">1</property>
<property name="mnemonic_widget">pos_x</property> <property name="mnemonic_widget">pos_x</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<layout>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Description</property> <property name="label" translatable="yes">Description</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<layout>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="physical_size"> <object class="GtkLabel" id="physical_size">
<property name="visible">True</property> <property name="wrap">1</property>
<property name="can_focus">False</property>
<property name="wrap">True</property>
<property name="wrap_mode">word-char</property> <property name="wrap_mode">word-char</property>
<property name="ellipsize">end</property> <property name="ellipsize">end</property>
<property name="xalign">0</property> <property name="xalign">0</property>
<layout>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Physical Size</property> <property name="label" translatable="yes">Physical Size</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<layout>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Si_ze</property> <property name="label" translatable="yes">Si_ze</property>
<property name="use_underline">True</property> <property name="use_underline">1</property>
<property name="mnemonic_widget">width</property> <property name="mnemonic_widget">width</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<layout>
<property name="left_attach">0</property>
<property name="top_attach">5</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">5</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property> <property name="halign">start</property>
<property name="spacing">8</property> <property name="spacing">8</property>
<child> <child>
<object class="GtkSpinButton" id="refresh"> <object class="GtkSpinButton" id="refresh">
<property name="visible">True</property> <property name="name">refresh</property>
<property name="can_focus">True</property> <property name="can_focus">1</property>
<property name="width_chars">9</property> <property name="width_chars">9</property>
<property name="input_purpose">number</property>
<property name="adjustment">refresh_adjustment</property> <property name="adjustment">refresh_adjustment</property>
<property name="digits">3</property> <property name="digits">3</property>
<property name="numeric">True</property> <property name="numeric">1</property>
<property name="update_policy">if-valid</property> <property name="update_policy">if-valid</property>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Hz</property> <property name="label" translatable="yes">Hz</property>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
<layout>
<property name="left_attach">1</property>
<property name="top_attach">6</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">6</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Refresh Rate</property> <property name="label" translatable="yes">_Refresh Rate</property>
<property name="use_underline">True</property> <property name="use_underline">1</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<layout>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkMenuButton" id="rotate_button"> <object class="GtkMenuButton" id="rotate_button">
<property name="visible">True</property> <property name="can_focus">1</property>
<property name="can_focus">True</property> <property name="receives_default">1</property>
<property name="receives_default">True</property> <layout>
<property name="popover">transforms</property> <property name="left_attach">1</property>
<child> <property name="top_attach">7</property>
<placeholder/> </layout>
</child>
</object> </object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">7</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Transform</property> <property name="label" translatable="yes">_Transform</property>
<property name="use_underline">True</property> <property name="use_underline">1</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<layout>
<property name="left_attach">0</property>
<property name="top_attach">7</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">7</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkCheckButton" id="flipped"> <object class="GtkCheckButton" id="flipped">
<property name="label" translatable="yes">_Flipped</property> <property name="label" translatable="yes">_Flipped</property>
<property name="visible">True</property> <property name="can_focus">1</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="halign">start</property> <property name="halign">start</property>
<property name="use_underline">True</property> <property name="use_underline">1</property>
<property name="draw_indicator">True</property> <layout>
<signal name="toggled" handler="flipped" swapped="no"/> <property name="left_attach">1</property>
<property name="top_attach">8</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">8</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkGrid"> <object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">8</property> <property name="row_spacing">8</property>
<child> <child>
<object class="GtkSpinButton" id="pos_x"> <object class="GtkSpinButton" id="pos_x">
<property name="visible">True</property> <property name="can_focus">1</property>
<property name="can_focus">True</property>
<property name="width_chars">6</property> <property name="width_chars">6</property>
<property name="text" translatable="yes">0</property> <property name="text" translatable="yes">0</property>
<property name="input_purpose">number</property>
<property name="adjustment">pos_x_adjustment</property> <property name="adjustment">pos_x_adjustment</property>
<property name="numeric">True</property> <property name="numeric">1</property>
<property name="update_policy">if-valid</property> <property name="update_policy">if-valid</property>
<layout>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkSpinButton" id="pos_y"> <object class="GtkSpinButton" id="pos_y">
<property name="visible">True</property> <property name="can_focus">1</property>
<property name="can_focus">True</property>
<property name="width_chars">6</property> <property name="width_chars">6</property>
<property name="text" translatable="yes">0</property> <property name="text" translatable="yes">0</property>
<property name="input_purpose">number</property>
<property name="adjustment">pos_y_adjustment</property> <property name="adjustment">pos_y_adjustment</property>
<property name="numeric">True</property> <property name="numeric">1</property>
<property name="update_policy">if-valid</property> <property name="update_policy">if-valid</property>
<layout>
<property name="left_attach">2</property>
<property name="top_attach">0</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">0</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkSpinButton" id="width"> <object class="GtkSpinButton" id="width">
<property name="visible">True</property> <property name="name">width</property>
<property name="can_focus">True</property> <property name="can_focus">1</property>
<property name="width_chars">4</property> <property name="width_chars">4</property>
<property name="text" translatable="yes">0</property> <property name="text" translatable="yes">0</property>
<property name="input_purpose">number</property>
<property name="adjustment">width_adjustment</property> <property name="adjustment">width_adjustment</property>
<property name="numeric">True</property> <property name="numeric">1</property>
<property name="update_policy">if-valid</property> <property name="update_policy">if-valid</property>
<layout>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="width_request">20</property> <property name="width_request">20</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">×</property> <property name="label" translatable="yes">×</property>
<layout>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkSpinButton" id="height"> <object class="GtkSpinButton" id="height">
<property name="visible">True</property> <property name="name">height</property>
<property name="can_focus">True</property> <property name="can_focus">1</property>
<property name="width_chars">4</property> <property name="width_chars">4</property>
<property name="text" translatable="yes">0</property> <property name="text" translatable="yes">0</property>
<property name="input_purpose">number</property>
<property name="adjustment">height_adjustment</property> <property name="adjustment">height_adjustment</property>
<property name="numeric">True</property> <property name="numeric">1</property>
<property name="update_policy">if-valid</property> <property name="update_policy">if-valid</property>
<layout>
<property name="left_attach">2</property>
<property name="top_attach">1</property>
</layout>
</object> </object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">1</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkMenuButton" id="mode_button"> <object class="GtkMenuButton" id="mode_button">
<property name="visible">True</property> <property name="can_focus">1</property>
<property name="can_focus">True</property> <property name="receives_default">1</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Select Mode Preset</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="margin_start">8</property>
<property name="popover">modes</property> <property name="icon_name">view-more-symbolic</property>
<child> <layout>
<object class="GtkImage"> <property name="left_attach">3</property>
<property name="visible">True</property> <property name="top_attach">1</property>
<property name="can_focus">False</property> </layout>
<property name="icon_name">view-more-symbolic</property>
</object>
</child>
</object> </object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
<property name="height">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<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> </child>
<layout>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
<property name="row_span">2</property>
</layout>
</object> </object>
</child> </child>
</object> </object>

View File

@ -1,9 +1,9 @@
gnome = import('gnome') gnome = import('gnome')
resources = gnome.compile_resources( resources = gnome.compile_resources(
'waydisplay-resources', 'resources.xml', 'wdisplays-resources', 'resources.xml',
source_dir : '.', source_dir : '.',
c_name : 'waydisplay_resources') c_name : 'wdisplays_resources')
scour = find_program('scour', required: false) scour = find_program('scour', required: false)

View File

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.0 -->
<interface> <interface>
<requires lib="gtk+" version="3.22"/> <requires lib="gtk+" version="3.22"/>
<!-- interface-css-provider-path style.css -->
<object class="GtkAdjustment" id="canvas_horiz"> <object class="GtkAdjustment" id="canvas_horiz">
<property name="step_increment">1</property> <property name="step_increment">1</property>
<property name="page_increment">10</property> <property name="page_increment">10</property>
@ -11,143 +9,56 @@
<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="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"> <object class="GtkWindow" id="heads_window">
<property name="can_focus">False</property>
<property name="title" translatable="yes">wdisplays</property> <property name="title" translatable="yes">wdisplays</property>
<signal name="destroy" handler="destroy" swapped="no"/>
<child> <child>
<object class="GtkOverlay" id="overlay"> <object class="GtkOverlay" id="overlay">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkInfoBar" id="heads_info"> <object class="GtkInfoBar" id="heads_info">
<property name="can_focus">False</property> <property name="visible">0</property>
<property name="no_show_all">True</property>
<property name="valign">start</property> <property name="valign">start</property>
<property name="message_type">error</property> <property name="message_type">error</property>
<property name="show_close_button">True</property> <property name="show_close_button">1</property>
<property name="revealed">False</property> <property name="revealed">0</property>
<signal name="response" handler="info_response" swapped="no"/> <child type="action">
<child internal-child="action_area"> <object class="GtkBox">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="spacing">6</property> <property name="spacing">6</property>
<property name="layout_style">end</property>
<child> <child>
<placeholder/> <placeholder/>
</child> </child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child> </child>
<child internal-child="content_area"> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="can_focus">False</property> <property name="visible">0</property>
<property name="spacing">16</property> <property name="spacing">16</property>
<child> <child>
<object class="GtkLabel" id="heads_info_label"> <object class="GtkLabel" id="heads_info_label">
<property name="visible">True</property> <property name="wrap">1</property>
<property name="can_focus">False</property>
<property name="wrap">True</property>
<property name="xalign">0</property> <property name="xalign">0</property>
</object> </object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child> </child>
<child> <child>
<placeholder/> <placeholder/>
</child> </child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkPaned"> <object class="GtkPaned" id="paned">
<property name="visible">True</property> <property name="resize_child2">0</property>
<property name="can_focus">True</property> <property name="shrink_child1">0</property>
<property name="shrink_child2">0</property>
<property name="can_focus">1</property>
<property name="position">400</property> <property name="position">400</property>
<property name="position_set">True</property> <property name="position_set">1</property>
<child> <child>
<object class="GtkScrolledWindow" id="heads_scroll"> <object class="GtkScrolledWindow" id="heads_scroll">
<property name="visible">True</property> <property name="can_focus">1</property>
<property name="can_focus">True</property>
<property name="hadjustment">canvas_horiz</property> <property name="hadjustment">canvas_horiz</property>
<property name="vadjustment">canvas_vert</property> <property name="vadjustment">canvas_vert</property>
<property name="min_content_width">400</property> <property name="min_content_width">400</property>
@ -156,232 +67,136 @@
<placeholder/> <placeholder/>
</child> </child>
</object> </object>
<packing>
<property name="resize">True</property>
<property name="shrink">False</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkStackSwitcher" id="heads_stack_switcher"> <object class="GtkStackSwitcher" id="heads_stack_switcher">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</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_start">8</property>
<property name="margin_end">8</property> <property name="margin_end">8</property>
<property name="margin_top">8</property> <property name="margin_top">8</property>
<property name="hexpand">True</property> <property name="hexpand">1</property>
<property name="stack">heads_stack</property> <property name="stack">heads_stack</property>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkStack" id="heads_stack"> <object class="GtkStack" id="heads_stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="transition_type">crossfade</property> <property name="transition_type">crossfade</property>
<child> <child>
<placeholder/> <placeholder/>
</child> </child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing>
<property name="index">-1</property>
</packing>
</child> </child>
<child type="overlay"> <child type="overlay">
<object class="GtkSpinner" id="spinner"> <object class="GtkSpinner" id="spinner">
<property name="visible">True</property> <property name="visible">0</property>
<property name="can_focus">False</property> <property name="hexpand">1</property>
<property name="no_show_all">True</property> <property name="vexpand">1</property>
<property name="hexpand">True</property> <property name="can_target">0</property>
<property name="vexpand">True</property>
</object> </object>
<packing>
<property name="pass_through">True</property>
</packing>
</child> </child>
</object> </object>
</child> </child>
<child type="titlebar"> <child type="titlebar">
<object class="GtkStack" id="header_stack"> <object class="GtkStack" id="header_stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="transition_type">crossfade</property> <property name="transition_type">crossfade</property>
<child> <child>
<object class="GtkHeaderBar"> <object class="GtkStackPage">
<property name="visible">True</property> <property name="name">title</property>
<property name="can_focus">False</property> <property name="child">
<property name="title" translatable="yes">wdisplays</property> <object class="GtkHeaderBar">
<property name="has_subtitle">False</property> <property name="title" translatable="yes">wdisplays</property>
<property name="show_close_button">True</property> <property name="has_subtitle">0</property>
<child> <property name="show_title_buttons">1</property>
<object class="GtkButtonBox" id="zoom_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">expand</property>
<child> <child>
<object class="GtkButton" id="zoom_out"> <object class="GtkBox" id="zoom_box">
<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"/>
<child> <child>
<object class="GtkImage"> <object class="GtkButton" id="zoom_out">
<property name="visible">True</property> <property name="can_focus">1</property>
<property name="can_focus">False</property> <property name="receives_default">1</property>
<property name="tooltip_text" translatable="yes">Zoom Out</property>
<property name="action_name">app.zoom-out</property>
<property name="icon_name">zoom-out-symbolic</property> <property name="icon_name">zoom-out-symbolic</property>
<accelerator key="minus" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object> </object>
</child> </child>
<accelerator key="minus" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="non_homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkButton" id="zoom_reset">
<property name="visible">True</property>
<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"/>
<accelerator key="0" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="non_homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkButton" id="zoom_in">
<property name="visible">True</property>
<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"/>
<child> <child>
<object class="GtkImage"> <object class="GtkButton" id="zoom_reset">
<property name="visible">True</property> <property name="can_focus">1</property>
<property name="can_focus">False</property> <property name="receives_default">1</property>
<property name="icon_name">zoom-in-symbolic</property> <property name="tooltip_text" translatable="yes">Zoom Reset</property>
<property name="action_name">app.zoom-reset</property>
<accelerator key="0" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object> </object>
</child> </child>
<accelerator key="equal" signal="clicked" modifiers="GDK_CONTROL_MASK"/> <child>
<object class="GtkButton" id="zoom_in">
<property name="can_focus">1</property>
<property name="receives_default">1</property>
<property name="tooltip_text" translatable="yes">Zoom In</property>
<property name="action_name">app.zoom-in</property>
<property name="icon_name">zoom-in-symbolic</property>
<accelerator key="equal" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<style>
<class name="linked"/>
</style>
</object> </object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="non_homogeneous">True</property>
</packing>
</child> </child>
</object> <child type="end">
</child> <object class="GtkMenuButton" id="menu_button">
<child> <property name="can_focus">1</property>
<object class="GtkMenuButton" id="menu_button"> <property name="receives_default">1</property>
<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>
<property name="can_focus">False</property>
<property name="icon_name">open-menu-symbolic</property> <property name="icon_name">open-menu-symbolic</property>
</object> </object>
</child> </child>
</object> </object>
<packing> </property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object> </object>
<packing>
<property name="name">title</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkHeaderBar"> <object class="GtkStackPage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child type="title">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Apply Changes?</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">_Apply</property>
<property name="visible">True</property>
<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"/>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="pack_type">end</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">_Cancel</property>
<property name="visible">True</property>
<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"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="name">apply</property> <property name="name">apply</property>
<property name="position">1</property> <property name="child">
</packing> <object class="GtkHeaderBar">
<child type="title">
<object class="GtkLabel">
<property name="label" translatable="yes">Apply Changes?</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">_Apply</property>
<property name="can_focus">1</property>
<property name="receives_default">1</property>
<property name="use_underline">1</property>
<property name="action_name">app.apply-changes</property>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
<child type="end">
<object class="GtkButton">
<property name="label" translatable="yes">_Cancel</property>
<property name="can_focus">1</property>
<property name="receives_default">1</property>
<property name="use_underline">1</property>
<property name="action_name">app.cancel-changes</property>
</object>
</child>
</object>
</property>
</object>
</child> </child>
</object> </object>
</child> </child>

View File

@ -2,7 +2,7 @@
/* Copyright (C) 2019 cyclopsian */ /* Copyright (C) 2019 cyclopsian */
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <gdk/gdkwayland.h> #include <gdk/wayland/gdkwayland.h>
#include "wdisplays.h" #include "wdisplays.h"
#include "glviewport.h" #include "glviewport.h"
@ -20,22 +20,18 @@ __attribute__((noreturn)) void wd_fatal_error(int status, const char *message) {
#define MAX_ZOOM 1000. #define MAX_ZOOM 1000.
#define CANVAS_MARGIN 40 #define CANVAS_MARGIN 40
static const char *HEAD_PREFIX = "head";
static const char *MODE_PREFIX = "mode"; static const char *MODE_PREFIX = "mode";
static const char *TRANSFORM_PREFIX = "transform"; static const char *ROTATE_PREFIX = "rotate";
static const char *APP_PREFIX = "app"; static const char *APP_PREFIX = "app";
#define NUM_ROTATIONS 4 static int32_t get_rotate_value(enum wl_output_transform transform) {
static const char *ROTATE_IDS[NUM_ROTATIONS] = {
"rotate_0", "rotate_90", "rotate_180", "rotate_270"
};
static int get_rotate_index(enum wl_output_transform transform) {
if (transform == WL_OUTPUT_TRANSFORM_90 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_90) { if (transform == WL_OUTPUT_TRANSFORM_90 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_90) {
return 1; return 90;
} else if (transform == WL_OUTPUT_TRANSFORM_180 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_180) { } else if (transform == WL_OUTPUT_TRANSFORM_180 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_180) {
return 2; return 180;
} else if (transform == WL_OUTPUT_TRANSFORM_270 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_270) { } else if (transform == WL_OUTPUT_TRANSFORM_270 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_270) {
return 3; return 270;
} }
return 0; return 0;
} }
@ -45,6 +41,7 @@ static bool has_changes(const 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) {
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form_iter->data), "builder")); GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form_iter->data), "builder"));
const struct wd_head *head = g_object_get_data(G_OBJECT(form_iter->data), "head"); const struct wd_head *head = g_object_get_data(G_OBJECT(form_iter->data), "head");
GAction *rotate_action = G_ACTION(g_object_get_data(G_OBJECT(form_iter->data), "rotate_action"));
if (head->enabled != gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "enabled")))) { if (head->enabled != gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "enabled")))) {
return TRUE; return TRUE;
} }
@ -71,16 +68,8 @@ static bool has_changes(const struct wd_state *state) {
if (r / 1000. != gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "refresh")))) { if (r / 1000. != gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "refresh")))) {
return TRUE; return TRUE;
} }
for (int i = 0; i < NUM_ROTATIONS; i++) { if (g_variant_get_int32(g_action_get_state(rotate_action)) != get_rotate_value(head->transform)) {
GtkWidget *rotate = GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[i])); return TRUE;
gboolean selected;
g_object_get(rotate, "active", &selected, NULL);
if (selected) {
if (i != get_rotate_index(head->transform)) {
return TRUE;
}
break;
}
} }
bool flipped = head->transform == WL_OUTPUT_TRANSFORM_FLIPPED bool flipped = head->transform == WL_OUTPUT_TRANSFORM_FLIPPED
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_90 || head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_90
@ -103,20 +92,13 @@ void fill_output_from_form(struct wd_head_config *output, GtkWidget *form) {
output->width = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "width"))); output->width = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "width")));
output->height = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "height"))); output->height = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "height")));
output->refresh = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "refresh"))) * 1000.; output->refresh = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "refresh"))) * 1000.;
int32_t rotate = g_variant_get_int32(g_action_get_state(G_ACTION(g_object_get_data(G_OBJECT(form), "rotate_action"))));
gboolean flipped = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "flipped"))); gboolean flipped = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "flipped")));
for (int i = 0; i < NUM_ROTATIONS; i++) { switch (rotate) {
GtkWidget *rotate = GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[i])); case 0: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED : WL_OUTPUT_TRANSFORM_NORMAL; break;
gboolean selected; case 90: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_90 : WL_OUTPUT_TRANSFORM_90; break;
g_object_get(rotate, "active", &selected, NULL); case 180: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_180 : WL_OUTPUT_TRANSFORM_180; break;
if (selected) { case 270: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_270 : WL_OUTPUT_TRANSFORM_270; break;
switch (i) {
case 0: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED : WL_OUTPUT_TRANSFORM_NORMAL; break;
case 1: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_90 : WL_OUTPUT_TRANSFORM_90; break;
case 2: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_180 : WL_OUTPUT_TRANSFORM_180; break;
case 3: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_270 : WL_OUTPUT_TRANSFORM_270; break;
}
break;
}
} }
} }
@ -131,8 +113,8 @@ static gboolean send_apply(gpointer data) {
wl_list_insert(outputs, &output->link); wl_list_insert(outputs, &output->link);
fill_output_from_form(output, GTK_WIDGET(form_iter->data)); fill_output_from_form(output, GTK_WIDGET(form_iter->data));
} }
GdkWindow *window = gtk_widget_get_window(state->stack); GdkSurface *surface = gtk_native_get_surface(gtk_widget_get_native(state->stack));
GdkDisplay *display = gdk_window_get_display(window); GdkDisplay *display = gdk_surface_get_display(surface);
struct wl_display *wl_display = gdk_wayland_display_get_wl_display(display); struct wl_display *wl_display = gdk_wayland_display_get_wl_display(display);
wd_apply_state(state, outputs, wl_display); wd_apply_state(state, outputs, wl_display);
state->apply_pending = FALSE; state->apply_pending = FALSE;
@ -143,7 +125,7 @@ static void apply_state(struct wd_state *state) {
gtk_stack_set_visible_child_name(GTK_STACK(state->header_stack), "title"); gtk_stack_set_visible_child_name(GTK_STACK(state->header_stack), "title");
if (!state->autoapply) { if (!state->autoapply) {
gtk_style_context_add_class(gtk_widget_get_style_context(state->spinner), "visible"); gtk_style_context_add_class(gtk_widget_get_style_context(state->spinner), "visible");
gtk_overlay_set_overlay_pass_through(GTK_OVERLAY(state->overlay), state->spinner, FALSE); gtk_widget_set_can_target(state->spinner, TRUE);
gtk_spinner_start(GTK_SPINNER(state->spinner)); gtk_spinner_start(GTK_SPINNER(state->spinner));
gtk_widget_set_sensitive(state->stack_switcher, FALSE); gtk_widget_set_sensitive(state->stack_switcher, FALSE);
@ -273,15 +255,15 @@ static void update_cursor(struct wd_state *state) {
break; break;
} }
} }
GdkWindow *window = gtk_widget_get_window(state->canvas); GdkSurface *surface = gtk_native_get_surface(gtk_widget_get_native(state->canvas));
if (any_hovered) { if (any_hovered) {
gdk_window_set_cursor(window, state->grab_cursor); gdk_surface_set_cursor(surface, state->grab_cursor);
} else if (state->clicked != NULL) { } else if (state->clicked != NULL) {
gdk_window_set_cursor(window, state->grabbing_cursor); gdk_surface_set_cursor(surface, state->grabbing_cursor);
} else if (state->panning) { } else if (state->panning) {
gdk_window_set_cursor(window, state->move_cursor); gdk_surface_set_cursor(surface, state->move_cursor);
} else { } else {
gdk_window_set_cursor(window, NULL); gdk_surface_set_cursor(surface, NULL);
} }
} }
@ -294,15 +276,13 @@ static inline void flip_anim(uint64_t *timer, uint64_t tick) {
} }
} }
static void update_hovered(struct wd_state *state) { static void update_hovered(struct wd_state *state,
GdkDisplay *display = gdk_display_get_default(); gdouble mouse_x, gdouble mouse_y) {
GdkWindow *window = gtk_widget_get_window(state->canvas);
if (!gtk_widget_get_realized(state->canvas)) { if (!gtk_widget_get_realized(state->canvas)) {
return; return;
} }
GdkFrameClock *clock = gtk_widget_get_frame_clock(state->canvas); GdkFrameClock *clock = gtk_widget_get_frame_clock(state->canvas);
uint64_t tick = gdk_frame_clock_get_frame_time(clock); uint64_t tick = gdk_frame_clock_get_frame_time(clock);
g_autoptr(GList) seats = gdk_display_list_seats(display);
bool any_hovered = FALSE; bool any_hovered = FALSE;
struct wd_render_head_data *render; struct wd_render_head_data *render;
wl_list_for_each(render, &state->render.heads, link) { wl_list_for_each(render, &state->render.heads, link) {
@ -315,18 +295,10 @@ static void update_hovered(struct wd_state *state) {
render->hovered = TRUE; render->hovered = TRUE;
any_hovered = TRUE; any_hovered = TRUE;
} else if (state->clicked == NULL) { } else if (state->clicked == NULL) {
for (GList *iter = seats; iter != NULL; iter = iter->next) { if (mouse_x >= render->x1 && mouse_x < render->x2 &&
double mouse_x; mouse_y >= render->y1 && mouse_y < render->y2) {
double mouse_y; render->hovered = TRUE;
any_hovered = TRUE;
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;
any_hovered = TRUE;
break;
}
} }
} }
if (init_hovered != render->hovered) { if (init_hovered != render->hovered) {
@ -348,18 +320,8 @@ static inline void color_to_float_array(GtkStyleContext *ctx,
} }
static unsigned form_get_rotation(GtkWidget *form) { static unsigned form_get_rotation(GtkWidget *form) {
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder")); int32_t rotate = g_variant_get_int32(g_action_get_state(G_ACTION(g_object_get_data(G_OBJECT(form), "rotate_action"))));
unsigned rot; return rotate / 90;
for (rot = 0; rot < NUM_ROTATIONS; rot++) {
GtkWidget *rotate = GTK_WIDGET(gtk_builder_get_object(builder,
ROTATE_IDS[rot]));
gboolean selected;
g_object_get(rotate, "active", &selected, NULL);
if (selected) {
return rot;
}
}
return -1;
} }
#define SWAP(_type, _a, _b) { _type _tmp = (_a); (_a) = (_b); (_b) = _tmp; } #define SWAP(_type, _a, _b) { _type _tmp = (_a); (_a) = (_b); (_b) = _tmp; }
@ -443,67 +405,81 @@ static void update_sensitivity(GtkWidget *form) {
} }
} }
static void select_rotate_option(GtkWidget *form, GtkWidget *model_button) { static void rotate_selected(GSimpleAction *action, GVariant *param, gpointer data) {
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder")); const struct wd_head *head = g_object_get_data(G_OBJECT(data), "head");
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(data, "builder"));
GtkWidget *rotate_button = GTK_WIDGET(gtk_builder_get_object(builder, "rotate_button")); GtkWidget *rotate_button = GTK_WIDGET(gtk_builder_get_object(builder, "rotate_button"));
for (int i = 0; i < NUM_ROTATIONS; i++) { GMenuModel *menu = gtk_menu_button_get_menu_model(GTK_MENU_BUTTON(rotate_button));
GtkWidget *rotate = GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[i])); int items = g_menu_model_get_n_items(menu);
gboolean selected = model_button == rotate; for (int i = 0; i < items; i++) {
g_object_set(rotate, "active", selected, NULL); g_autoptr(GVariant) target = g_menu_model_get_item_attribute_value(menu, i, G_MENU_ATTRIBUTE_TARGET, NULL);
if (selected) { g_autoptr(GVariant) label = g_menu_model_get_item_attribute_value(menu, i, G_MENU_ATTRIBUTE_LABEL, NULL);
g_autofree gchar *rotate_text = NULL; if (g_variant_get_int32(target) == g_variant_get_int32(param)) {
g_object_get(rotate, "text", &rotate_text, NULL); gtk_menu_button_set_label(GTK_MENU_BUTTON(rotate_button), g_variant_get_string(label, NULL));
gtk_button_set_label(GTK_BUTTON(rotate_button), rotate_text); break;
} }
} }
} g_simple_action_set_state(action, param);
static void rotate_selected(GSimpleAction *action, GVariant *param, gpointer data) {
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");
update_ui(head->state); update_ui(head->state);
} }
static void select_mode_option(GtkWidget *form, int32_t w, int32_t h, int32_t r) { static GVariant *create_mode_variant(int32_t w, int32_t h, int32_t r) {
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder")); GVariant * const children[] = {
GtkWidget *mode_box = GTK_WIDGET(gtk_builder_get_object(builder, "mode_box")); g_variant_new_int32(w),
g_autoptr(GList) children = gtk_container_get_children(GTK_CONTAINER(mode_box)); g_variant_new_int32(h),
for (GList *child = children; child != NULL; child = child->next) { g_variant_new_int32(r),
const struct wd_mode *mode = g_object_get_data(G_OBJECT(child->data), "mode"); };
g_object_set(child->data, "active", w == mode->width && h == mode->height && r == mode->refresh, NULL); return g_variant_new_tuple(children, G_N_ELEMENTS(children));
}
} }
static void update_mode_entries(GtkWidget *form, int32_t w, int32_t h, int32_t r) { struct vid_mode {
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder")); int32_t width;
GtkWidget *width = GTK_WIDGET(gtk_builder_get_object(builder, "width")); int32_t height;
GtkWidget *height = GTK_WIDGET(gtk_builder_get_object(builder, "height")); int32_t refresh;
GtkWidget *refresh = GTK_WIDGET(gtk_builder_get_object(builder, "refresh")); };
gtk_spin_button_set_value(GTK_SPIN_BUTTON(width), w); static void unpack_mode_variant(GVariant *value, struct vid_mode *mode) {
gtk_spin_button_set_value(GTK_SPIN_BUTTON(height), h); g_autoptr(GVariant) width = g_variant_get_child_value(value, 0);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(refresh), r / 1000.); 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 mode_spin_changed(GtkSpinButton *spin_button, gpointer data) {
GtkWidget *form = data;
const struct wd_head *head = g_object_get_data(G_OBJECT(form), "head");
struct vid_mode mode;
GAction *mode_action = G_ACTION(g_object_get_data(G_OBJECT(form), "mode_action"));
GVariant *value = g_action_get_state(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(mode_action, create_mode_variant(mode.width, mode.height, mode.refresh));
update_ui(head->state);
} }
static void mode_selected(GSimpleAction *action, GVariant *param, gpointer data) { static void mode_selected(GSimpleAction *action, GVariant *param, gpointer data) {
GtkWidget *form = data; GtkWidget *form = data;
const struct wd_head *head = g_object_get_data(G_OBJECT(form), "head"); const struct wd_head *head = g_object_get_data(G_OBJECT(form), "head");
const struct wd_mode *mode = g_object_get_data(G_OBJECT(action), "mode"); GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder"));
struct vid_mode mode;
unpack_mode_variant(param, &mode);
update_mode_entries(form, mode->width, mode->height, mode->refresh); g_simple_action_set_state(action, param);
select_mode_option(form, mode->width, mode->height, mode->refresh); gtk_spin_button_set_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "width")), mode.width);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "height")), mode.height);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "refresh")), mode.refresh / 1000.);
update_ui(head->state); update_ui(head->state);
} }
// END FORM CALLBACKS // END FORM CALLBACKS
static void clear_menu(GtkWidget *box, GActionMap *action_map) {
g_autoptr(GList) children = gtk_container_get_children(GTK_CONTAINER(box));
for (GList *child = children; child != NULL; child = child->next) {
g_action_map_remove_action(action_map, strchr(gtk_actionable_get_action_name(GTK_ACTIONABLE(child->data)), '.') + 1);
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"));
@ -512,12 +488,14 @@ static void update_head_form(GtkWidget *form, unsigned int fields) {
GtkWidget *scale = GTK_WIDGET(gtk_builder_get_object(builder, "scale")); GtkWidget *scale = GTK_WIDGET(gtk_builder_get_object(builder, "scale"));
GtkWidget *pos_x = GTK_WIDGET(gtk_builder_get_object(builder, "pos_x")); GtkWidget *pos_x = GTK_WIDGET(gtk_builder_get_object(builder, "pos_x"));
GtkWidget *pos_y = GTK_WIDGET(gtk_builder_get_object(builder, "pos_y")); GtkWidget *pos_y = GTK_WIDGET(gtk_builder_get_object(builder, "pos_y"));
GtkWidget *mode_box = GTK_WIDGET(gtk_builder_get_object(builder, "mode_box")); GtkWidget *mode_button = GTK_WIDGET(gtk_builder_get_object(builder, "mode_button"));
GtkWidget *flipped = GTK_WIDGET(gtk_builder_get_object(builder, "flipped")); GtkWidget *flipped = GTK_WIDGET(gtk_builder_get_object(builder, "flipped"));
const struct wd_head *head = g_object_get_data(G_OBJECT(form), "head"); const struct wd_head *head = g_object_get_data(G_OBJECT(form), "head");
GAction *mode_action = G_ACTION(g_object_get_data(G_OBJECT(form), "mode_action"));
GAction *rotate_action = G_ACTION(g_object_get_data(G_OBJECT(form), "rotate_action"));
if (fields & WD_FIELD_NAME) { if (fields & WD_FIELD_NAME) {
gtk_container_child_set(GTK_CONTAINER(head->state->stack), form, "title", head->name, NULL); g_object_set(gtk_stack_get_page(GTK_STACK(head->state->stack), form), "title", head->name, NULL);
} }
if (fields & WD_FIELD_DESCRIPTION) { if (fields & WD_FIELD_DESCRIPTION) {
gtk_label_set_text(GTK_LABEL(description), head->description); gtk_label_set_text(GTK_LABEL(description), head->description);
@ -538,27 +516,17 @@ static void update_head_form(GtkWidget *form, unsigned int fields) {
} }
if (fields & WD_FIELD_MODE) { if (fields & WD_FIELD_MODE) {
GActionMap *mode_actions = G_ACTION_MAP(g_object_get_data(G_OBJECT(form), "mode-group")); GMenu *mode_menu = g_menu_new();
clear_menu(mode_box, mode_actions);
struct wd_mode *mode; 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) { 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.); g_autofree gchar *name = g_strdup_printf("%d×%d@%0.3fHz", mode->width, mode->height, mode->refresh / 1000.);
GSimpleAction *action = g_simple_action_new(name, NULL); GMenuItem *item = g_menu_item_new(name, action);
g_action_map_add_action(G_ACTION_MAP(mode_actions), G_ACTION(action)); g_menu_item_set_attribute_value(item, G_MENU_ATTRIBUTE_TARGET,
g_signal_connect(action, "activate", G_CALLBACK(mode_selected), form); create_mode_variant(mode->width, mode->height, mode->refresh));
g_object_set_data(G_OBJECT(action), "mode", mode); g_menu_append_item(mode_menu, item);
g_object_unref(action);
GtkWidget *button = gtk_model_button_new();
g_autoptr(GString) prefixed_name = g_string_new(MODE_PREFIX);
g_string_append(prefixed_name, ".");
g_string_append(prefixed_name, name);
gtk_actionable_set_action_name(GTK_ACTIONABLE(button), prefixed_name->str);
g_object_set(button, "role", GTK_BUTTON_ROLE_RADIO, "text", name, NULL);
gtk_box_pack_start(GTK_BOX(mode_box), button, FALSE, FALSE, 0);
g_object_set_data(G_OBJECT(button), "mode", mode);
gtk_widget_show_all(button);
} }
gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(mode_button), G_MENU_MODEL(mode_menu));
// Mode entries // Mode entries
int w = head->custom_mode.width; int w = head->custom_mode.width;
int h = head->custom_mode.height; int h = head->custom_mode.height;
@ -579,14 +547,12 @@ static void update_head_form(GtkWidget *form, unsigned int fields) {
} }
} }
update_mode_entries(form, w, h, r); g_action_change_state(mode_action, create_mode_variant(w, h, r));
select_mode_option(form, w, h, r);
gtk_widget_show_all(mode_box);
} }
if (fields & WD_FIELD_TRANSFORM) { if (fields & WD_FIELD_TRANSFORM) {
int active_rotate = get_rotate_index(head->transform); int active_rotate = get_rotate_value(head->transform);
select_rotate_option(form, GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[active_rotate]))); g_action_change_state(rotate_action, g_variant_new_int32(active_rotate));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(flipped), gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(flipped),
head->transform == WL_OUTPUT_TRANSFORM_FLIPPED head->transform == WL_OUTPUT_TRANSFORM_FLIPPED
@ -625,36 +591,48 @@ void wd_ui_reset_heads(struct wd_state *state) {
GtkWidget *mode_button = GTK_WIDGET(gtk_builder_get_object(builder, "mode_button")); GtkWidget *mode_button = GTK_WIDGET(gtk_builder_get_object(builder, "mode_button"));
GtkWidget *rotate_button = GTK_WIDGET(gtk_builder_get_object(builder, "rotate_button")); GtkWidget *rotate_button = GTK_WIDGET(gtk_builder_get_object(builder, "rotate_button"));
GSimpleActionGroup *mode_actions = g_simple_action_group_new(); GSimpleActionGroup *head_actions = g_simple_action_group_new();
gtk_widget_insert_action_group(mode_button, MODE_PREFIX, G_ACTION_GROUP(mode_actions)); gtk_widget_insert_action_group(mode_button, HEAD_PREFIX, G_ACTION_GROUP(head_actions));
g_object_set_data(G_OBJECT(form), "mode-group", mode_actions); gtk_widget_insert_action_group(rotate_button, HEAD_PREFIX, G_ACTION_GROUP(head_actions));
g_object_unref(mode_actions);
GSimpleActionGroup *transform_actions = g_simple_action_group_new(); GMenu *rotate_menu = g_menu_new();
gtk_widget_insert_action_group(rotate_button, TRANSFORM_PREFIX, G_ACTION_GROUP(transform_actions)); g_menu_append(rotate_menu, "Don't Rotate", "head.rotate(0)");
g_object_unref(transform_actions); 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(rotate_button), G_MENU_MODEL(rotate_menu));
for (int i = 0; i < NUM_ROTATIONS; i++) { static const GVariantType * const mode_types[] = {
GtkWidget *button = GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[i])); G_VARIANT_TYPE_INT32,
g_object_set(button, "role", GTK_BUTTON_ROLE_RADIO, NULL); G_VARIANT_TYPE_INT32,
GSimpleAction *action = g_simple_action_new(ROTATE_IDS[i], NULL); G_VARIANT_TYPE_INT32
g_action_map_add_action(G_ACTION_MAP(transform_actions), G_ACTION(action)); };
g_signal_connect(action, "activate", G_CALLBACK(rotate_selected), form); GSimpleAction *action = g_simple_action_new_stateful("mode",
g_object_set_data(G_OBJECT(action), "widget", button); g_variant_type_new_tuple(mode_types, G_N_ELEMENTS(mode_types)),
g_object_unref(action); 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_set_data(G_OBJECT(form), "mode_action", action);
g_object_unref(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_set_data(G_OBJECT(form), "rotate_action", action);
g_object_unref(action);
g_object_unref(head_actions);
update_head_form(form, WD_FIELDS_ALL); update_head_form(form, WD_FIELDS_ALL);
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(gtk_builder_get_object(builder, "width"), "value-changed", G_CALLBACK(mode_spin_changed), form);
g_signal_connect(gtk_builder_get_object(builder, "height"), "value-changed", G_CALLBACK(mode_spin_changed), form);
g_signal_connect(gtk_builder_get_object(builder, "refresh"), "value-changed", G_CALLBACK(mode_spin_changed), form);
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, "enabled"), "toggled", G_CALLBACK(update_ui), 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, "scale"), "value-changed", G_CALLBACK(update_ui), 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_x"), "value-changed", G_CALLBACK(update_ui), 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, "pos_y"), "value-changed", G_CALLBACK(update_ui), 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(update_ui), 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(update_ui), state); g_signal_connect_swapped(gtk_builder_get_object(builder, "flipped"), "toggled", G_CALLBACK(update_ui), state);
} else { } else {
@ -705,7 +683,7 @@ void wd_ui_reset_all(struct wd_state *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) {
gtk_style_context_remove_class(gtk_widget_get_style_context(state->spinner), "visible"); gtk_style_context_remove_class(gtk_widget_get_style_context(state->spinner), "visible");
gtk_overlay_set_overlay_pass_through(GTK_OVERLAY(state->overlay), state->spinner, TRUE); gtk_widget_set_can_target(state->spinner, FALSE);
gtk_spinner_stop(GTK_SPINNER(state->spinner)); gtk_spinner_stop(GTK_SPINNER(state->spinner));
gtk_widget_set_sensitive(state->stack_switcher, TRUE); gtk_widget_set_sensitive(state->stack_switcher, TRUE);
@ -786,15 +764,18 @@ static void zoom_to(struct wd_state *state, double zoom) {
update_zoom(state); update_zoom(state);
} }
static void zoom_out(struct wd_state *state) { static void zoom_out(GSimpleAction *action, GVariant *param, gpointer data) {
struct wd_state *state = data;
zoom_to(state, state->zoom * 0.75); zoom_to(state, state->zoom * 0.75);
} }
static void zoom_reset(struct wd_state *state) { static void zoom_reset(GSimpleAction *action, GVariant *param, gpointer data) {
struct wd_state *state = data;
zoom_to(state, DEFAULT_ZOOM); zoom_to(state, DEFAULT_ZOOM);
} }
static void zoom_in(struct wd_state *state) { static void zoom_in(GSimpleAction *action, GVariant *param, gpointer data) {
struct wd_state *state = data;
zoom_to(state, state->zoom / 0.75); zoom_to(state, state->zoom / 0.75);
} }
@ -923,82 +904,68 @@ static void set_clicked_head(struct wd_state *state,
state->clicked = clicked; state->clicked = clicked;
} }
static gboolean canvas_click(GtkWidget *widget, GdkEvent *event, static void canvas_drag_begin(GtkGestureDrag *drag,
gpointer data) { gdouble mouse_x, gdouble mouse_y, gpointer data) {
struct wd_state *state = data; struct wd_state *state = data;
if (event->button.type == GDK_BUTTON_PRESS) { guint button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(drag));
if (event->button.button == 1) {
if (button == 1) {
struct wd_render_head_data *render;
state->clicked = NULL;
wl_list_for_each(render, &state->render.heads, link) {
if (mouse_x >= render->x1 && mouse_x < render->x2 &&
mouse_y >= render->y1 && mouse_y < render->y2) {
set_clicked_head(state, render);
state->click_offset.x = mouse_x - render->x1;
state->click_offset.y = mouse_y - render->y1;
break;
}
}
if (state->clicked != NULL) {
wl_list_remove(&state->clicked->link);
wl_list_insert(&state->render.heads, &state->clicked->link);
struct wd_render_head_data *render; struct wd_render_head_data *render;
state->clicked = NULL;
wl_list_for_each(render, &state->render.heads, link) { wl_list_for_each(render, &state->render.heads, link) {
double mouse_x = event->button.x; render->updated_at = 0;
double mouse_y = event->button.y; render->preview = TRUE;
if (mouse_x >= render->x1 && mouse_x < render->x2 && }
mouse_y >= render->y1 && mouse_y < render->y2) { gtk_gl_area_queue_render(GTK_GL_AREA(state->canvas));
set_clicked_head(state, render); g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack));
state->click_offset.x = event->button.x - render->x1; for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
state->click_offset.y = event->button.y - render->y1; const struct wd_head *other = g_object_get_data(G_OBJECT(form_iter->data), "head");
if (state->clicked == other->render) {
gtk_stack_set_visible_child(GTK_STACK(state->stack), form_iter->data);
break; break;
} }
} }
if (state->clicked != NULL) {
wl_list_remove(&state->clicked->link);
wl_list_insert(&state->render.heads, &state->clicked->link);
struct wd_render_head_data *render;
wl_list_for_each(render, &state->render.heads, link) {
render->updated_at = 0;
render->preview = TRUE;
}
gtk_gl_area_queue_render(GTK_GL_AREA(state->canvas));
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->render) {
gtk_stack_set_visible_child(GTK_STACK(state->stack), form_iter->data);
break;
}
}
}
} else if (event->button.button == 2) {
state->panning = TRUE;
state->pan_last.x = event->button.x;
state->pan_last.y = event->button.y;
} }
} else if (button == 2) {
state->panning = TRUE;
state->pan_last.x = mouse_x;
state->pan_last.y = mouse_y;
} }
return TRUE;
}
static gboolean canvas_release(GtkWidget *widget, GdkEvent *event,
gpointer data) {
struct wd_state *state = data;
if (event->button.button == 1) {
set_clicked_head(state, NULL);
}
if (event->button.button == 2) {
state->panning = FALSE;
}
update_cursor(state);
return TRUE;
} }
#define SNAP_DIST 6. #define SNAP_DIST 6.
static gboolean canvas_motion(GtkWidget *widget, GdkEvent *event, static void canvas_drag_update(GtkGestureDrag *drag,
gpointer data) { gdouble mouse_x, gdouble mouse_y, gpointer data) {
struct wd_state *state = data; struct wd_state *state = data;
if (event->motion.state & GDK_BUTTON2_MASK) { guint button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(drag));
if (button == 2) {
GtkAdjustment *xadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller)); GtkAdjustment *xadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller));
GtkAdjustment *yadj = gtk_scrolled_window_get_vadjustment(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_x = mouse_x - state->pan_last.x;
double delta_y = event->motion.y - state->pan_last.y; double delta_y = mouse_y - state->pan_last.y;
gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) + delta_x); gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) + delta_x);
gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) + delta_y); gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) + delta_y);
state->pan_last.x = event->motion.x; state->pan_last.x = mouse_x;
state->pan_last.y = event->motion.y; state->pan_last.y = mouse_y;
queue_canvas_draw(state); queue_canvas_draw(state);
} }
if ((event->motion.state & GDK_BUTTON1_MASK) && state->clicked != NULL) { if (button == 1 && state->clicked != NULL) {
GtkWidget *form = NULL; GtkWidget *form = 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) {
@ -1024,9 +991,9 @@ static gboolean canvas_motion(GtkWidget *widget, GdkEvent *event,
SWAP(int, size.x, size.y); SWAP(int, size.x, size.y);
} }
struct wd_point tl = { struct wd_point tl = {
.x = (event->motion.x - state->click_offset.x .x = (mouse_x - state->click_offset.x
+ state->render.x_origin + state->render.scroll_x) / state->zoom, + state->render.x_origin + state->render.scroll_x) / state->zoom,
.y = (event->motion.y - state->click_offset.y .y = (mouse_y - state->click_offset.y
+ state->render.y_origin + state->render.scroll_y) / state->zoom + state->render.y_origin + state->render.scroll_y) / state->zoom
}; };
const struct wd_point br = { const struct wd_point br = {
@ -1036,9 +1003,12 @@ static gboolean canvas_motion(GtkWidget *widget, GdkEvent *event,
struct wd_point new_pos = tl; struct wd_point new_pos = tl;
float snap = SNAP_DIST / state->zoom; float snap = SNAP_DIST / state->zoom;
GdkEvent *event = gtk_get_current_event();
GdkModifierType mod_state = gdk_event_get_modifier_state(event);
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) {
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 (other->render != state->clicked && !(event->motion.state & GDK_SHIFT_MASK)) { if (other->render != state->clicked && !(mod_state & GDK_SHIFT_MASK)) {
GtkBuilder *other_builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form_iter->data), "builder")); 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 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 y1 = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(other_builder, "pos_y")));
@ -1088,96 +1058,87 @@ static gboolean canvas_motion(GtkWidget *widget, GdkEvent *event,
gtk_spin_button_set_value(GTK_SPIN_BUTTON(pos_y), new_pos.y); 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, static void canvas_drag_end(GtkGestureDrag *drag,
gpointer data) { gdouble mouse_x, gdouble mouse_y, gpointer data) {
struct wd_state *state = data; struct wd_state *state = data;
if (!(event->crossing.state & GDK_BUTTON1_MASK)) { guint button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(drag));
if (button == 1) {
set_clicked_head(state, NULL); set_clicked_head(state, NULL);
} }
if (!(event->crossing.state & GDK_BUTTON2_MASK)) { if (button == 2) {
state->panning = FALSE; state->panning = FALSE;
} }
update_cursor(state); update_cursor(state);
return TRUE;
} }
static gboolean canvas_leave(GtkWidget *widget, GdkEvent *event, static void canvas_motion(GtkEventControllerMotion *controller,
gpointer data) { gdouble mouse_x, gdouble mouse_y, gpointer data) {
struct wd_state *state = data;
update_hovered(state, mouse_x, mouse_y);
}
static void canvas_enter(GtkEventControllerMotion *controller,
gdouble x, gdouble y, GdkCrossingMode crossing_mode, gpointer data) {
struct wd_state *state = data;
GdkEvent *event = gtk_get_current_event();
GdkModifierType mod_state = gdk_event_get_modifier_state(event);
if (!(mod_state & GDK_BUTTON1_MASK)) {
set_clicked_head(state, NULL);
}
if (!(mod_state & GDK_BUTTON2_MASK)) {
state->panning = FALSE;
}
update_cursor(state);
}
static void canvas_leave(GtkEventControllerMotion *controller,
GdkCrossingMode crossing_mode, gpointer data) {
struct wd_state *state = data; struct wd_state *state = data;
struct wd_render_head_data *render; struct wd_render_head_data *render;
wl_list_for_each(render, &state->render.heads, link) { wl_list_for_each(render, &state->render.heads, link) {
render->hovered = FALSE; render->hovered = FALSE;
} }
update_tick_callback(state); update_tick_callback(state);
return TRUE;
} }
static gboolean canvas_scroll(GtkWidget *widget, GdkEvent *event, static gboolean canvas_scroll(GtkEventControllerScroll *controller,
gpointer data) { gdouble delta_x, gdouble delta_y, gpointer data) {
struct wd_state *state = data; struct wd_state *state = data;
if (event->scroll.state & GDK_CONTROL_MASK) { GdkEvent *event = gtk_get_current_event();
switch (event->scroll.direction) { GdkModifierType mod_state = gdk_event_get_modifier_state(event);
case GDK_SCROLL_UP:
zoom_in(state); if (mod_state & GDK_CONTROL_MASK) {
break; if (delta_y)
case GDK_SCROLL_DOWN: zoom_to(state, state->zoom * pow(0.75, delta_y));
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 { } else {
GtkAdjustment *xadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller)); GtkAdjustment *xadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller));
GtkAdjustment *yadj = gtk_scrolled_window_get_vadjustment(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 xstep = gtk_adjustment_get_step_increment(xadj);
double ystep = gtk_adjustment_get_step_increment(yadj); double ystep = gtk_adjustment_get_step_increment(yadj);
switch (event->scroll.direction) { if (delta_x)
case GDK_SCROLL_UP: gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) + xstep * delta_x);
gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) - ystep); if (delta_y)
break; gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) + ystep * delta_y);
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; return TRUE;
} }
static void canvas_resize(GtkWidget *widget, GdkRectangle *allocation, static void canvas_resize(GtkWidget *widget, gint width, gint height,
gpointer data) { gint baseline, gpointer data) {
struct wd_state *state = data; struct wd_state *state = data;
update_scroll_size(state); update_scroll_size(state);
} }
static void cancel_changes(GtkButton *button, gpointer data) { static void cancel_changes(GSimpleAction *action, GVariant *param, 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");
wd_ui_reset_all(state); wd_ui_reset_all(state);
} }
static void apply_changes(GtkButton *button, gpointer data) { static void apply_changes(GSimpleAction *action, GVariant *param, gpointer data) {
apply_state(data); apply_state(data);
} }
@ -1195,8 +1156,8 @@ static void info_bar_animation_done(GObject *object, GParamSpec *pspec, gpointer
static void auto_apply_selected(GSimpleAction *action, GVariant *param, gpointer data) { static void auto_apply_selected(GSimpleAction *action, GVariant *param, gpointer data) {
struct wd_state *state = data; struct wd_state *state = data;
state->autoapply = !state->autoapply; state->autoapply = g_variant_get_boolean(param);
g_simple_action_set_state(action, g_variant_new_boolean(state->autoapply)); g_simple_action_set_state(action, param);
} }
static gboolean redraw_canvas(GtkWidget *widget, GdkFrameClock *frame_clock, gpointer data) { static gboolean redraw_canvas(GtkWidget *widget, GdkFrameClock *frame_clock, gpointer data) {
@ -1211,15 +1172,15 @@ static gboolean redraw_canvas(GtkWidget *widget, GdkFrameClock *frame_clock, gpo
static void capture_selected(GSimpleAction *action, GVariant *param, gpointer data) { static void capture_selected(GSimpleAction *action, GVariant *param, gpointer data) {
struct wd_state *state = data; struct wd_state *state = data;
state->capture = !state->capture; state->capture = g_variant_get_boolean(param);
g_simple_action_set_state(action, g_variant_new_boolean(state->capture)); g_simple_action_set_state(action, param);
update_tick_callback(state); update_tick_callback(state);
} }
static void overlay_selected(GSimpleAction *action, GVariant *param, gpointer data) { static void overlay_selected(GSimpleAction *action, GVariant *param, gpointer data) {
struct wd_state *state = data; struct wd_state *state = data;
state->show_overlay = !state->show_overlay; state->show_overlay = g_variant_get_boolean(param);
g_simple_action_set_state(action, g_variant_new_boolean(state->show_overlay)); g_simple_action_set_state(action, param);
struct wd_output *output; struct wd_output *output;
wl_list_for_each(output, &state->outputs, link) { wl_list_for_each(output, &state->outputs, link) {
@ -1245,15 +1206,18 @@ static void activate(GtkApplication* app, gpointer user_data) {
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_display(gdk_display, 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"); GdkCursor *default_cursor = gdk_cursor_new_from_name("default", NULL);
state->grabbing_cursor = gdk_cursor_new_from_name(gdk_display, "grabbing"); state->grab_cursor = gdk_cursor_new_from_name("grab", default_cursor);
state->move_cursor = gdk_cursor_new_from_name(gdk_display, "move"); state->grabbing_cursor = gdk_cursor_new_from_name("grabbing", default_cursor);
state->move_cursor = gdk_cursor_new_from_name("move", default_cursor);
g_object_unref(default_cursor);
GtkBuilder *builder = gtk_builder_new_from_resource("/wdisplays.ui"); GtkBuilder *builder = gtk_builder_new_from_resource("/wdisplays.ui");
GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "heads_window")); GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "heads_window"));
GtkWidget *paned = GTK_WIDGET(gtk_builder_get_object(builder, "paned"));
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"));
@ -1267,36 +1231,37 @@ static void activate(GtkApplication* app, gpointer user_data) {
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, "apply_changes", G_CALLBACK(apply_changes)); g_signal_connect(window, "destroy", G_CALLBACK(cleanup), state);
gtk_builder_add_callback_symbol(builder, "cancel_changes", G_CALLBACK(cancel_changes));
gtk_builder_add_callback_symbol(builder, "zoom_out", G_CALLBACK(zoom_out)); g_object_set(paned, "shrink-child1", FALSE, "shrink-child2", FALSE, NULL);
gtk_builder_add_callback_symbol(builder, "zoom_reset", G_CALLBACK(zoom_reset));
gtk_builder_add_callback_symbol(builder, "zoom_in", G_CALLBACK(zoom_in));
gtk_builder_add_callback_symbol(builder, "info_response", G_CALLBACK(info_response));
gtk_builder_add_callback_symbol(builder, "destroy", G_CALLBACK(cleanup));
gtk_builder_connect_signals(builder, state);
gtk_box_set_homogeneous(GTK_BOX(gtk_builder_get_object(builder, "zoom_box")), FALSE);
state->canvas = wd_gl_viewport_new(); state->canvas = wd_gl_viewport_new();
gtk_container_add(GTK_CONTAINER(state->scroller), state->canvas); gtk_widget_set_hexpand(state->canvas, TRUE);
gtk_widget_add_events(state->canvas, GDK_POINTER_MOTION_MASK gtk_widget_set_vexpand(state->canvas, TRUE);
| 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, "realize", G_CALLBACK(canvas_realize), state);
g_signal_connect(state->canvas, "render", G_CALLBACK(canvas_render), 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, "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); 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_required_version(GTK_GL_AREA(state->canvas), 2, 0); gtk_gl_area_set_required_version(GTK_GL_AREA(state->canvas), 2, 0);
gtk_gl_area_set_has_alpha(GTK_GL_AREA(state->canvas), TRUE); gtk_gl_area_set_use_es(GTK_GL_AREA(state->canvas), TRUE);
gtk_gl_area_set_auto_render(GTK_GL_AREA(state->canvas), state->capture); gtk_gl_area_set_auto_render(GTK_GL_AREA(state->canvas), state->capture);
GtkGesture *canvas_drag_controller = gtk_gesture_drag_new();
gtk_widget_add_controller(state->canvas, GTK_EVENT_CONTROLLER(canvas_drag_controller));
GtkEventController *canvas_motion_controller = gtk_event_controller_motion_new();
gtk_widget_add_controller(state->canvas, canvas_motion_controller);
GtkEventController *canvas_scroll_controller = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
gtk_widget_add_controller(state->canvas, canvas_scroll_controller);
g_signal_connect(canvas_drag_controller, "drag-begin", G_CALLBACK(canvas_drag_begin), state);
g_signal_connect(canvas_drag_controller, "drag-update", G_CALLBACK(canvas_drag_update), state);
g_signal_connect(canvas_drag_controller, "drag-end", G_CALLBACK(canvas_drag_end), state);
g_signal_connect(canvas_motion_controller, "enter", G_CALLBACK(canvas_enter), state);
g_signal_connect(canvas_motion_controller, "leave", G_CALLBACK(canvas_leave), state);
g_signal_connect(canvas_motion_controller, "motion", G_CALLBACK(canvas_motion), state);
g_signal_connect(canvas_scroll_controller, "scroll", G_CALLBACK(canvas_scroll), state);
gtk_container_add(GTK_CONTAINER(state->scroller), state->canvas);
GtkAdjustment *scroll_x_adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller)); 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)); 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_x_adj, "value-changed", G_CALLBACK(queue_canvas_draw), state);
@ -1305,24 +1270,51 @@ static void activate(GtkApplication* app, gpointer user_data) {
update_zoom(state); update_zoom(state);
GSimpleActionGroup *main_actions = g_simple_action_group_new(); GSimpleActionGroup *main_actions = g_simple_action_group_new();
gtk_widget_insert_action_group(state->menu_button, APP_PREFIX, G_ACTION_GROUP(main_actions)); gtk_widget_insert_action_group(window, APP_PREFIX, G_ACTION_GROUP(main_actions));
g_object_unref(main_actions); g_object_unref(main_actions);
GSimpleAction *autoapply_action = g_simple_action_new_stateful("auto-apply", NULL, GSimpleAction *action = g_simple_action_new("apply-changes", NULL);
g_signal_connect(action, "activate", G_CALLBACK(apply_changes), state);
g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(action));
action = g_simple_action_new("cancel-changes", NULL);
g_signal_connect(action, "activate", G_CALLBACK(cancel_changes), state);
g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(action));
action = g_simple_action_new("zoom-out", NULL);
g_signal_connect(action, "activate", G_CALLBACK(zoom_out), state);
g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(action));
action = g_simple_action_new("zoom-reset", NULL);
g_signal_connect(action, "activate", G_CALLBACK(zoom_reset), state);
g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(action));
action = g_simple_action_new("zoom-in", NULL);
g_signal_connect(action, "activate", G_CALLBACK(zoom_in), state);
g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(action));
action = g_simple_action_new_stateful("auto-apply", NULL,
g_variant_new_boolean(state->autoapply)); g_variant_new_boolean(state->autoapply));
g_signal_connect(autoapply_action, "activate", G_CALLBACK(auto_apply_selected), state); g_signal_connect(action, "change-state", 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(action));
GSimpleAction *capture_action = g_simple_action_new_stateful("capture-screens", NULL, GSimpleAction *capture_action = g_simple_action_new_stateful("capture-screens", NULL,
g_variant_new_boolean(state->capture)); g_variant_new_boolean(state->capture));
g_signal_connect(capture_action, "activate", G_CALLBACK(capture_selected), state); g_signal_connect(capture_action, "change-state", G_CALLBACK(capture_selected), state);
g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(capture_action)); g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(capture_action));
GSimpleAction *overlay_action = g_simple_action_new_stateful("show-overlay", NULL, GSimpleAction *overlay_action = g_simple_action_new_stateful("show-overlay", NULL,
g_variant_new_boolean(state->show_overlay)); g_variant_new_boolean(state->show_overlay));
g_signal_connect(overlay_action, "activate", G_CALLBACK(overlay_selected), state); g_signal_connect(overlay_action, "change-state", G_CALLBACK(overlay_selected), state);
g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(overlay_action)); g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(overlay_action));
GMenu *main_menu = g_menu_new();
g_menu_append(main_menu, "_Automatically Apply Changes", "app.auto-apply");
g_menu_append(main_menu, "_Show Screen Contents", "app.capture-screens");
g_menu_append(main_menu, "_Overlay Screen Names", "app.show-overlay");
gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(state->menu_button), G_MENU_MODEL(main_menu));
g_signal_connect(state->info_bar, "response", G_CALLBACK(info_response), state);
/* 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);
@ -1357,14 +1349,14 @@ static void activate(GtkApplication* app, gpointer user_data) {
g_signal_connect(gdk_display, "monitor-removed", G_CALLBACK(monitor_removed), 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);
g_object_unref(builder); g_object_unref(builder);
update_tick_callback(state); update_tick_callback(state);
gtk_window_present(GTK_WINDOW(window));
} }
// END GLOBAL CALLBACKS // END GLOBAL CALLBACKS
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
g_setenv("GDK_GL", "gles", FALSE);
GtkApplication *app = gtk_application_new(WDISPLAYS_APP_ID, G_APPLICATION_FLAGS_NONE); GtkApplication *app = gtk_application_new(WDISPLAYS_APP_ID, G_APPLICATION_FLAGS_NONE);
g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
int status = g_application_run(G_APPLICATION(app), argc, argv); int status = g_application_run(G_APPLICATION(app), argc, argv);

View File

@ -2,10 +2,10 @@
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) rt_dep = cc.find_library('rt', required : false)
gdk = dependency('gdk-3.0') gtk = dependency('gtk4')
gtk = dependency('gtk+-3.0') assert(gtk.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') epoxy = dependency('epoxy')
gmodule_export = dependency('gmodule-export-2.0')
configure_file(input: 'config.h.in', output: 'config.h', configuration: conf) configure_file(input: 'config.h.in', output: 'config.h', configuration: conf)
@ -16,7 +16,7 @@ executable(
'outputs.c', 'outputs.c',
'render.c', 'render.c',
'glviewport.c', 'glviewport.c',
'overlay.c', #'overlay.c',
resources, resources,
], ],
dependencies : [ dependencies : [
@ -25,7 +25,9 @@ executable(
wayland_client, wayland_client,
client_protos, client_protos,
epoxy, epoxy,
gtk gtk,
gmodule_export
], ],
c_args: ['-DGDK_DISABLE_DEPRECATED', '-DGTK_DISABLE_DEPRECATED'],
install: true install: true
) )

View File

@ -7,7 +7,7 @@
#include <errno.h> #include <errno.h>
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <gdk/gdkwayland.h> #include <gdk/wayland/gdkwayland.h>
#include "wdisplays.h" #include "wdisplays.h"
@ -39,8 +39,8 @@ static inline int min(int a, int b) {
static PangoLayout *create_text_layout(struct wd_head *head, static PangoLayout *create_text_layout(struct wd_head *head,
PangoContext *pango, GtkStyleContext *style) { PangoContext *pango, GtkStyleContext *style) {
GtkStyleContext *desc_style = gtk_style_context_new(); GtkStyleContext *desc_style = gtk_style_context_new();
gtk_style_context_set_screen(desc_style, gtk_style_context_set_display(desc_style,
gtk_style_context_get_screen(style)); gtk_style_context_get_display(style));
GtkWidgetPath *desc_path = gtk_widget_path_copy( GtkWidgetPath *desc_path = gtk_widget_path_copy(
gtk_style_context_get_path(style)); gtk_style_context_get_path(style));
gtk_widget_path_append_type(desc_path, G_TYPE_NONE); gtk_widget_path_append_type(desc_path, G_TYPE_NONE);
@ -48,8 +48,7 @@ static PangoLayout *create_text_layout(struct wd_head *head,
gtk_style_context_add_class(desc_style, "description"); gtk_style_context_add_class(desc_style, "description");
double desc_font_size = 16.; double desc_font_size = 16.;
gtk_style_context_get(desc_style, GTK_STATE_FLAG_NORMAL, gtk_style_context_get(desc_style, "font-size", &desc_font_size, NULL);
"font-size", &desc_font_size, NULL);
g_autofree gchar *str = g_strdup_printf("%s\n<span size=\"%d\">%s</span>", g_autofree gchar *str = g_strdup_printf("%s\n<span size=\"%d\">%s</span>",
head->name, (int) (desc_font_size * PANGO_SCALE), head->description); head->name, (int) (desc_font_size * PANGO_SCALE), head->description);
@ -70,7 +69,7 @@ static void resize(struct wd_output *output) {
} }
uint32_t margin = min(screen_width, screen_height) * SCREEN_MARGIN_PERCENT; uint32_t margin = min(screen_width, screen_height) * SCREEN_MARGIN_PERCENT;
GdkWindow *window = gtk_widget_get_window(output->overlay_window); GdkSurface *surface = gtk_widget_get_surface(output->overlay_window);
PangoContext *pango = gtk_widget_get_pango_context(output->overlay_window); PangoContext *pango = gtk_widget_get_pango_context(output->overlay_window);
GtkStyleContext *style_ctx = gtk_widget_get_style_context( GtkStyleContext *style_ctx = gtk_widget_get_style_context(
output->overlay_window); output->overlay_window);
@ -83,7 +82,7 @@ static void resize(struct wd_output *output) {
GtkBorder padding; GtkBorder padding;
gtk_style_context_get_padding(style_ctx, GTK_STATE_FLAG_NORMAL, &padding); gtk_style_context_get_padding(style_ctx, &padding);
width = min(width, screen_width - margin * 2) width = min(width, screen_width - margin * 2)
+ padding.left + padding.right; + padding.left + padding.right;
@ -95,10 +94,10 @@ static void resize(struct wd_output *output) {
zwlr_layer_surface_v1_set_size(output->overlay_layer_surface, zwlr_layer_surface_v1_set_size(output->overlay_layer_surface,
width, height); width, height);
struct wl_surface *surface = gdk_wayland_window_get_wl_surface(window); struct wl_surface *wl_surface = gdk_wayland_surface_get_wl_surface(surface);
wl_surface_commit(surface); wl_surface_commit(wl_surface);
GdkDisplay *display = gdk_window_get_display(window); GdkDisplay *display = gdk_surface_get_display(surface);
wl_display_roundtrip(gdk_wayland_display_get_wl_display(display)); wl_display_roundtrip(gdk_wayland_display_get_wl_display(display));
} }
@ -110,22 +109,23 @@ void wd_redraw_overlay(struct wd_output *output) {
} }
void window_realize(GtkWidget *widget, gpointer data) { void window_realize(GtkWidget *widget, gpointer data) {
GdkWindow *window = gtk_widget_get_window(widget); //FIXME - custom surfaces in GTK4 wayland?
gdk_wayland_window_set_use_custom_surface(window); GdkSurface *surface = gtk_widget_get_surface(widget);
gdk_wayland_surface_set_use_custom_surface(surface);
} }
void window_map(GtkWidget *widget, gpointer data) { void window_map(GtkWidget *widget, gpointer data) {
struct wd_output *output = data; struct wd_output *output = data;
GdkWindow *window = gtk_widget_get_window(widget); GdkSurface *surface = gtk_widget_get_surface(widget);
cairo_region_t *region = cairo_region_create(); cairo_region_t *region = cairo_region_create();
gdk_window_input_shape_combine_region(window, region, 0, 0); gdk_surface_input_shape_combine_region(surface, region, 0, 0);
cairo_region_destroy(region); cairo_region_destroy(region);
struct wl_surface *surface = gdk_wayland_window_get_wl_surface(window); struct wl_surface *wl_surface = gdk_wayland_surface_get_wl_surface(surface);
output->overlay_layer_surface = zwlr_layer_shell_v1_get_layer_surface( output->overlay_layer_surface = zwlr_layer_shell_v1_get_layer_surface(
output->state->layer_shell, surface, output->wl_output, output->state->layer_shell, wl_surface, output->wl_output,
ZWLR_LAYER_SHELL_V1_LAYER_TOP, "output-overlay"); ZWLR_LAYER_SHELL_V1_LAYER_TOP, "output-overlay");
zwlr_layer_surface_v1_add_listener(output->overlay_layer_surface, zwlr_layer_surface_v1_add_listener(output->overlay_layer_surface,
@ -149,14 +149,14 @@ gboolean window_draw(GtkWidget *widget, cairo_t *cr, gpointer data) {
GtkStyleContext *style_ctx = gtk_widget_get_style_context(widget); GtkStyleContext *style_ctx = gtk_widget_get_style_context(widget);
GdkRGBA fg; GdkRGBA fg;
gtk_style_context_get_color(style_ctx, GTK_STATE_FLAG_NORMAL, &fg); gtk_style_context_get_color(style_ctx, &fg);
int width = gtk_widget_get_allocated_width(widget); int width = gtk_widget_get_allocated_width(widget);
int height = gtk_widget_get_allocated_height(widget); int height = gtk_widget_get_allocated_height(widget);
gtk_render_background(style_ctx, cr, 0, 0, width, height); gtk_render_background(style_ctx, cr, 0, 0, width, height);
GtkBorder padding; GtkBorder padding;
gtk_style_context_get_padding(style_ctx, GTK_STATE_FLAG_NORMAL, &padding); gtk_style_context_get_padding(style_ctx, &padding);
PangoContext *pango = gtk_widget_get_pango_context(widget); PangoContext *pango = gtk_widget_get_pango_context(widget);
PangoLayout *layout = create_text_layout(head, pango, style_ctx); PangoLayout *layout = create_text_layout(head, pango, style_ctx);
@ -171,7 +171,6 @@ void wd_create_overlay(struct wd_output *output) {
output->overlay_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); output->overlay_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_decorated(GTK_WINDOW(output->overlay_window), FALSE); gtk_window_set_decorated(GTK_WINDOW(output->overlay_window), FALSE);
gtk_window_set_resizable(GTK_WINDOW(output->overlay_window), FALSE); gtk_window_set_resizable(GTK_WINDOW(output->overlay_window), FALSE);
gtk_widget_add_events(output->overlay_window, GDK_STRUCTURE_MASK);
g_signal_connect(output->overlay_window, "realize", g_signal_connect(output->overlay_window, "realize",
G_CALLBACK(window_realize), output); G_CALLBACK(window_realize), output);

View File

@ -335,16 +335,16 @@ void wd_gl_cleanup(struct wd_gl_data *res);
* Create an overlay on the screen that contains a textual description of the * Create an overlay on the screen that contains a textual description of the
* output. This is to help the user identify the outputs visually. * output. This is to help the user identify the outputs visually.
*/ */
void wd_create_overlay(struct wd_output *output); static inline void wd_create_overlay(struct wd_output *output) {}
/* /*
* Forces redrawing of the screen overlay on the given output. * Forces redrawing of the screen overlay on the given output.
*/ */
void wd_redraw_overlay(struct wd_output *output); static inline void wd_redraw_overlay(struct wd_output *output) {}
/* /*
* Destroys the screen overlay on the given output. * Destroys the screen overlay on the given output.
*/ */
void wd_destroy_overlay(struct wd_output *output); static inline void wd_destroy_overlay(struct wd_output *output) {}
#endif #endif