Iced graphics applet (#26)
* feat: builds deb * feat: working iced graphics applet * fix: update deps to get fixes for iced-sctk * fix: better popup size * fix: better styling of button & popup * fix: better popup container * feat: sticky graphics mode after selection * fix: refactor & handle close requests
This commit is contained in:
parent
e3be1c1809
commit
eab7ddea1d
15 changed files with 3836 additions and 360 deletions
53
Cargo.lock
generated
53
Cargo.lock
generated
|
|
@ -384,20 +384,6 @@ dependencies = [
|
|||
"zbus 2.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmic-applet-graphics"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"gtk4",
|
||||
"libadwaita",
|
||||
"libcosmic",
|
||||
"libcosmic-applet",
|
||||
"once_cell",
|
||||
"relm4-macros",
|
||||
"tokio",
|
||||
"zbus 2.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmic-applet-network"
|
||||
version = "0.1.0"
|
||||
|
|
@ -1512,6 +1498,16 @@ dependencies = [
|
|||
"unic-langid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.0-rc1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f65dae1d3aa98e6877917ab4e6fdbfdfb00e95885ea7c4f4f29e3a5dfc08fdf"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.3"
|
||||
|
|
@ -2914,12 +2910,13 @@ checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
|
|||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.1.0-beta.9"
|
||||
source = "git+https://github.com/smithay/wayland-rs#fa4be85e8f3b57c8b6e8dd2476d6b4734b810e59"
|
||||
version = "0.1.0-beta.13"
|
||||
source = "git+https://github.com/smithay/wayland-rs#051cbf6e38f4532c74ab5fc9a8d39ed84e1f2e04"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"nix 0.24.2",
|
||||
"io-lifetimes",
|
||||
"nix 0.25.0",
|
||||
"scoped-tls",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
|
|
@ -2927,13 +2924,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.30.0-beta.9"
|
||||
source = "git+https://github.com/smithay/wayland-rs#fa4be85e8f3b57c8b6e8dd2476d6b4734b810e59"
|
||||
version = "0.30.0-beta.13"
|
||||
source = "git+https://github.com/smithay/wayland-rs#051cbf6e38f4532c74ab5fc9a8d39ed84e1f2e04"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"nix 0.24.2",
|
||||
"nix 0.25.0",
|
||||
"thiserror",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
|
|
@ -2941,8 +2938,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.30.0-beta.9"
|
||||
source = "git+https://github.com/smithay/wayland-rs#fa4be85e8f3b57c8b6e8dd2476d6b4734b810e59"
|
||||
version = "0.30.0-beta.13"
|
||||
source = "git+https://github.com/smithay/wayland-rs#051cbf6e38f4532c74ab5fc9a8d39ed84e1f2e04"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"wayland-backend",
|
||||
|
|
@ -2952,8 +2949,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wayland-protocols-wlr"
|
||||
version = "0.1.0-beta.9"
|
||||
source = "git+https://github.com/smithay/wayland-rs#fa4be85e8f3b57c8b6e8dd2476d6b4734b810e59"
|
||||
version = "0.1.0-beta.13"
|
||||
source = "git+https://github.com/smithay/wayland-rs#051cbf6e38f4532c74ab5fc9a8d39ed84e1f2e04"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"wayland-backend",
|
||||
|
|
@ -2964,8 +2961,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.30.0-beta.9"
|
||||
source = "git+https://github.com/smithay/wayland-rs#fa4be85e8f3b57c8b6e8dd2476d6b4734b810e59"
|
||||
version = "0.30.0-beta.13"
|
||||
source = "git+https://github.com/smithay/wayland-rs#051cbf6e38f4532c74ab5fc9a8d39ed84e1f2e04"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
|
|
@ -2975,8 +2972,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.30.0-beta.9"
|
||||
source = "git+https://github.com/smithay/wayland-rs#fa4be85e8f3b57c8b6e8dd2476d6b4734b810e59"
|
||||
version = "0.30.0-beta.13"
|
||||
source = "git+https://github.com/smithay/wayland-rs#051cbf6e38f4532c74ab5fc9a8d39ed84e1f2e04"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"log",
|
||||
|
|
|
|||
14
Cargo.toml
14
Cargo.toml
|
|
@ -2,7 +2,6 @@
|
|||
members = [
|
||||
"applets/cosmic-applet-audio",
|
||||
"applets/cosmic-applet-battery",
|
||||
"applets/cosmic-applet-graphics",
|
||||
"applets/cosmic-applet-network",
|
||||
"applets/cosmic-applet-notifications",
|
||||
"applets/cosmic-applet-power",
|
||||
|
|
@ -13,11 +12,14 @@ members = [
|
|||
"applets/cosmic-panel-button",
|
||||
"libcosmic-applet",
|
||||
]
|
||||
exclude = [
|
||||
"applets/cosmic-applet-graphics",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
wayland-protocols = { git = "https://github.com/smithay/wayland-rs", version = "0.30.0-beta.9"}
|
||||
wayland-protocols = { git = "https://github.com/smithay/wayland-rs", version = "0.30.0-beta.13"}
|
||||
wayland-protocols-wlr = { git = "https://github.com/smithay/wayland-rs", version = "0.1.0-beta.9"}
|
||||
wayland-sys = { git = "https://github.com/smithay/wayland-rs", version = "0.30.0-beta.9"}
|
||||
wayland-backend = { git = "https://github.com/smithay/wayland-rs", version = "0.1.0-beta.9"}
|
||||
wayland-scanner = { git = "https://github.com/smithay/wayland-rs", version = "0.30.0-beta.9"}
|
||||
wayland-client = { git = "https://github.com/smithay/wayland-rs", version = "0.30.0-beta.9"}
|
||||
wayland-sys = { git = "https://github.com/smithay/wayland-rs", version = "0.30.0-beta.13"}
|
||||
wayland-backend = { git = "https://github.com/smithay/wayland-rs", version = "0.1.0-beta.13"}
|
||||
wayland-scanner = { git = "https://github.com/smithay/wayland-rs", version = "0.30.0-beta.13"}
|
||||
wayland-client = { git = "https://github.com/smithay/wayland-rs", version = "0.30.0-beta.13"}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Utilities for using an [`EventQueue`] from wayland-client with an event loop that performs polling with
|
||||
//! [`calloop`](https://crates.io/crates/calloop).
|
||||
|
||||
use std::{io, os::unix::prelude::RawFd};
|
||||
use std::{io, os::unix::prelude::{RawFd, AsRawFd}};
|
||||
|
||||
use calloop::{
|
||||
generic::Generic, EventSource, InsertError, Interest, LoopHandle, Mode, Poll, PostAction,
|
||||
|
|
@ -30,7 +30,7 @@ impl<D> WaylandSource<D> {
|
|||
/// Wrap an [`EventQueue`] as a [`WaylandSource`].
|
||||
pub fn new(queue: EventQueue<D>) -> Result<WaylandSource<D>, WaylandError> {
|
||||
let guard = queue.prepare_read()?;
|
||||
let fd = Generic::new(guard.connection_fd(), Interest::READ, Mode::Level);
|
||||
let fd = Generic::new(guard.connection_fd().as_raw_fd(), Interest::READ, Mode::Level);
|
||||
drop(guard);
|
||||
|
||||
Ok(WaylandSource {
|
||||
|
|
@ -192,12 +192,12 @@ impl<D> WaylandSource<D> {
|
|||
break Err(Errno::EPROTO.into());
|
||||
}
|
||||
|
||||
Err(DispatchError::BadMessage { msg, interface }) => {
|
||||
Err(DispatchError::BadMessage { sender_id, interface, opcode }) => {
|
||||
log::error!(
|
||||
"Bad message on interface \"{}\": (opcode: {}, args: {:?})",
|
||||
"Bad message on interface \"{}\": (opcode: {}, sender_id: {:?})",
|
||||
interface,
|
||||
msg.opcode,
|
||||
msg.args,
|
||||
opcode,
|
||||
sender_id,
|
||||
);
|
||||
|
||||
break Err(Errno::EPROTO.into());
|
||||
|
|
|
|||
3426
applets/cosmic-applet-graphics/Cargo.lock
generated
Normal file
3426
applets/cosmic-applet-graphics/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -6,11 +6,10 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
gtk4 = { git = "https://github.com/gtk-rs/gtk4-rs", features = ["v4_2"] }
|
||||
adw = { git = "https://gitlab.gnome.org/World/Rust/libadwaita-rs", package = "libadwaita"}
|
||||
libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = false }
|
||||
libcosmic-applet = { path = "../../libcosmic-applet" }
|
||||
once_cell = "1.9.0"
|
||||
relm4-macros = { git = "https://github.com/Relm4/Relm4.git", branch = "next" }
|
||||
tokio = { version = "1.16.1", features = ["full"] }
|
||||
zbus = "2.1.1"
|
||||
zbus = "3.4"
|
||||
libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "sctk-cosmic-design-system", default-features = false, features = ["wayland", "applet"] }
|
||||
cosmic-panel-config = {git = "https://github.com/pop-os/cosmic-panel", default-features = false }
|
||||
iced_sctk = { git = "https://github.com/pop-os/iced-sctk" }
|
||||
# iced_sctk = { path = "../../../fork/iced-sctk" }
|
||||
sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit" }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//! # DBus interface proxy for: `com.system76.PowerDaemon`
|
||||
//!
|
||||
//! This code was generated by `zbus-xmlgen` `2.0.1` from DBus introspection data.
|
||||
//! This code was generated by `zbus-xmlgen` `3.0.0` from DBus introspection data.
|
||||
//! Source: `Interface '/com/system76/PowerDaemon' from service 'com.system76.PowerDaemon' on system bus`.
|
||||
//!
|
||||
//! You may prefer to adapt it, instead of using it verbatim.
|
||||
|
|
@ -18,11 +18,10 @@
|
|||
//!
|
||||
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
|
||||
|
||||
use zbus::dbus_proxy;
|
||||
use zbus::{dbus_proxy, Connection};
|
||||
|
||||
#[dbus_proxy(
|
||||
interface = "com.system76.PowerDaemon",
|
||||
default_service = "com.system76.PowerDaemon",
|
||||
default_path = "/com/system76/PowerDaemon"
|
||||
)]
|
||||
trait PowerDaemon {
|
||||
|
|
@ -78,3 +77,17 @@ trait PowerDaemon {
|
|||
#[dbus_proxy(signal)]
|
||||
fn power_profile_switch(&self, profile: &str) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
pub async fn init() -> Option<(Connection, PowerDaemonProxy<'static>)> {
|
||||
let conn = match Connection::system().await {
|
||||
Ok(conn) => conn,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let proxy = match PowerDaemonProxy::new(&conn).await {
|
||||
Ok(p) => p,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some((conn, proxy))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
use crate::dbus::PowerDaemonProxy;
|
||||
use zbus::Result;
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum Graphics {
|
||||
Integrated,
|
||||
Hybrid,
|
||||
|
|
@ -10,7 +10,7 @@ pub enum Graphics {
|
|||
Compute,
|
||||
}
|
||||
|
||||
pub async fn get_current_graphics(daemon: &PowerDaemonProxy<'_>) -> Result<Graphics> {
|
||||
pub async fn get_current_graphics(daemon: PowerDaemonProxy<'_>) -> Result<Graphics> {
|
||||
let graphics = daemon.get_graphics().await?;
|
||||
match graphics.as_str() {
|
||||
"integrated" => Ok(Graphics::Integrated),
|
||||
|
|
@ -21,7 +21,7 @@ pub async fn get_current_graphics(daemon: &PowerDaemonProxy<'_>) -> Result<Graph
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn set_graphics(daemon: &PowerDaemonProxy<'_>, graphics: Graphics) -> Result<()> {
|
||||
pub async fn set_graphics(daemon: PowerDaemonProxy<'_>, graphics: Graphics) -> Result<()> {
|
||||
let graphics_str = match graphics {
|
||||
Graphics::Integrated => "integrated",
|
||||
Graphics::Hybrid => "hybrid",
|
||||
|
|
|
|||
|
|
@ -1,178 +1,37 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
mod dbus;
|
||||
mod graphics;
|
||||
mod window;
|
||||
|
||||
#![allow(unused_parens, clippy::double_parens)] // needed for a quirk in the view! macro
|
||||
|
||||
#[macro_use]
|
||||
extern crate relm4_macros;
|
||||
|
||||
pub mod dbus;
|
||||
pub mod graphics;
|
||||
pub mod mode_box;
|
||||
|
||||
use self::{dbus::PowerDaemonProxy, graphics::Graphics, mode_box::ModeSelection};
|
||||
use gtk4::{
|
||||
glib::{self, clone, MainContext, PRIORITY_DEFAULT},
|
||||
prelude::*,
|
||||
Align, Label, ListBox, ListBoxRow, Orientation, Overlay, Separator, Spinner,
|
||||
use cosmic::{
|
||||
iced::{sctk_settings::InitialSurface, Application},
|
||||
iced_native::command::platform_specific::wayland::window::SctkWindowSettings,
|
||||
iced_native::window::Settings,
|
||||
settings,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use tokio::runtime::Runtime;
|
||||
use cosmic_panel_config::PanelSize;
|
||||
use window::*;
|
||||
|
||||
static RT: Lazy<Runtime> = Lazy::new(|| Runtime::new().expect("failed to build tokio runtime"));
|
||||
|
||||
async fn get_current_graphics() -> zbus::Result<Graphics> {
|
||||
let connection = zbus::Connection::system().await?;
|
||||
let proxy = PowerDaemonProxy::new(&connection).await?;
|
||||
graphics::get_current_graphics(&proxy).await
|
||||
}
|
||||
|
||||
async fn set_graphics(graphics_mode: Graphics) -> zbus::Result<()> {
|
||||
let connection = zbus::Connection::system().await?;
|
||||
let proxy = PowerDaemonProxy::new(&connection).await?;
|
||||
graphics::set_graphics(&proxy, graphics_mode).await
|
||||
}
|
||||
|
||||
fn row_clicked(_: &ListBox, row: &ListBoxRow) {
|
||||
let child = row.child().expect("UNEXPECTED: row has no child");
|
||||
let selector = child
|
||||
.downcast::<ModeSelection>()
|
||||
.expect("UNEXPECTED: child is not a mode selector");
|
||||
selector.emit_activate();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _monitors = libcosmic::init();
|
||||
|
||||
let provider = gtk4::CssProvider::new();
|
||||
provider.load_from_data(include_bytes!("style.css"));
|
||||
gtk4::StyleContext::add_provider_for_display(
|
||||
>k4::gdk::Display::default().expect("Could not connect to a display."),
|
||||
&provider,
|
||||
gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
);
|
||||
|
||||
let current_graphics = RT
|
||||
.block_on(get_current_graphics())
|
||||
.expect("failed to connect to system76-power");
|
||||
|
||||
let (tx, rx) = MainContext::channel::<bool>(PRIORITY_DEFAULT);
|
||||
|
||||
view! {
|
||||
window = libcosmic_applet::AppletWindow {
|
||||
set_title: Some("COSMIC Graphics Applet"),
|
||||
#[wrap(Some)]
|
||||
set_child = &libcosmic_applet::AppletButton {
|
||||
set_button_icon_name: "input-gaming-symbolic",
|
||||
#[wrap(Some)]
|
||||
set_popover_child: main_overlay = &Overlay {
|
||||
add_overlay: loading_box = >k4::Box {
|
||||
append: loading_explain_box = >k4::Box {
|
||||
set_orientation: Orientation::Vertical,
|
||||
set_halign: Align::Center,
|
||||
set_valign: Align::Center,
|
||||
append: loading_spinner = &Spinner {
|
||||
set_halign: Align::Center,
|
||||
},
|
||||
append: loading_explain = &Label {
|
||||
set_label: "Please wait while your graphics mode is set...",
|
||||
set_halign: Align::Center,
|
||||
},
|
||||
},
|
||||
set_halign: Align::Center,
|
||||
set_valign: Align::Center,
|
||||
set_hexpand: true,
|
||||
set_vexpand: true,
|
||||
set_visible: false,
|
||||
add_css_class: "loading-overlay",
|
||||
},
|
||||
#[wrap(Some)]
|
||||
set_child: main_box = >k4::Box {
|
||||
set_orientation: Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
set_margin_top: 20,
|
||||
set_margin_bottom: 20,
|
||||
set_margin_start: 24,
|
||||
set_margin_end: 24,
|
||||
append: mode_label = &Label {
|
||||
set_text: "Graphics Mode"
|
||||
},
|
||||
append: separator = &Separator {
|
||||
set_orientation: Orientation::Horizontal
|
||||
},
|
||||
append: graphics_modes_list = &ListBox {
|
||||
connect_row_activated: row_clicked,
|
||||
append: integrated_selector = &ModeSelection {
|
||||
set_title: "Integrated Graphics",
|
||||
set_description: "Disables external displays. Requires Restart.",
|
||||
set_active: (current_graphics == Graphics::Integrated),
|
||||
connect_toggled: clone!(@strong tx => move |_| {
|
||||
tx.send(true).expect("failed to send to main context");
|
||||
let tx = tx.clone();
|
||||
RT.spawn(async move {
|
||||
set_graphics(Graphics::Integrated).await.expect("failed to set graphics mode");
|
||||
tx.send(false).expect("failed to send to main context");
|
||||
});
|
||||
})
|
||||
},
|
||||
append: nvidia_selector = &ModeSelection {
|
||||
set_title: "NVIDIA Graphics",
|
||||
set_group: Some(&integrated_selector),
|
||||
set_active: (current_graphics == Graphics::Nvidia),
|
||||
connect_toggled: clone!(@strong tx => move |_| {
|
||||
tx.send(true).expect("failed to send to main context");
|
||||
let tx = tx.clone();
|
||||
RT.spawn(async move {
|
||||
set_graphics(Graphics::Nvidia).await.expect("failed to set graphics mode");
|
||||
tx.send(false).expect("failed to send to main context");
|
||||
});
|
||||
})
|
||||
},
|
||||
append: hybrid_selector = &ModeSelection {
|
||||
set_title: "Hybrid Graphics",
|
||||
set_description: "Requires Restart.",
|
||||
set_group: Some(&integrated_selector),
|
||||
set_active: (current_graphics == Graphics::Hybrid),
|
||||
connect_toggled: clone!(@strong tx => move |_| {
|
||||
tx.send(true).expect("failed to send to main context");
|
||||
let tx = tx.clone();
|
||||
RT.spawn(async move {
|
||||
set_graphics(Graphics::Hybrid).await.expect("failed to set graphics mode");
|
||||
tx.send(false).expect("failed to send to main context");
|
||||
});
|
||||
})
|
||||
},
|
||||
append: compute_selector = &ModeSelection {
|
||||
set_title: "Compute Graphics",
|
||||
set_description: "Disables external displays. Requires Restart.",
|
||||
set_group: Some(&integrated_selector),
|
||||
set_active: (current_graphics == Graphics::Compute),
|
||||
connect_toggled: clone!(@strong tx => move |_| {
|
||||
tx.send(true).expect("failed to send to main context");
|
||||
let tx = tx.clone();
|
||||
RT.spawn(async move {
|
||||
set_graphics(Graphics::Compute).await.expect("failed to set graphics mode");
|
||||
tx.send(false).expect("failed to send to main context");
|
||||
});
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rx.attach(
|
||||
None,
|
||||
clone!(@weak loading_box, @weak loading_spinner => @default-return Continue(true), move |val| {
|
||||
loading_box.set_visible(val);
|
||||
loading_spinner.set_spinning(val);
|
||||
Continue(true)
|
||||
}),
|
||||
);
|
||||
|
||||
window.show();
|
||||
|
||||
let main_loop = glib::MainLoop::new(None, false);
|
||||
main_loop.run();
|
||||
pub fn main() -> cosmic::iced::Result {
|
||||
let mut settings = settings();
|
||||
let pixels = std::env::var("COSMIC_PANEL_SIZE")
|
||||
.ok()
|
||||
.and_then(|size| match size.parse::<PanelSize>() {
|
||||
Ok(PanelSize::XL) => Some(64),
|
||||
Ok(PanelSize::L) => Some(48),
|
||||
Ok(PanelSize::M) => Some(36),
|
||||
Ok(PanelSize::S) => Some(24),
|
||||
Ok(PanelSize::XS) => Some(18),
|
||||
Err(_) => Some(36),
|
||||
})
|
||||
.unwrap_or(36);
|
||||
settings.initial_surface = InitialSurface::XdgWindow(SctkWindowSettings {
|
||||
iced_settings: Settings {
|
||||
size: (pixels + 32, pixels + 16),
|
||||
min_size: Some((pixels + 32, pixels + 16)),
|
||||
max_size: Some((pixels + 32, pixels + 16)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
Window::run(settings)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,115 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use gtk4::{
|
||||
glib::{self, Object},
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
Align, CheckButton, Label, Orientation,
|
||||
};
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct ModeSelection(ObjectSubclass<ModeSelectionImp>)
|
||||
@extends gtk4::Widget,
|
||||
@implements gtk4::Accessible;
|
||||
}
|
||||
|
||||
impl ModeSelection {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn set_title(&self, title: &str) {
|
||||
self.inner().label.set_text(title);
|
||||
}
|
||||
|
||||
pub fn set_description(&self, title: &str) {
|
||||
let inner = self.inner();
|
||||
inner.description.set_text(title);
|
||||
inner.description.show();
|
||||
}
|
||||
|
||||
pub fn is_active(&self) -> bool {
|
||||
self.inner().check.is_active()
|
||||
}
|
||||
|
||||
pub fn set_active(&self, setting: bool) {
|
||||
self.inner().check.set_active(setting)
|
||||
}
|
||||
|
||||
pub fn set_group(&self, group: Option<&ModeSelection>) {
|
||||
self.inner()
|
||||
.check
|
||||
.set_group(group.map(|x| &x.inner().check))
|
||||
}
|
||||
|
||||
pub fn connect_toggled<F: Fn(&CheckButton) + 'static>(&self, f: F) {
|
||||
self.inner().check.connect_toggled(f);
|
||||
}
|
||||
|
||||
pub fn emit_activate(&self) {
|
||||
self.inner().check.emit_activate();
|
||||
}
|
||||
|
||||
fn inner(&self) -> &ModeSelectionImp {
|
||||
ModeSelectionImp::from_instance(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ModeSelection {
|
||||
fn default() -> Self {
|
||||
Object::new(&[]).expect("Failed to create `ModeSelection`.")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ModeSelectionImp {
|
||||
inner_box: gtk4::Box,
|
||||
label_box: gtk4::Box,
|
||||
label: Label,
|
||||
description: Label,
|
||||
check: CheckButton,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for ModeSelectionImp {
|
||||
const NAME: &'static str = "ModeSelection";
|
||||
type Type = ModeSelection;
|
||||
type ParentType = gtk4::Widget;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.set_layout_manager_type::<gtk4::BinLayout>();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for ModeSelectionImp {
|
||||
fn constructed(&self, obj: &Self::Type) {
|
||||
self.parent_constructed(obj);
|
||||
|
||||
self.check.set_halign(Align::End);
|
||||
|
||||
self.label.set_halign(Align::Start);
|
||||
self.label.add_css_class("title");
|
||||
|
||||
self.description.set_halign(Align::Start);
|
||||
self.description.add_css_class("description");
|
||||
self.description.hide();
|
||||
|
||||
self.label_box.set_orientation(Orientation::Vertical);
|
||||
self.label_box.set_hexpand(true);
|
||||
self.label_box.append(&self.label);
|
||||
self.label_box.append(&self.description);
|
||||
|
||||
self.inner_box.set_orientation(Orientation::Horizontal);
|
||||
self.inner_box.append(&self.label_box);
|
||||
self.inner_box.append(&self.check);
|
||||
self.inner_box.set_parent(obj);
|
||||
}
|
||||
|
||||
fn dispose(&self, _obj: &Self::Type) {
|
||||
self.inner_box.remove(&self.label);
|
||||
self.inner_box.remove(&self.check);
|
||||
self.inner_box.unparent();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for ModeSelectionImp {}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
.loading-overlay {
|
||||
background-color: #2f2f2f;
|
||||
opacity: 0.85;
|
||||
}
|
||||
287
applets/cosmic-applet-graphics/src/window.rs
Normal file
287
applets/cosmic-applet-graphics/src/window.rs
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
use crate::dbus::{self, PowerDaemonProxy};
|
||||
use crate::graphics::{get_current_graphics, set_graphics, Graphics};
|
||||
use cosmic::applet::{get_popup_settings, icon_button, popup_container};
|
||||
use cosmic::iced_style::application::{self, Appearance};
|
||||
use cosmic::separator;
|
||||
use cosmic::{
|
||||
iced::widget::{column, radio, text},
|
||||
iced::{self, Application, Command, Length},
|
||||
iced_native::window,
|
||||
theme::{self, Theme},
|
||||
Element,
|
||||
};
|
||||
use cosmic_panel_config::{PanelAnchor, PanelSize};
|
||||
use iced_sctk::alignment::Horizontal;
|
||||
use iced_sctk::application::SurfaceIdWrapper;
|
||||
use iced_sctk::commands::popup::{destroy_popup, get_popup};
|
||||
use iced_sctk::Color;
|
||||
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<window::Id>,
|
||||
graphics_mode: Option<GraphicsMode>,
|
||||
id_ctr: u32,
|
||||
icon_size: u16,
|
||||
anchor: PanelAnchor,
|
||||
theme: Theme,
|
||||
dbus: Option<(Connection, PowerDaemonProxy<'static>)>,
|
||||
state: State,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
CurrentGraphics(Option<Graphics>),
|
||||
SelectedGraphicsMode(Option<Graphics>),
|
||||
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<Self::Message>) {
|
||||
let mut window = Window::default();
|
||||
let pixels = std::env::var("COSMIC_PANEL_SIZE")
|
||||
.ok()
|
||||
.and_then(|size| match size.parse::<PanelSize>() {
|
||||
Ok(PanelSize::XL) => Some(64),
|
||||
Ok(PanelSize::L) => Some(36),
|
||||
Ok(PanelSize::M) => Some(24),
|
||||
Ok(PanelSize::S) => Some(16),
|
||||
Ok(PanelSize::XS) => Some(12),
|
||||
Err(_) => Some(12),
|
||||
})
|
||||
.unwrap_or(16);
|
||||
window.icon_size = pixels;
|
||||
window.anchor = std::env::var("COSMIC_PANEL_ANCHOR")
|
||||
.ok()
|
||||
.map(|size| match size.parse::<PanelAnchor>() {
|
||||
Ok(p) => p,
|
||||
Err(_) => PanelAnchor::Top,
|
||||
})
|
||||
.unwrap_or(PanelAnchor::Top);
|
||||
(
|
||||
window,
|
||||
Command::perform(dbus::init(), |dbus_init| Message::DBusInit(dbus_init)),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Cosmic Graphics Applet")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> iced::Command<Self::Message> {
|
||||
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 =
|
||||
get_popup_settings(window::Id::new(0), new_id, (200, 240), 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<Message> {
|
||||
match id {
|
||||
SurfaceIdWrapper::LayerSurface(_) => unimplemented!(),
|
||||
SurfaceIdWrapper::Window(_) => icon_button().on_press(Message::TogglePopup).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()),
|
||||
|g| Message::SelectGraphicsMode(g),
|
||||
)
|
||||
.into(),
|
||||
radio(
|
||||
"Nvidia Graphics",
|
||||
Graphics::Nvidia,
|
||||
self.graphics_mode.map(|g| g.inner()),
|
||||
|g| Message::SelectGraphicsMode(g),
|
||||
)
|
||||
.into(),
|
||||
radio(
|
||||
"Hybrid Graphics",
|
||||
Graphics::Hybrid,
|
||||
self.graphics_mode.map(|g| g.inner()),
|
||||
|g| Message::SelectGraphicsMode(g),
|
||||
)
|
||||
.into(),
|
||||
radio(
|
||||
"Compute Graphics",
|
||||
Graphics::Compute,
|
||||
self.graphics_mode.map(|g| g.inner()),
|
||||
|g| Message::SelectGraphicsMode(g),
|
||||
)
|
||||
.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()
|
||||
}
|
||||
};
|
||||
popup_container(
|
||||
column(vec![
|
||||
text("Graphics Mode")
|
||||
.width(Length::Fill)
|
||||
.horizontal_alignment(Horizontal::Center)
|
||||
.size(24)
|
||||
.into(),
|
||||
separator!(1).into(),
|
||||
content,
|
||||
])
|
||||
.padding(4)
|
||||
.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) -> <Self::Theme as application::StyleSheet>::Style {
|
||||
<Self::Theme as application::StyleSheet>::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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
//! Utilities for using an [`EventQueue`] from wayland-client with an event loop that performs polling with
|
||||
//! [`calloop`](https://crates.io/crates/calloop).
|
||||
|
||||
use std::{io, os::unix::prelude::RawFd};
|
||||
use std::{io, os::unix::prelude::{RawFd, AsRawFd}};
|
||||
|
||||
use calloop::{
|
||||
generic::Generic, EventSource, InsertError, Interest, LoopHandle, Mode, Poll, PostAction,
|
||||
|
|
@ -30,7 +30,7 @@ impl<D> WaylandSource<D> {
|
|||
/// Wrap an [`EventQueue`] as a [`WaylandSource`].
|
||||
pub fn new(queue: EventQueue<D>) -> Result<WaylandSource<D>, WaylandError> {
|
||||
let guard = queue.prepare_read()?;
|
||||
let fd = Generic::new(guard.connection_fd(), Interest::READ, Mode::Level);
|
||||
let fd = Generic::new(guard.connection_fd().as_raw_fd(), Interest::READ, Mode::Level);
|
||||
drop(guard);
|
||||
|
||||
Ok(WaylandSource {
|
||||
|
|
@ -192,12 +192,12 @@ impl<D> WaylandSource<D> {
|
|||
break Err(Errno::EPROTO.into());
|
||||
}
|
||||
|
||||
Err(DispatchError::BadMessage { msg, interface }) => {
|
||||
Err(DispatchError::BadMessage { sender_id, interface, opcode }) => {
|
||||
log::error!(
|
||||
"Bad message on interface \"{}\": (opcode: {}, args: {:?})",
|
||||
"Bad message on interface \"{}\": (opcode: {}, sender_id: {:?})",
|
||||
interface,
|
||||
msg.opcode,
|
||||
msg.args,
|
||||
opcode,
|
||||
sender_id,
|
||||
);
|
||||
|
||||
break Err(Errno::EPROTO.into());
|
||||
|
|
|
|||
7
debian/rules
vendored
7
debian/rules
vendored
|
|
@ -13,6 +13,7 @@ override_dh_shlibdeps:
|
|||
override_dh_auto_clean:
|
||||
if test "${CLEAN}" = "1"; then \
|
||||
cargo clean; \
|
||||
cargo clean --manifest-path applets/cosmic-applet-graphics/Cargo.toml; \
|
||||
fi
|
||||
|
||||
if ! ischroot && test "${VENDOR}" = "1"; then \
|
||||
|
|
@ -21,6 +22,12 @@ override_dh_auto_clean:
|
|||
echo 'directory = "vendor"' >> .cargo/config; \
|
||||
tar pcf vendor.tar vendor; \
|
||||
rm -rf vendor; \
|
||||
cd applets/cosmic-applet-graphics/; \
|
||||
mkdir -p .cargo; \
|
||||
cargo vendor --sync Cargo.toml | head -n -1 > .cargo/config; \
|
||||
echo 'directory = "vendor"' >> .cargo/config; \
|
||||
tar pcf vendor.tar vendor; \
|
||||
rm -rf vendor; \
|
||||
fi
|
||||
|
||||
override_dh_auto_build:
|
||||
|
|
|
|||
17
justfile
17
justfile
|
|
@ -27,6 +27,10 @@ app_button_id := 'com.system76.CosmicPanelAppButton'
|
|||
workspaces_button_id := 'com.system76.CosmicPanelWorkspacesButton'
|
||||
|
||||
all: _extract_vendor
|
||||
#!/usr/bin/env bash
|
||||
pushd applets/cosmic-applet-graphics/
|
||||
cargo build {{cargo_args}}
|
||||
popd
|
||||
cargo build {{cargo_args}}
|
||||
|
||||
# Installs files into the system
|
||||
|
|
@ -47,12 +51,6 @@ install:
|
|||
install -Dm0644 applets/cosmic-applet-battery/data/icons/{{battery_id}}.svg {{iconsdir}}/{{battery_id}}.svg
|
||||
install -Dm0644 applets/cosmic-applet-battery/data/{{battery_id}}.desktop {{sharedir}}/applications/{{battery_id}}.desktop
|
||||
install -Dm0755 target/release/cosmic-applet-battery {{bindir}}/cosmic-applet-battery
|
||||
|
||||
# graphics
|
||||
install -Dm0644 applets/cosmic-applet-graphics/data/icons/{{graphics_id}}.svg {{iconsdir}}/{{graphics_id}}.svg
|
||||
install -Dm0644 applets/cosmic-applet-graphics/data/{{graphics_id}}.desktop {{sharedir}}/applications/{{graphics_id}}.desktop
|
||||
install -Dm0755 target/release/cosmic-applet-graphics {{bindir}}/cosmic-applet-graphics
|
||||
|
||||
# network
|
||||
install -Dm0644 applets/cosmic-applet-network/data/icons/{{network_id}}.svg {{iconsdir}}/{{network_id}}.svg
|
||||
install -Dm0644 applets/cosmic-applet-network/data/{{network_id}}.desktop {{sharedir}}/applications/{{network_id}}.desktop
|
||||
|
|
@ -94,9 +92,16 @@ install:
|
|||
# panel button
|
||||
install -Dm0755 target/release/cosmic-panel-button {{bindir}}/cosmic-panel-button
|
||||
|
||||
# graphics
|
||||
install -Dm0644 applets/cosmic-applet-graphics/data/icons/{{graphics_id}}.svg {{iconsdir}}/{{graphics_id}}.svg
|
||||
install -Dm0644 applets/cosmic-applet-graphics/data/{{graphics_id}}.desktop {{sharedir}}/applications/{{graphics_id}}.desktop
|
||||
install -Dm0755 applets/cosmic-applet-graphics/target/release/cosmic-applet-graphics {{bindir}}/cosmic-applet-graphics
|
||||
|
||||
|
||||
# Extracts vendored dependencies if vendor=1
|
||||
_extract_vendor:
|
||||
#!/usr/bin/env sh
|
||||
if test {{vendor}} = 1; then
|
||||
rm -rf vendor; tar pxf vendor.tar
|
||||
rm -rf applets/cosmic-applet-graphics/vendor; tar xf applets/cosmic-applet-graphics/vendor.tar --directory applets/cosmic-applet-graphics
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
1.63
|
||||
1.65
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue