use crate::dbus::{self, PowerDaemonProxy}; use crate::graphics::{get_current_graphics, set_graphics, Graphics}; use cosmic::applet::CosmicAppletHelper; use cosmic::iced::wayland::popup::{destroy_popup, get_popup}; use cosmic::iced::wayland::SurfaceIdWrapper; use cosmic::iced_native::alignment::Horizontal; use cosmic::iced_style::application::{self, Appearance}; use cosmic::iced_style::Color; use cosmic::theme::Button; use cosmic::{ applet::cosmic_panel_config::{PanelAnchor, PanelSize}, iced::widget::{column, radio, text}, iced::{self, Application, Command, Length}, iced_native::window, theme::Theme, widget::horizontal_rule, Element, }; use zbus::Connection; #[derive(Clone, Copy)] enum State { SelectGraphicsMode(bool), SettingGraphicsMode(Graphics), } #[derive(Clone, Copy)] enum GraphicsMode { SelectedGraphicsMode(Graphics), CurrentGraphicsMode(Graphics), } impl GraphicsMode { fn inner(&self) -> Graphics { match self { GraphicsMode::SelectedGraphicsMode(g) => *g, GraphicsMode::CurrentGraphicsMode(g) => *g, } } } impl Default for State { fn default() -> Self { Self::SelectGraphicsMode(false) } } #[derive(Default)] pub struct Window { popup: Option, graphics_mode: Option, id_ctr: u32, icon_size: u16, anchor: PanelAnchor, theme: Theme, dbus: Option<(Connection, PowerDaemonProxy<'static>)>, state: State, applet_helper: CosmicAppletHelper, } #[allow(dead_code)] #[derive(Clone, Debug)] pub enum Message { CurrentGraphics(Option), SelectedGraphicsMode(Option), DBusInit(Option<(Connection, PowerDaemonProxy<'static>)>), SelectGraphicsMode(Graphics), TogglePopup, PopupClosed(window::Id), } impl Application for Window { type Executor = iced::executor::Default; type Flags = (); type Message = Message; type Theme = Theme; fn new(_flags: ()) -> (Self, Command) { let mut window = Window::default(); let pixels = std::env::var("COSMIC_PANEL_SIZE") .ok() .map(|size| match size.parse::() { Ok(PanelSize::XL) => 64, Ok(PanelSize::L) => 36, Ok(PanelSize::M) => 24, Ok(PanelSize::S) => 16, Ok(PanelSize::XS) => 12, Err(_) => 12, }) .unwrap_or(16); window.icon_size = pixels; window.anchor = std::env::var("COSMIC_PANEL_ANCHOR") .ok() .map(|size| match size.parse::() { Ok(p) => p, Err(_) => PanelAnchor::Top, }) .unwrap_or(PanelAnchor::Top); (window, Command::perform(dbus::init(), Message::DBusInit)) } fn title(&self) -> String { String::from("Cosmic Graphics Applet") } fn update(&mut self, message: Message) -> iced::Command { match message { Message::SelectGraphicsMode(new_graphics_mode) => { if let Some((_, proxy)) = self.dbus.as_ref() { self.state = State::SettingGraphicsMode(new_graphics_mode); return Command::perform( set_graphics(proxy.clone(), new_graphics_mode), move |success| { Message::SelectedGraphicsMode(success.ok().map(|_| new_graphics_mode)) }, ); } } Message::SelectedGraphicsMode(g) => { if let Some(g) = g { self.graphics_mode .replace(GraphicsMode::SelectedGraphicsMode(g)); self.state = State::SelectGraphicsMode(true); } } Message::TogglePopup => { if let Some(p) = self.popup.take() { return destroy_popup(p); } else { self.id_ctr += 1; let new_id = window::Id::new(self.id_ctr); self.popup.replace(new_id); let mut commands = Vec::new(); if let Some((_, proxy)) = self.dbus.as_ref() { commands.push(Command::perform( get_current_graphics(proxy.clone()), |cur_graphics| Message::CurrentGraphics(cur_graphics.ok()), )); } let popup_settings = self.applet_helper.get_popup_settings( window::Id::new(0), new_id, None, None, None, ); commands.push(get_popup(popup_settings)); return Command::batch(commands); } } Message::DBusInit(dbus) => { self.dbus = dbus; return Command::perform( get_current_graphics(self.dbus.as_ref().unwrap().1.clone()), |cur_graphics| { Message::CurrentGraphics(match cur_graphics { Ok(g) => Some(g), Err(err) => { dbg!(err); None } }) }, ); } Message::CurrentGraphics(g) => { if let Some(g) = g { self.graphics_mode = Some(match self.graphics_mode.take() { Some(GraphicsMode::CurrentGraphicsMode(_)) | None => { GraphicsMode::CurrentGraphicsMode(g) } Some(g) => g, }); } } Message::PopupClosed(id) => { if self.popup.as_ref() == Some(&id) { self.popup = None; } } } Command::none() } fn view(&self, id: SurfaceIdWrapper) -> Element { match id { SurfaceIdWrapper::LayerSurface(_) => unimplemented!(), SurfaceIdWrapper::Window(_) => self .applet_helper .icon_button("input-gaming-symbolic") .on_press(Message::TogglePopup) .style(Button::Text) .into(), SurfaceIdWrapper::Popup(_) => { let content = match self.state { State::SelectGraphicsMode(pending_restart) => { let mut content_list = vec![ radio( "Integrated Graphics", Graphics::Integrated, self.graphics_mode.map(|g| g.inner()), Message::SelectGraphicsMode, ) .into(), radio( "Nvidia Graphics", Graphics::Nvidia, self.graphics_mode.map(|g| g.inner()), Message::SelectGraphicsMode, ) .into(), radio( "Hybrid Graphics", Graphics::Hybrid, self.graphics_mode.map(|g| g.inner()), Message::SelectGraphicsMode, ) .into(), radio( "Compute Graphics", Graphics::Compute, self.graphics_mode.map(|g| g.inner()), Message::SelectGraphicsMode, ) .into(), ]; if pending_restart { content_list.insert( 0, text("Restart to apply changes") .width(Length::Fill) .horizontal_alignment(Horizontal::Center) .size(16) .into(), ) } column(content_list).padding([8, 0]).spacing(8).into() } State::SettingGraphicsMode(graphics) => { let graphics_str = match graphics { Graphics::Integrated => "integrated", Graphics::Hybrid => "hybrid", Graphics::Nvidia => "nvidia", Graphics::Compute => "compute", }; column(vec![text(format!( "Setting graphics mode to {graphics_str}..." )) .width(Length::Fill) .horizontal_alignment(Horizontal::Center) .into()]) .into() } }; self.applet_helper .popup_container( column(vec![ text("Graphics Mode") .width(Length::Fill) .horizontal_alignment(Horizontal::Center) .size(24) .into(), horizontal_rule(1).into(), content, ]) .padding(8) .spacing(4), ) .into() } } } fn close_requested(&self, id: SurfaceIdWrapper) -> Self::Message { match id { SurfaceIdWrapper::LayerSurface(_) | SurfaceIdWrapper::Window(_) => unimplemented!(), SurfaceIdWrapper::Popup(id) => Message::PopupClosed(id), } } fn style(&self) -> ::Style { ::Style::Custom(|theme| Appearance { background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0), text_color: theme.cosmic().on_bg_color().into(), }) } fn should_exit(&self) -> bool { false } fn theme(&self) -> Theme { self.theme } }