dropdown for capture_method + random tweaks
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -6930,6 +6930,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
"strum",
|
||||
"walkdir",
|
||||
"wayvr-ipc",
|
||||
"wgui",
|
||||
|
||||
1
dash-frontend/assets/dashboard/down.svg
Symbolic link
1
dash-frontend/assets/dashboard/down.svg
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../wlx-overlay-s/src/assets/keyboard/down.svg
|
||||
27
dash-frontend/assets/gui/t_dropdown_button.xml
Normal file
27
dash-frontend/assets/gui/t_dropdown_button.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<layout>
|
||||
<include src="theme.xml" />
|
||||
|
||||
<macro name="dropdown_button"
|
||||
flex_direction="row"
|
||||
border="2"
|
||||
color="#00000055"
|
||||
border_color="#FFFFFF66"
|
||||
justify_content="space_between" />
|
||||
|
||||
<!-- id, text, translation, tooltip -->
|
||||
<template name="DropdownButton">
|
||||
<label text="${text}" translation="${translation}" />
|
||||
<Button id="${id}" height="32" tooltip="${tooltip}" >
|
||||
<div padding_left="8" padding_right="8" min_width="200">
|
||||
<label id="${id}_value" weight="bold" />
|
||||
</div>
|
||||
<div gap="2">
|
||||
<div padding_top="4" padding_bottom="4">
|
||||
<rectangle width="2" height="100%" color="#FFFFFF66" />
|
||||
</div>
|
||||
<sprite margin_left="-4" width="30" height="30" color="~text_color" src_builtin="dashboard/down.svg" />
|
||||
</div>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
</layout>
|
||||
@@ -1,6 +1,7 @@
|
||||
<layout>
|
||||
<include src="t_tab_title.xml" />
|
||||
<include src="../t_group_box.xml" />
|
||||
<include src="../t_dropdown_button.xml" />
|
||||
|
||||
<template name="SettingsGroupBox">
|
||||
<rectangle macro="group_box" id="${id}" flex_grow="1">
|
||||
@@ -27,7 +28,10 @@
|
||||
</template>
|
||||
|
||||
<template name="DangerButton">
|
||||
<Button id="${id}" color="#AA3333" height="32" width="100%" sprite_src_builtin="${icon}" translation="${translation}" tooltip="${translation}_HELP" />
|
||||
<Button id="${id}" color="#AA3333" height="32" tooltip="${translation}_HELP" padding="4" gap="8" >
|
||||
<sprite src_builtin="${icon}" height="24" width="24" />
|
||||
<label align="left" translation="${translation}" weight="bold" min_width="200" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
|
||||
@@ -69,7 +69,16 @@
|
||||
"CLEAR_SAVED_STATE_HELP": "Reset sets & overlay positions",
|
||||
"CLEAR_PIPEWIRE_TOKENS_HELP": "Prompt for screen selection on next start",
|
||||
"DELETE_ALL_CONFIGS_HELP": "Remove all configuration files from conf.d",
|
||||
"RESTART_SOFTWARE_HELP": "Apply settings that require a restart"
|
||||
"RESTART_SOFTWARE_HELP": "Apply settings that require a restart",
|
||||
|
||||
"CAPTURE_METHOD": "Wayland screen capture",
|
||||
"CAPTURE_METHOD_HELP": "Try changing this if experiencing\nblack or glitchy screens",
|
||||
|
||||
"OPTION": {
|
||||
"PIPEWIRE_HELP": "Fast GPU capture. Recommended",
|
||||
"PW_FALLBACK_HELP": "Slow. Try in case PipeWire GPU doesn't work",
|
||||
"SCREENCOPY_HELP": "Slow. Works on: Hyprland, Niri, River, Sway"
|
||||
}
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "Application launcher",
|
||||
"APPLICATION_STARTED": "Application started",
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
use std::{collections::HashMap, marker::PhantomData, rc::Rc};
|
||||
use std::{collections::HashMap, marker::PhantomData, rc::Rc, str::FromStr};
|
||||
|
||||
use strum::AsRefStr;
|
||||
use glam::Vec2;
|
||||
use strum::{AsRefStr, EnumProperty, EnumString, VariantArray};
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::{button::ComponentButton, checkbox::ComponentCheckbox, slider::ComponentSlider},
|
||||
event::{CallbackDataCommon, EventAlterables},
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
log::LogErr,
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
task::Tasks,
|
||||
widget::label::WidgetLabel,
|
||||
windowing::context_menu::{self, Blueprint, ContextMenu, TickResult},
|
||||
};
|
||||
use wlx_common::{config::GeneralConfig, config_io::ConfigRoot};
|
||||
|
||||
@@ -20,6 +25,7 @@ enum Task {
|
||||
UpdateBool(SettingType, bool),
|
||||
UpdateFloat(SettingType, f32),
|
||||
UpdateInt(SettingType, i32),
|
||||
OpenContextMenu(Vec2, Vec<context_menu::Cell>),
|
||||
ClearPipewireTokens,
|
||||
ClearSavedState,
|
||||
DeleteAllConfigs,
|
||||
@@ -30,6 +36,8 @@ pub struct TabSettings<T> {
|
||||
#[allow(dead_code)]
|
||||
pub state: ParserState,
|
||||
|
||||
context_menu: ContextMenu,
|
||||
|
||||
tasks: Tasks<Task>,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
@@ -76,17 +84,56 @@ impl<T> Tab<T> for TabSettings<T> {
|
||||
frontend.interface.restart(data);
|
||||
return Ok(());
|
||||
}
|
||||
Task::OpenContextMenu(position, cells) => {
|
||||
self.context_menu.open(context_menu::OpenParams {
|
||||
on_custom_attribs: None,
|
||||
position,
|
||||
blueprint: Blueprint::Cells(cells),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dropdown handling
|
||||
if let TickResult::Action(name) = self.context_menu.tick(&mut frontend.layout, &mut self.state)? {
|
||||
if let (Some(setting), Some(id), Some(value), Some(text), Some(translated)) = {
|
||||
let mut s = name.splitn(5, ';');
|
||||
(s.next(), s.next(), s.next(), s.next(), s.next())
|
||||
} {
|
||||
let mut label = self
|
||||
.state
|
||||
.fetch_widget_as::<WidgetLabel>(&frontend.layout.state, &format!("{id}_value"))?;
|
||||
|
||||
let mut alterables = EventAlterables::default();
|
||||
let mut common = CallbackDataCommon {
|
||||
alterables: &mut alterables,
|
||||
state: &frontend.layout.state,
|
||||
};
|
||||
|
||||
let translation = Translation {
|
||||
text: text.into(),
|
||||
translated: translated == "1",
|
||||
};
|
||||
|
||||
label.set_text(&mut common, translation);
|
||||
|
||||
let setting = SettingType::from_str(setting).expect("Invalid Enum string");
|
||||
setting.set_enum(config, value);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify overlays of the change
|
||||
if changed {
|
||||
frontend.interface.config_changed(data);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Clone, Copy, AsRefStr)]
|
||||
#[derive(Clone, Copy, AsRefStr, EnumString)]
|
||||
enum SettingType {
|
||||
AnimationSpeed,
|
||||
RoundMultiplier,
|
||||
@@ -120,6 +167,7 @@ enum SettingType {
|
||||
HideUsername,
|
||||
OpaqueBackground,
|
||||
XwaylandByDefault,
|
||||
CaptureMethod,
|
||||
}
|
||||
|
||||
impl SettingType {
|
||||
@@ -173,6 +221,40 @@ impl SettingType {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_enum<'a>(self, config: &'a mut GeneralConfig, value: &str) {
|
||||
match self {
|
||||
Self::CaptureMethod => {
|
||||
config.capture_method = wlx_common::config::CaptureMethod::from_str(value).expect("Invalid enum value!")
|
||||
}
|
||||
_ => panic!("Requested enum for non-enum SettingType"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_enum_title<'a>(self, config: &'a mut GeneralConfig) -> Translation {
|
||||
match self {
|
||||
Self::CaptureMethod => Self::get_enum_title_inner(config.capture_method),
|
||||
_ => panic!("Requested enum for non-enum SettingType"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_enum_title_inner<E>(value: E) -> Translation
|
||||
where
|
||||
E: EnumProperty + AsRef<str>,
|
||||
{
|
||||
value
|
||||
.get_str("Translation")
|
||||
.map(|x| Translation::from_translation_key(x))
|
||||
.or_else(|| value.get_str("Text").map(|x| Translation::from_raw_text(x)))
|
||||
.unwrap_or_else(|| Translation::from_raw_text(value.as_ref()))
|
||||
}
|
||||
|
||||
fn get_enum_tooltip_inner<E>(value: E) -> Option<Translation>
|
||||
where
|
||||
E: EnumProperty + AsRef<str>,
|
||||
{
|
||||
value.get_str("Tooltip").map(|x| Translation::from_translation_key(x))
|
||||
}
|
||||
|
||||
/// Ok is translation, Err is raw text
|
||||
fn get_translation(self) -> Result<&'static str, &'static str> {
|
||||
match self {
|
||||
@@ -208,6 +290,7 @@ impl SettingType {
|
||||
Self::HideUsername => Ok("APP_SETTINGS.HIDE_USERNAME"),
|
||||
Self::OpaqueBackground => Ok("APP_SETTINGS.OPAQUE_BACKGROUND"),
|
||||
Self::XwaylandByDefault => Ok("APP_SETTINGS.XWAYLAND_BY_DEFAULT"),
|
||||
Self::CaptureMethod => Ok("APP_SETTINGS.CAPTURE_METHOD"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,6 +307,7 @@ impl SettingType {
|
||||
Self::UseSkybox => Some("APP_SETTINGS.USE_SKYBOX_HELP"),
|
||||
Self::UsePassthrough => Some("APP_SETTINGS.USE_PASSTHROUGH_HELP"),
|
||||
Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"),
|
||||
Self::CaptureMethod => Some("APP_SETTINGS.CAPTURE_METHOD_HELP"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -381,6 +465,72 @@ macro_rules! slider_i32 {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! dropdown {
|
||||
($mp:expr, $root:expr, $setting:expr, $options:expr) => {
|
||||
let id = $mp.idx.to_string();
|
||||
$mp.idx += 1;
|
||||
|
||||
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
|
||||
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
|
||||
|
||||
match $setting.get_translation() {
|
||||
Ok(translation) => params.insert(Rc::from("translation"), translation.into()),
|
||||
Err(raw_text) => params.insert(Rc::from("text"), raw_text.into()),
|
||||
};
|
||||
|
||||
if let Some(tooltip) = $setting.get_tooltip() {
|
||||
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
|
||||
}
|
||||
|
||||
$mp
|
||||
.parser_state
|
||||
.instantiate_template($mp.doc_params, "DropdownButton", $mp.layout, $root, params)?;
|
||||
|
||||
let setting_str = $setting.as_ref();
|
||||
let title = $setting.get_enum_title($mp.config);
|
||||
|
||||
{
|
||||
let mut label = $mp
|
||||
.parser_state
|
||||
.fetch_widget_as::<WidgetLabel>(&$mp.layout.state, &format!("{id}_value"))?;
|
||||
label.set_text_simple(&mut $mp.layout.state.globals.get(), title);
|
||||
}
|
||||
|
||||
let btn = $mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
|
||||
btn.on_click(Box::new({
|
||||
let tasks = $mp.tasks.clone();
|
||||
move |_common, e| {
|
||||
tasks.push(Task::OpenContextMenu(
|
||||
e.mouse_pos_absolute.unwrap_or_default(),
|
||||
$options
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
if item.get_bool("Hidden").unwrap_or(false) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = item.as_ref();
|
||||
let title = SettingType::get_enum_title_inner(*item);
|
||||
let tooltip = SettingType::get_enum_tooltip_inner(*item);
|
||||
|
||||
let text = &title.text;
|
||||
let translated = if title.translated { "1" } else { "0" };
|
||||
|
||||
Some(context_menu::Cell {
|
||||
action_name: Some(format!("{setting_str};{id};{value};{text};{translated}").into()),
|
||||
title,
|
||||
tooltip,
|
||||
attribs: vec![],
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! button {
|
||||
($mp:expr, $root:expr, $translation:expr, $icon:expr, $task:expr) => {
|
||||
let id = $mp.idx.to_string();
|
||||
@@ -474,27 +624,33 @@ impl<T> TabSettings<T> {
|
||||
checkbox!(mp, c, SettingType::UprightScreenFix);
|
||||
checkbox!(mp, c, SettingType::DoubleCursorFix);
|
||||
checkbox!(mp, c, SettingType::ScreenRenderDown);
|
||||
dropdown!(
|
||||
mp,
|
||||
c,
|
||||
SettingType::CaptureMethod,
|
||||
wlx_common::config::CaptureMethod::VARIANTS
|
||||
);
|
||||
|
||||
let c = category!(mp, root, "APP_SETTINGS.TROUBLESHOOTING", "dashboard/blocks.svg")?;
|
||||
button!(
|
||||
mp,
|
||||
c,
|
||||
"APP_SETTINGS.CLEAR_SAVED_STATE",
|
||||
"dashboard/remove_circle.svg",
|
||||
Task::ClearSavedState
|
||||
);
|
||||
button!(
|
||||
mp,
|
||||
c,
|
||||
"APP_SETTINGS.CLEAR_PIPEWIRE_TOKENS",
|
||||
"dashboard/remove_circle.svg",
|
||||
"dashboard/display.svg",
|
||||
Task::ClearPipewireTokens
|
||||
);
|
||||
button!(
|
||||
mp,
|
||||
c,
|
||||
"APP_SETTINGS.CLEAR_SAVED_STATE",
|
||||
"dashboard/binary.svg",
|
||||
Task::ClearSavedState
|
||||
);
|
||||
button!(
|
||||
mp,
|
||||
c,
|
||||
"APP_SETTINGS.DELETE_ALL_CONFIGS",
|
||||
"dashboard/remove_circle.svg",
|
||||
"dashboard/circle.svg",
|
||||
Task::DeleteAllConfigs
|
||||
);
|
||||
button!(
|
||||
@@ -509,6 +665,7 @@ impl<T> TabSettings<T> {
|
||||
tasks: mp.tasks,
|
||||
state: parser_state,
|
||||
marker: PhantomData,
|
||||
context_menu: ContextMenu::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,8 +259,10 @@ impl TestbedGeneric {
|
||||
on_custom_attribs: Some(Rc::new(move |custom_attribs| {
|
||||
log::info!("custom attribs {:?}", custom_attribs.pairs);
|
||||
})),
|
||||
template_name: "my_context_menu".into(),
|
||||
template_params: Default::default(),
|
||||
blueprint: context_menu::Blueprint::Template {
|
||||
template_name: "my_context_menu".into(),
|
||||
template_params: Default::default(),
|
||||
},
|
||||
position,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<layout>
|
||||
<!-- text: str -->
|
||||
<template name="Cell">
|
||||
<Button id="button" text="${text}" weight="bold" border="0" padding="4" color="#FFFFFF00" />
|
||||
<Button id="button" text="${text}" tooltip="${tooltip}" weight="bold" border="0" padding="4" color="#FFFFFF00" />
|
||||
</template>
|
||||
|
||||
<template name="Separator">
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
components::{Component, ComponentBase, ComponentTrait, RefreshData, checkbox::ComponentCheckbox},
|
||||
components::{checkbox::ComponentCheckbox, Component, ComponentBase, ComponentTrait, RefreshData},
|
||||
event::CallbackDataCommon,
|
||||
layout::WidgetPair,
|
||||
widget::{ConstructEssentials, div::WidgetDiv},
|
||||
widget::{div::WidgetDiv, ConstructEssentials},
|
||||
};
|
||||
|
||||
pub struct RadioValueChangeEvent {
|
||||
|
||||
@@ -9,11 +9,11 @@ use crate::{
|
||||
layout::{self, LayoutTask, LayoutTasks, WidgetID, WidgetPair},
|
||||
renderer_vk::text::{FontWeight, TextStyle},
|
||||
widget::{
|
||||
ConstructEssentials,
|
||||
div::WidgetDiv,
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
rectangle::{WidgetRectangle, WidgetRectangleParams},
|
||||
util::WLength,
|
||||
ConstructEssentials,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -259,11 +259,11 @@ impl ParserState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn context_menu_create_blueprint(
|
||||
pub(crate) fn context_menu_parse_cells(
|
||||
&mut self,
|
||||
template_name: &str,
|
||||
template_params: &HashMap<Rc<str>, Rc<str>>,
|
||||
) -> anyhow::Result<context_menu::Blueprint> {
|
||||
) -> anyhow::Result<Vec<context_menu::Cell>> {
|
||||
let Some(template) = self.data.templates.get(template_name) else {
|
||||
anyhow::bail!("no template named \"{template_name}\" found");
|
||||
};
|
||||
@@ -283,6 +283,7 @@ impl ParserState {
|
||||
"" => {}
|
||||
"cell" => {
|
||||
let mut title: Option<Translation> = None;
|
||||
let mut tooltip: Option<Translation> = None;
|
||||
let mut action_name: Option<Rc<str>> = None;
|
||||
let mut attribs = Vec::<AttribPair>::new();
|
||||
|
||||
@@ -292,6 +293,8 @@ impl ParserState {
|
||||
match key {
|
||||
"text" => title = Some(Translation::from_raw_text(value)),
|
||||
"translation" => title = Some(Translation::from_translation_key(value)),
|
||||
"tooltip" => tooltip = Some(Translation::from_translation_key(value)),
|
||||
"tooltip_str" => tooltip = Some(Translation::from_raw_text(value)),
|
||||
"action" => action_name = Some(value.into()),
|
||||
other => {
|
||||
if !other.starts_with('_') {
|
||||
@@ -305,6 +308,7 @@ impl ParserState {
|
||||
let title = title.context("No text/translation provided")?;
|
||||
cells.push(context_menu::Cell {
|
||||
title,
|
||||
tooltip,
|
||||
action_name,
|
||||
attribs,
|
||||
});
|
||||
@@ -316,9 +320,7 @@ impl ParserState {
|
||||
}
|
||||
|
||||
Ok(
|
||||
context_menu::Blueprint {
|
||||
cells,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,19 +15,23 @@ use crate::{
|
||||
|
||||
pub struct Cell {
|
||||
pub title: Translation,
|
||||
pub tooltip: Option<Translation>,
|
||||
pub action_name: Option<Rc<str>>,
|
||||
pub attribs: Vec<parser::AttribPair>,
|
||||
}
|
||||
|
||||
pub(crate) struct Blueprint {
|
||||
pub cells: Vec<Cell>,
|
||||
pub enum Blueprint {
|
||||
Cells(Vec<Cell>),
|
||||
Template {
|
||||
template_name: Rc<str>,
|
||||
template_params: HashMap<Rc<str>, Rc<str>>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct OpenParams {
|
||||
pub on_custom_attribs: Option<parser::OnCustomAttribsFunc>,
|
||||
pub template_name: Rc<str>,
|
||||
pub template_params: HashMap<Rc<str>, Rc<str>>,
|
||||
pub position: Vec2,
|
||||
pub blueprint: Blueprint,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -74,11 +78,17 @@ impl ContextMenu {
|
||||
|
||||
fn open_process(
|
||||
&mut self,
|
||||
params: &mut OpenParams,
|
||||
params: OpenParams,
|
||||
layout: &mut Layout,
|
||||
parser_state: &mut ParserState,
|
||||
) -> anyhow::Result<()> {
|
||||
let blueprint = parser_state.context_menu_create_blueprint(¶ms.template_name, ¶ms.template_params)?;
|
||||
let cells = match params.blueprint {
|
||||
Blueprint::Template {
|
||||
template_name,
|
||||
template_params,
|
||||
} => parser_state.context_menu_parse_cells(&template_name, &template_params)?,
|
||||
Blueprint::Cells(cells) => cells,
|
||||
};
|
||||
|
||||
let globals = layout.state.globals.clone();
|
||||
|
||||
@@ -100,9 +110,13 @@ impl ContextMenu {
|
||||
|
||||
let id_buttons = inner_parser.get_widget_id("buttons")?;
|
||||
|
||||
for (idx, cell) in blueprint.cells.iter().enumerate() {
|
||||
for (idx, cell) in cells.iter().enumerate() {
|
||||
let mut par = HashMap::new();
|
||||
par.insert(Rc::from("text"), cell.title.generate(&mut globals.i18n()));
|
||||
if let Some(tooltip) = cell.tooltip.as_ref() {
|
||||
par.insert(Rc::from("tooltip_str"), tooltip.generate(&mut globals.i18n()));
|
||||
}
|
||||
|
||||
let mut data_cell = inner_parser.parse_template(&doc_params, "Cell", layout, id_buttons, par)?;
|
||||
|
||||
let button = data_cell.fetch_component_as::<ComponentButton>("button")?;
|
||||
@@ -112,7 +126,7 @@ impl ContextMenu {
|
||||
.tasks
|
||||
.handle_button(&button, Task::ActionClicked(cell.action_name.clone()));
|
||||
|
||||
if let Some(c) = &mut params.on_custom_attribs {
|
||||
if let Some(c) = params.on_custom_attribs.as_ref() {
|
||||
(*c)(parser::CustomAttribsInfo {
|
||||
pairs: &cell.attribs,
|
||||
parent_id: id_buttons,
|
||||
@@ -121,7 +135,7 @@ impl ContextMenu {
|
||||
});
|
||||
}
|
||||
|
||||
if idx < blueprint.cells.len() - 1 {
|
||||
if idx < cells.len() - 1 {
|
||||
inner_parser.parse_template(&doc_params, "Separator", layout, id_buttons, Default::default())?;
|
||||
}
|
||||
}
|
||||
@@ -129,8 +143,8 @@ impl ContextMenu {
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, layout: &mut Layout, parser_state: &mut ParserState) -> anyhow::Result<TickResult> {
|
||||
if let Some(mut p) = self.pending_open.take() {
|
||||
self.open_process(&mut p, layout, parser_state)?;
|
||||
if let Some(p) = self.pending_open.take() {
|
||||
self.open_process(p, layout, parser_state)?;
|
||||
let _ = self.tasks.drain();
|
||||
return Ok(TickResult::Opened);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@ idmap-derive.workspace = true
|
||||
log.workspace = true
|
||||
serde = { workspace = true, features = ["rc"] }
|
||||
serde_json.workspace = true
|
||||
strum.workspace = true
|
||||
xdg.workspace = true
|
||||
|
||||
chrono = "0.4.42"
|
||||
smol = "2.0.2"
|
||||
wgui = { path = "../wgui/" }
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::{collections::HashMap, sync::Arc};
|
||||
use chrono::Offset;
|
||||
use idmap::IdMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{AsRefStr, EnumProperty, EnumString, VariantArray};
|
||||
|
||||
use crate::{
|
||||
astr_containers::{AStrMap, AStrSet},
|
||||
@@ -13,6 +14,22 @@ use crate::{
|
||||
pub type PwTokenMap = AStrMap<String>;
|
||||
pub type SerializedWindowStates = HashMap<Arc<str>, OverlayWindowState>;
|
||||
|
||||
#[derive(Default, Clone, Copy, Serialize, Deserialize, AsRefStr, EnumString, EnumProperty, VariantArray)]
|
||||
pub enum CaptureMethod {
|
||||
#[default]
|
||||
#[serde(alias = "pipewire", alias = "auto")]
|
||||
#[strum(props(Text = "PipeWire GPU", Tooltip = "APP_SETTINGS.OPTION.PIPEWIRE_HELP"))]
|
||||
PipeWire,
|
||||
|
||||
#[serde(alias = "pw-fallback")]
|
||||
#[strum(props(Text = "PipeWire CPU", Tooltip = "APP_SETTINGS.OPTION.PW_FALLBACK_HELP"))]
|
||||
PwFallback,
|
||||
|
||||
#[serde(alias = "screencopy")]
|
||||
#[strum(props(Text = "ScreenCopy CPU", Tooltip = "APP_SETTINGS.OPTION.SCREENCOPY_HELP"))]
|
||||
ScreenCopy,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct SerializedWindowSet {
|
||||
pub name: Arc<str>,
|
||||
@@ -76,10 +93,6 @@ fn def_timezones() -> Vec<String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn def_auto() -> Arc<str> {
|
||||
"auto".into()
|
||||
}
|
||||
|
||||
fn def_empty() -> Arc<str> {
|
||||
"".into()
|
||||
}
|
||||
@@ -174,8 +187,8 @@ pub struct GeneralConfig {
|
||||
#[serde(default)]
|
||||
pub custom_panels: AStrSet,
|
||||
|
||||
#[serde(default = "def_auto")]
|
||||
pub capture_method: Arc<str>,
|
||||
#[serde(default)]
|
||||
pub capture_method: CaptureMethod,
|
||||
|
||||
#[serde(default = "def_point7")]
|
||||
pub xr_click_sensitivity: f32,
|
||||
|
||||
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use wlx_common::{
|
||||
astr_containers::AStrMap,
|
||||
config::{GeneralConfig, SerializedWindowSet, SerializedWindowStates},
|
||||
config::{CaptureMethod, GeneralConfig, SerializedWindowSet, SerializedWindowStates},
|
||||
config_io,
|
||||
overlays::BackendAttribValue,
|
||||
};
|
||||
@@ -138,6 +138,7 @@ pub struct AutoSettings {
|
||||
pub opaque_background: bool,
|
||||
pub xwayland_by_default: bool,
|
||||
pub context_menu_hold_and_release: bool,
|
||||
pub capture_method: CaptureMethod,
|
||||
}
|
||||
|
||||
fn get_settings_path() -> PathBuf {
|
||||
@@ -181,6 +182,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
|
||||
opaque_background: config.opaque_background,
|
||||
xwayland_by_default: config.xwayland_by_default,
|
||||
context_menu_hold_and_release: config.context_menu_hold_and_release,
|
||||
capture_method: config.capture_method,
|
||||
};
|
||||
|
||||
let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic
|
||||
|
||||
@@ -20,7 +20,7 @@ use wgui::{
|
||||
parser::{self, AttribPair, CustomAttribsInfoOwned, Fetchable, ParserState},
|
||||
taffy,
|
||||
widget::EventResult,
|
||||
windowing::context_menu::{ContextMenu, OpenParams},
|
||||
windowing::context_menu::{Blueprint, ContextMenu, OpenParams},
|
||||
};
|
||||
use wlx_common::overlays::ToastTopic;
|
||||
|
||||
@@ -207,8 +207,9 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
let callback: EventCallback<AppState, S> = match command {
|
||||
"::ContextMenuOpen" => {
|
||||
let Some(template_name) = args.next() else {
|
||||
log::warn!(
|
||||
"{command} has incorrect arguments. Should be: {command} <context_menu>"
|
||||
log::error!(
|
||||
"{:?}: {command} has invalid arguments",
|
||||
parser_state.path.get_path_buf()
|
||||
);
|
||||
return;
|
||||
};
|
||||
@@ -230,8 +231,10 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
move |_common, data, _app, _| {
|
||||
context_menu.borrow_mut().open(OpenParams {
|
||||
on_custom_attribs: Some(on_custom_attribs.clone()),
|
||||
template_name: template_name.clone(),
|
||||
template_params: template_params.clone(),
|
||||
blueprint: Blueprint::Template {
|
||||
template_name: template_name.clone(),
|
||||
template_params: template_params.clone(),
|
||||
},
|
||||
position: data.metadata.get_mouse_pos_absolute().unwrap(), //want panic
|
||||
});
|
||||
Ok(EventResult::Consumed)
|
||||
@@ -249,14 +252,18 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
}
|
||||
"::ElementSetDisplay" => {
|
||||
let (Some(id), Some(value)) = (args.next(), args.next()) else {
|
||||
log::warn!(
|
||||
"{command} has incorrect arguments. Should be: {command} <element_id> <display>"
|
||||
log::error!(
|
||||
"{:?}: {command} has invalid arguments",
|
||||
parser_state.path.get_path_buf()
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(widget_id) = parser_state.data.get_widget_id(id) else {
|
||||
log::warn!("{command}: no element exists with ID '{id}'");
|
||||
log::warn!(
|
||||
"{:?}: {command}: no element exists with ID '{id}'",
|
||||
parser_state.path.get_path_buf()
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -290,7 +297,10 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
"::SetToggle" => {
|
||||
let arg = args.next().unwrap_or_default();
|
||||
let Ok(set_idx) = arg.parse() else {
|
||||
log::error!("{command} has invalid argument: \"{arg}\"");
|
||||
log::error!(
|
||||
"{:?}: {command} has invalid argument: \"{arg}\"",
|
||||
parser_state.path.get_path_buf()
|
||||
);
|
||||
return;
|
||||
};
|
||||
Box::new(move |_common, data, app, _| {
|
||||
@@ -306,7 +316,10 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
"::SetSwitch" => {
|
||||
let arg = args.next().unwrap_or_default();
|
||||
let Ok(set_idx) = arg.parse::<i32>() else {
|
||||
log::error!("{command} has invalid argument: \"{arg}\"");
|
||||
log::error!(
|
||||
"{:?}: {command} has invalid argument: \"{arg}\"",
|
||||
parser_state.path.get_path_buf()
|
||||
);
|
||||
return;
|
||||
};
|
||||
let maybe_set = if set_idx < 0 {
|
||||
@@ -327,7 +340,10 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
"::OverlayReset" => {
|
||||
let arg: Arc<str> = args.collect::<Vec<_>>().join(" ").into();
|
||||
if arg.len() < 1 {
|
||||
log::error!("{command} has missing arguments");
|
||||
log::error!(
|
||||
"{:?}: {command} has missing arguments",
|
||||
parser_state.path.get_path_buf()
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -346,7 +362,10 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
"::OverlayToggle" => {
|
||||
let arg: Arc<str> = args.collect::<Vec<_>>().join(" ").into();
|
||||
if arg.len() < 1 {
|
||||
log::error!("{command} has missing arguments");
|
||||
log::error!(
|
||||
"{:?}: {command} has missing arguments",
|
||||
parser_state.path.get_path_buf()
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -366,7 +385,10 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
"::OverlayDrop" => {
|
||||
let arg: Arc<str> = args.collect::<Vec<_>>().join(" ").into();
|
||||
if arg.len() < 1 {
|
||||
log::error!("{command} has missing arguments");
|
||||
log::error!(
|
||||
"{:?}: {command} has missing arguments",
|
||||
parser_state.path.get_path_buf()
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -402,7 +424,10 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
"::CustomOverlayReload" => {
|
||||
let arg: Arc<str> = args.collect::<Vec<_>>().join(" ").into();
|
||||
if arg.len() < 1 {
|
||||
log::error!("{command} has missing arguments");
|
||||
log::error!(
|
||||
"{:?}: {command} has missing arguments",
|
||||
parser_state.path.get_path_buf()
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -440,7 +465,10 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
"::WvrOverlayCloseWindow" => {
|
||||
let arg: Arc<str> = args.collect::<Vec<_>>().join(" ").into();
|
||||
if arg.len() < 1 {
|
||||
log::error!("{command} has missing arguments");
|
||||
log::error!(
|
||||
"{:?}: {command} has missing arguments",
|
||||
parser_state.path.get_path_buf()
|
||||
);
|
||||
return;
|
||||
};
|
||||
Box::new(move |_common, data, app, _| {
|
||||
@@ -463,7 +491,10 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
"::WvrOverlayKillProcess" | "::WvrOverlayTermProcess" => {
|
||||
let arg: Arc<str> = args.collect::<Vec<_>>().join(" ").into();
|
||||
if arg.len() < 1 {
|
||||
log::error!("{command} has missing arguments");
|
||||
log::error!(
|
||||
"{:?}: {command} has missing arguments",
|
||||
parser_state.path.get_path_buf()
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -585,7 +616,10 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
}),
|
||||
"::SendKey" => {
|
||||
let Some(key) = args.next().and_then(|s| VirtualKey::from_str(s).ok()) else {
|
||||
log::error!("{command} has bad/missing arguments");
|
||||
log::error!(
|
||||
"{:?}: {command} has bad/missing arguments",
|
||||
parser_state.path.get_path_buf()
|
||||
);
|
||||
return;
|
||||
};
|
||||
let Some(down) = args.next().and_then(|s| match s.to_lowercase().as_str() {
|
||||
@@ -593,7 +627,10 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
"up" => Some(false),
|
||||
_ => None,
|
||||
}) else {
|
||||
log::error!("{command} has bad/missing arguments");
|
||||
log::error!(
|
||||
"{:?}: {command} has bad/missing arguments",
|
||||
parser_state.path.get_path_buf()
|
||||
);
|
||||
return;
|
||||
};
|
||||
Box::new(move |_common, data, app, _| {
|
||||
@@ -640,7 +677,10 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
use crate::subsystem::osc::parse_osc_value;
|
||||
|
||||
let Some(address) = args.next().map(std::string::ToString::to_string) else {
|
||||
log::error!("{command} has missing arguments");
|
||||
log::error!(
|
||||
"{:?}: {command} has bad/missing arguments",
|
||||
parser_state.path.get_path_buf()
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{any, cell::RefCell, rc::Rc};
|
||||
|
||||
use button::setup_custom_button;
|
||||
use glam::{Affine2, Vec2, vec2};
|
||||
|
||||
@@ -18,7 +18,10 @@ use crate::{
|
||||
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, ui_transform,
|
||||
},
|
||||
};
|
||||
use wlx_common::overlays::{BackendAttrib, BackendAttribValue, MouseTransform, StereoMode};
|
||||
use wlx_common::{
|
||||
config::CaptureMethod,
|
||||
overlays::{BackendAttrib, BackendAttribValue, MouseTransform, StereoMode},
|
||||
};
|
||||
|
||||
use super::capture::{ScreenPipeline, WlxCaptureIn, WlxCaptureOut, receive_callback};
|
||||
|
||||
@@ -144,10 +147,12 @@ impl OverlayBackend for ScreenBackend {
|
||||
.ext_external_memory_dma_buf
|
||||
&& self.capture.supports_dmbuf();
|
||||
|
||||
let allow_dmabuf = &*app.session.config.capture_method != "pw_fallback"
|
||||
&& &*app.session.config.capture_method != "screencopy";
|
||||
let capture_method = app.session.config.capture_method;
|
||||
|
||||
let capture_method = app.session.config.capture_method.clone();
|
||||
let allow_dmabuf = !matches!(
|
||||
capture_method,
|
||||
CaptureMethod::PwFallback | CaptureMethod::ScreenCopy
|
||||
);
|
||||
|
||||
let dmabuf_formats = if !supports_dmabuf {
|
||||
log::info!("Capture method does not support DMA-buf");
|
||||
@@ -158,7 +163,10 @@ impl OverlayBackend for ScreenBackend {
|
||||
}
|
||||
&Vec::new()
|
||||
} else if !allow_dmabuf {
|
||||
log::info!("Not using DMA-buf capture due to {capture_method}");
|
||||
log::info!(
|
||||
"Not using DMA-buf capture due to {}",
|
||||
capture_method.as_ref()
|
||||
);
|
||||
if app.gfx_extras.queue_capture.is_none() {
|
||||
log::warn!(
|
||||
"Current GPU does not support multiple queues. Software capture will take place on the main thread. Expect degraded performance."
|
||||
|
||||
@@ -6,7 +6,10 @@ use wlx_capture::{
|
||||
wlr_dmabuf::WlrDmabufCapture,
|
||||
wlr_screencopy::WlrScreencopyCapture,
|
||||
};
|
||||
use wlx_common::{astr_containers::AStrMapExt, config::PwTokenMap};
|
||||
use wlx_common::{
|
||||
astr_containers::AStrMapExt,
|
||||
config::{CaptureMethod, PwTokenMap},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
overlays::screen::create_screen_from_backend,
|
||||
@@ -43,18 +46,14 @@ impl ScreenBackend {
|
||||
#[allow(clippy::useless_let_if_seq)]
|
||||
pub fn create_screen_renderer_wl(
|
||||
output: &WlxOutput,
|
||||
has_wlr_dmabuf: bool,
|
||||
has_wlr_screencopy: bool,
|
||||
pw_token_store: &mut PwTokenMap,
|
||||
app: &mut AppState,
|
||||
) -> Option<ScreenBackend> {
|
||||
let mut capture: Option<ScreenBackend> = None;
|
||||
if (&*app.session.config.capture_method == "wlr-dmabuf") && has_wlr_dmabuf {
|
||||
log::info!("{}: Using Wlr DMA-Buf", &output.name);
|
||||
capture = ScreenBackend::new_wlr_dmabuf(output, app);
|
||||
}
|
||||
|
||||
if &*app.session.config.capture_method == "screencopy" && has_wlr_screencopy {
|
||||
if matches!(app.session.config.capture_method, CaptureMethod::ScreenCopy) && has_wlr_screencopy
|
||||
{
|
||||
log::info!("{}: Using Wlr Screencopy Wl-SHM", &output.name);
|
||||
capture = ScreenBackend::new_wlr_screencopy(output, app);
|
||||
}
|
||||
@@ -102,7 +101,6 @@ pub fn create_screens_wayland(wl: &mut WlxClient, app: &mut AppState) -> ScreenC
|
||||
let mut pw_tokens: PwTokenMap = load_pw_token_config().unwrap_or_default();
|
||||
|
||||
let pw_tokens_copy = pw_tokens.clone();
|
||||
let has_wlr_dmabuf = wl.maybe_wlr_dmabuf_mgr.is_some();
|
||||
let has_wlr_screencopy = wl.maybe_wlr_screencopy_mgr.is_some();
|
||||
|
||||
for (id, output) in &wl.outputs {
|
||||
@@ -118,13 +116,9 @@ pub fn create_screens_wayland(wl: &mut WlxClient, app: &mut AppState) -> ScreenC
|
||||
output.logical_pos,
|
||||
);
|
||||
|
||||
if let Some(mut backend) = create_screen_renderer_wl(
|
||||
output,
|
||||
has_wlr_dmabuf,
|
||||
has_wlr_screencopy,
|
||||
&mut pw_tokens,
|
||||
app,
|
||||
) {
|
||||
if let Some(mut backend) =
|
||||
create_screen_renderer_wl(output, has_wlr_screencopy, &mut pw_tokens, app)
|
||||
{
|
||||
backend.logical_pos = vec2(output.logical_pos.0 as f32, output.logical_pos.1 as f32);
|
||||
backend.logical_size = vec2(output.logical_size.0 as f32, output.logical_size.1 as f32);
|
||||
backend.mouse_transform_original = output.transform;
|
||||
|
||||
Reference in New Issue
Block a user