Merge settings app into the main app

This commit is contained in:
Exidex 2025-07-27 16:50:07 +02:00
parent 136b2c2ab6
commit f41ab9233b
No known key found for this signature in database
GPG key ID: AC63AA86DD4F2D45
43 changed files with 679 additions and 1420 deletions

16
Cargo.lock generated
View file

@ -4500,7 +4500,6 @@ dependencies = [
"clap",
"gauntlet-client",
"gauntlet-common",
"gauntlet-management-client",
"gauntlet-plugin-runtime",
"gauntlet-server",
"gauntlet-utils",
@ -4598,21 +4597,6 @@ dependencies = [
"serde_json",
]
[[package]]
name = "gauntlet-management-client"
version = "0.0.0"
dependencies = [
"anyhow",
"gauntlet-common",
"gauntlet-common-ui",
"gauntlet-utils",
"iced",
"iced_fonts",
"itertools 0.13.0",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "gauntlet-manifest-schema"
version = "0.0.0"

View file

@ -5,7 +5,6 @@ repository = "https://github.com/project-gauntlet/gauntlet"
[workspace]
members = [
"rust/management_client",
"rust/client",
"rust/server",
"rust/common",
@ -35,7 +34,6 @@ gauntlet-common = { path = "./rust/common" }
gauntlet-common-ui = { path = "./rust/common_ui" }
gauntlet-common-plugin-runtime = { path = "./rust/common_plugin_runtime" }
gauntlet-plugin-runtime = { path = "./rust/plugin_runtime" }
gauntlet-management-client = { path = "./rust/management_client" }
gauntlet-client = { path = "./rust/client" }
gauntlet-server = { path = "./rust/server" }
gauntlet-utils = { path = "./rust/utils" }

View file

@ -4,7 +4,6 @@ edition.workspace = true
[dependencies]
# workspaces
gauntlet-management-client.workspace = true
gauntlet-client.workspace = true
gauntlet-server.workspace = true
gauntlet-plugin-runtime.workspace = true

View file

@ -7,10 +7,10 @@ use std::time::UNIX_EPOCH;
use clap::Parser;
use gauntlet_common::cli::is_server_running;
use gauntlet_common::cli::open_settings_window;
use gauntlet_common::cli::open_window;
use gauntlet_common::cli::run_action;
use gauntlet_common::dirs::Dirs;
use gauntlet_management_client::start_management_client;
use gauntlet_server::PLUGIN_CONNECT_ENV;
use gauntlet_server::PLUGIN_UUID_ENV;
use tracing_subscriber::EnvFilter;
@ -110,7 +110,7 @@ pub fn init() {
Some(command) => {
match command {
Commands::Open => open_window(),
Commands::Settings => start_management_client(),
Commands::Settings => open_settings_window(),
Commands::Run {
plugin_id,
entrypoint_id,

View file

@ -52,6 +52,7 @@ use iced::widget::text;
use iced::widget::text::Shaping;
use iced::widget::text_input;
use iced::widget::text_input::focus;
use iced::widget::themer;
use iced::window;
use iced_fonts::BOOTSTRAP_FONT_BYTES;
@ -68,6 +69,7 @@ mod custom_widgets;
mod grid_navigation;
mod scroll_handle;
mod search_list;
mod settings;
mod state;
#[cfg(any(target_os = "macos", target_os = "windows"))]
mod sys_tray;
@ -89,6 +91,13 @@ use crate::ui::scenario_runner::ScenarioRunnerMsg;
use crate::ui::scenario_runner::handle_scenario_runner_msg;
use crate::ui::scroll_handle::ScrollHandle;
use crate::ui::server::handle_server_message;
use crate::ui::settings::theme::GauntletSettingsTheme;
use crate::ui::settings::ui::SettingsMsg;
use crate::ui::settings::ui::SettingsParams;
use crate::ui::settings::ui::SettingsWindowState;
use crate::ui::settings::ui::subscription_settings;
use crate::ui::settings::ui::update_settings;
use crate::ui::settings::ui::view_settings;
use crate::ui::state::ErrorViewData;
use crate::ui::state::Focus;
use crate::ui::state::GlobalState;
@ -100,8 +109,8 @@ use crate::ui::widget::action_panel::ActionPanel;
use crate::ui::widget::action_panel::ActionPanelItem;
use crate::ui::widget::events::ComponentWidgetEvent;
use crate::ui::widget::root::render_root;
use crate::ui::windows::MainWindowState;
use crate::ui::windows::WindowActionMsg;
use crate::ui::windows::WindowState;
#[cfg(target_os = "linux")]
use crate::ui::windows::x11_focus::x11_linux_focus_change_subscription;
@ -112,7 +121,8 @@ pub struct AppModel {
#[cfg(any(target_os = "macos", target_os = "windows"))]
_tray_icon: tray_icon::TrayIcon,
theme: GauntletComplexTheme,
window: WindowState,
main_window_state: MainWindowState,
settings_window_state: SettingsWindowState,
// ephemeral state
prompt: String,
@ -197,10 +207,7 @@ pub enum AppMsg {
plugin_preferences_required: bool,
entrypoint_preferences_required: bool,
},
OpenSettingsPreferences {
plugin_id: PluginId,
entrypoint_id: Option<EntrypointId>,
},
OpenSettings(SettingsParams),
OnOpenView {
action_shortcuts: HashMap<String, PhysicalShortcut>,
},
@ -278,6 +285,7 @@ pub enum AppMsg {
display: String,
},
HandleScenario(ScenarioRunnerMsg),
Settings(SettingsMsg),
}
pub fn run(minimized: bool, scenario_runner_data: Option<ScenarioRunnerData>) {
@ -333,7 +341,7 @@ fn new(minimized: bool, #[allow(unused)] scenario_runner_data: Option<ScenarioRu
let client_context = ClientContext::new();
let global_state = GlobalState::new(text_input::Id::unique());
let window = WindowState::new(
let window = MainWindowState::new(
setup_data.window_position_file,
setup_data.close_on_unfocus,
setup_data.window_position_mode,
@ -342,6 +350,13 @@ fn new(minimized: bool, #[allow(unused)] scenario_runner_data: Option<ScenarioRu
#[cfg(target_os = "linux")]
layer_shell,
);
let settings_state = SettingsWindowState::new(
application_manager.clone(),
#[cfg(target_os = "linux")]
wayland,
#[cfg(not(target_os = "linux"))]
false,
);
(
AppModel {
@ -351,7 +366,8 @@ fn new(minimized: bool, #[allow(unused)] scenario_runner_data: Option<ScenarioRu
#[cfg(any(target_os = "macos", target_os = "windows"))]
_tray_icon: sys_tray::create_tray(application_manager.clone()),
theme,
window,
main_window_state: window,
settings_window_state: settings_state,
// ephemeral state
prompt: "".to_string(),
@ -368,10 +384,10 @@ fn new(minimized: bool, #[allow(unused)] scenario_runner_data: Option<ScenarioRu
}
fn title(state: &AppModel, window: window::Id) -> String {
if Some(window) == state.window.main_window_id {
"Gauntlet".to_owned()
} else {
"Gauntlet HUD".to_owned()
match window {
_ if Some(window) == state.main_window_state.main_window_id => "Gauntlet".to_owned(),
_ if Some(window) == state.settings_window_state.settings_window_id => "Gauntlet Settings".to_owned(),
_ => "Gauntlet HUD".to_owned(),
}
}
@ -648,7 +664,7 @@ fn update(state: &mut AppModel, message: AppMsg) -> Task<AppMsg> {
}
}
AppMsg::IcedEvent(window_id, Event::Keyboard(event)) => {
let Some(main_window_id) = state.window.main_window_id else {
let Some(main_window_id) = state.main_window_state.main_window_id else {
return Task::none();
};
@ -742,13 +758,20 @@ fn update(state: &mut AppModel, message: AppMsg) -> Task<AppMsg> {
}
}
AppMsg::IcedEvent(window_id, Event::Window(window::Event::Focused)) => {
state.window.handle_focused_event(window_id)
state.main_window_state.handle_focused_event(window_id)
}
AppMsg::IcedEvent(window_id, Event::Window(window::Event::Unfocused)) => {
state.window.handle_unfocused_event(window_id)
state.main_window_state.handle_unfocused_event(window_id)
}
AppMsg::IcedEvent(window_id, Event::Window(window::Event::Moved(point))) => {
state.window.handle_move_event(window_id, point)
state.main_window_state.handle_move_event(window_id, point)
}
AppMsg::IcedEvent(window_id, Event::Window(window::Event::Closed)) => {
if state.settings_window_state.settings_window_id == Some(window_id) {
Task::done(AppMsg::Settings(SettingsMsg::WindowDestroyed))
} else {
Task::none()
}
}
AppMsg::IcedEvent(_, _) => Task::none(),
AppMsg::WidgetEvent {
@ -807,10 +830,7 @@ fn update(state: &mut AppModel, message: AppMsg) -> Task<AppMsg> {
},
)
}
AppMsg::OpenSettingsPreferences {
plugin_id,
entrypoint_id,
} => state.open_settings_window_preferences(plugin_id, entrypoint_id),
AppMsg::OpenSettings(params) => Task::done(AppMsg::Settings(SettingsMsg::OpenSettings(params))),
AppMsg::OnOpenView { action_shortcuts } => {
match &mut state.global_state {
GlobalState::MainView {
@ -1086,7 +1106,7 @@ fn update(state: &mut AppModel, message: AppMsg) -> Task<AppMsg> {
Task::none()
}
AppMsg::FocusPluginViewSearchBar { widget_id } => state.client_context.focus_search_bar(widget_id),
AppMsg::WindowAction(action) => state.window.handle_action(action),
AppMsg::WindowAction(action) => state.main_window_state.handle_action(action),
AppMsg::SetTheme { theme } => {
state.theme = GauntletComplexTheme::new(theme);
@ -1192,17 +1212,28 @@ fn update(state: &mut AppModel, message: AppMsg) -> Task<AppMsg> {
Task::none()
}
AppMsg::HandleScenario(msg) => {
handle_scenario_runner_msg(msg, state.application_manager.clone(), state.window.main_window_id)
.map(AppMsg::HandleScenario)
handle_scenario_runner_msg(
msg,
state.application_manager.clone(),
state.main_window_state.main_window_id,
)
.map(AppMsg::HandleScenario)
}
AppMsg::Settings(msg) => {
update_settings(&mut state.settings_window_state, &state.global_hotkey_manager, msg).map(AppMsg::Settings)
}
}
}
fn view(state: &AppModel, window: window::Id) -> Element<'_, AppMsg> {
if Some(window) == state.window.main_window_id {
view_main(state)
} else {
view_hud(state)
match window {
_ if Some(window) == state.main_window_state.main_window_id => view_main(state),
_ if Some(window) == state.settings_window_state.settings_window_id => {
let themer: Element<_> = themer(GauntletSettingsTheme, view_settings(&state.settings_window_state)).into();
themer.map(AppMsg::Settings)
}
_ => view_hud(state),
}
}
@ -1257,27 +1288,25 @@ fn view_main(state: &AppModel) -> Element<'_, AppMsg> {
"Before using, plugin and entrypoint preferences need to be specified";
// note:
// we open plugin view and not entrypoint even though both need to be specified
let msg = AppMsg::OpenSettingsPreferences {
let msg = AppMsg::OpenSettings(SettingsParams::PluginPreferences {
plugin_id: plugin_id.clone(),
entrypoint_id: None,
};
});
(description_text, msg)
}
(false, true) => {
// TODO do not show "entrypoint" name to user
let description_text = "Before using, entrypoint preferences need to be specified";
let msg = AppMsg::OpenSettingsPreferences {
let msg = AppMsg::OpenSettings(SettingsParams::EntrypointPreferences {
plugin_id: plugin_id.clone(),
entrypoint_id: Some(entrypoint_id.clone()),
};
entrypoint_id: entrypoint_id.clone(),
});
(description_text, msg)
}
(true, false) => {
let description_text = "Before using, plugin preferences need to be specified";
let msg = AppMsg::OpenSettingsPreferences {
let msg = AppMsg::OpenSettings(SettingsParams::PluginPreferences {
plugin_id: plugin_id.clone(),
entrypoint_id: None,
};
});
(description_text, msg)
}
(false, false) => unreachable!(),
@ -1744,7 +1773,9 @@ fn view_main(state: &AppModel) -> Element<'_, AppMsg> {
}
fn subscription(#[allow(unused)] state: &AppModel) -> Subscription<AppMsg> {
let events_subscription = event::listen_with(|event, status, window_id| {
let mut subscriptions = vec![];
subscriptions.push(event::listen_with(|event, status, window_id| {
match status {
event::Status::Ignored => Some(AppMsg::IcedEvent(window_id, event)),
event::Status::Captured => {
@ -1757,25 +1788,23 @@ fn subscription(#[allow(unused)] state: &AppModel) -> Subscription<AppMsg> {
}
}
}
});
}));
#[allow(unused_mut)]
let mut subscriptions = vec![
Subscription::run(|| {
stream::channel(10, async move |sender| {
register_global_shortcut_listener(sender.clone());
subscriptions.push(Subscription::run(|| {
stream::channel(10, async move |sender| {
register_global_shortcut_listener(sender.clone());
std::future::pending::<()>().await;
std::future::pending::<()>().await;
unreachable!()
})
.map(AppMsg::HandleGlobalShortcut)
}),
events_subscription,
];
unreachable!()
})
.map(AppMsg::HandleGlobalShortcut)
}));
subscriptions.push(subscription_settings(&state.settings_window_state).map(AppMsg::Settings));
#[cfg(target_os = "linux")]
if !state.window.wayland {
if !state.main_window_state.wayland {
subscriptions.push(x11_linux_focus_change_subscription())
}
@ -1951,17 +1980,6 @@ impl AppModel {
Task::done(msg)
}
fn open_settings_window_preferences(
&self,
plugin_id: PluginId,
entrypoint_id: Option<EntrypointId>,
) -> Task<AppMsg> {
self.application_manager
.open_settings_window_preferences(plugin_id, entrypoint_id);
Task::none()
}
fn inline_view_shortcuts(&self) -> Task<AppMsg> {
let result = self
.application_manager
@ -1998,11 +2016,7 @@ impl AppModel {
modifier_control: cfg!(not(target_os = "macos")),
modifier_alt: false,
modifier_meta: cfg!(target_os = "macos"),
}) => {
self.application_manager.open_settings_window();
Task::none()
}
}) => Task::done(AppMsg::OpenSettings(SettingsParams::Default)),
Some(PhysicalShortcut {
physical_key: PhysicalKey::KeyK,
modifier_shift: false,

View file

@ -2,7 +2,6 @@ use std::ops::Deref;
use std::sync::Arc;
use std::sync::Mutex;
use anyhow::anyhow;
use gauntlet_common::model::UiSetupData;
use gauntlet_common::rpc::frontend_api::FrontendApiRequestData;
use gauntlet_common::rpc::frontend_api::FrontendApiResponseData;
@ -22,6 +21,7 @@ use tokio::sync::RwLock as TokioRwLock;
use crate::ui::AppModel;
use crate::ui::AppMsg;
use crate::ui::settings::ui::SettingsParams;
#[cfg(target_os = "linux")]
use crate::ui::wayland::layer_shell_supported;
use crate::ui::windows::WindowActionMsg;
@ -234,6 +234,11 @@ async fn request_loop(
action_index,
}
}
FrontendApiRequestData::ShowSettings {} => {
responder.respond(Ok(FrontendApiResponseData::ShowSettings { data: () }));
AppMsg::OpenSettings(SettingsParams::Default)
}
}
};
@ -260,9 +265,7 @@ pub fn handle_server_message(
ServerGrpcApiRequestData::ShowSettingsWindow {} => {
responder.respond(Ok(ServerGrpcApiResponseData::ShowSettingsWindow { data: () }));
state.application_manager.open_settings_window();
Task::none()
Task::done(AppMsg::OpenSettings(SettingsParams::Default))
}
ServerGrpcApiRequestData::RunAction {
plugin_id,
@ -293,230 +296,6 @@ pub fn handle_server_message(
responder.respond(result);
Task::none()
}
ServerGrpcApiRequestData::Plugins {} => {
let result = state
.application_manager
.plugins()
.map(|data| ServerGrpcApiResponseData::Plugins { data });
responder.respond(result.into());
Task::none()
}
ServerGrpcApiRequestData::SetPluginState { plugin_id, enabled } => {
let result = state
.application_manager
.set_plugin_state(plugin_id.clone(), *enabled)
.map(|data| ServerGrpcApiResponseData::SetPluginState { data });
responder.respond(result.into());
Task::none()
}
ServerGrpcApiRequestData::SetEntrypointState {
plugin_id,
entrypoint_id,
enabled,
} => {
let result = state
.application_manager
.set_entrypoint_state(plugin_id.clone(), entrypoint_id.clone(), *enabled)
.map(|data| ServerGrpcApiResponseData::SetEntrypointState { data });
responder.respond(result);
Task::none()
}
ServerGrpcApiRequestData::SetGlobalShortcut { shortcut } => {
let Some(global_hotkey_manager) = &state.global_hotkey_manager else {
responder.respond(Err(anyhow!("Global hotkey manager is disabled")));
return Task::none();
};
let result = state
.application_manager
.set_global_shortcut(global_hotkey_manager, shortcut.clone());
responder.respond(Ok(ServerGrpcApiResponseData::SetGlobalShortcut { data: result }));
Task::none()
}
ServerGrpcApiRequestData::GetGlobalShortcut {} => {
let result = state
.application_manager
.get_global_shortcut()
.map(|data| ServerGrpcApiResponseData::GetGlobalShortcut { data });
responder.respond(result);
Task::none()
}
ServerGrpcApiRequestData::SetGlobalEntrypointShortcut {
plugin_id,
entrypoint_id,
shortcut,
} => {
let Some(global_hotkey_manager) = &state.global_hotkey_manager else {
responder.respond(Err(anyhow!("Global hotkey manager is disabled")));
return Task::none();
};
let result = state
.application_manager
.set_global_entrypoint_shortcut(
global_hotkey_manager,
plugin_id.clone(),
entrypoint_id.clone(),
shortcut.clone(),
)
.map(|data| ServerGrpcApiResponseData::SetGlobalEntrypointShortcut { data });
responder.respond(result);
Task::none()
}
ServerGrpcApiRequestData::GetGlobalEntrypointShortcuts {} => {
let result = state
.application_manager
.get_global_entrypoint_shortcut()
.map(|data| ServerGrpcApiResponseData::GetGlobalEntrypointShortcuts { data });
responder.respond(result);
Task::none()
}
ServerGrpcApiRequestData::SetEntrypointSearchAlias {
plugin_id,
entrypoint_id,
alias,
} => {
let result = state
.application_manager
.set_entrypoint_search_alias(plugin_id.clone(), entrypoint_id.clone(), alias.clone())
.map(|data| ServerGrpcApiResponseData::SetEntrypointSearchAlias { data });
responder.respond(result);
Task::none()
}
ServerGrpcApiRequestData::GetEntrypointSearchAliases {} => {
let result = state
.application_manager
.get_entrypoint_search_aliases()
.map(|data| ServerGrpcApiResponseData::GetEntrypointSearchAliases { data });
responder.respond(result);
Task::none()
}
ServerGrpcApiRequestData::SetTheme { theme } => {
let application_manager = state.application_manager.clone();
let theme = theme.clone();
Task::future(async move {
let result = application_manager
.set_theme(theme)
.await
.map(|data| ServerGrpcApiResponseData::SetTheme { data });
responder.respond(result);
AppMsg::Noop
})
}
ServerGrpcApiRequestData::GetTheme {} => {
let result = state
.application_manager
.get_theme()
.map(|data| ServerGrpcApiResponseData::GetTheme { data });
responder.respond(result);
Task::none()
}
ServerGrpcApiRequestData::SetWindowPositionMode { mode } => {
let application_manager = state.application_manager.clone();
let mode = mode.clone();
Task::future(async move {
let result = application_manager
.set_window_position_mode(mode)
.await
.map(|data| ServerGrpcApiResponseData::SetWindowPositionMode { data });
responder.respond(result);
AppMsg::Noop
})
}
ServerGrpcApiRequestData::GetWindowPositionMode {} => {
let result = state
.application_manager
.get_window_position_mode()
.map(|data| ServerGrpcApiResponseData::GetWindowPositionMode { data });
responder.respond(result);
Task::none()
}
ServerGrpcApiRequestData::SetPreferenceValue {
plugin_id,
entrypoint_id,
preference_id,
preference_value,
} => {
let result = state
.application_manager
.set_preference_value(
plugin_id.clone(),
entrypoint_id.clone(),
preference_id.clone(),
preference_value.clone(),
)
.map(|data| ServerGrpcApiResponseData::SetPreferenceValue { data });
responder.respond(result);
Task::none()
}
ServerGrpcApiRequestData::DownloadPlugin { plugin_id } => {
let result = state
.application_manager
.download_plugin(plugin_id.clone())
.map(|data| ServerGrpcApiResponseData::DownloadPlugin { data });
responder.respond(result);
Task::none()
}
ServerGrpcApiRequestData::DownloadStatus {} => {
let data = state.application_manager.download_status();
responder.respond(Ok(ServerGrpcApiResponseData::DownloadStatus { data }));
Task::none()
}
ServerGrpcApiRequestData::RemovePlugin { plugin_id } => {
let result = state
.application_manager
.remove_plugin(plugin_id.clone())
.map(|data| ServerGrpcApiResponseData::RemovePlugin { data });
responder.respond(result);
Task::none()
}
ServerGrpcApiRequestData::WaylandGlobalShortcutsEnabled {} => {
let result = state.application_manager.config().map(|data| {
ServerGrpcApiResponseData::WaylandGlobalShortcutsEnabled {
data: data.wayland_use_legacy_x11_api,
}
});
responder.respond(result);
Task::none()
}
}

View file

@ -33,10 +33,10 @@ use iced::widget::tooltip;
use iced::widget::tooltip::Position;
use iced_fonts::bootstrap::exclamation_triangle_fill;
use crate::theme::Element;
use crate::theme::GauntletSettingsTheme;
use crate::theme::container::ContainerStyle;
use crate::theme::text::TextStyle;
use crate::ui::settings::theme::Element;
use crate::ui::settings::theme::GauntletSettingsTheme;
use crate::ui::settings::theme::container::ContainerStyle;
use crate::ui::settings::theme::text::TextStyle;
pub struct ShortcutData {
pub shortcut: Option<PhysicalShortcut>,

View file

@ -0,0 +1,4 @@
mod components;
pub mod theme;
pub mod ui;
pub mod views;

View file

@ -1,7 +1,3 @@
use iced::theme;
use iced::theme::Palette;
use iced::theme::Style;
pub mod button;
pub mod checkbox;
pub mod container;
@ -14,22 +10,9 @@ pub mod text_input;
pub type Element<'a, Message> = iced::Element<'a, Message, GauntletSettingsTheme>;
#[derive(Default)]
#[derive(Default, Clone)]
pub struct GauntletSettingsTheme;
impl theme::Base for GauntletSettingsTheme {
fn base(&self) -> Style {
Style {
background_color: BACKGROUND_DARKEST.to_iced(),
text_color: TEXT_LIGHTEST.to_iced(),
}
}
fn palette(&self) -> Option<Palette> {
Some(Palette::FERRA)
}
}
// keep colors more or less in sync with main ui
#[allow(unused)]
pub const NOT_INTENDED_TO_BE_USED: ThemeColor = ThemeColor::new(0xAF5BFF, 1.0);

View file

@ -3,16 +3,16 @@ use iced::widget::button;
use iced::widget::button::Status;
use iced::widget::button::Style;
use crate::theme::BACKGROUND_DARKER;
use crate::theme::BACKGROUND_LIGHTER;
use crate::theme::BUTTON_BORDER_RADIUS;
use crate::theme::DANGER;
use crate::theme::GauntletSettingsTheme;
use crate::theme::PRIMARY;
use crate::theme::PRIMARY_HOVERED;
use crate::theme::SUCCESS;
use crate::theme::TEXT_DARKEST;
use crate::theme::TEXT_LIGHTEST;
use crate::ui::settings::theme::BACKGROUND_DARKER;
use crate::ui::settings::theme::BACKGROUND_LIGHTER;
use crate::ui::settings::theme::BUTTON_BORDER_RADIUS;
use crate::ui::settings::theme::DANGER;
use crate::ui::settings::theme::GauntletSettingsTheme;
use crate::ui::settings::theme::PRIMARY;
use crate::ui::settings::theme::PRIMARY_HOVERED;
use crate::ui::settings::theme::SUCCESS;
use crate::ui::settings::theme::TEXT_DARKEST;
use crate::ui::settings::theme::TEXT_LIGHTEST;
pub enum ButtonStyle {
Primary,

View file

@ -3,12 +3,12 @@ use iced::widget::checkbox;
use iced::widget::checkbox::Status;
use iced::widget::checkbox::Style;
use crate::theme::BACKGROUND_DARKER;
use crate::theme::BACKGROUND_DARKEST;
use crate::theme::BACKGROUND_LIGHTER;
use crate::theme::GauntletSettingsTheme;
use crate::theme::PRIMARY;
use crate::theme::PRIMARY_HOVERED;
use crate::ui::settings::theme::BACKGROUND_DARKER;
use crate::ui::settings::theme::BACKGROUND_DARKEST;
use crate::ui::settings::theme::BACKGROUND_LIGHTER;
use crate::ui::settings::theme::GauntletSettingsTheme;
use crate::ui::settings::theme::PRIMARY;
use crate::ui::settings::theme::PRIMARY_HOVERED;
impl checkbox::Catalog for GauntletSettingsTheme {
type Class<'a> = ();

View file

@ -1,15 +1,19 @@
use iced::Background;
use iced::Border;
use iced::Color;
use iced::widget::container;
use iced::widget::container::Style;
use crate::theme::BACKGROUND_DARKER;
use crate::theme::BACKGROUND_LIGHTER;
use crate::theme::DANGER;
use crate::theme::GauntletSettingsTheme;
use crate::theme::TRANSPARENT;
use crate::ui::settings::theme::BACKGROUND_DARKER;
use crate::ui::settings::theme::BACKGROUND_DARKEST;
use crate::ui::settings::theme::BACKGROUND_LIGHTER;
use crate::ui::settings::theme::DANGER;
use crate::ui::settings::theme::GauntletSettingsTheme;
use crate::ui::settings::theme::TEXT_LIGHTEST;
use crate::ui::settings::theme::TRANSPARENT;
pub enum ContainerStyle {
WindowRoot,
Transparent,
Box,
TextInputMissingValue,
@ -60,6 +64,13 @@ impl container::Catalog for GauntletSettingsTheme {
..Default::default()
}
}
ContainerStyle::WindowRoot => {
Style {
background: Some(Background::Color(BACKGROUND_DARKEST.to_iced())),
text_color: Some(TEXT_LIGHTEST.to_iced()),
..Default::default()
}
}
}
}
}

View file

@ -2,14 +2,14 @@ use iced::Border;
use iced::overlay;
use iced::widget::pick_list;
use crate::theme::BACKGROUND_DARKER;
use crate::theme::BACKGROUND_DARKEST;
use crate::theme::BUTTON_BORDER_RADIUS;
use crate::theme::GauntletSettingsTheme;
use crate::theme::PRIMARY;
use crate::theme::PRIMARY_HOVERED;
use crate::theme::TEXT_DARKEST;
use crate::theme::TEXT_LIGHTEST;
use crate::ui::settings::theme::BACKGROUND_DARKER;
use crate::ui::settings::theme::BACKGROUND_DARKEST;
use crate::ui::settings::theme::BUTTON_BORDER_RADIUS;
use crate::ui::settings::theme::GauntletSettingsTheme;
use crate::ui::settings::theme::PRIMARY;
use crate::ui::settings::theme::PRIMARY_HOVERED;
use crate::ui::settings::theme::TEXT_DARKEST;
use crate::ui::settings::theme::TEXT_LIGHTEST;
impl pick_list::Catalog for GauntletSettingsTheme {
type Class<'a> = ();

View file

@ -1,8 +1,8 @@
use iced::widget::rule;
use iced::widget::rule::Style;
use crate::theme::BACKGROUND_DARKER;
use crate::theme::GauntletSettingsTheme;
use crate::ui::settings::theme::BACKGROUND_DARKER;
use crate::ui::settings::theme::GauntletSettingsTheme;
impl rule::Catalog for GauntletSettingsTheme {
type Class<'a> = ();

View file

@ -6,8 +6,8 @@ use iced::widget::scrollable;
use iced::widget::scrollable::Status;
use iced::widget::scrollable::Style;
use crate::theme::GauntletSettingsTheme;
use crate::theme::PRIMARY;
use crate::ui::settings::theme::GauntletSettingsTheme;
use crate::ui::settings::theme::PRIMARY;
impl scrollable::Catalog for GauntletSettingsTheme {
type Class<'a> = ();

View file

@ -1,14 +1,14 @@
use iced::Border;
use iced::widget::container::Style;
use crate::components::shortcut_selector;
use crate::components::shortcut_selector::Status;
use crate::theme::BACKGROUND_DARKER;
use crate::theme::BACKGROUND_LIGHTER;
use crate::theme::BUTTON_BORDER_RADIUS;
use crate::theme::GauntletSettingsTheme;
use crate::theme::PRIMARY;
use crate::theme::TRANSPARENT;
use crate::ui::settings::components::shortcut_selector;
use crate::ui::settings::components::shortcut_selector::Status;
use crate::ui::settings::theme::BACKGROUND_DARKER;
use crate::ui::settings::theme::BACKGROUND_LIGHTER;
use crate::ui::settings::theme::BUTTON_BORDER_RADIUS;
use crate::ui::settings::theme::GauntletSettingsTheme;
use crate::ui::settings::theme::PRIMARY;
use crate::ui::settings::theme::TRANSPARENT;
impl shortcut_selector::Catalog for GauntletSettingsTheme {
type Class<'a> = ();

View file

@ -1,10 +1,10 @@
use iced::widget::text;
use iced::widget::text::Style;
use crate::theme::DANGER_BRIGHT;
use crate::theme::GauntletSettingsTheme;
use crate::theme::SUCCESS;
use crate::theme::TEXT_DARKER;
use crate::ui::settings::theme::DANGER_BRIGHT;
use crate::ui::settings::theme::GauntletSettingsTheme;
use crate::ui::settings::theme::SUCCESS;
use crate::ui::settings::theme::TEXT_DARKER;
pub enum TextStyle {
Default,

View file

@ -4,14 +4,14 @@ use iced::widget::text_input;
use iced::widget::text_input::Status;
use iced::widget::text_input::Style;
use crate::theme::BACKGROUND_DARKER;
use crate::theme::BACKGROUND_LIGHTER;
use crate::theme::BACKGROUND_LIGHTEST;
use crate::theme::BUTTON_BORDER_RADIUS;
use crate::theme::GauntletSettingsTheme;
use crate::theme::TEXT_DARKER;
use crate::theme::TEXT_LIGHTEST;
use crate::theme::TRANSPARENT;
use crate::ui::settings::theme::BACKGROUND_DARKER;
use crate::ui::settings::theme::BACKGROUND_LIGHTER;
use crate::ui::settings::theme::BACKGROUND_LIGHTEST;
use crate::ui::settings::theme::BUTTON_BORDER_RADIUS;
use crate::ui::settings::theme::GauntletSettingsTheme;
use crate::ui::settings::theme::TEXT_DARKER;
use crate::ui::settings::theme::TEXT_LIGHTEST;
use crate::ui::settings::theme::TRANSPARENT;
pub enum TextInputStyle {
FormInput,

View file

@ -1,29 +1,23 @@
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use gauntlet_common::model::DownloadStatus;
use gauntlet_common::model::EntrypointId;
use gauntlet_common::model::PhysicalShortcut;
use gauntlet_common::model::PluginId;
use gauntlet_common::model::SettingsTheme;
use gauntlet_common::model::WindowPositionMode;
use gauntlet_common::rpc::backend_api::BackendForSettingsApi;
use gauntlet_common::rpc::backend_api::BackendForSettingsApiProxy;
use gauntlet_common::rpc::backend_api::GrpcBackendApi;
use gauntlet_common_ui::padding;
use gauntlet_server::global_hotkey::GlobalHotKeyManager;
use gauntlet_server::plugins::ApplicationManager;
use gauntlet_utils::channel::RequestError;
use gauntlet_utils::channel::RequestResult;
use iced::Alignment;
use iced::Length;
use iced::Padding;
use iced::Renderer;
use iced::Size;
use iced::Subscription;
use iced::Task;
use iced::advanced::text::Shaping;
use iced::alignment;
use iced::font;
use iced::futures;
use iced::padding;
use iced::time;
use iced::widget::button;
@ -37,54 +31,73 @@ use iced::widget::scrollable;
use iced::widget::stack;
use iced::widget::text;
use iced::window;
use iced_fonts::BOOTSTRAP_FONT_BYTES;
use iced_fonts::bootstrap::exclamation_triangle_fill;
use iced_fonts::bootstrap::gear_fill;
use iced_fonts::bootstrap::patch_check_fill;
use iced_fonts::bootstrap::puzzle_fill;
use itertools::Itertools;
use crate::components::spinner::Spinner;
use crate::theme::Element;
use crate::theme::GauntletSettingsTheme;
use crate::theme::button::ButtonStyle;
use crate::theme::container::ContainerStyle;
use crate::theme::text::TextStyle;
use crate::views::general::ManagementAppGeneralMsgIn;
use crate::views::general::ManagementAppGeneralMsgOut;
use crate::views::general::ManagementAppGeneralState;
use crate::views::plugins::ManagementAppPluginMsgIn;
use crate::views::plugins::ManagementAppPluginMsgOut;
use crate::views::plugins::ManagementAppPluginsState;
use crate::ui::settings::components::spinner::Spinner;
use crate::ui::settings::theme::Element;
use crate::ui::settings::theme::button::ButtonStyle;
use crate::ui::settings::theme::container::ContainerStyle;
use crate::ui::settings::theme::text::TextStyle;
use crate::ui::settings::views::general::SettingsGeneralMsgIn;
use crate::ui::settings::views::general::SettingsGeneralMsgOut;
use crate::ui::settings::views::general::SettingsGeneralState;
use crate::ui::settings::views::plugins::SelectedItem;
use crate::ui::settings::views::plugins::SettingsPluginMsgIn;
use crate::ui::settings::views::plugins::SettingsPluginMsgOut;
use crate::ui::settings::views::plugins::SettingsPluginsState;
pub fn run() {
iced::application::<ManagementAppModel, ManagementAppMsg, GauntletSettingsTheme, Renderer>(new, update, view)
.title("Gauntlet Settings")
.window(window::Settings {
size: Size::new(1150.0, 700.0),
..Default::default()
})
.subscription(subscription)
.theme(|_| GauntletSettingsTheme::default())
.run()
.expect("Unable to start settings application");
}
struct ManagementAppModel {
backend_api: Option<BackendForSettingsApiProxy>,
pub struct SettingsWindowState {
pub settings_window_id: Option<window::Id>,
application_manager: Arc<ApplicationManager>,
wayland: bool,
error_view: Option<ErrorView>,
downloads_info: HashMap<PluginId, DownloadInfo>,
download_info_shown: bool,
current_settings_view: SettingsView,
general_state: ManagementAppGeneralState,
plugins_state: ManagementAppPluginsState,
general_state: SettingsGeneralState,
plugins_state: SettingsPluginsState,
}
impl SettingsWindowState {
pub fn new(application_manager: Arc<ApplicationManager>, wayland: bool) -> SettingsWindowState {
SettingsWindowState {
settings_window_id: None,
application_manager: application_manager.clone(),
wayland,
error_view: None,
downloads_info: HashMap::new(),
download_info_shown: false,
current_settings_view: SettingsView::Plugins,
general_state: SettingsGeneralState::new(application_manager.clone()),
plugins_state: SettingsPluginsState::new(application_manager.clone()),
}
}
}
#[derive(Clone, Debug)]
pub enum SettingsParams {
Default,
PluginPreferences {
plugin_id: PluginId,
},
EntrypointPreferences {
plugin_id: PluginId,
entrypoint_id: EntrypointId,
},
}
#[derive(Debug, Clone)]
pub enum ManagementAppMsg {
FontLoaded(Result<(), font::Error>),
General(ManagementAppGeneralMsgIn),
Plugin(ManagementAppPluginMsgIn),
pub enum SettingsMsg {
Refresh,
OpenSettings(SettingsParams),
WindowCreated(window::Id),
WindowDestroyed,
General(SettingsGeneralMsgIn),
Plugin(SettingsPluginMsgIn),
SwitchView(SettingsView),
DownloadStatus { plugins: HashMap<PluginId, DownloadStatus> },
HandleBackendError(RequestError),
@ -113,130 +126,34 @@ pub enum DownloadInfo {
Successful,
}
fn new() -> (ManagementAppModel, Task<ManagementAppMsg>) {
let backend_api = futures::executor::block_on(async {
anyhow::Ok(BackendForSettingsApiProxy::new(GrpcBackendApi::new().await?))
})
.inspect_err(|err| tracing::error!("Unable to connect to server: {:?}", err))
.ok();
let wayland = if cfg!(target_os = "linux") {
std::env::var("WAYLAND_DISPLAY")
.or_else(|_| std::env::var("WAYLAND_SOCKET"))
.is_ok()
} else {
false
};
(
ManagementAppModel {
backend_api: backend_api.clone(),
error_view: None,
downloads_info: HashMap::new(),
download_info_shown: false,
current_settings_view: SettingsView::Plugins,
general_state: ManagementAppGeneralState::new(backend_api.clone()),
plugins_state: ManagementAppPluginsState::new(backend_api.clone()),
},
Task::batch([
font::load(BOOTSTRAP_FONT_BYTES).map(ManagementAppMsg::FontLoaded),
Task::done(ManagementAppMsg::Plugin(ManagementAppPluginMsgIn::FetchPlugins)),
Task::future(async {
match backend_api {
Some(backend_api) => Some(init_data(backend_api).await),
None => None,
}
})
.then(move |init_data| {
match init_data {
None => Task::done(ManagementAppMsg::General(ManagementAppGeneralMsgIn::Noop)),
Some(init) => {
match init {
Ok(init) => {
Task::batch([
Task::done(ManagementAppMsg::General(ManagementAppGeneralMsgIn::InitSetting {
theme: init.theme,
window_position_mode: init.window_position_mode,
shortcut: init.global_shortcut,
shortcut_error: init.global_shortcut_error,
global_shortcuts_unsupported: wayland && !init.wayland_global_shortcuts_enabled,
})),
Task::done(ManagementAppMsg::Plugin(ManagementAppPluginMsgIn::InitSetting {
global_entrypoint_shortcuts: init.global_entrypoint_shortcuts,
show_global_shortcuts: !wayland || init.wayland_global_shortcuts_enabled,
})),
])
}
Err(err) => Task::done(ManagementAppMsg::HandleBackendError(err)),
}
}
}
}),
]),
)
}
struct InitSettingsData {
global_shortcut: Option<PhysicalShortcut>,
global_shortcut_error: Option<String>,
theme: SettingsTheme,
window_position_mode: WindowPositionMode,
global_entrypoint_shortcuts: HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option<String>)>,
wayland_global_shortcuts_enabled: bool,
}
async fn init_data(backend_api: impl BackendForSettingsApi) -> RequestResult<InitSettingsData> {
let (global_shortcut, global_shortcut_error) = backend_api.get_global_shortcut().await?;
let global_entrypoint_shortcuts = backend_api.get_global_entrypoint_shortcuts().await?;
let theme = backend_api.get_theme().await?;
let window_position_mode = backend_api.get_window_position_mode().await?;
let wayland_global_shortcuts_enabled = backend_api.wayland_global_shortcuts_enabled().await?;
Ok(InitSettingsData {
global_shortcut,
global_shortcut_error,
global_entrypoint_shortcuts,
theme,
window_position_mode,
wayland_global_shortcuts_enabled,
})
}
fn update(state: &mut ManagementAppModel, message: ManagementAppMsg) -> Task<ManagementAppMsg> {
let backend_api = match &state.backend_api {
Some(backend_api) => backend_api.clone(),
None => return Task::none(),
};
pub fn update_settings(
state: &mut SettingsWindowState,
global_hotkey_manager: &Option<GlobalHotKeyManager>,
message: SettingsMsg,
) -> Task<SettingsMsg> {
match message {
ManagementAppMsg::Plugin(message) => {
state.plugins_state.update(message).map(|msg| {
SettingsMsg::Plugin(message) => {
state.plugins_state.update(global_hotkey_manager, message).map(|msg| {
match msg {
ManagementAppPluginMsgOut::Inner(msg) => ManagementAppMsg::Plugin(msg),
ManagementAppPluginMsgOut::Outer(msg) => msg,
SettingsPluginMsgOut::Inner(msg) => SettingsMsg::Plugin(msg),
SettingsPluginMsgOut::Outer(msg) => msg,
}
})
}
ManagementAppMsg::General(message) => {
state.general_state.update(message).map(|msg| {
SettingsMsg::General(message) => {
state.general_state.update(global_hotkey_manager, message).map(|msg| {
match msg {
ManagementAppGeneralMsgOut::Inner(msg) => ManagementAppMsg::General(msg),
ManagementAppGeneralMsgOut::Outer(msg) => msg,
SettingsGeneralMsgOut::Inner(msg) => SettingsMsg::General(msg),
SettingsGeneralMsgOut::Outer(msg) => msg,
}
})
}
ManagementAppMsg::FontLoaded(result) => {
result.expect("unable to load font");
Task::none()
}
ManagementAppMsg::SwitchView(view) => {
SettingsMsg::SwitchView(view) => {
state.current_settings_view = view;
Task::none()
}
ManagementAppMsg::HandleBackendError(err) => {
SettingsMsg::HandleBackendError(err) => {
state.error_view = Some(match err {
RequestError::Timeout => ErrorView::Timeout,
RequestError::Other { display } => ErrorView::UnknownError { display },
@ -249,7 +166,7 @@ fn update(state: &mut ManagementAppModel, message: ManagementAppMsg) -> Task<Man
Task::none()
}
ManagementAppMsg::DownloadStatus { plugins } => {
SettingsMsg::DownloadStatus { plugins } => {
for (plugin, status) in plugins {
match status {
DownloadStatus::InProgress => {
@ -266,13 +183,13 @@ fn update(state: &mut ManagementAppModel, message: ManagementAppMsg) -> Task<Man
}
}
let backend_api = backend_api.clone();
let application_manager = state.application_manager.clone();
Task::perform(
async move {
let plugins = backend_api.plugins().await?;
let global_entrypoint_shortcuts = backend_api.get_global_entrypoint_shortcuts().await?;
let entrypoint_search_aliases = backend_api.get_entrypoint_search_aliases().await?;
let plugins = application_manager.plugins()?;
let global_entrypoint_shortcuts = application_manager.get_global_entrypoint_shortcuts()?;
let entrypoint_search_aliases = application_manager.get_entrypoint_search_aliases()?;
Ok((plugins, global_entrypoint_shortcuts, entrypoint_search_aliases))
},
@ -280,7 +197,7 @@ fn update(state: &mut ManagementAppModel, message: ManagementAppMsg) -> Task<Man
handle_backend_error(
result,
|(plugins, global_entrypoint_shortcuts, entrypoint_search_aliases)| {
ManagementAppMsg::Plugin(ManagementAppPluginMsgIn::PluginsReloaded(
SettingsMsg::Plugin(SettingsPluginMsgIn::PluginsReloaded(
plugins,
global_entrypoint_shortcuts,
entrypoint_search_aliases,
@ -290,24 +207,17 @@ fn update(state: &mut ManagementAppModel, message: ManagementAppMsg) -> Task<Man
},
)
}
ManagementAppMsg::CheckDownloadStatus => {
SettingsMsg::CheckDownloadStatus => {
if state.downloads_info.is_empty() {
Task::none()
} else {
let backend_client = backend_api.clone();
let plugins = state.application_manager.download_status();
Task::perform(
async move {
let plugins = backend_client.download_status().await?;
Ok(plugins)
},
|result| handle_backend_error(result, |plugins| ManagementAppMsg::DownloadStatus { plugins }),
)
Task::done(SettingsMsg::DownloadStatus { plugins })
}
}
ManagementAppMsg::DownloadPlugin { plugin_id } => {
let backend_client = backend_api.clone();
SettingsMsg::DownloadPlugin { plugin_id } => {
let backend_client = state.application_manager.clone();
let already_downloading = state
.downloads_info
@ -317,39 +227,97 @@ fn update(state: &mut ManagementAppModel, message: ManagementAppMsg) -> Task<Man
if already_downloading {
Task::none()
} else {
Task::perform(
async move {
backend_client.download_plugin(plugin_id).await?;
backend_client.download_plugin(plugin_id);
Ok(())
},
|result| handle_backend_error(result, |()| ManagementAppMsg::Noop),
)
Task::none()
}
}
ManagementAppMsg::Noop => Task::none(),
ManagementAppMsg::ToggleDownloadInfo => {
SettingsMsg::Noop => Task::none(),
SettingsMsg::ToggleDownloadInfo => {
state.download_info_shown = !state.download_info_shown;
Task::none()
}
SettingsMsg::Refresh => {
fn run(state: &mut SettingsWindowState) -> anyhow::Result<Task<SettingsMsg>> {
let (global_shortcut, global_shortcut_error) =
state.application_manager.get_global_shortcut().map(|data| {
data.map(|(shortcut, error)| (Some(shortcut), error))
.unwrap_or((None, None))
})?;
let global_entrypoint_shortcuts = state.application_manager.get_global_entrypoint_shortcuts()?;
let theme = state.application_manager.get_theme()?;
let window_position_mode = state.application_manager.get_window_position_mode()?;
let wayland_global_shortcuts_enabled = state.application_manager.config()?.wayland_use_legacy_x11_api;
Ok(Task::batch([
Task::done(SettingsMsg::General(SettingsGeneralMsgIn::InitSetting {
theme,
window_position_mode,
shortcut: global_shortcut,
shortcut_error: global_shortcut_error,
global_shortcuts_unsupported: state.wayland && !wayland_global_shortcuts_enabled,
})),
Task::done(SettingsMsg::Plugin(SettingsPluginMsgIn::InitSetting {
global_entrypoint_shortcuts,
show_global_shortcuts: !state.wayland || wayland_global_shortcuts_enabled,
})),
]))
}
run(state).unwrap_or_else(|err| Task::done(SettingsMsg::HandleBackendError(err.into())))
}
SettingsMsg::WindowCreated(window_id) => {
state.settings_window_id = Some(window_id);
Task::none()
}
SettingsMsg::WindowDestroyed => {
state.settings_window_id = None;
Task::none()
}
SettingsMsg::OpenSettings(settings_params) => {
let item = match settings_params {
SettingsParams::Default => SelectedItem::None,
SettingsParams::PluginPreferences { plugin_id } => SelectedItem::Plugin { plugin_id },
SettingsParams::EntrypointPreferences {
plugin_id,
entrypoint_id,
} => {
SelectedItem::Entrypoint {
plugin_id,
entrypoint_id,
}
}
};
let open = match state.settings_window_id {
None => {
let settings = window::Settings {
size: Size::new(1150.0, 700.0),
..Default::default()
};
let (_, open) = window::open(settings);
open.map(SettingsMsg::WindowCreated)
}
Some(window_id) => window::gain_focus(window_id),
};
Task::batch([
open,
Task::done(SettingsMsg::Plugin(SettingsPluginMsgIn::FetchPlugins)),
Task::done(SettingsMsg::Refresh),
Task::done(SettingsMsg::SwitchView(SettingsView::Plugins)),
Task::done(SettingsMsg::Plugin(SettingsPluginMsgIn::SelectItem(item))),
])
}
}
}
fn view(state: &ManagementAppModel) -> Element<'_, ManagementAppMsg> {
if let None = &state.backend_api {
let description: Element<_> =
text("Unable to connect to server. Please check if you have Gauntlet running on your PC").into();
let content: Element<_> = container(description)
.align_x(Alignment::Center)
.align_y(Alignment::Center)
.width(Length::Fill)
.height(Length::Fill)
.into();
return content;
}
pub fn view_settings(state: &SettingsWindowState) -> Element<'_, SettingsMsg> {
if let Some(err) = &state.error_view {
return match err {
ErrorView::Timeout => {
@ -421,8 +389,8 @@ fn view(state: &ManagementAppModel) -> Element<'_, ManagementAppMsg> {
}
let content = match state.current_settings_view {
SettingsView::General => state.general_state.view().map(|msg| ManagementAppMsg::General(msg)),
SettingsView::Plugins => state.plugins_state.view().map(|msg| ManagementAppMsg::Plugin(msg)),
SettingsView::General => state.general_state.view().map(|msg| SettingsMsg::General(msg)),
SettingsView::Plugins => state.plugins_state.view().map(|msg| SettingsMsg::Plugin(msg)),
};
let icon_general: Element<_> = gear_fill()
@ -445,7 +413,7 @@ fn view(state: &ManagementAppModel) -> Element<'_, ManagementAppMsg> {
.into();
let general_button: Element<_> = button(general_button)
.on_press(ManagementAppMsg::SwitchView(SettingsView::General))
.on_press(SettingsMsg::SwitchView(SettingsView::General))
.height(Length::Fill)
.width(80)
.class(
@ -479,7 +447,7 @@ fn view(state: &ManagementAppModel) -> Element<'_, ManagementAppMsg> {
.into();
let plugins_button: Element<_> = button(plugins_button)
.on_press(ManagementAppMsg::SwitchView(SettingsView::Plugins))
.on_press(SettingsMsg::SwitchView(SettingsView::Plugins))
.height(Length::Fill)
.width(80)
.class(
@ -587,7 +555,7 @@ fn view(state: &ManagementAppModel) -> Element<'_, ManagementAppMsg> {
let top_bar_right: Element<_> = button(top_bar_right)
.class(ButtonStyle::DownloadInfo)
.on_press(ManagementAppMsg::ToggleDownloadInfo)
.on_press(SettingsMsg::ToggleDownloadInfo)
.padding(Padding::from([4, 8]))
.height(Length::Fill)
.into();
@ -735,12 +703,17 @@ fn view(state: &ManagementAppModel) -> Element<'_, ManagementAppMsg> {
.into()
};
let content = container(content)
.width(Length::Fill)
.height(Length::Fill)
.class(ContainerStyle::WindowRoot);
let content: Element<_> = mouse_area(content)
.on_press(
if state.download_info_shown {
ManagementAppMsg::ToggleDownloadInfo
SettingsMsg::ToggleDownloadInfo
} else {
ManagementAppMsg::Noop
SettingsMsg::Noop
},
)
.into();
@ -754,16 +727,16 @@ fn view(state: &ManagementAppModel) -> Element<'_, ManagementAppMsg> {
stack(content).into()
}
fn subscription(_state: &ManagementAppModel) -> Subscription<ManagementAppMsg> {
time::every(Duration::from_millis(300)).map(|_| ManagementAppMsg::CheckDownloadStatus)
}
pub fn handle_backend_error<T>(
result: RequestResult<T>,
convert: impl FnOnce(T) -> ManagementAppMsg,
) -> ManagementAppMsg {
match result {
Ok(val) => convert(val),
Err(err) => ManagementAppMsg::HandleBackendError(err),
pub fn subscription_settings(state: &SettingsWindowState) -> Subscription<SettingsMsg> {
match state.settings_window_id {
None => Subscription::none(),
Some(_) => time::every(Duration::from_millis(300)).map(|_| SettingsMsg::CheckDownloadStatus),
}
}
pub fn handle_backend_error<T>(result: RequestResult<T>, convert: impl FnOnce(T) -> SettingsMsg) -> SettingsMsg {
match result {
Ok(val) => convert(val),
Err(err) => SettingsMsg::HandleBackendError(err),
}
}

View file

@ -1,8 +1,10 @@
use std::sync::Arc;
use gauntlet_common::model::PhysicalShortcut;
use gauntlet_common::model::SettingsTheme;
use gauntlet_common::model::WindowPositionMode;
use gauntlet_common::rpc::backend_api::BackendForSettingsApi;
use gauntlet_common::rpc::backend_api::BackendForSettingsApiProxy;
use gauntlet_server::global_hotkey::GlobalHotKeyManager;
use gauntlet_server::plugins::ApplicationManager;
use gauntlet_utils::channel::RequestResult;
use iced::Alignment;
use iced::Font;
@ -20,15 +22,15 @@ use iced::widget::row;
use iced::widget::text;
use iced::widget::text::Shaping;
use crate::components::shortcut_selector::ShortcutData;
use crate::components::shortcut_selector::render_shortcut_error;
use crate::components::shortcut_selector::shortcut_selector;
use crate::theme::Element;
use crate::theme::container::ContainerStyle;
use crate::ui::ManagementAppMsg;
use crate::ui::settings::components::shortcut_selector::ShortcutData;
use crate::ui::settings::components::shortcut_selector::render_shortcut_error;
use crate::ui::settings::components::shortcut_selector::shortcut_selector;
use crate::ui::settings::theme::Element;
use crate::ui::settings::theme::container::ContainerStyle;
use crate::ui::settings::ui::SettingsMsg;
pub struct ManagementAppGeneralState {
backend_api: Option<BackendForSettingsApiProxy>,
pub struct SettingsGeneralState {
application_manager: Arc<ApplicationManager>,
theme: SettingsTheme,
window_position_mode: WindowPositionMode,
current_shortcut: ShortcutData,
@ -36,7 +38,7 @@ pub struct ManagementAppGeneralState {
}
#[derive(Debug, Clone)]
pub enum ManagementAppGeneralMsgIn {
pub enum SettingsGeneralMsgIn {
ShortcutCaptured(Option<PhysicalShortcut>),
ThemeChanged(SettingsTheme),
WindowPositionModeChanged(WindowPositionMode),
@ -51,19 +53,18 @@ pub enum ManagementAppGeneralMsgIn {
shortcut_error: Option<String>,
global_shortcuts_unsupported: bool,
},
Noop,
}
#[derive(Debug, Clone)]
pub enum ManagementAppGeneralMsgOut {
Inner(ManagementAppGeneralMsgIn),
Outer(ManagementAppMsg),
pub enum SettingsGeneralMsgOut {
Inner(SettingsGeneralMsgIn),
Outer(SettingsMsg),
}
impl ManagementAppGeneralState {
pub fn new(backend_api: Option<BackendForSettingsApiProxy>) -> Self {
impl SettingsGeneralState {
pub fn new(application_manager: Arc<ApplicationManager>) -> Self {
Self {
backend_api,
application_manager,
theme: SettingsTheme::AutoDetect,
window_position_mode: WindowPositionMode::Static,
current_shortcut: ShortcutData {
@ -74,40 +75,29 @@ impl ManagementAppGeneralState {
}
}
pub fn update(&mut self, message: ManagementAppGeneralMsgIn) -> Task<ManagementAppGeneralMsgOut> {
let backend_api = match &self.backend_api {
Some(backend_api) => backend_api.clone(),
None => return Task::none(),
};
pub fn update(
&mut self,
global_hotkey_manager: &Option<GlobalHotKeyManager>,
message: SettingsGeneralMsgIn,
) -> Task<SettingsGeneralMsgOut> {
match message {
ManagementAppGeneralMsgIn::ShortcutCaptured(shortcut) => {
let backend_api = backend_api.clone();
SettingsGeneralMsgIn::ShortcutCaptured(shortcut) => {
let Some(global_hotkey_manager) = &global_hotkey_manager else {
return Task::none();
};
Task::perform(
{
let shortcut = shortcut.clone();
let error = self
.application_manager
.set_global_shortcut(global_hotkey_manager, shortcut.clone());
async move {
let error = backend_api.set_global_shortcut(shortcut).await?;
Ok(error)
}
Task::done(SettingsGeneralMsgOut::Inner(
SettingsGeneralMsgIn::HandleShortcutResponse {
shortcut,
shortcut_error: error,
},
move |result| {
let shortcut = shortcut.clone();
handle_backend_error(result, move |shortcut_error| {
ManagementAppGeneralMsgOut::Inner(ManagementAppGeneralMsgIn::HandleShortcutResponse {
shortcut,
shortcut_error,
})
})
},
)
))
}
ManagementAppGeneralMsgIn::Noop => Task::none(),
ManagementAppGeneralMsgIn::InitSetting {
SettingsGeneralMsgIn::InitSetting {
theme,
window_position_mode,
shortcut,
@ -124,39 +114,35 @@ impl ManagementAppGeneralState {
Task::none()
}
ManagementAppGeneralMsgIn::ThemeChanged(theme) => {
SettingsGeneralMsgIn::ThemeChanged(theme) => {
self.theme = theme.clone();
let backend_api = backend_api.clone();
let application_manager = self.application_manager.clone();
Task::perform(
async move {
backend_api.set_theme(theme).await?;
application_manager.set_theme(theme).await?;
Ok(())
},
|result| {
handle_backend_error(result, |()| ManagementAppGeneralMsgOut::Outer(ManagementAppMsg::Noop))
},
|result| handle_backend_error(result, |()| SettingsGeneralMsgOut::Outer(SettingsMsg::Noop)),
)
}
ManagementAppGeneralMsgIn::WindowPositionModeChanged(mode) => {
SettingsGeneralMsgIn::WindowPositionModeChanged(mode) => {
self.window_position_mode = mode.clone();
let backend_api = backend_api.clone();
let application_manager = self.application_manager.clone();
Task::perform(
async move {
backend_api.set_window_position_mode(mode).await?;
application_manager.set_window_position_mode(mode).await?;
Ok(())
},
|result| {
handle_backend_error(result, |()| ManagementAppGeneralMsgOut::Outer(ManagementAppMsg::Noop))
},
|result| handle_backend_error(result, |()| SettingsGeneralMsgOut::Outer(SettingsMsg::Noop)),
)
}
ManagementAppGeneralMsgIn::HandleShortcutResponse {
SettingsGeneralMsgIn::HandleShortcutResponse {
shortcut,
shortcut_error,
} => {
@ -170,7 +156,7 @@ impl ManagementAppGeneralState {
}
}
pub fn view(&self) -> Element<ManagementAppGeneralMsgIn> {
pub fn view(&self) -> Element<SettingsGeneralMsgIn> {
let global_shortcut_selector: Element<_> = if self.global_shortcuts_unsupported {
let text = text("Not supported").font(Font {
style: Style::Italic,
@ -186,7 +172,7 @@ impl ManagementAppGeneralState {
} else {
shortcut_selector(
&self.current_shortcut,
move |shortcut| ManagementAppGeneralMsgIn::ShortcutCaptured(shortcut),
move |shortcut| SettingsGeneralMsgIn::ShortcutCaptured(shortcut),
ContainerStyle::Box,
false,
)
@ -221,7 +207,7 @@ impl ManagementAppGeneralState {
content
}
fn theme_field(&self) -> Element<ManagementAppGeneralMsgIn> {
fn theme_field(&self) -> Element<SettingsGeneralMsgIn> {
let theme_field = match &self.theme {
SettingsTheme::ThemeFile => {
let theme_field: Element<_> = text("Unable to change because theme config file is present ")
@ -250,7 +236,7 @@ impl ManagementAppGeneralState {
];
let theme_field: Element<_> = pick_list(theme_items, Some(self.theme.clone()), move |item| {
ManagementAppGeneralMsgIn::ThemeChanged(item)
SettingsGeneralMsgIn::ThemeChanged(item)
})
.into();
@ -266,11 +252,11 @@ impl ManagementAppGeneralState {
}
#[allow(unused)]
fn window_position_mode_field(&self) -> Element<ManagementAppGeneralMsgIn> {
fn window_position_mode_field(&self) -> Element<SettingsGeneralMsgIn> {
let items = [WindowPositionMode::Static, WindowPositionMode::ActiveMonitor];
let field: Element<_> = pick_list(items, Some(self.window_position_mode.clone()), move |item| {
ManagementAppGeneralMsgIn::WindowPositionModeChanged(item)
SettingsGeneralMsgIn::WindowPositionModeChanged(item)
})
.into();
@ -284,9 +270,9 @@ impl ManagementAppGeneralState {
fn view_field<'a>(
&'a self,
label: &'a str,
input: Element<'a, ManagementAppGeneralMsgIn>,
after: Option<Element<'a, ManagementAppGeneralMsgIn>>,
) -> Element<'a, ManagementAppGeneralMsgIn> {
input: Element<'a, SettingsGeneralMsgIn>,
after: Option<Element<'a, SettingsGeneralMsgIn>>,
) -> Element<'a, SettingsGeneralMsgIn> {
let label: Element<_> = text(label)
.shaping(Shaping::Advanced)
.align_x(Horizontal::Right)
@ -306,7 +292,7 @@ impl ManagementAppGeneralState {
row
}
fn shortcut_capture_after(&self) -> Element<ManagementAppGeneralMsgIn> {
fn shortcut_capture_after(&self) -> Element<SettingsGeneralMsgIn> {
if let Some(current_shortcut_error) = &self.current_shortcut.error {
let content = render_shortcut_error(current_shortcut_error.clone());
@ -325,10 +311,10 @@ impl ManagementAppGeneralState {
fn handle_backend_error<T>(
result: RequestResult<T>,
convert: impl FnOnce(T) -> ManagementAppGeneralMsgOut,
) -> ManagementAppGeneralMsgOut {
convert: impl FnOnce(T) -> SettingsGeneralMsgOut,
) -> SettingsGeneralMsgOut {
match result {
Ok(val) => convert(val),
Err(err) => ManagementAppGeneralMsgOut::Outer(ManagementAppMsg::HandleBackendError(err)),
Err(err) => SettingsGeneralMsgOut::Outer(SettingsMsg::HandleBackendError(err)),
}
}

View file

@ -1,18 +1,16 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
use gauntlet_common::SETTINGS_ENV;
use gauntlet_common::SettingsEnvData;
use gauntlet_common::model::EntrypointId;
use gauntlet_common::model::PhysicalShortcut;
use gauntlet_common::model::PluginId;
use gauntlet_common::model::PluginPreferenceUserData;
use gauntlet_common::model::SettingsEntrypointType;
use gauntlet_common::model::SettingsPlugin;
use gauntlet_common::rpc::backend_api::BackendForSettingsApi;
use gauntlet_common::rpc::backend_api::BackendForSettingsApiProxy;
use gauntlet_common::settings_env_data_from_string;
use gauntlet_server::global_hotkey::GlobalHotKeyManager;
use gauntlet_server::plugins::ApplicationManager;
use gauntlet_utils::channel::RequestResult;
use iced::Alignment;
use iced::Length;
@ -30,22 +28,22 @@ use iced::widget::text_input;
use iced::widget::vertical_rule;
use iced_fonts::bootstrap::plus;
use crate::theme::Element;
use crate::theme::button::ButtonStyle;
use crate::theme::text::TextStyle;
use crate::ui::ManagementAppMsg;
use crate::views::plugins::preferences::PluginPreferencesMsg;
use crate::views::plugins::preferences::SelectItem;
use crate::views::plugins::preferences::preferences_ui;
use crate::views::plugins::table::PluginTableMsgIn;
use crate::views::plugins::table::PluginTableMsgOut;
use crate::views::plugins::table::PluginTableState;
use crate::ui::settings::theme::Element;
use crate::ui::settings::theme::button::ButtonStyle;
use crate::ui::settings::theme::text::TextStyle;
use crate::ui::settings::ui::SettingsMsg;
use crate::ui::settings::views::plugins::preferences::PluginPreferencesMsg;
use crate::ui::settings::views::plugins::preferences::SelectItem;
use crate::ui::settings::views::plugins::preferences::preferences_ui;
use crate::ui::settings::views::plugins::table::PluginTableMsgIn;
use crate::ui::settings::views::plugins::table::PluginTableMsgOut;
use crate::ui::settings::views::plugins::table::PluginTableState;
mod preferences;
mod table;
#[derive(Debug, Clone)]
pub enum ManagementAppPluginMsgIn {
pub enum SettingsPluginMsgIn {
InitSetting {
global_entrypoint_shortcuts: HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option<String>)>,
show_global_shortcuts: bool,
@ -74,13 +72,13 @@ pub enum ManagementAppPluginMsgIn {
SelectItem(SelectedItem),
}
pub enum ManagementAppPluginMsgOut {
Inner(ManagementAppPluginMsgIn),
Outer(ManagementAppMsg),
pub enum SettingsPluginMsgOut {
Inner(SettingsPluginMsgIn),
Outer(SettingsMsg),
}
pub struct ManagementAppPluginsState {
backend_api: Option<BackendForSettingsApiProxy>,
pub struct SettingsPluginsState {
application_manager: Arc<ApplicationManager>,
table_state: PluginTableState,
plugin_data: Rc<RefCell<PluginDataContainer>>,
preference_user_data: HashMap<(PluginId, Option<EntrypointId>, String), PluginPreferenceUserDataState>,
@ -89,52 +87,27 @@ pub struct ManagementAppPluginsState {
entrypoint_search_aliases: HashMap<(PluginId, EntrypointId), String>,
}
impl ManagementAppPluginsState {
pub fn new(backend_api: Option<BackendForSettingsApiProxy>) -> Self {
let settings_env_data = std::env::var(SETTINGS_ENV)
.ok()
.filter(|value| !value.is_empty())
.map(|val| settings_env_data_from_string(val));
let select_item = match settings_env_data {
None => SelectedItem::None,
Some(SettingsEnvData::OpenEntrypointPreferences {
plugin_id,
entrypoint_id,
}) => {
SelectedItem::Entrypoint {
plugin_id: PluginId::from_string(plugin_id),
entrypoint_id: EntrypointId::from_string(entrypoint_id),
}
}
Some(SettingsEnvData::OpenPluginPreferences { plugin_id }) => {
SelectedItem::Plugin {
plugin_id: PluginId::from_string(plugin_id),
}
}
};
tracing::debug!("Opening selected item: {:?}", select_item);
impl SettingsPluginsState {
pub fn new(application_manager: Arc<ApplicationManager>) -> Self {
Self {
backend_api: backend_api.clone(),
application_manager: application_manager.clone(),
plugin_data: Rc::new(RefCell::new(PluginDataContainer::new())),
preference_user_data: HashMap::new(),
selected_item: select_item,
selected_item: SelectedItem::None,
table_state: PluginTableState::new(),
global_entrypoint_shortcuts: HashMap::new(),
entrypoint_search_aliases: HashMap::new(),
}
}
pub fn update(&mut self, message: ManagementAppPluginMsgIn) -> Task<ManagementAppPluginMsgOut> {
let backend_api = match &self.backend_api {
Some(backend_api) => backend_api.clone(),
None => return Task::none(),
};
pub fn update(
&mut self,
global_hotkey_manager: &Option<GlobalHotKeyManager>,
message: SettingsPluginMsgIn,
) -> Task<SettingsPluginMsgOut> {
let application_manager = self.application_manager.clone();
match message {
ManagementAppPluginMsgIn::InitSetting {
SettingsPluginMsgIn::InitSetting {
global_entrypoint_shortcuts,
show_global_shortcuts,
} => {
@ -143,156 +116,162 @@ impl ManagementAppPluginsState {
Task::none()
}
ManagementAppPluginMsgIn::PluginTableMsg(message) => {
self.table_state.update(message).then(move |msg| {
match msg {
PluginTableMsgOut::SetPluginState { enabled, plugin_id } => {
let backend_client = backend_api.clone();
SettingsPluginMsgIn::PluginTableMsg(message) => {
let application_manager = application_manager.clone();
match self.table_state.update(message) {
PluginTableMsgOut::SetPluginState { enabled, plugin_id } => {
let application_manager = application_manager.clone();
Task::perform(
async move {
backend_client.set_plugin_state(plugin_id, enabled).await?;
Task::perform(
async move {
application_manager.set_plugin_state(plugin_id, enabled)?;
let plugins = backend_client.plugins().await?;
let global_entrypoint_shortcuts =
backend_client.get_global_entrypoint_shortcuts().await?;
let entrypoint_aliases = backend_client.get_entrypoint_search_aliases().await?;
let plugins = application_manager.plugins()?;
let global_entrypoint_shortcuts =
application_manager.get_global_entrypoint_shortcuts()?;
let entrypoint_aliases = application_manager.get_entrypoint_search_aliases()?;
Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases))
},
|result| {
handle_backend_error(
result,
|(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| {
ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded(
plugins,
global_entrypoint_shortcuts,
entrypoint_aliases,
))
},
)
},
)
}
PluginTableMsgOut::SetEntrypointState {
enabled,
Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases))
},
|result| {
handle_backend_error(
result,
|(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| {
SettingsPluginMsgOut::Inner(SettingsPluginMsgIn::PluginsReloaded(
plugins,
global_entrypoint_shortcuts,
entrypoint_aliases,
))
},
)
},
)
}
PluginTableMsgOut::SetEntrypointState {
enabled,
plugin_id,
entrypoint_id,
} => {
let application_manager = application_manager.clone();
Task::perform(
async move {
application_manager.set_entrypoint_state(plugin_id, entrypoint_id, enabled)?;
let plugins = application_manager.plugins()?;
let global_entrypoint_shortcuts =
application_manager.get_global_entrypoint_shortcuts()?;
let entrypoint_aliases = application_manager.get_entrypoint_search_aliases()?;
Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases))
},
|result| {
handle_backend_error(
result,
|(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| {
SettingsPluginMsgOut::Inner(SettingsPluginMsgIn::PluginsReloaded(
plugins,
global_entrypoint_shortcuts,
entrypoint_aliases,
))
},
)
},
)
}
PluginTableMsgOut::SelectItem(selected_item) => {
Task::done(SettingsPluginMsgOut::Inner(SettingsPluginMsgIn::SelectItem(
selected_item,
)))
}
PluginTableMsgOut::ToggleShowEntrypoints { plugin_id } => {
Task::done(SettingsPluginMsgOut::Inner(SettingsPluginMsgIn::ToggleShowEntrypoint {
plugin_id,
entrypoint_id,
} => {
let backend_client = backend_api.clone();
}))
}
PluginTableMsgOut::ToggleShowGeneratedEntrypoints {
plugin_id,
entrypoint_id,
} => {
Task::done(SettingsPluginMsgOut::Inner(
SettingsPluginMsgIn::ToggleShowGeneratedEntrypoint {
plugin_id,
entrypoint_id,
},
))
}
PluginTableMsgOut::ShortcutCaptured(plugin_id, entrypoint_id, shortcut) => {
let Some(global_hotkey_manager) = &global_hotkey_manager else {
return Task::none();
};
Task::perform(
async move {
backend_client
.set_entrypoint_state(plugin_id, entrypoint_id, enabled)
.await?;
fn run(
application_manager: &ApplicationManager,
global_hotkey_manager: &GlobalHotKeyManager,
plugin_id: PluginId,
entrypoint_id: EntrypointId,
shortcut: Option<PhysicalShortcut>,
) -> anyhow::Result<SettingsPluginMsgOut> {
application_manager.set_global_entrypoint_shortcut(
global_hotkey_manager,
plugin_id,
entrypoint_id,
shortcut,
)?;
let plugins = backend_client.plugins().await?;
let global_entrypoint_shortcuts =
backend_client.get_global_entrypoint_shortcuts().await?;
let entrypoint_aliases = backend_client.get_entrypoint_search_aliases().await?;
let plugins = application_manager.plugins()?;
let global_entrypoint_shortcuts = application_manager.get_global_entrypoint_shortcuts()?;
let entrypoint_aliases = application_manager.get_entrypoint_search_aliases()?;
Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases))
},
|result| {
handle_backend_error(
result,
|(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| {
ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded(
plugins,
global_entrypoint_shortcuts,
entrypoint_aliases,
))
},
)
},
)
}
PluginTableMsgOut::SelectItem(selected_item) => {
Task::done(ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::SelectItem(
selected_item,
Ok(SettingsPluginMsgOut::Inner(SettingsPluginMsgIn::PluginsReloaded(
plugins,
global_entrypoint_shortcuts,
entrypoint_aliases,
)))
}
PluginTableMsgOut::ToggleShowEntrypoints { plugin_id } => {
Task::done(ManagementAppPluginMsgOut::Inner(
ManagementAppPluginMsgIn::ToggleShowEntrypoint { plugin_id },
))
}
PluginTableMsgOut::ToggleShowGeneratedEntrypoints {
let msg_out = run(
&application_manager,
global_hotkey_manager,
plugin_id,
entrypoint_id,
} => {
Task::done(ManagementAppPluginMsgOut::Inner(
ManagementAppPluginMsgIn::ToggleShowGeneratedEntrypoint {
plugin_id,
entrypoint_id,
},
))
}
PluginTableMsgOut::ShortcutCaptured(plugin_id, entrypoint_id, shortcut) => {
let backend_client = backend_api.clone();
shortcut,
)
.unwrap_or_else(|err| SettingsPluginMsgOut::Outer(SettingsMsg::HandleBackendError(err.into())));
Task::perform(
async move {
backend_client
.set_global_entrypoint_shortcut(plugin_id, entrypoint_id, shortcut)
.await?;
let plugins = backend_client.plugins().await?;
let global_entrypoint_shortcuts =
backend_client.get_global_entrypoint_shortcuts().await?;
let entrypoint_aliases = backend_client.get_entrypoint_search_aliases().await?;
Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases))
},
|result| {
handle_backend_error(
result,
|(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| {
ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded(
plugins,
global_entrypoint_shortcuts,
entrypoint_aliases,
))
},
)
},
)
}
PluginTableMsgOut::AliasChanged(plugin_id, entrypoint_id, shortcut) => {
let backend_client = backend_api.clone();
Task::perform(
async move {
backend_client
.set_entrypoint_search_alias(plugin_id, entrypoint_id, shortcut)
.await?;
let plugins = backend_client.plugins().await?;
let global_entrypoint_shortcuts =
backend_client.get_global_entrypoint_shortcuts().await?;
let entrypoint_aliases = backend_client.get_entrypoint_search_aliases().await?;
Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases))
},
|result| {
handle_backend_error(
result,
|(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| {
ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded(
plugins,
global_entrypoint_shortcuts,
entrypoint_aliases,
))
},
)
},
)
}
Task::done(msg_out)
}
})
PluginTableMsgOut::AliasChanged(plugin_id, entrypoint_id, shortcut) => {
let application_manager = application_manager.clone();
Task::perform(
async move {
application_manager.set_entrypoint_search_alias(plugin_id, entrypoint_id, shortcut)?;
let plugins = application_manager.plugins()?;
let global_entrypoint_shortcuts =
application_manager.get_global_entrypoint_shortcuts()?;
let entrypoint_aliases = application_manager.get_entrypoint_search_aliases()?;
Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases))
},
|result| {
handle_backend_error(
result,
|(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| {
SettingsPluginMsgOut::Inner(SettingsPluginMsgIn::PluginsReloaded(
plugins,
global_entrypoint_shortcuts,
entrypoint_aliases,
))
},
)
},
)
}
}
}
ManagementAppPluginMsgIn::ToggleShowEntrypoint { plugin_id } => {
SettingsPluginMsgIn::ToggleShowEntrypoint { plugin_id } => {
let plugins = {
let mut plugin_data = self.plugin_data.borrow_mut();
let settings_plugin_data = plugin_data.plugins_state.get_mut(&plugin_id).unwrap();
@ -309,7 +288,7 @@ impl ManagementAppPluginsState {
Task::none()
}
ManagementAppPluginMsgIn::ToggleShowGeneratedEntrypoint {
SettingsPluginMsgIn::ToggleShowGeneratedEntrypoint {
plugin_id,
entrypoint_id,
} => {
@ -334,7 +313,7 @@ impl ManagementAppPluginsState {
Task::none()
}
ManagementAppPluginMsgIn::PluginPreferenceMsg(msg) => {
SettingsPluginMsgIn::PluginPreferenceMsg(msg) => {
match msg {
PluginPreferencesMsg::UpdatePreferenceValue {
plugin_id,
@ -347,39 +326,38 @@ impl ManagementAppPluginsState {
user_data.clone(),
);
let backend_api = backend_api.clone();
let application_manager = application_manager.clone();
Task::perform(
async move {
backend_api
.set_preference_value(plugin_id, entrypoint_id, id, user_data.to_user_data())
.await?;
application_manager.set_preference_value(
plugin_id,
entrypoint_id,
id,
user_data.to_user_data(),
)?;
Ok(())
},
|result| {
handle_backend_error(result, |()| {
ManagementAppPluginMsgOut::Outer(ManagementAppMsg::Noop)
})
},
|result| handle_backend_error(result, |()| SettingsPluginMsgOut::Outer(SettingsMsg::Noop)),
)
}
}
}
ManagementAppPluginMsgIn::FetchPlugins => {
let backend_api = backend_api.clone();
SettingsPluginMsgIn::FetchPlugins => {
let application_manager = self.application_manager.clone();
Task::perform(
async move {
let plugins = backend_api.plugins().await?;
let global_entrypoint_shortcuts = backend_api.get_global_entrypoint_shortcuts().await?;
let entrypoint_aliases = backend_api.get_entrypoint_search_aliases().await?;
let plugins = application_manager.plugins()?;
let global_entrypoint_shortcuts = application_manager.get_global_entrypoint_shortcuts()?;
let entrypoint_aliases = application_manager.get_entrypoint_search_aliases()?;
Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases))
},
|result| {
handle_backend_error(result, |(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| {
ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded(
SettingsPluginMsgOut::Inner(SettingsPluginMsgIn::PluginsReloaded(
plugins,
global_entrypoint_shortcuts,
entrypoint_aliases,
@ -388,29 +366,29 @@ impl ManagementAppPluginsState {
},
)
}
ManagementAppPluginMsgIn::PluginsReloaded(plugins, shortcuts, entrypoint_aliases) => {
SettingsPluginMsgIn::PluginsReloaded(plugins, shortcuts, entrypoint_aliases) => {
self.apply_plugin_fetch(plugins, shortcuts, entrypoint_aliases);
Task::none()
}
ManagementAppPluginMsgIn::RemovePlugin { plugin_id } => {
SettingsPluginMsgIn::RemovePlugin { plugin_id } => {
self.selected_item = SelectedItem::None;
let backend_client = backend_api.clone();
let application_manager = application_manager.clone();
Task::perform(
async move {
backend_client.remove_plugin(plugin_id).await?;
application_manager.remove_plugin(plugin_id)?;
let plugins = backend_client.plugins().await?;
let global_entrypoint_shortcuts = backend_client.get_global_entrypoint_shortcuts().await?;
let entrypoint_aliases = backend_client.get_entrypoint_search_aliases().await?;
let plugins = application_manager.plugins()?;
let global_entrypoint_shortcuts = application_manager.get_global_entrypoint_shortcuts()?;
let entrypoint_aliases = application_manager.get_entrypoint_search_aliases()?;
Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases))
},
|result| {
handle_backend_error(result, |(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| {
ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded(
SettingsPluginMsgOut::Inner(SettingsPluginMsgIn::PluginsReloaded(
plugins,
global_entrypoint_shortcuts,
entrypoint_aliases,
@ -419,12 +397,10 @@ impl ManagementAppPluginsState {
},
)
}
ManagementAppPluginMsgIn::DownloadPlugin { plugin_id } => {
Task::done(ManagementAppPluginMsgOut::Outer(ManagementAppMsg::DownloadPlugin {
plugin_id,
}))
SettingsPluginMsgIn::DownloadPlugin { plugin_id } => {
Task::done(SettingsPluginMsgOut::Outer(SettingsMsg::DownloadPlugin { plugin_id }))
}
ManagementAppPluginMsgIn::SelectItem(selected_item) => {
SettingsPluginMsgIn::SelectItem(selected_item) => {
self.selected_item = selected_item;
Task::none()
@ -522,11 +498,11 @@ impl ManagementAppPluginsState {
)
}
pub fn view(&self) -> Element<ManagementAppPluginMsgIn> {
pub fn view(&self) -> Element<SettingsPluginMsgIn> {
let table: Element<_> = self
.table_state
.view()
.map(|msg| ManagementAppPluginMsgIn::PluginTableMsg(msg));
.map(|msg| SettingsPluginMsgIn::PluginTableMsg(msg));
let table: Element<_> = container(table).padding(Padding::new(8.0)).into();
@ -593,7 +569,7 @@ impl ManagementAppPluginsState {
column_content.push(
preferences_ui(plugin_id.clone(), None, &plugin.preferences, &self.preference_user_data)
.map(|msg| ManagementAppPluginMsgIn::PluginPreferenceMsg(msg)),
.map(|msg| SettingsPluginMsgIn::PluginPreferenceMsg(msg)),
);
let content: Element<_> = column(column_content).spacing(12).into();
@ -614,7 +590,7 @@ impl ManagementAppPluginsState {
let check_for_updates_button: Element<_> = button(check_for_updates_text_container)
.width(Length::Fill)
.class(ButtonStyle::Primary)
.on_press(ManagementAppPluginMsgIn::DownloadPlugin {
.on_press(SettingsPluginMsgIn::DownloadPlugin {
plugin_id: plugin.plugin_id.clone(),
})
.into();
@ -632,7 +608,7 @@ impl ManagementAppPluginsState {
let remove_button: Element<_> = button(remove_button_text_container)
.width(Length::Fill)
.class(ButtonStyle::Destructive)
.on_press(ManagementAppPluginMsgIn::RemovePlugin {
.on_press(SettingsPluginMsgIn::RemovePlugin {
plugin_id: plugin.plugin_id.clone(),
})
.into();
@ -699,7 +675,7 @@ impl ManagementAppPluginsState {
&entrypoint.preferences,
&self.preference_user_data,
)
.map(|msg| ManagementAppPluginMsgIn::PluginPreferenceMsg(msg)),
.map(|msg| SettingsPluginMsgIn::PluginPreferenceMsg(msg)),
);
let column: Element<_> = column(column_content).spacing(12).into();
@ -717,9 +693,9 @@ impl ManagementAppPluginsState {
SelectedItem::NewPlugin { repository_url } => {
let url_input: Element<_> = text_input("Enter Git Repository URL", &repository_url)
.on_input(|value| {
ManagementAppPluginMsgIn::SelectItem(SelectedItem::NewPlugin { repository_url: value })
SettingsPluginMsgIn::SelectItem(SelectedItem::NewPlugin { repository_url: value })
})
.on_submit(ManagementAppPluginMsgIn::DownloadPlugin {
.on_submit(SettingsPluginMsgIn::DownloadPlugin {
plugin_id: PluginId::from_string(repository_url),
})
.into();
@ -805,12 +781,12 @@ impl ManagementAppPluginsState {
let top_button_action = match plugin_url {
Some(plugin_url) => {
ManagementAppPluginMsgIn::DownloadPlugin {
SettingsPluginMsgIn::DownloadPlugin {
plugin_id: PluginId::from_string(plugin_url),
}
}
None => {
ManagementAppPluginMsgIn::SelectItem(SelectedItem::NewPlugin {
SettingsPluginMsgIn::SelectItem(SelectedItem::NewPlugin {
repository_url: Default::default(),
})
}
@ -960,10 +936,10 @@ impl PluginPreferenceUserDataState {
pub fn handle_backend_error<T>(
result: RequestResult<T>,
convert: impl FnOnce(T) -> ManagementAppPluginMsgOut,
) -> ManagementAppPluginMsgOut {
convert: impl FnOnce(T) -> SettingsPluginMsgOut,
) -> SettingsPluginMsgOut {
match result {
Ok(val) => convert(val),
Err(err) => ManagementAppPluginMsgOut::Outer(ManagementAppMsg::HandleBackendError(err)),
Err(err) => SettingsPluginMsgOut::Outer(SettingsMsg::HandleBackendError(err)),
}
}

View file

@ -19,11 +19,11 @@ use iced::widget::text_input;
use iced_fonts::bootstrap::dash;
use iced_fonts::bootstrap::plus;
use crate::theme::Element;
use crate::theme::button::ButtonStyle;
use crate::theme::container::ContainerStyle;
use crate::theme::text::TextStyle;
use crate::views::plugins::PluginPreferenceUserDataState;
use crate::ui::settings::theme::Element;
use crate::ui::settings::theme::button::ButtonStyle;
use crate::ui::settings::theme::container::ContainerStyle;
use crate::ui::settings::theme::text::TextStyle;
use crate::ui::settings::views::plugins::PluginPreferenceUserDataState;
#[derive(Debug, Clone)]
pub enum PluginPreferencesMsg {

View file

@ -9,7 +9,6 @@ use gauntlet_common::model::SettingsEntrypointType;
use gauntlet_common::model::SettingsPlugin;
use iced::Alignment;
use iced::Length;
use iced::Task;
use iced::advanced::text::Shaping;
use iced::padding;
use iced::widget::Space;
@ -25,15 +24,15 @@ use iced::widget::text_input;
use iced_fonts::bootstrap::caret_down;
use iced_fonts::bootstrap::caret_right;
use crate::components::shortcut_selector::ShortcutData;
use crate::components::shortcut_selector::shortcut_selector;
use crate::theme::Element;
use crate::theme::button::ButtonStyle;
use crate::theme::container::ContainerStyle;
use crate::theme::text_input::TextInputStyle;
use crate::views::plugins::PluginDataContainer;
use crate::views::plugins::SelectedItem;
use crate::views::plugins::SettingsPluginData;
use crate::ui::settings::components::shortcut_selector::ShortcutData;
use crate::ui::settings::components::shortcut_selector::shortcut_selector;
use crate::ui::settings::theme::Element;
use crate::ui::settings::theme::button::ButtonStyle;
use crate::ui::settings::theme::container::ContainerStyle;
use crate::ui::settings::theme::text_input::TextInputStyle;
use crate::ui::settings::views::plugins::PluginDataContainer;
use crate::ui::settings::views::plugins::SelectedItem;
use crate::ui::settings::views::plugins::SettingsPluginData;
#[derive(Debug, Clone)]
pub enum PluginTableMsgIn {
@ -85,47 +84,47 @@ impl PluginTableState {
}
}
pub fn update(&mut self, message: PluginTableMsgIn) -> Task<PluginTableMsgOut> {
pub fn update(&mut self, message: PluginTableMsgIn) -> PluginTableMsgOut {
match message {
PluginTableMsgIn::EnabledToggleItem(item) => {
match item {
EnabledItem::Plugin { enabled, plugin_id } => {
Task::done(PluginTableMsgOut::SetPluginState { enabled, plugin_id })
PluginTableMsgOut::SetPluginState { enabled, plugin_id }
}
EnabledItem::Entrypoint {
enabled,
plugin_id,
entrypoint_id,
} => {
Task::done(PluginTableMsgOut::SetEntrypointState {
PluginTableMsgOut::SetEntrypointState {
enabled,
plugin_id,
entrypoint_id,
})
}
}
}
}
PluginTableMsgIn::SelectItem(item) => Task::done(PluginTableMsgOut::SelectItem(item)),
PluginTableMsgIn::SelectItem(item) => PluginTableMsgOut::SelectItem(item),
PluginTableMsgIn::ToggleShowEntrypoints { plugin_id } => {
Task::done(PluginTableMsgOut::ToggleShowEntrypoints { plugin_id })
PluginTableMsgOut::ToggleShowEntrypoints { plugin_id }
}
PluginTableMsgIn::ToggleShowGeneratedEntrypoints {
plugin_id,
entrypoint_id,
} => {
Task::done(PluginTableMsgOut::ToggleShowGeneratedEntrypoints {
PluginTableMsgOut::ToggleShowGeneratedEntrypoints {
plugin_id,
entrypoint_id,
})
}
}
PluginTableMsgIn::ShortcutCaptured(plugin_id, entrypoint_id, shortcut) => {
Task::done(PluginTableMsgOut::ShortcutCaptured(plugin_id, entrypoint_id, shortcut))
PluginTableMsgOut::ShortcutCaptured(plugin_id, entrypoint_id, shortcut)
}
PluginTableMsgIn::AliasChanged(plugin_id, entrypoint_id, alias) => {
let alias = alias.trim().to_owned();
let alias = if alias.is_empty() { None } else { Some(alias) };
Task::done(PluginTableMsgOut::AliasChanged(plugin_id, entrypoint_id, alias))
PluginTableMsgOut::AliasChanged(plugin_id, entrypoint_id, alias)
}
}
}

View file

@ -16,7 +16,7 @@ pub mod hud;
#[cfg(target_os = "linux")]
pub mod x11_focus;
pub struct WindowState {
pub struct MainWindowState {
pub main_window_id: Option<window::Id>,
focused: bool,
#[cfg(target_os = "linux")]
@ -31,14 +31,14 @@ pub struct WindowState {
open_position: Position,
}
impl WindowState {
impl MainWindowState {
pub fn new(
window_position_file: Option<PathBuf>,
close_on_unfocus: bool,
window_position_mode: WindowPositionMode,
#[cfg(target_os = "linux")] wayland: bool,
#[cfg(target_os = "linux")] layer_shell: bool,
) -> WindowState {
) -> MainWindowState {
let open_position = window_position_file
.as_ref()
.map(|window_position_file| fs::read_to_string(window_position_file).ok())
@ -73,7 +73,7 @@ impl WindowState {
}
}
impl WindowState {
impl MainWindowState {
pub fn handle_action(&mut self, action: WindowActionMsg) -> Task<AppMsg> {
match action {
WindowActionMsg::SetWindowPositionMode { mode } => {

View file

@ -1,25 +1,5 @@
use serde::Deserialize;
use serde::Serialize;
pub mod cli;
pub mod detached_process;
pub mod dirs;
pub mod model;
pub mod rpc;
pub const SETTINGS_ENV: &'static str = "__GAUNTLET_INTERNAL_SETTINGS__";
#[derive(Debug, Deserialize, Serialize)]
#[serde(tag = "type")]
pub enum SettingsEnvData {
OpenPluginPreferences { plugin_id: String },
OpenEntrypointPreferences { plugin_id: String, entrypoint_id: String },
}
pub fn settings_env_data_to_string(data: SettingsEnvData) -> String {
serde_json::to_string(&data).expect("unable to serialize settings env data")
}
pub fn settings_env_data_from_string(data: String) -> SettingsEnvData {
serde_json::from_str(&data).expect("unable to serialize settings env data")
}

View file

@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::sync::Arc;
use gauntlet_utils::channel::RequestResult;
@ -7,15 +6,9 @@ use tokio::sync::Mutex;
use tonic::Request;
use tonic::transport::Channel;
use crate::model::DownloadStatus;
use crate::model::EntrypointId;
use crate::model::LocalSaveData;
use crate::model::PhysicalShortcut;
use crate::model::PluginId;
use crate::model::PluginPreferenceUserData;
use crate::model::SettingsPlugin;
use crate::model::SettingsTheme;
use crate::model::WindowPositionMode;
use crate::rpc::grpc::RpcBincode;
use crate::rpc::grpc::RpcSaveLocalPluginRequest;
use crate::rpc::grpc::rpc_backend_client::RpcBackendClient;
@ -42,69 +35,6 @@ pub trait BackendForToolsApi {
async fn save_local_plugin(&self, path: String) -> RequestResult<LocalSaveData>;
}
#[boundary_gen(bincode, grpc)]
#[tonic::async_trait]
pub trait BackendForSettingsApi {
async fn wayland_global_shortcuts_enabled(&self) -> RequestResult<bool>;
async fn plugins(&self) -> RequestResult<HashMap<PluginId, SettingsPlugin>>;
async fn set_plugin_state(&self, plugin_id: PluginId, enabled: bool) -> RequestResult<()>;
async fn set_entrypoint_state(
&self,
plugin_id: PluginId,
entrypoint_id: EntrypointId,
enabled: bool,
) -> RequestResult<()>;
async fn set_global_shortcut(&self, shortcut: Option<PhysicalShortcut>) -> RequestResult<Option<String>>;
async fn get_global_shortcut(&self) -> RequestResult<(Option<PhysicalShortcut>, Option<String>)>;
async fn set_global_entrypoint_shortcut(
&self,
plugin_id: PluginId,
entrypoint_id: EntrypointId,
shortcut: Option<PhysicalShortcut>,
) -> RequestResult<()>;
async fn get_global_entrypoint_shortcuts(
&self,
) -> RequestResult<HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option<String>)>>;
async fn set_entrypoint_search_alias(
&self,
plugin_id: PluginId,
entrypoint_id: EntrypointId,
alias: Option<String>,
) -> RequestResult<()>;
async fn get_entrypoint_search_aliases(&self) -> RequestResult<HashMap<(PluginId, EntrypointId), String>>;
async fn set_theme(&self, theme: SettingsTheme) -> RequestResult<()>;
async fn get_theme(&self) -> RequestResult<SettingsTheme>;
async fn set_window_position_mode(&self, mode: WindowPositionMode) -> RequestResult<()>;
async fn get_window_position_mode(&self) -> RequestResult<WindowPositionMode>;
async fn set_preference_value(
&self,
plugin_id: PluginId,
entrypoint_id: Option<EntrypointId>,
preference_id: String,
preference_value: PluginPreferenceUserData,
) -> RequestResult<()>;
async fn download_plugin(&self, plugin_id: PluginId) -> RequestResult<()>;
async fn download_status(&self) -> RequestResult<HashMap<PluginId, DownloadStatus>>;
async fn remove_plugin(&self, plugin_id: PluginId) -> RequestResult<()>;
}
#[derive(Debug, Clone)]
pub struct GrpcBackendApi {
client: Arc<Mutex<RpcBackendClient<Channel>>>,
@ -117,20 +47,6 @@ impl GrpcBackendApi {
})
}
pub async fn backend_for_settings_api(&self, bytes: Vec<u8>) -> RequestResult<Vec<u8>> {
let request = RpcBincode { data: bytes };
let mut client = self.client.lock().await;
let response = client
.backend_for_settings_api(Request::new(request))
.await?
.into_inner()
.data;
Ok(response)
}
pub async fn backend_for_cli_api(&self, bytes: Vec<u8>) -> RequestResult<Vec<u8>> {
let request = RpcBincode { data: bytes };

View file

@ -8,10 +8,8 @@ use tonic::Status;
use tonic::transport::Server;
use crate::rpc::backend_api::BackendForCliApi;
use crate::rpc::backend_api::BackendForSettingsApi;
use crate::rpc::backend_api::BackendForToolsApi;
use crate::rpc::backend_api::handle_grpc_request_backend_for_cli_api;
use crate::rpc::backend_api::handle_grpc_request_backend_for_settings_api;
use crate::rpc::grpc::RpcBincode;
use crate::rpc::grpc::RpcSaveLocalPluginRequest;
use crate::rpc::grpc::RpcSaveLocalPluginResponse;
@ -33,12 +31,11 @@ pub async fn wait_for_backend_server() {
pub async fn start_backend_server(
cli: Box<dyn BackendForCliApi + Sync + Send>,
tools: Box<dyn BackendForToolsApi + Sync + Send>,
settings: Box<dyn BackendForSettingsApi + Sync + Send>,
) {
let addr = "127.0.0.1:42320".parse().unwrap();
Server::builder()
.add_service(RpcBackendServer::new(RpcBackendServerImpl::new(cli, tools, settings)))
.add_service(RpcBackendServer::new(RpcBackendServerImpl::new(cli, tools)))
.serve(addr)
.await
.expect("unable to start backend server");
@ -47,16 +44,11 @@ pub async fn start_backend_server(
struct RpcBackendServerImpl {
cli: Box<dyn BackendForCliApi + Sync + Send>,
tools: Box<dyn BackendForToolsApi + Sync + Send>,
settings: Box<dyn BackendForSettingsApi + Sync + Send>,
}
impl RpcBackendServerImpl {
pub fn new(
cli: Box<dyn BackendForCliApi + Sync + Send>,
tools: Box<dyn BackendForToolsApi + Sync + Send>,
settings: Box<dyn BackendForSettingsApi + Sync + Send>,
) -> Self {
Self { cli, settings, tools }
pub fn new(cli: Box<dyn BackendForCliApi + Sync + Send>, tools: Box<dyn BackendForToolsApi + Sync + Send>) -> Self {
Self { cli, tools }
}
}
@ -70,14 +62,6 @@ impl RpcBackend for RpcBackendServerImpl {
Ok(Response::new(RpcBincode { data: encoded }))
}
async fn backend_for_settings_api(&self, request: Request<RpcBincode>) -> Result<Response<RpcBincode>, Status> {
let data = request.into_inner().data;
let encoded = handle_grpc_request_backend_for_settings_api(self.settings.as_ref(), data).await?;
Ok(Response::new(RpcBincode { data: encoded }))
}
async fn save_local_plugin(
&self,
request: Request<RpcSaveLocalPluginRequest>,

View file

@ -32,6 +32,8 @@ pub trait FrontendApi {
async fn hide_window(&self) -> RequestResult<()>;
async fn show_settings(&self) -> RequestResult<()>;
async fn show_preference_required_view(
&self,
plugin_id: PluginId,

View file

@ -1,17 +1,9 @@
use std::collections::HashMap;
use gauntlet_utils::channel::RequestResult;
use gauntlet_utils_macros::boundary_gen;
use crate::model::DownloadStatus;
use crate::model::EntrypointId;
use crate::model::LocalSaveData;
use crate::model::PhysicalShortcut;
use crate::model::PluginId;
use crate::model::PluginPreferenceUserData;
use crate::model::SettingsPlugin;
use crate::model::SettingsTheme;
use crate::model::WindowPositionMode;
#[allow(async_fn_in_trait)]
#[boundary_gen(in_process)]
@ -28,63 +20,4 @@ pub trait ServerGrpcApi {
) -> RequestResult<()>;
async fn save_local_plugin(&self, path: String) -> RequestResult<LocalSaveData>;
async fn plugins(&self) -> RequestResult<HashMap<PluginId, SettingsPlugin>>;
async fn set_plugin_state(&self, plugin_id: PluginId, enabled: bool) -> RequestResult<()>;
async fn set_entrypoint_state(
&self,
plugin_id: PluginId,
entrypoint_id: EntrypointId,
enabled: bool,
) -> RequestResult<()>;
async fn set_global_shortcut(&self, shortcut: Option<PhysicalShortcut>) -> RequestResult<Option<String>>;
async fn get_global_shortcut(&self) -> RequestResult<Option<(PhysicalShortcut, Option<String>)>>;
async fn set_global_entrypoint_shortcut(
&self,
plugin_id: PluginId,
entrypoint_id: EntrypointId,
shortcut: Option<PhysicalShortcut>,
) -> RequestResult<()>;
async fn get_global_entrypoint_shortcuts(
&self,
) -> RequestResult<HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option<String>)>>;
async fn set_entrypoint_search_alias(
&self,
plugin_id: PluginId,
entrypoint_id: EntrypointId,
alias: Option<String>,
) -> RequestResult<()>;
async fn get_entrypoint_search_aliases(&self) -> RequestResult<HashMap<(PluginId, EntrypointId), String>>;
async fn set_theme(&self, theme: SettingsTheme) -> RequestResult<()>;
async fn get_theme(&self) -> RequestResult<SettingsTheme>;
async fn set_window_position_mode(&self, mode: WindowPositionMode) -> RequestResult<()>;
async fn get_window_position_mode(&self) -> RequestResult<WindowPositionMode>;
async fn set_preference_value(
&self,
plugin_id: PluginId,
entrypoint_id: Option<EntrypointId>,
preference_id: String,
preference_value: PluginPreferenceUserData,
) -> RequestResult<()>;
async fn download_plugin(&self, plugin_id: PluginId) -> RequestResult<()>;
async fn download_status(&self) -> RequestResult<HashMap<PluginId, DownloadStatus>>;
async fn remove_plugin(&self, plugin_id: PluginId) -> RequestResult<()>;
async fn wayland_global_shortcuts_enabled(&self) -> RequestResult<bool>;
}

View file

@ -35,6 +35,7 @@ pub trait BackendForPluginRuntimeApi {
async fn ui_update_loading_bar(&self, entrypoint_id: EntrypointId, show: bool) -> RequestResult<()>;
async fn ui_show_hud(&self, display: String) -> RequestResult<()>;
async fn ui_hide_window(&self) -> RequestResult<()>;
async fn ui_show_settings(&self) -> RequestResult<()>;
async fn ui_get_action_id_for_shortcut(
&self,
entrypoint_id: EntrypointId,

View file

@ -1,17 +0,0 @@
[package]
name = "gauntlet-management-client"
edition.workspace = true
[dependencies]
# workspaces
gauntlet-common.workspace = true
gauntlet-common-ui.workspace = true
gauntlet-utils.workspace = true
# shared
anyhow.workspace = true
iced.workspace = true
iced_fonts.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
itertools.workspace = true

View file

@ -1,8 +0,0 @@
mod components;
mod theme;
mod ui;
mod views;
pub fn start_management_client() {
ui::run();
}

View file

@ -1,5 +0,0 @@
fn main() {
tracing_subscriber::fmt::init();
gauntlet_management_client::start_management_client();
}

View file

@ -1,17 +1,22 @@
use anyhow::anyhow;
use std::cell::RefCell;
use std::rc::Rc;
use deno_core::OpState;
use deno_core::op2;
use gauntlet_common::detached_process::CommandExt;
use gauntlet_common_plugin_runtime::api::BackendForPluginRuntimeApi;
use gauntlet_common_plugin_runtime::api::BackendForPluginRuntimeApiProxy;
use crate::deno::GauntletJsError;
#[op2(fast)]
pub fn open_settings() -> Result<(), GauntletJsError> {
let current_exe = std::env::current_exe().map_err(|err| anyhow!(err))?;
#[op2(async)]
pub async fn open_settings(state: Rc<RefCell<OpState>>) -> Result<(), GauntletJsError> {
let api = {
let state = state.borrow();
std::process::Command::new(current_exe)
.args(["settings"])
.spawn_detached()
.map_err(|err| anyhow!(err))?;
let api = state.borrow::<BackendForPluginRuntimeApiProxy>().clone();
Ok(())
api
};
api.ui_show_settings().await.map_err(Into::into)
}

View file

@ -412,7 +412,6 @@ impl DataDbRepository {
}
pub fn list_plugins_and_entrypoints(&self) -> anyhow::Result<Vec<(DbReadPlugin, Vec<DbReadPluginEntrypoint>)>> {
// language=SQLite
let plugins = self.list_plugins()?;
let result = plugins

View file

@ -961,6 +961,12 @@ impl BackendForPluginRuntimeApi for BackendForPluginRuntimeApiImpl {
Ok(())
}
async fn ui_show_settings(&self) -> RequestResult<()> {
self.frontend_api.show_settings().await?;
Ok(())
}
async fn ui_get_action_id_for_shortcut(
&self,
entrypoint_id: EntrypointId,

View file

@ -56,7 +56,7 @@ impl PluginLoader {
self.download_status_holder.download_status()
}
pub fn download_plugin(&self, plugin_id: PluginId) -> anyhow::Result<()> {
pub fn download_plugin(&self, plugin_id: PluginId) {
let download_status_guard = self.download_status_holder.download_started(plugin_id.clone());
let data_db_repository = self.db_repository.clone();
@ -104,8 +104,6 @@ impl PluginLoader {
})
})
.expect("failed to spawn thread");
Ok(())
}
pub fn save_local_plugin(&self, path: &str) -> anyhow::Result<PluginId> {

View file

@ -1,9 +1,6 @@
use std::collections::HashMap;
use anyhow::anyhow;
use gauntlet_common::SETTINGS_ENV;
use gauntlet_common::SettingsEnvData;
use gauntlet_common::detached_process::CommandExt;
use gauntlet_common::dirs::Dirs;
use gauntlet_common::model::DownloadStatus;
use gauntlet_common::model::EntrypointId;
@ -30,7 +27,6 @@ use gauntlet_common::rpc::frontend_api::FrontendApi;
use gauntlet_common::rpc::frontend_api::FrontendApiProxy;
use gauntlet_common::rpc::frontend_api::FrontendApiRequestData;
use gauntlet_common::rpc::frontend_api::FrontendApiResponseData;
use gauntlet_common::settings_env_data_to_string;
use gauntlet_common_plugin_runtime::model::JsPluginCode;
use gauntlet_common_plugin_runtime::model::JsPluginPermissionsExec;
use gauntlet_common_plugin_runtime::model::JsPluginPermissionsFileSystem;
@ -174,7 +170,7 @@ impl ApplicationManager {
})
}
pub fn download_plugin(&self, plugin_id: PluginId) -> anyhow::Result<()> {
pub fn download_plugin(&self, plugin_id: PluginId) {
self.plugin_downloader.download_plugin(plugin_id)
}
@ -571,7 +567,7 @@ impl ApplicationManager {
.set_global_entrypoint_shortcut(global_hotkey_manager, plugin_id, entrypoint_id, shortcut)
}
pub fn get_global_entrypoint_shortcut(
pub fn get_global_entrypoint_shortcuts(
&self,
) -> anyhow::Result<HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option<String>)>> {
self.settings.global_entrypoint_shortcuts()
@ -812,36 +808,6 @@ impl ApplicationManager {
.expect("failed to toggle window");
}
pub fn open_settings_window(&self) {
let current_exe = std::env::current_exe().expect("unable to get current_exe");
std::process::Command::new(current_exe)
.args(["settings"])
.spawn_detached()
.expect("failed to execute settings process");
}
pub fn open_settings_window_preferences(&self, plugin_id: PluginId, entrypoint_id: Option<EntrypointId>) {
let data = if let Some(entrypoint_id) = entrypoint_id {
SettingsEnvData::OpenEntrypointPreferences {
plugin_id: plugin_id.to_string(),
entrypoint_id: entrypoint_id.to_string(),
}
} else {
SettingsEnvData::OpenPluginPreferences {
plugin_id: plugin_id.to_string(),
}
};
let current_exe = std::env::current_exe().expect("unable to get current_exe");
std::process::Command::new(current_exe)
.args(["settings"])
.env(SETTINGS_ENV, settings_env_data_to_string(data))
.spawn_detached()
.expect("failed to execute settings process"); // this can fail in dev if binary was replaced by more recent compilation
}
fn reload_plugin(&self, plugin_id: PluginId) -> anyhow::Result<()> {
tracing::info!(target = "plugin", "Reloading plugin with id: {:?}", plugin_id);

View file

@ -1,16 +1,7 @@
use std::collections::HashMap;
use gauntlet_common::model::DownloadStatus;
use gauntlet_common::model::EntrypointId;
use gauntlet_common::model::LocalSaveData;
use gauntlet_common::model::PhysicalShortcut;
use gauntlet_common::model::PluginId;
use gauntlet_common::model::PluginPreferenceUserData;
use gauntlet_common::model::SettingsPlugin;
use gauntlet_common::model::SettingsTheme;
use gauntlet_common::model::WindowPositionMode;
use gauntlet_common::rpc::backend_api::BackendForCliApi;
use gauntlet_common::rpc::backend_api::BackendForSettingsApi;
use gauntlet_common::rpc::backend_api::BackendForToolsApi;
use gauntlet_common::rpc::backend_server::start_backend_server;
use gauntlet_common::rpc::server_grpc_api::ServerGrpcApi;
@ -33,7 +24,6 @@ pub async fn run_grpc_server(grpc_api: ServerGrpcApiProxy) {
start_backend_server(
Box::new(BackendServerImpl::new(grpc_api.clone())),
Box::new(BackendServerImpl::new(grpc_api.clone())),
Box::new(BackendServerImpl::new(grpc_api.clone())),
)
.await
}
@ -77,197 +67,3 @@ impl BackendForToolsApi for BackendServerImpl {
Ok(result)
}
}
#[tonic::async_trait]
impl BackendForSettingsApi for BackendServerImpl {
async fn wayland_global_shortcuts_enabled(&self) -> RequestResult<bool> {
self.proxy.wayland_global_shortcuts_enabled().await
}
async fn plugins(&self) -> RequestResult<HashMap<PluginId, SettingsPlugin>> {
self.proxy.plugins().await
}
async fn set_plugin_state(&self, plugin_id: PluginId, enabled: bool) -> RequestResult<()> {
let result = self.proxy.set_plugin_state(plugin_id, enabled).await;
if let Err(err) = &result {
tracing::warn!(
target = "rpc",
"error occurred when handling 'set_plugin_state' request {:?}",
err
)
}
Ok(())
}
async fn set_entrypoint_state(
&self,
plugin_id: PluginId,
entrypoint_id: EntrypointId,
enabled: bool,
) -> RequestResult<()> {
let result = self.proxy.set_entrypoint_state(plugin_id, entrypoint_id, enabled).await;
if let Err(err) = &result {
tracing::warn!(
target = "rpc",
"error occurred when handling 'set_entrypoint_state' request {:?}",
err
)
}
Ok(())
}
async fn set_global_shortcut(&self, shortcut: Option<PhysicalShortcut>) -> RequestResult<Option<String>> {
let result = self.proxy.set_global_shortcut(shortcut).await;
if let Err(err) = &result {
tracing::warn!(
target = "rpc",
"error occurred when handling 'set_global_shortcut' request {:?}",
err
)
}
result
}
async fn get_global_shortcut(&self) -> RequestResult<(Option<PhysicalShortcut>, Option<String>)> {
let result = self
.proxy
.get_global_shortcut()
.await?
.map(|(shortcut, error)| (Some(shortcut), error))
.unwrap_or((None, None));
Ok(result)
}
async fn set_global_entrypoint_shortcut(
&self,
plugin_id: PluginId,
entrypoint_id: EntrypointId,
shortcut: Option<PhysicalShortcut>,
) -> RequestResult<()> {
let result = self
.proxy
.set_global_entrypoint_shortcut(plugin_id, entrypoint_id, shortcut)
.await;
if let Err(err) = &result {
tracing::warn!(
target = "rpc",
"error occurred when handling 'set_global_entrypoint_shortcut' request {:?}",
err
)
}
result
}
async fn get_global_entrypoint_shortcuts(
&self,
) -> RequestResult<HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option<String>)>> {
self.proxy.get_global_entrypoint_shortcuts().await
}
async fn set_entrypoint_search_alias(
&self,
plugin_id: PluginId,
entrypoint_id: EntrypointId,
alias: Option<String>,
) -> RequestResult<()> {
let result = self
.proxy
.set_entrypoint_search_alias(plugin_id, entrypoint_id, alias)
.await;
if let Err(err) = &result {
tracing::warn!(
target = "rpc",
"error occurred when handling 'set_entrypoint_search_alias' request {:?}",
err
)
}
result
}
async fn get_entrypoint_search_aliases(&self) -> RequestResult<HashMap<(PluginId, EntrypointId), String>> {
self.proxy.get_entrypoint_search_aliases().await
}
async fn set_theme(&self, theme: SettingsTheme) -> RequestResult<()> {
self.proxy.set_theme(theme).await
}
async fn get_theme(&self) -> RequestResult<SettingsTheme> {
self.proxy.get_theme().await
}
async fn set_window_position_mode(&self, mode: WindowPositionMode) -> RequestResult<()> {
self.proxy.set_window_position_mode(mode).await
}
async fn get_window_position_mode(&self) -> RequestResult<WindowPositionMode> {
self.proxy.get_window_position_mode().await
}
async fn set_preference_value(
&self,
plugin_id: PluginId,
entrypoint_id: Option<EntrypointId>,
preference_id: String,
preference_value: PluginPreferenceUserData,
) -> RequestResult<()> {
let result = self
.proxy
.set_preference_value(plugin_id, entrypoint_id, preference_id, preference_value)
.await;
if let Err(err) = &result {
tracing::warn!(
target = "rpc",
"error occurred when handling 'set_preference_value' request {:?}",
err
)
}
Ok(())
}
async fn download_plugin(&self, plugin_id: PluginId) -> RequestResult<()> {
let result = self.proxy.download_plugin(plugin_id).await;
if let Err(err) = &result {
tracing::warn!(
target = "rpc",
"error occurred when handling 'download_plugin' request {:?}",
err
)
}
Ok(())
}
async fn download_status(&self) -> RequestResult<HashMap<PluginId, DownloadStatus>> {
self.proxy.download_status().await
}
async fn remove_plugin(&self, plugin_id: PluginId) -> RequestResult<()> {
let result = self.proxy.remove_plugin(plugin_id).await;
if let Err(err) = &result {
tracing::warn!(
target = "rpc",
"error occurred when handling 'remove_plugin' request {:?}",
err
)
}
Ok(())
}
}

View file

@ -4,9 +4,6 @@ service RpcBackend {
// cli
rpc BackendForCliApi(RpcBincode) returns (RpcBincode);
// settings
rpc BackendForSettingsApi(RpcBincode) returns (RpcBincode);
// dev tools, screenshot gen
rpc SaveLocalPlugin (RpcSaveLocalPluginRequest) returns (RpcSaveLocalPluginResponse);
}