config: Generate configs
This commit is contained in:
@@ -8,10 +8,6 @@ edition = "2021"
|
|||||||
lto = "fat"
|
lto = "fat"
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
[features]
|
|
||||||
fontloading = ["fontconfig"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wayland-client = { version = "0.30", features = ["calloop", "log"] }
|
wayland-client = { version = "0.30", features = ["calloop", "log"] }
|
||||||
wayland-protocols = { version = "0.30", features = ["client", "unstable", "staging"] }
|
wayland-protocols = { version = "0.30", features = ["client", "unstable", "staging"] }
|
||||||
|
|||||||
+115
-42
@@ -6,7 +6,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
event::Events,
|
event::Events,
|
||||||
fonts::FontMap,
|
fonts::{FontMap, find_font},
|
||||||
widgets::{
|
widgets::{
|
||||||
Backlight, Battery, Calendar, Clock, Date, HorizontalLayout, IndexedLayout, Interface,
|
Backlight, Battery, Calendar, Clock, Date, HorizontalLayout, IndexedLayout, Interface,
|
||||||
InvertedHorizontalLayout, Layout, Line, Margin, PulseAudio, VerticalLayout, Widget as RealWidget,
|
InvertedHorizontalLayout, Layout, Line, Margin, PulseAudio, VerticalLayout, Widget as RealWidget,
|
||||||
@@ -14,7 +14,14 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum OperationMode {
|
||||||
|
LayerSurface((u32, u32)),
|
||||||
|
XdgToplevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum Widget {
|
pub enum Widget {
|
||||||
Margin {
|
Margin {
|
||||||
margins: (u32, u32, u32, u32),
|
margins: (u32, u32, u32, u32),
|
||||||
@@ -58,38 +65,47 @@ pub enum Widget {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for OperationMode {
|
||||||
|
fn default() -> OperationMode {
|
||||||
|
OperationMode::LayerSurface((1024, 768))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub font_paths: Option<HashMap<String, String>>,
|
pub font_paths: Option<HashMap<String, String>>,
|
||||||
pub widget: Option<Widget>,
|
pub widget: Widget,
|
||||||
|
pub mode: OperationMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Config {
|
fn default() -> Config {
|
||||||
Config{
|
Config{
|
||||||
font_paths: None,
|
font_paths: None,
|
||||||
widget: None,
|
widget: Default::default(),
|
||||||
|
mode: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn leak_or_default(v: Option<String>, d: &'static str) -> &'static str {
|
impl Config {
|
||||||
match v {
|
pub fn generate_v1() -> Config {
|
||||||
Some(v) => Box::leak(v.into_boxed_str()),
|
let has_battery = Battery::detect();
|
||||||
None => d,
|
let has_backlight = Backlight::detect("intel_backlight");
|
||||||
}
|
let sans = find_font("sans");
|
||||||
}
|
let monospace = find_font("monospace");
|
||||||
|
|
||||||
impl Default for Widget {
|
let mut bars = Vec::new();
|
||||||
fn default() -> Widget {
|
if has_battery {
|
||||||
Self::v2()
|
bars.push(Widget::Margin{ margins: (0, 0, 0, 8), widget: Box::new(Widget::Battery { font: None, font_size: 24. })});
|
||||||
}
|
}
|
||||||
|
if has_backlight {
|
||||||
|
bars.push(Widget::Margin{ margins: (0, 0, 0, 8), widget: Box::new(Widget::Backlight { device: Some("intel_backlight".to_string()), font: None, font_size: 24. })});
|
||||||
}
|
}
|
||||||
|
bars.push(Widget::Margin{ margins: (0, 0, 0, 8), widget: Box::new(Widget::PulseAudio { font: None, font_size: 24. })});
|
||||||
|
|
||||||
impl Widget {
|
let widget = Widget::Margin {
|
||||||
pub fn v1() -> Widget {
|
|
||||||
Widget::Margin {
|
|
||||||
margins: (20, 20, 20, 20),
|
margins: (20, 20, 20, 20),
|
||||||
widget: Box::new(Widget::VerticalLayout(vec![
|
widget: Box::new(Widget::VerticalLayout(vec![
|
||||||
Widget::HorizontalLayout(vec![
|
Widget::HorizontalLayout(vec![
|
||||||
@@ -105,30 +121,7 @@ impl Widget {
|
|||||||
]),
|
]),
|
||||||
Widget::Margin {
|
Widget::Margin {
|
||||||
margins: (88, 0, 0, 0),
|
margins: (88, 0, 0, 0),
|
||||||
widget: Box::new(Widget::VerticalLayout(vec![
|
widget: Box::new(Widget::VerticalLayout(bars)),
|
||||||
Widget::Margin {
|
|
||||||
margins: (0, 0, 0, 8),
|
|
||||||
widget: Box::new(Widget::Battery {
|
|
||||||
font: None,
|
|
||||||
font_size: 24.,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
Widget::Margin {
|
|
||||||
margins: (0, 0, 0, 8),
|
|
||||||
widget: Box::new(Widget::Backlight {
|
|
||||||
device: None,
|
|
||||||
font: None,
|
|
||||||
font_size: 24.,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
Widget::Margin {
|
|
||||||
margins: (0, 0, 0, 8),
|
|
||||||
widget: Box::new(Widget::PulseAudio {
|
|
||||||
font: None,
|
|
||||||
font_size: 24.,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
])),
|
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
Widget::Calendar {
|
Widget::Calendar {
|
||||||
@@ -143,10 +136,88 @@ impl Widget {
|
|||||||
font_size: 32.,
|
font_size: 32.,
|
||||||
},
|
},
|
||||||
])),
|
])),
|
||||||
|
};
|
||||||
|
|
||||||
|
let map = HashMap::from([
|
||||||
|
("sans".to_string(), sans),
|
||||||
|
("monospace".to_string(), monospace),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Config{
|
||||||
|
font_paths: Some(map),
|
||||||
|
widget: widget,
|
||||||
|
mode: OperationMode::LayerSurface((1024, 768)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn v2() -> Widget {
|
pub fn generate_v2(clay: bool) -> Config {
|
||||||
|
let has_battery = Battery::detect();
|
||||||
|
let has_backlight = Backlight::detect("intel_backlight");
|
||||||
|
let sans = find_font("sans");
|
||||||
|
let monospace = find_font("monospace");
|
||||||
|
|
||||||
|
let mut bars = Vec::new();
|
||||||
|
if has_battery {
|
||||||
|
bars.push(Widget::Margin{ margins: (16, 8, 8, 0), widget: Box::new(Widget::Battery { font: None, font_size: 24. })});
|
||||||
|
}
|
||||||
|
if has_backlight {
|
||||||
|
bars.push(Widget::Margin{ margins: (16, 8, 8, 0), widget: Box::new(Widget::Backlight { device: Some("intel_backlight".to_string()), font: None, font_size: 24. })});
|
||||||
|
}
|
||||||
|
bars.push(Widget::Margin{ margins: (16, 8, 8, 0), widget: Box::new(Widget::PulseAudio { font: None, font_size: 24. })});
|
||||||
|
|
||||||
|
let widget = Widget::VerticalLayout(vec![
|
||||||
|
Widget::HorizontalLayout(vec![
|
||||||
|
Widget::Clock {
|
||||||
|
font: None,
|
||||||
|
font_size: 128.,
|
||||||
|
},
|
||||||
|
Widget::Margin {
|
||||||
|
margins: (16, 16, 0, 0),
|
||||||
|
widget: Box::new(Widget::Date {
|
||||||
|
font: None,
|
||||||
|
font_size: 48.,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Widget::VerticalLayout(bars),
|
||||||
|
]),
|
||||||
|
Widget::Line(1),
|
||||||
|
Widget::InvertedHorizontalLayout(vec![
|
||||||
|
Widget::Calendar {
|
||||||
|
font_primary: None,
|
||||||
|
font_secondary: None,
|
||||||
|
font_size: 24.,
|
||||||
|
sections_x: 1,
|
||||||
|
sections_y: -1,
|
||||||
|
},
|
||||||
|
Widget::Launcher {
|
||||||
|
font: None,
|
||||||
|
font_size: 32.,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let map = HashMap::from([
|
||||||
|
("sans".to_string(), sans),
|
||||||
|
("monospace".to_string(), monospace),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Config{
|
||||||
|
font_paths: Some(map),
|
||||||
|
widget: widget,
|
||||||
|
mode: if clay { OperationMode::XdgToplevel } else { OperationMode::LayerSurface((1024, 768)) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn leak_or_default(v: Option<String>, d: &'static str) -> &'static str {
|
||||||
|
match v {
|
||||||
|
Some(v) => Box::leak(v.into_boxed_str()),
|
||||||
|
None => d,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Widget {
|
||||||
|
fn default() -> Widget {
|
||||||
Widget::VerticalLayout(vec![
|
Widget::VerticalLayout(vec![
|
||||||
Widget::HorizontalLayout(vec![
|
Widget::HorizontalLayout(vec![
|
||||||
Widget::Clock {
|
Widget::Clock {
|
||||||
@@ -201,7 +272,9 @@ impl Widget {
|
|||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
pub fn construct_widgets(
|
pub fn construct_widgets(
|
||||||
self,
|
self,
|
||||||
v: &mut Vec<Box<dyn RealWidget>>,
|
v: &mut Vec<Box<dyn RealWidget>>,
|
||||||
|
|||||||
+6
-12
@@ -9,12 +9,9 @@ use std::{
|
|||||||
/// FontRef is used to store Fonts on widgets.
|
/// FontRef is used to store Fonts on widgets.
|
||||||
pub type FontRef<'a> = &'a rusttype::Font<'a>;
|
pub type FontRef<'a> = &'a rusttype::Font<'a>;
|
||||||
|
|
||||||
#[cfg(feature = "fontloading")]
|
#[cfg(feature = "fontconfig")]
|
||||||
mod fc {
|
pub(crate) fn find_font(name: &str) -> String {
|
||||||
use fontconfig::Fontconfig as FontConfig;
|
use fontconfig::Fontconfig as FontConfig;
|
||||||
/// Acts like fc-match.
|
|
||||||
/// Given a string, it matches it to a font file and returns its path.
|
|
||||||
pub(crate) fn from_string(name: &str) -> String {
|
|
||||||
let fc = FontConfig::new().unwrap();
|
let fc = FontConfig::new().unwrap();
|
||||||
fc.find(name, None)
|
fc.find(name, None)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -23,13 +20,10 @@ mod fc {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "fontloading"))]
|
#[cfg(not(feature = "fontconfig"))]
|
||||||
mod fc {
|
pub(crate) fn find_font(_name: &str) -> String {
|
||||||
pub(crate) fn from_string(_name: &str) -> String {
|
panic!("fontconfig not enabled so font search not available");
|
||||||
panic!("fontloading feature not enabled");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FontLoader is a marker struct that is used to load files
|
/// FontLoader is a marker struct that is used to load files
|
||||||
@@ -110,7 +104,7 @@ impl FontMap {
|
|||||||
let path = match self.font_paths.get(font_name) {
|
let path = match self.font_paths.get(font_name) {
|
||||||
Some(res) => res,
|
Some(res) => res,
|
||||||
_ => {
|
_ => {
|
||||||
let s = fc::from_string(font_name);
|
let s = find_font(font_name);
|
||||||
self.font_paths.insert(font_name, s);
|
self.font_paths.insert(font_name, s);
|
||||||
self.font_paths.get(font_name).unwrap()
|
self.font_paths.get(font_name).unwrap()
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-31
@@ -20,39 +20,36 @@ use calloop::{
|
|||||||
use chrono::{Duration, Local, Timelike};
|
use chrono::{Duration, Local, Timelike};
|
||||||
|
|
||||||
use buffer::BufferView;
|
use buffer::BufferView;
|
||||||
use config::{Config, Widget as ConfigWidget};
|
use config::Config;
|
||||||
use event::{Event, Events};
|
use event::{Event, Events};
|
||||||
use fonts::{FontMap, MaybeFontMap};
|
use fonts::{FontMap, MaybeFontMap};
|
||||||
use state::{OperationMode, State};
|
use state::State;
|
||||||
use widgets::{Geometry, Widget};
|
use widgets::{Geometry, Widget};
|
||||||
|
|
||||||
use std::{env, rc::Rc, thread};
|
use std::{env, rc::Rc, thread};
|
||||||
|
|
||||||
enum UIVersion {
|
|
||||||
V1,
|
|
||||||
V2,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut args = env::args();
|
let mut args = env::args();
|
||||||
// Skip program name
|
// Skip program name
|
||||||
_ = args.next();
|
_ = args.next();
|
||||||
|
|
||||||
let mut version = UIVersion::V2;
|
|
||||||
let mut mode = OperationMode::XdgToplevel;
|
|
||||||
loop {
|
loop {
|
||||||
match args.next() {
|
match args.next() {
|
||||||
Some(ref s) if s == "layer_shell" => {
|
Some(ref s) if s == "generate" => {
|
||||||
let width = args.next().map(|x| x.parse::<u32>().ok()).flatten();
|
let config = match args.next() {
|
||||||
let height = args.next().map(|x| x.parse::<u32>().ok()).flatten();
|
Some(ref s) if s == "v1" => Config::generate_v1(),
|
||||||
match (width, height) {
|
Some(ref s) if s == "v2" => Config::generate_v2(false),
|
||||||
(Some(w), Some(h)) => mode = OperationMode::LayerSurface((w, h)),
|
Some(ref s) if s == "clay" => Config::generate_v2(true),
|
||||||
_ => panic!("invalid arguments"),
|
None => Config::generate_v2(false),
|
||||||
|
Some(s) => panic!("unknown argument: {}", s),
|
||||||
|
};
|
||||||
|
let home = env::var_os("HOME").unwrap().into_string().unwrap();
|
||||||
|
match File::create(format!("{}/.config/wldash/config.yml", home)) {
|
||||||
|
Ok(f) => serde_yaml::to_writer(f, &config).unwrap(),
|
||||||
|
Err(_) => panic!("uh"),
|
||||||
}
|
}
|
||||||
}
|
std::process::exit(0);
|
||||||
Some(ref s) if s == "xdg_shell" => mode = OperationMode::XdgToplevel,
|
},
|
||||||
Some(ref s) if s == "v1" => version = UIVersion::V1,
|
|
||||||
Some(ref s) if s == "v2" => version = UIVersion::V2,
|
|
||||||
Some(_) => panic!("unknown argument"),
|
Some(_) => panic!("unknown argument"),
|
||||||
None => break,
|
None => break,
|
||||||
}
|
}
|
||||||
@@ -122,24 +119,15 @@ fn main() {
|
|||||||
Err(_) => Default::default(),
|
Err(_) => Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let widget = match config.widget {
|
|
||||||
Some(w) => w,
|
|
||||||
None => match version {
|
|
||||||
UIVersion::V1 => ConfigWidget::v1(),
|
|
||||||
UIVersion::V2 => ConfigWidget::v2(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(font_paths) = config.font_paths {
|
if let Some(font_paths) = config.font_paths {
|
||||||
for (key, value) in font_paths.into_iter() {
|
for (key, value) in font_paths.into_iter() {
|
||||||
fm.add_font_path(Box::leak(key.into_boxed_str()), value);
|
fm.add_font_path(Box::leak(key.into_boxed_str()), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let layout = Rc::new(widget.construct_layout(&mut 0));
|
let layout = Rc::new(config.widget.construct_layout(&mut 0));
|
||||||
let mut widgets: Vec<Box<dyn Widget>> = Vec::new();
|
let mut widgets: Vec<Box<dyn Widget>> = Vec::new();
|
||||||
widget.construct_widgets(&mut widgets, &mut fm, &events);
|
config.widget.construct_widgets(&mut widgets, &mut fm, &events);
|
||||||
|
|
||||||
|
|
||||||
let font_thread = thread::Builder::new()
|
let font_thread = thread::Builder::new()
|
||||||
.name("fontloader".to_string())
|
.name("fontloader".to_string())
|
||||||
@@ -150,7 +138,7 @@ fn main() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut state = State::new(
|
let mut state = State::new(
|
||||||
mode,
|
config.mode,
|
||||||
widgets,
|
widgets,
|
||||||
layout,
|
layout,
|
||||||
MaybeFontMap::Waiting(font_thread),
|
MaybeFontMap::Waiting(font_thread),
|
||||||
|
|||||||
+1
-6
@@ -29,6 +29,7 @@ use wayland_protocols::{
|
|||||||
use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};
|
use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
config::OperationMode,
|
||||||
buffer::BufferManager,
|
buffer::BufferManager,
|
||||||
event::{Event, Events, PointerButton, PointerEvent},
|
event::{Event, Events, PointerButton, PointerEvent},
|
||||||
fonts::{FontMap, MaybeFontMap},
|
fonts::{FontMap, MaybeFontMap},
|
||||||
@@ -36,12 +37,6 @@ use crate::{
|
|||||||
widgets::{Geometry, Layout, Widget, WidgetUpdater},
|
widgets::{Geometry, Layout, Widget, WidgetUpdater},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum OperationMode {
|
|
||||||
LayerSurface((u32, u32)),
|
|
||||||
XdgToplevel,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct MainSurface {
|
pub struct MainSurface {
|
||||||
pub wl_surface: Option<wl_surface::WlSurface>,
|
pub wl_surface: Option<wl_surface::WlSurface>,
|
||||||
|
|||||||
@@ -39,6 +39,14 @@ impl Backlight {
|
|||||||
dev.update();
|
dev.update();
|
||||||
BarWidget::new(Box::new(dev), fm, font, size)
|
BarWidget::new(Box::new(dev), fm, font, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn detect(path: &str) -> bool {
|
||||||
|
let path = Path::new("/sys/class/backlight").to_path_buf().join(path);
|
||||||
|
match read_file_as_u64(path.join("brightness").as_path()) {
|
||||||
|
Ok(_) => true,
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BarWidgetImpl for Backlight {
|
impl BarWidgetImpl for Backlight {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::{
|
|||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
thread,
|
thread,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
|
error::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
use dbus::blocking::{
|
use dbus::blocking::{
|
||||||
@@ -46,6 +47,19 @@ struct InnerBattery {
|
|||||||
dirty: bool,
|
dirty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_battery_state() -> Result<UpowerBatteryState, Box<dyn Error>> {
|
||||||
|
let conn = LocalConnection::new_system()?;
|
||||||
|
let device_path = "/org/freedesktop/UPower/devices/DisplayDevice";
|
||||||
|
let proxy = conn.with_proxy(
|
||||||
|
"org.freedesktop.UPower",
|
||||||
|
device_path,
|
||||||
|
Duration::from_millis(500),
|
||||||
|
);
|
||||||
|
let state: u32 = proxy
|
||||||
|
.get("org.freedesktop.UPower.Device", "State")?;
|
||||||
|
Ok(UpowerBatteryState::from_dbus(state as u64))
|
||||||
|
}
|
||||||
|
|
||||||
fn start_monitor(inner: Arc<Mutex<InnerBattery>>, events: Arc<Mutex<Events>>) {
|
fn start_monitor(inner: Arc<Mutex<InnerBattery>>, events: Arc<Mutex<Events>>) {
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
.name("battmon".to_string())
|
.name("battmon".to_string())
|
||||||
@@ -131,6 +145,13 @@ impl Battery {
|
|||||||
start_monitor(battery.inner.clone(), events);
|
start_monitor(battery.inner.clone(), events);
|
||||||
BarWidget::new(Box::new(battery), fm, font, size)
|
BarWidget::new(Box::new(battery), fm, font, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn detect() -> bool {
|
||||||
|
match get_battery_state() {
|
||||||
|
Ok(_) => true,
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BarWidgetImpl for Battery {
|
impl BarWidgetImpl for Battery {
|
||||||
|
|||||||
Reference in New Issue
Block a user