Merge pull request #349 from Helooprototo/main

Allow controlling of the Watch and Kbd via wayvrctl
This commit is contained in:
Helooprototo
2026-01-09 20:05:50 +01:00
committed by GitHub
parent 9547da21ce
commit e5f0131730
7 changed files with 237 additions and 211 deletions

View File

@@ -19,8 +19,8 @@ use wlx_common::{audio, dash_interface::BoxDashInterface, timestep::Timestep};
use crate::{
assets,
tab::{
apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses, settings::TabSettings,
Tab, TabType,
Tab, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses,
settings::TabSettings,
},
util::{
popup_manager::{MountPopupParams, PopupManager, PopupManagerParams},

View File

@@ -1,5 +1,6 @@
use std::{cell::RefCell, rc::Rc};
use anyhow::Context;
use button::setup_custom_button;
use glam::{Affine2, Vec2, vec2};
use label::setup_custom_label;
@@ -8,15 +9,23 @@ use wgui::{
components::button::ComponentButton,
drawing,
event::{
Event as WguiEvent, EventCallback, EventListenerID, EventListenerKind,
InternalStateChangeEvent, MouseButtonIndex, MouseDownEvent, MouseLeaveEvent,
MouseMotionEvent, MouseUpEvent, MouseWheelEvent,
CallbackDataCommon, Event as WguiEvent, EventAlterables, EventCallback, EventListenerID,
EventListenerKind, InternalStateChangeEvent, MouseButtonIndex, MouseDownEvent,
MouseLeaveEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent,
},
gfx::cmd::WGfxClearMode,
i18n::Translation,
layout::{Layout, LayoutParams, LayoutUpdateParams, WidgetID},
parser::{self, CustomAttribsInfoOwned, Fetchable, ParseDocumentExtra, ParserState},
parser::{
self, CustomAttribsInfoOwned, Fetchable, ParseDocumentExtra, ParserState, parse_color_hex,
},
renderer_vk::context::Context as WguiContext,
widget::{EventResult, label::WidgetLabel},
renderer_vk::text::custom_glyph::CustomGlyphData,
taffy,
widget::{
EventResult, image::WidgetImage, label::WidgetLabel, rectangle::WidgetRectangle,
sprite::WidgetSprite,
},
windowing::context_menu::{self, ContextMenu},
};
use wlx_common::overlays::{BackendAttrib, BackendAttribValue};
@@ -25,6 +34,7 @@ use wlx_common::timestep::Timestep;
use crate::{
app_misc,
backend::input::{Haptics, HoverResult, PointerHit, PointerMode},
backend::task::ModifyPanelCommand,
state::AppState,
subsystem::hid::WheelDelta,
windowing::backend::{
@@ -474,3 +484,103 @@ fn log_cmd_invalid_arg(
parser_state.path.get_path_buf()
)
}
pub fn apply_custom_command<T>(
panel: &mut GuiPanel<T>,
app: &mut AppState,
element: &str,
command: &ModifyPanelCommand,
) -> anyhow::Result<()> {
let mut alterables = EventAlterables::default();
let mut com = CallbackDataCommon {
alterables: &mut alterables,
state: &panel.layout.state,
};
match command {
ModifyPanelCommand::SetText(text) => {
if let Ok(mut label) = panel
.parser_state
.fetch_widget_as::<WidgetLabel>(&panel.layout.state, element)
{
label.set_text(&mut com, Translation::from_raw_text(text));
} else if let Ok(button) = panel
.parser_state
.fetch_component_as::<ComponentButton>(element)
{
button.set_text(&mut com, Translation::from_raw_text(text));
} else {
anyhow::bail!("No <label> or <Button> with such id.");
}
}
ModifyPanelCommand::SetImage(path) => {
if let Ok(pair) = panel
.parser_state
.fetch_widget(&panel.layout.state, element)
{
let data = CustomGlyphData::from_assets(
&app.wgui_globals,
wgui::assets::AssetPath::File(path),
)
.context("Could not load content from supplied path.")?;
if let Some(mut sprite) = pair.widget.get_as::<WidgetSprite>() {
sprite.set_content(&mut com, Some(data));
} else if let Some(mut image) = pair.widget.get_as::<WidgetImage>() {
image.set_content(&mut com, Some(data));
} else {
anyhow::bail!("No <sprite> or <image> with such id.");
}
} else {
anyhow::bail!("No <sprite> or <image> with such id.");
}
}
ModifyPanelCommand::SetColor(color) => {
let color = parse_color_hex(color)
.context("Invalid color format, must be a html hex color!")?;
if let Ok(pair) = panel
.parser_state
.fetch_widget(&panel.layout.state, element)
{
if let Some(mut rect) = pair.widget.get_as::<WidgetRectangle>() {
rect.set_color(&mut com, color);
} else if let Some(mut label) = pair.widget.get_as::<WidgetLabel>() {
label.set_color(&mut com, color, true);
} else if let Some(mut sprite) = pair.widget.get_as::<WidgetSprite>() {
sprite.set_color(&mut com, color);
} else {
anyhow::bail!("No <rectangle> or <label> or <sprite> with such id.");
}
} else {
anyhow::bail!("No <rectangle> or <label> or <sprite> with such id.");
}
}
ModifyPanelCommand::SetVisible(visible) => {
let wid = panel
.parser_state
.get_widget_id(element)
.context("No widget with such id.")?;
let display = if *visible {
taffy::Display::Flex
} else {
taffy::Display::None
};
com.alterables
.set_style(wid, wgui::event::StyleSetRequest::Display(display));
com.alterables.mark_redraw();
}
ModifyPanelCommand::SetStickyState(sticky_down) => {
let button = panel
.parser_state
.fetch_component_as::<ComponentButton>(element)
.context("No <Button> with such id.")?;
button.set_sticky_state(&mut com, *sticky_down);
}
}
panel.layout.process_alterables(alterables)?;
Ok(())
}

View File

@@ -1,24 +1,11 @@
use std::{sync::Arc, time::Duration};
use anyhow::Context;
use glam::{Affine3A, Quat, Vec3, vec3};
use wgui::{
components::button::ComponentButton,
event::{CallbackDataCommon, EventAlterables},
i18n::Translation,
parser::{Fetchable, parse_color_hex},
renderer_vk::text::custom_glyph::CustomGlyphData,
taffy,
widget::{
image::WidgetImage, label::WidgetLabel, rectangle::WidgetRectangle, sprite::WidgetSprite,
},
};
use wlx_common::windowing::OverlayWindowState;
use crate::{
backend::task::ModifyPanelCommand,
gui::{
panel::{GuiPanel, NewGuiPanelParams},
panel::{GuiPanel, NewGuiPanelParams, apply_custom_command},
timer::GuiTimer,
},
state::AppState,
@@ -83,102 +70,3 @@ pub fn create_custom(app: &mut AppState, name: Arc<str>) -> Option<OverlayWindow
..OverlayWindowConfig::from_backend(Box::new(panel))
})
}
fn apply_custom_command(
panel: &mut GuiPanel<CustomPanelState>,
app: &mut AppState,
element: &str,
command: &ModifyPanelCommand,
) -> anyhow::Result<()> {
let mut alterables = EventAlterables::default();
let mut com = CallbackDataCommon {
alterables: &mut alterables,
state: &panel.layout.state,
};
match command {
ModifyPanelCommand::SetText(text) => {
if let Ok(mut label) = panel
.parser_state
.fetch_widget_as::<WidgetLabel>(&panel.layout.state, element)
{
label.set_text(&mut com, Translation::from_raw_text(text));
} else if let Ok(button) = panel
.parser_state
.fetch_component_as::<ComponentButton>(element)
{
button.set_text(&mut com, Translation::from_raw_text(text));
} else {
anyhow::bail!("No <label> or <Button> with such id.");
}
}
ModifyPanelCommand::SetImage(path) => {
if let Ok(pair) = panel
.parser_state
.fetch_widget(&panel.layout.state, element)
{
let data = CustomGlyphData::from_assets(
&app.wgui_globals,
wgui::assets::AssetPath::File(path),
)
.context("Could not load content from supplied path.")?;
if let Some(mut sprite) = pair.widget.get_as::<WidgetSprite>() {
sprite.set_content(&mut com, Some(data));
} else if let Some(mut image) = pair.widget.get_as::<WidgetImage>() {
image.set_content(&mut com, Some(data));
} else {
anyhow::bail!("No <sprite> or <image> with such id.");
}
} else {
anyhow::bail!("No <sprite> or <image> with such id.");
}
}
ModifyPanelCommand::SetColor(color) => {
let color = parse_color_hex(color)
.context("Invalid color format, must be a html hex color!")?;
if let Ok(pair) = panel
.parser_state
.fetch_widget(&panel.layout.state, element)
{
if let Some(mut rect) = pair.widget.get_as::<WidgetRectangle>() {
rect.set_color(&mut com, color);
} else if let Some(mut label) = pair.widget.get_as::<WidgetLabel>() {
label.set_color(&mut com, color, true);
} else if let Some(mut sprite) = pair.widget.get_as::<WidgetSprite>() {
sprite.set_color(&mut com, color);
} else {
anyhow::bail!("No <rectangle> or <label> or <sprite> with such id.");
}
} else {
anyhow::bail!("No <rectangle> or <label> or <sprite> with such id.");
}
}
ModifyPanelCommand::SetVisible(visible) => {
let wid = panel
.parser_state
.get_widget_id(element)
.context("No widget with such id.")?;
let display = if *visible {
taffy::Display::Flex
} else {
taffy::Display::None
};
com.alterables
.set_style(wid, wgui::event::StyleSetRequest::Display(display));
}
ModifyPanelCommand::SetStickyState(sticky_down) => {
let button = panel
.parser_state
.fetch_component_as::<ComponentButton>(element)
.context("No <Button> with such id.")?;
button.set_sticky_state(&mut com, *sticky_down);
}
}
panel.layout.process_alterables(alterables)?;
Ok(())
}

View File

@@ -3,7 +3,7 @@ use std::{collections::HashMap, rc::Rc, time::Duration};
use crate::{
app_misc,
gui::{
panel::{GuiPanel, NewGuiPanelParams},
panel::{GuiPanel, NewGuiPanelParams, apply_custom_command},
timer::GuiTimer,
},
overlays::keyboard::alt_modifier_to_key,
@@ -260,7 +260,9 @@ pub(super) fn create_keyboard_panel(
}
}
panel.on_notify = Some(Box::new(move |panel, app, event_data| {
panel.on_notify = Some(Box::new({
let name = "kbd";
move |panel, app, event_data| {
let mut alterables = EventAlterables::default();
let mut elems_changed = panel.state.overlay_list.on_notify(
@@ -283,13 +285,24 @@ pub(super) fn create_keyboard_panel(
panel.process_custom_elems(app);
}
if matches!(event_data, OverlayEventData::SettingsChanged) {
match event_data {
OverlayEventData::SettingsChanged => {
panel.state.alt_modifier =
alt_modifier_to_key(app.session.config.keyboard_middle_click_mode);
}
OverlayEventData::CustomCommand { element, command } => {
if let Err(e) = apply_custom_command(panel, app, &element, &command) {
log::warn!("Could not apply {command:?} on {name}/{element}: {e:?}");
}
}
_ => {}
}
panel.layout.process_alterables(alterables)?;
Ok(())
}
}));
panel

View File

@@ -17,8 +17,8 @@ use wlx_common::{
use crate::{
gui::{
panel::{
GuiPanel, NewGuiPanelParams, device_list::DeviceList, overlay_list::OverlayList,
set_list::SetList,
GuiPanel, NewGuiPanelParams, apply_custom_command, device_list::DeviceList,
overlay_list::OverlayList, set_list::SetList,
},
timer::GuiTimer,
},
@@ -74,7 +74,9 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
extra: panel.doc_extra.take().unwrap_or_default(),
};
panel.on_notify = Some(Box::new(move |panel, app, event_data| {
panel.on_notify = Some(Box::new({
let name = WATCH_NAME;
move |panel, app, event_data| {
let mut alterables = EventAlterables::default();
let mut elems_changed = panel.state.overlay_list.on_notify(
@@ -135,6 +137,13 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
elems_changed = true;
}
}
OverlayEventData::CustomCommand { element, command } => {
if let Err(e) = apply_custom_command(panel, app, &element, &command) {
log::warn!("Could not apply {command:?} on {name}/{element}: {e:?}");
} else {
elems_changed = true;
}
}
_ => {}
}
@@ -144,6 +153,7 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
panel.layout.process_alterables(alterables)?;
Ok(())
}
}));
panel

View File

@@ -355,7 +355,12 @@ where
if let Some(oid) = self.lookup(&task.overlay)
&& let Some(o) = self.mut_by_id(oid)
{
if !matches!(o.config.category, OverlayCategory::Panel) {
if !matches!(
o.config.category,
OverlayCategory::Panel
| OverlayCategory::Keyboard
| OverlayCategory::Internal
) {
log::warn!(
"Received command for '{}', but this overlay does not support commands",
&task.overlay

View File

@@ -7,26 +7,26 @@ use std::{marker::PhantomData, slice::Iter, sync::Arc};
use cmd::{GfxCommandBuffer, XferCommandBuffer};
use pipeline::WGfxPipeline;
use vulkano::{
DeviceSize,
buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, IndexBuffer, Subbuffer},
command_buffer::{
allocator::{StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo},
AutoCommandBufferBuilder, CommandBufferUsage,
allocator::{StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo},
},
descriptor_set::allocator::{StandardDescriptorSetAllocator, StandardDescriptorSetAllocatorCreateInfo},
device::{Device, Queue},
format::Format,
image::{sampler::Filter, Image, ImageCreateInfo, ImageType, ImageUsage},
image::{Image, ImageCreateInfo, ImageType, ImageUsage, sampler::Filter},
instance::Instance,
memory::{
allocator::{AllocationCreateInfo, GenericMemoryAllocatorCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
ExternalMemoryHandleTypes, MemoryPropertyFlags,
allocator::{AllocationCreateInfo, GenericMemoryAllocatorCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
},
pipeline::graphics::{
color_blend::{AttachmentBlend, BlendFactor, BlendOp},
vertex_input::Vertex,
},
shader::ShaderModule,
DeviceSize,
};
use crate::gfx::pipeline::WPipelineCreateInfo;