v2
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/target
|
||||||
|
*.old
|
||||||
|
.env
|
||||||
5798
Cargo.lock
generated
Normal file
5798
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[workspace]
|
||||||
|
resolver = "3"
|
||||||
|
members = ["dashboard","trekstor"]
|
||||||
|
default-members = ["dashboard"]
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
slint = { version = "1.17.1", default-features = false, features = ["std", "compat-1-2", "libm", "renderer-software", "unsafe-single-threaded"] }
|
||||||
8
build
Executable file
8
build
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cargo b -r
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
scp target/release/dashboard trekstor:~/dashboard
|
||||||
|
ssh -t trekstor -- "systemctl --user restart dashboard"
|
||||||
1
dashboard/.gitignore
vendored
Normal file
1
dashboard/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
20
dashboard/Cargo.toml
Normal file
20
dashboard/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "dashboard"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
trekstor.path = "../trekstor"
|
||||||
|
slint.workspace = true
|
||||||
|
hass-rs = "0.5.0"
|
||||||
|
serde_json = "1.0.151"
|
||||||
|
tokio = { version = "1.53.1", features = ["full"] }
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
url = "2.5.8"
|
||||||
|
chrono = "0.4.45"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
dev = ["slint/backend-winit-x11"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
slint-build = "1.17.1"
|
||||||
9
dashboard/build.rs
Normal file
9
dashboard/build.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
fn main() {
|
||||||
|
slint_build::compile_with_config(
|
||||||
|
"ui/main.slint",
|
||||||
|
slint_build::CompilerConfiguration::new()
|
||||||
|
.with_style("material-dark".into())
|
||||||
|
.embed_resources(slint_build::EmbedResourcesKind::EmbedFiles),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
78
dashboard/src/ha_ext.rs
Normal file
78
dashboard/src/ha_ext.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
use crate::*;
|
||||||
|
use hass_rs::HassEntity;
|
||||||
|
use slint::ToSharedString;
|
||||||
|
|
||||||
|
pub trait HassEntityExt {
|
||||||
|
fn is_on(&self) -> bool;
|
||||||
|
fn available(&self) -> bool;
|
||||||
|
fn friendly_name(&self) -> &str;
|
||||||
|
fn unit_of_measurement(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HassEntityExt for HassEntity {
|
||||||
|
fn is_on(&self) -> bool {
|
||||||
|
self.state == "on"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn available(&self) -> bool {
|
||||||
|
self.state != "unavailable"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn friendly_name(&self) -> &str {
|
||||||
|
self.attributes
|
||||||
|
.get("friendly_name")
|
||||||
|
.and_then(serde_json::Value::as_str)
|
||||||
|
.unwrap_or(&self.entity_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unit_of_measurement(&self) -> &str {
|
||||||
|
self.attributes
|
||||||
|
.get("unit_of_measurement")
|
||||||
|
.and_then(serde_json::Value::as_str)
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&HassEntity> for LightData {
|
||||||
|
fn from(value: &HassEntity) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.entity_id.clone().into(),
|
||||||
|
name: value.friendly_name().into(),
|
||||||
|
available: value.available(),
|
||||||
|
on: value.is_on(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&HassEntity> for ThermometerData {
|
||||||
|
fn from(value: &HassEntity) -> Self {
|
||||||
|
let v: f32 = value.state.parse().unwrap_or_default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
id: value.entity_id.clone().into(),
|
||||||
|
name: value.friendly_name().into(),
|
||||||
|
value: format!("{:.1}", v).into(),
|
||||||
|
unit: value.unit_of_measurement().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&HassEntity> for DateTimeData {
|
||||||
|
fn from(value: &HassEntity) -> Self {
|
||||||
|
if let Ok(datetime) =
|
||||||
|
chrono::NaiveDateTime::parse_from_str(&value.state, "%Y-%m-%dT%H:%M:%S")
|
||||||
|
{
|
||||||
|
return Self {
|
||||||
|
id: value.entity_id.clone().into(),
|
||||||
|
date: datetime.format("%a %m %b").to_shared_string(),
|
||||||
|
time: datetime.format("%H:%M").to_shared_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
id: value.entity_id.clone().into(),
|
||||||
|
date: "0-0-0000".into(),
|
||||||
|
time: "00:00".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
208
dashboard/src/main.rs
Normal file
208
dashboard/src/main.rs
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
mod ha_ext;
|
||||||
|
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
use dotenvy::var;
|
||||||
|
use hass_rs::{HassClient, HassEntity};
|
||||||
|
use serde_json::{Value, json};
|
||||||
|
use slint::{ModelRc, VecModel, Weak};
|
||||||
|
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
||||||
|
|
||||||
|
slint::include_modules!();
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum C2sMessage {
|
||||||
|
CallService(String, String, Option<Value>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum S2cMessage {
|
||||||
|
EntityUpdated,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
dotenvy::dotenv().ok();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "dev"))]
|
||||||
|
slint::platform::set_platform(Box::new(trekstor::Trekstor::new())).expect("set platform");
|
||||||
|
|
||||||
|
let app = AppWindow::new()?;
|
||||||
|
|
||||||
|
let state = State::default();
|
||||||
|
let (c2s_tx, c2s_rx) = mpsc::unbounded_channel::<C2sMessage>();
|
||||||
|
let (s2c_tx, mut s2c_rx) = mpsc::unbounded_channel::<S2cMessage>();
|
||||||
|
|
||||||
|
hass(s2c_tx, c2s_rx, state.clone()).await?;
|
||||||
|
let app_weak = app.as_weak();
|
||||||
|
apply_state(&app_weak, state.clone());
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let app_weak = app.as_weak();
|
||||||
|
let state = state.clone();
|
||||||
|
async move {
|
||||||
|
loop {
|
||||||
|
if let Some(S2cMessage::EntityUpdated) = s2c_rx.recv().await {
|
||||||
|
apply_state(&app_weak, state.clone());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on_toggle_light({
|
||||||
|
let state = state.clone();
|
||||||
|
move |entity_id| {
|
||||||
|
let id = entity_id.to_string();
|
||||||
|
|
||||||
|
let payload = Some(json!({
|
||||||
|
"entity_id": id
|
||||||
|
}));
|
||||||
|
|
||||||
|
if let Some(entity) = state.get(&id) {
|
||||||
|
let action = if entity.state == "on" {
|
||||||
|
"turn_off"
|
||||||
|
} else {
|
||||||
|
"turn_on"
|
||||||
|
};
|
||||||
|
c2s_tx
|
||||||
|
.send(C2sMessage::CallService(
|
||||||
|
"light".into(),
|
||||||
|
action.into(),
|
||||||
|
payload.clone(),
|
||||||
|
))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.run()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_state(app: &Weak<AppWindow>, state: State) {
|
||||||
|
app.upgrade_in_event_loop(move |app| {
|
||||||
|
// Lights
|
||||||
|
let lights = state
|
||||||
|
.domain("light")
|
||||||
|
.iter()
|
||||||
|
.filter_map(|light| {
|
||||||
|
if light.entity_id.ends_with("screen")
|
||||||
|
|| light.entity_id.find(|c: char| c.is_numeric()).is_some()
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(light.into())
|
||||||
|
})
|
||||||
|
.collect::<Vec<LightData>>();
|
||||||
|
app.set_lights(ModelRc::new(VecModel::from(lights)));
|
||||||
|
|
||||||
|
// Thermometers
|
||||||
|
let badges = state
|
||||||
|
.domain("sensor")
|
||||||
|
.iter()
|
||||||
|
.filter_map(|sensor| {
|
||||||
|
if sensor.entity_id.ends_with("thermometer_temperature") {
|
||||||
|
return Some(sensor.into());
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.collect::<Vec<ThermometerData>>();
|
||||||
|
app.set_badges(ModelRc::new(VecModel::from(badges)));
|
||||||
|
|
||||||
|
// Clock
|
||||||
|
if let Some(date_time_iso) = state.get("sensor.date_time_iso").as_ref() {
|
||||||
|
app.set_date_time(date_time_iso.into());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
struct State(Arc<RwLock<Vec<HassEntity>>>);
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn update(&self, entity: &HassEntity) {
|
||||||
|
let mut entities = self.0.write().unwrap();
|
||||||
|
|
||||||
|
if let Some(existing) = entities
|
||||||
|
.iter_mut()
|
||||||
|
.find(|e| e.entity_id == entity.entity_id)
|
||||||
|
{
|
||||||
|
*existing = entity.clone();
|
||||||
|
} else {
|
||||||
|
entities.push(entity.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn domain(&self, domain: &str) -> Vec<HassEntity> {
|
||||||
|
let entities = self.0.read().unwrap();
|
||||||
|
|
||||||
|
entities
|
||||||
|
.iter()
|
||||||
|
.filter(|e| e.entity_id.starts_with(domain))
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, entity_id: &str) -> Option<HassEntity> {
|
||||||
|
let entities = self.0.read().unwrap();
|
||||||
|
|
||||||
|
entities.iter().find(|e| e.entity_id == entity_id).cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn hass(
|
||||||
|
s2c_tx: UnboundedSender<S2cMessage>,
|
||||||
|
mut c2s_rx: UnboundedReceiver<C2sMessage>,
|
||||||
|
state: State,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let Ok(url) = var("HASS_URL") else {
|
||||||
|
return Err("NO HASS_URL SET".into());
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(hass_token) = var("HASS_TOKEN") else {
|
||||||
|
return Err("NO HASS_TOKEN SET".into());
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(url) = url::Url::parse(&url) else {
|
||||||
|
return Err("INVALID HASS_URL".into());
|
||||||
|
};
|
||||||
|
|
||||||
|
let url = format!("ws://{}/api/websocket", url.authority());
|
||||||
|
|
||||||
|
let mut client = HassClient::new(&url).await?;
|
||||||
|
client.auth_with_longlivedtoken(&hass_token).await?;
|
||||||
|
|
||||||
|
let states = client.get_states().await?;
|
||||||
|
|
||||||
|
states.iter().for_each(|entity| {
|
||||||
|
state.update(entity);
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut event_receiver = client.subscribe_event("state_changed").await?;
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while let Some(message) = event_receiver.recv().await {
|
||||||
|
if let Some(entity) = &message.event.data.new_state {
|
||||||
|
state.update(entity);
|
||||||
|
s2c_tx.send(S2cMessage::EntityUpdated).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
if let Some(C2sMessage::CallService(domain, service, service_data)) =
|
||||||
|
c2s_rx.recv().await
|
||||||
|
{
|
||||||
|
client
|
||||||
|
.call_service(domain, service, service_data)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
201
dashboard/ui/main.slint
Normal file
201
dashboard/ui/main.slint
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
import {
|
||||||
|
Palette,
|
||||||
|
StyleMetrics,
|
||||||
|
Button,
|
||||||
|
ScrollView,
|
||||||
|
VerticalBox,
|
||||||
|
HorizontalBox,
|
||||||
|
StandardTableView,
|
||||||
|
} from "std-widgets.slint";
|
||||||
|
|
||||||
|
export struct LightData {
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
on: bool,
|
||||||
|
available: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
export struct ThermometerData {
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
value: string,
|
||||||
|
unit: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export struct DateTimeData {
|
||||||
|
id: string,
|
||||||
|
date: string,
|
||||||
|
time: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
component Clock {
|
||||||
|
in property <DateTimeData> date-time;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: StyleMetrics.layout-padding;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: date-time.date;
|
||||||
|
height: 16px;
|
||||||
|
font-weight: 100;
|
||||||
|
horizontal-alignment: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: date-time.time;
|
||||||
|
font-size: 19px;
|
||||||
|
height: 19px;
|
||||||
|
font-weight: 500;
|
||||||
|
horizontal-alignment: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component Thermometer inherits Rectangle {
|
||||||
|
in property <ThermometerData> badge;
|
||||||
|
border-radius: 16px + 19px;
|
||||||
|
border-width: 2px;
|
||||||
|
border-color: Palette.alternate-background;
|
||||||
|
background: Palette.alternate-background;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
padding-left: parent.border-radius / 2;
|
||||||
|
padding-right: parent.border-radius / 2;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: badge.name;
|
||||||
|
height: 16px;
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "\{badge.value} \{badge.unit}";
|
||||||
|
height: 19px;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export component AppWindow inherits Window {
|
||||||
|
width: 1024px;
|
||||||
|
height: 600px;
|
||||||
|
default-font-size: 16px;
|
||||||
|
|
||||||
|
property <int> page: 0;
|
||||||
|
|
||||||
|
in property <DateTimeData> date-time;
|
||||||
|
in property <[ThermometerData]> badges;
|
||||||
|
|
||||||
|
in property <[LightData]> lights;
|
||||||
|
callback toggle-light(string);
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
width: root.width;
|
||||||
|
height: root.height;
|
||||||
|
HorizontalLayout {
|
||||||
|
alignment: center;
|
||||||
|
height: 64px;
|
||||||
|
spacing: 15px;
|
||||||
|
padding: StyleMetrics.layout-padding / 2;
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
preferred-width: parent.width;
|
||||||
|
height: parent.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
for badge[index] in root.badges: Thermometer {
|
||||||
|
badge: badge;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
preferred-width: parent.width;
|
||||||
|
height: parent.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
Clock {
|
||||||
|
date-time: date-time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalLayout {
|
||||||
|
height: root.height - 64px;
|
||||||
|
Rectangle {
|
||||||
|
preferred-width: root.width;
|
||||||
|
VerticalLayout {
|
||||||
|
if root.page == 0: Rectangle {
|
||||||
|
StandardTableView {
|
||||||
|
width: parent.width;
|
||||||
|
height: parent.height;
|
||||||
|
columns: [
|
||||||
|
{ title: "Header 1" },
|
||||||
|
{ title: "Header 2" },
|
||||||
|
];
|
||||||
|
rows: [
|
||||||
|
[
|
||||||
|
{ text: "Item 1" },
|
||||||
|
{ text: "Item 2" },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ text: "Item 1" },
|
||||||
|
{ text: "Item 2" },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ text: "Item 1" },
|
||||||
|
{ text: "Item 2" },
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if root.page == 1: Rectangle {
|
||||||
|
background: red;
|
||||||
|
// pages go here
|
||||||
|
}
|
||||||
|
|
||||||
|
if root.page == 2: Rectangle {
|
||||||
|
background: yellow;
|
||||||
|
// pages go here
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
height: 64px;
|
||||||
|
HorizontalBox {
|
||||||
|
padding-top: 0;
|
||||||
|
Button {
|
||||||
|
text: "Climate";
|
||||||
|
clicked => { root.page = 0; }
|
||||||
|
primary: root.page == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Calendar";
|
||||||
|
clicked => { root.page = 1; }
|
||||||
|
primary: root.page == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Energy";
|
||||||
|
clicked => { root.page = 2; }
|
||||||
|
primary: root.page == 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalBox {
|
||||||
|
preferred-width: 0;
|
||||||
|
for light[index] in root.lights: Button {
|
||||||
|
text: light.name;
|
||||||
|
primary: light.on;
|
||||||
|
enabled: light.available;
|
||||||
|
clicked => { toggle-light(light.id) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
152
samples
Normal file
152
samples
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
Status Sample name
|
||||||
|
[G...] aarch64-ol7u9-linux-gnu
|
||||||
|
[G...] aarch64-ol8u10-linux-gnu
|
||||||
|
[G...] aarch64-ol8u6-linux-gnu
|
||||||
|
[G...] aarch64-ol8u7-linux-gnu
|
||||||
|
[G...] aarch64-ol8u8-linux-gnu
|
||||||
|
[G...] aarch64-ol8u9-linux-gnu
|
||||||
|
[G...] aarch64-ol9u2-linux-gnu
|
||||||
|
[G...] aarch64-ol9u3-linux-gnu
|
||||||
|
[G...] aarch64-ol9u4-linux-gnu
|
||||||
|
[G...] aarch64-ol9u5-linux-gnu
|
||||||
|
[G...] aarch64-rpi3-linux-gnu
|
||||||
|
[G...] aarch64-rpi4-linux-gnu
|
||||||
|
[G...] aarch64-unknown-linux-gnu
|
||||||
|
[G...] aarch64-unknown-linux-musl
|
||||||
|
[G...] aarch64-unknown-linux-uclibc
|
||||||
|
[G...] alphaev56-unknown-linux-gnu
|
||||||
|
[G...] alphaev67-unknown-linux-gnu
|
||||||
|
[G...] arc-arc700-linux-uclibc
|
||||||
|
[G...] arc-archs-linux-gnu
|
||||||
|
[G...] arc-multilib-elf32
|
||||||
|
[G...] arc-multilib-linux-gnu
|
||||||
|
[G...] arc-multilib-linux-uclibc
|
||||||
|
[G...] arm-bare_newlib_cortex_m3_nommu-eabi
|
||||||
|
[G...] arm-cortex_a15-linux-gnueabihf
|
||||||
|
[G..X] arm-cortexa5-linux-uclibcgnueabihf
|
||||||
|
[G...] arm-cortex_a8-linux-gnueabi
|
||||||
|
[G..X] arm-cortexa9_neon-linux-gnueabihf
|
||||||
|
[G..X] x86_64-w64-mingw32,arm-cortexa9_neon-linux-gnueabihf
|
||||||
|
[G...] armeb-unknown-eabi
|
||||||
|
[G...] armeb-unknown-linux-gnueabi
|
||||||
|
[G...] armeb-unknown-linux-uclibcgnueabi
|
||||||
|
[G...] arm-multilib-linux-uclibcgnueabi
|
||||||
|
[G...] arm-nano-eabi
|
||||||
|
[G...] arm-none-eabi
|
||||||
|
[G...] arm-ol7u9-linux-gnueabi
|
||||||
|
[G...] arm-ol7u9-linux-gnueabihf
|
||||||
|
[G..X] arm-picolibc-default
|
||||||
|
[G..X] arm-picolibc-eabi
|
||||||
|
[G..X] x86_64-unknown-linux-gnu,arm-picolibc-eabi
|
||||||
|
[G...] arm-unknown-eabi
|
||||||
|
[G...] arm-unknown-linux-gnueabi
|
||||||
|
[G..X] arm-unknown-linux-musleabi
|
||||||
|
[G...] arm-unknown-linux-uclibcgnueabi
|
||||||
|
[G..X] arm-unknown-linux-uclibcgnueabihf
|
||||||
|
[G...] armv6-nommu-linux-uclibcgnueabi
|
||||||
|
[G...] armv6-unknown-linux-gnueabi
|
||||||
|
[G...] armv6-unknown-linux-gnueabihf
|
||||||
|
[G...] armv7-rpi2-linux-gnueabihf
|
||||||
|
[G...] armv8-rpi3-linux-gnueabihf
|
||||||
|
[G...] armv8-rpi4-linux-gnueabihf
|
||||||
|
[G...] avr
|
||||||
|
[G...] bpf-unknown-none
|
||||||
|
[G..X] hppa-unknown-linux-gnu
|
||||||
|
[G...] i586-geode-linux-uclibc
|
||||||
|
[G...] i686-centos6-linux-gnu
|
||||||
|
[G...] i686-centos7-linux-gnu
|
||||||
|
[G...] i686-nptl-linux-gnu
|
||||||
|
[G...] i686-ol8u6-linux-gnu
|
||||||
|
[G...] i686-ol8u7-linux-gnu
|
||||||
|
[G...] i686-ol8u8-linux-gnu
|
||||||
|
[G...] i686-ol9u2-linux-gnu
|
||||||
|
[G...] i686-ubuntu14.04-linux-gnu
|
||||||
|
[G...] i686-ubuntu16.04-linux-gnu
|
||||||
|
[G..X] i686-w64-mingw32
|
||||||
|
[G..X] lm32-unknown-elf
|
||||||
|
[G..X] loongarch64-unknown-linux-gnu
|
||||||
|
[G..X] loongarch64-unknown-linux-musl
|
||||||
|
[G...] m68k-unknown-elf
|
||||||
|
[G...] m68k-unknown-linux-gnu
|
||||||
|
[G...] m68k-unknown-uclinux-uclibc
|
||||||
|
[G...] powerpc-unknown-linux-uclibc,m68k-unknown-uclinux-uclibc
|
||||||
|
[G...] mips64el-multilib-linux-uclibc
|
||||||
|
[G...] mips64-ol9u5-linux-gnu
|
||||||
|
[G...] mips64-unknown-linux-gnu
|
||||||
|
[G...] mips-ar2315-linux-gnu
|
||||||
|
[G...] mipsel-multilib-linux-gnu
|
||||||
|
[G...] mipsel-sde-elf
|
||||||
|
[G...] mipsel-unknown-linux-gnu
|
||||||
|
[G...] mips-malta-linux-gnu
|
||||||
|
[G...] mips-unknown-elf
|
||||||
|
[G...] mips-unknown-linux-gnu
|
||||||
|
[G...] mips-unknown-linux-uclibc
|
||||||
|
[G..X] moxie-unknown-elf
|
||||||
|
[G..X] moxie-unknown-moxiebox
|
||||||
|
[G..X] x86_64-multilib-linux-uclibc,moxie-unknown-moxiebox
|
||||||
|
[G..X] msp430-unknown-elf
|
||||||
|
[G...] nios2-altera-linux-gnu
|
||||||
|
[G..X] i686-w64-mingw32,nios2-spico-elf
|
||||||
|
[G...] nios2-unknown-elf
|
||||||
|
[G..X] or1k-unknown-elf
|
||||||
|
[G..X] or1k-unknown-linux-gnu
|
||||||
|
[G..X] or1k-unknown-linux-musl
|
||||||
|
[G...] powerpc-405-linux-gnu
|
||||||
|
[G...] powerpc64le-unknown-linux-gnu
|
||||||
|
[G...] powerpc64-multilib-linux-gnu
|
||||||
|
[G...] powerpc64-unknown-linux-gnu
|
||||||
|
[G...] powerpc64-unknown-linux-musl
|
||||||
|
[G...] powerpc-8540-linux-gnu
|
||||||
|
[G...] powerpc-860-linux-gnu
|
||||||
|
[G...] powerpc-e300c3-linux-gnu
|
||||||
|
[G...] powerpc-e500v2-linux-gnuspe
|
||||||
|
[G...] x86_64-multilib-linux-uclibc,powerpc-unknown-elf
|
||||||
|
[G...] powerpc-unknown-linux-gnu
|
||||||
|
[G...] powerpc-unknown-linux-uclibc
|
||||||
|
[G...] powerpc-unknown_nofpu-linux-gnu
|
||||||
|
[G...] pru
|
||||||
|
[G...] riscv32-hifive1-elf
|
||||||
|
[G...] riscv32-picolibc-elf
|
||||||
|
[G...] riscv32-unknown-elf
|
||||||
|
[G...] riscv64-multilib-elf
|
||||||
|
[G...] riscv64-unknown-elf
|
||||||
|
[G...] riscv64-unknown-linux-gnu
|
||||||
|
[G..X] rx-unknown-elf
|
||||||
|
[G..X] s390-ibm-linux-gnu
|
||||||
|
[G..X] s390-unknown-linux-gnu
|
||||||
|
[G...] s390x-ibm-linux-gnu
|
||||||
|
[G...] s390x-unknown-linux-gnu
|
||||||
|
[G...] sh4-multilib-linux-gnu
|
||||||
|
[G...] sh4-multilib-linux-uclibc
|
||||||
|
[G...] sh-unknown-elf
|
||||||
|
[G...] sparc64-multilib-linux-gnu
|
||||||
|
[G...] sparc-leon-linux-uclibc
|
||||||
|
[G...] sparc-unknown-linux-gnu
|
||||||
|
[G..X] tic6x-uclinux
|
||||||
|
[G..X] tricore-none-eabi
|
||||||
|
[G...] x86_64-centos7-linux-gnu
|
||||||
|
[G...] x86_64-multilib-linux-gnu
|
||||||
|
[G...] x86_64-multilib-linux-musl
|
||||||
|
[G...] x86_64-multilib-linux-uclibc
|
||||||
|
[G...] x86_64-ol8u10-linux-gnu
|
||||||
|
[G...] x86_64-ol8u6-linux-gnu
|
||||||
|
[G...] x86_64-ol8u7-linux-gnu
|
||||||
|
[G...] x86_64-ol8u8-linux-gnu
|
||||||
|
[G...] x86_64-ol8u9-linux-gnu
|
||||||
|
[G...] x86_64-ol9u2-linux-gnu
|
||||||
|
[G...] x86_64-ol9u3-linux-gnu
|
||||||
|
[G...] x86_64-ol9u4-linux-gnu
|
||||||
|
[G...] x86_64-ol9u5-linux-gnu
|
||||||
|
[G..X] x86_64-w64-mingw32,x86_64-pc-linux-gnu
|
||||||
|
[G...] x86_64-ubuntu14.04-linux-gnu
|
||||||
|
[G...] x86_64-ubuntu16.04-linux-gnu
|
||||||
|
[G...] x86_64-unknown-linux-gnu
|
||||||
|
[G...] x86_64-unknown-linux-uclibc
|
||||||
|
[G..X] x86_64-w64-mingw32
|
||||||
|
[G..X] xtensa-fsf-elf
|
||||||
|
[G...] xtensa-fsf-linux-uclibc
|
||||||
|
L (Local) : sample was found in current directory
|
||||||
|
G (Global) : sample was installed with crosstool-NG
|
||||||
|
X (EXPERIMENTAL): sample may use EXPERIMENTAL features
|
||||||
|
B (BROKEN) : sample is currently broken
|
||||||
|
O (OBSOLETE) : sample needs to be upgraded
|
||||||
293
tree
Normal file
293
tree
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
arrayvec v0.7.8
|
||||||
|
├── kurbo v0.13.1
|
||||||
|
│ ├── svgtypes v0.16.1
|
||||||
|
│ │ ├── resvg v0.47.0
|
||||||
|
│ │ │ ├── i-slint-common v1.17.1
|
||||||
|
│ │ │ │ ├── i-slint-core v1.17.1
|
||||||
|
│ │ │ │ │ ├── i-slint-backend-selector v1.17.1
|
||||||
|
│ │ │ │ │ │ └── slint v1.17.1
|
||||||
|
│ │ │ │ │ │ ├── dashboard v0.1.0 (/home/avii/source/rust/trekstor/dashboard)
|
||||||
|
│ │ │ │ │ │ └── trekstor v0.1.0 (/home/avii/source/rust/trekstor/trekstor)
|
||||||
|
│ │ │ │ │ │ └── dashboard v0.1.0 (/home/avii/source/rust/trekstor/dashboard)
|
||||||
|
│ │ │ │ │ ├── i-slint-renderer-software v1.17.1
|
||||||
|
│ │ │ │ │ │ └── slint v1.17.1 (*)
|
||||||
|
│ │ │ │ │ └── slint v1.17.1 (*)
|
||||||
|
│ │ │ │ ├── i-slint-renderer-software v1.17.1 (*)
|
||||||
|
│ │ │ │ └── slint v1.17.1 (*)
|
||||||
|
│ │ │ └── i-slint-core v1.17.1 (*)
|
||||||
|
│ │ └── usvg v0.47.0
|
||||||
|
│ │ └── resvg v0.47.0 (*)
|
||||||
|
│ └── usvg v0.47.0 (*)
|
||||||
|
├── lyon_geom v1.0.19
|
||||||
|
│ ├── i-slint-core v1.17.1 (*)
|
||||||
|
│ └── lyon_path v1.0.19
|
||||||
|
│ ├── i-slint-core v1.17.1 (*)
|
||||||
|
│ ├── i-slint-renderer-software v1.17.1 (*)
|
||||||
|
│ ├── lyon_algorithms v1.0.20
|
||||||
|
│ │ └── i-slint-core v1.17.1 (*)
|
||||||
|
│ └── lyon_extra v1.1.0
|
||||||
|
│ └── i-slint-core v1.17.1 (*)
|
||||||
|
├── polycool v0.4.0
|
||||||
|
│ └── kurbo v0.13.1 (*)
|
||||||
|
├── taffy v0.10.1
|
||||||
|
│ └── i-slint-core v1.17.1 (*)
|
||||||
|
└── tiny-skia v0.12.0
|
||||||
|
└── resvg v0.47.0 (*)
|
||||||
|
|
||||||
|
arrayvec v0.7.8
|
||||||
|
└── lyon_geom v1.0.19
|
||||||
|
└── lyon_path v1.0.19
|
||||||
|
├── i-slint-compiler v1.17.1
|
||||||
|
│ └── slint-macros v1.17.1 (proc-macro)
|
||||||
|
│ └── slint v1.17.1 (*)
|
||||||
|
└── lyon_extra v1.1.0
|
||||||
|
└── i-slint-compiler v1.17.1 (*)
|
||||||
|
|
||||||
|
euclid v0.22.14
|
||||||
|
├── i-slint-core v1.17.1 (*)
|
||||||
|
├── i-slint-renderer-software v1.17.1 (*)
|
||||||
|
└── lyon_geom v1.0.19 (*)
|
||||||
|
|
||||||
|
euclid v0.22.14
|
||||||
|
└── lyon_geom v1.0.19 (*)
|
||||||
|
|
||||||
|
font-types v0.11.3
|
||||||
|
└── read-fonts v0.39.2
|
||||||
|
├── fontique v0.10.0
|
||||||
|
│ ├── i-slint-common v1.17.1 (*)
|
||||||
|
│ └── parley v0.10.0
|
||||||
|
│ └── i-slint-core v1.17.1 (*)
|
||||||
|
├── harfrust v0.8.4
|
||||||
|
│ └── parley v0.10.0 (*)
|
||||||
|
└── skrifa v0.42.1
|
||||||
|
├── i-slint-common v1.17.1 (*)
|
||||||
|
├── i-slint-core v1.17.1 (*)
|
||||||
|
├── i-slint-renderer-software v1.17.1 (*)
|
||||||
|
└── parley v0.10.0 (*)
|
||||||
|
|
||||||
|
font-types v0.12.2
|
||||||
|
└── read-fonts v0.41.0
|
||||||
|
└── skrifa v0.44.0
|
||||||
|
└── swash v0.2.10
|
||||||
|
├── i-slint-core v1.17.1 (*)
|
||||||
|
└── i-slint-renderer-software v1.17.1 (*)
|
||||||
|
|
||||||
|
hashbrown v0.14.5
|
||||||
|
└── rowan v0.16.1
|
||||||
|
└── i-slint-compiler v1.17.1 (*)
|
||||||
|
|
||||||
|
hashbrown v0.16.1
|
||||||
|
└── clru v0.6.3
|
||||||
|
├── i-slint-core v1.17.1 (*)
|
||||||
|
└── i-slint-renderer-software v1.17.1 (*)
|
||||||
|
|
||||||
|
hashbrown v0.17.1
|
||||||
|
└── indexmap v2.14.0
|
||||||
|
└── toml_edit v0.25.13+spec-1.1.0
|
||||||
|
└── proc-macro-crate v3.5.0
|
||||||
|
└── num_enum_derive v0.7.6 (proc-macro)
|
||||||
|
└── num_enum v0.7.6
|
||||||
|
└── i-slint-compiler v1.17.1 (*)
|
||||||
|
|
||||||
|
hashbrown v0.17.1
|
||||||
|
├── fontique v0.10.0 (*)
|
||||||
|
└── parley v0.10.0 (*)
|
||||||
|
|
||||||
|
i-slint-common v1.17.1 (*)
|
||||||
|
|
||||||
|
i-slint-common v1.17.1
|
||||||
|
└── i-slint-compiler v1.17.1 (*)
|
||||||
|
[build-dependencies]
|
||||||
|
└── i-slint-backend-selector v1.17.1 (*)
|
||||||
|
|
||||||
|
lyon_extra v1.1.0 (*)
|
||||||
|
|
||||||
|
lyon_extra v1.1.0 (*)
|
||||||
|
|
||||||
|
lyon_geom v1.0.19 (*)
|
||||||
|
|
||||||
|
lyon_geom v1.0.19 (*)
|
||||||
|
|
||||||
|
lyon_path v1.0.19 (*)
|
||||||
|
|
||||||
|
lyon_path v1.0.19 (*)
|
||||||
|
|
||||||
|
num-traits v0.2.19
|
||||||
|
├── chrono v0.4.45
|
||||||
|
│ └── i-slint-core v1.17.1 (*)
|
||||||
|
├── euclid v0.22.14 (*)
|
||||||
|
├── i-slint-core v1.17.1 (*)
|
||||||
|
├── i-slint-renderer-software v1.17.1 (*)
|
||||||
|
├── image v0.25.10
|
||||||
|
│ └── i-slint-core v1.17.1 (*)
|
||||||
|
├── integer-sqrt v0.1.5
|
||||||
|
│ └── i-slint-renderer-software v1.17.1 (*)
|
||||||
|
├── lyon_algorithms v1.0.20 (*)
|
||||||
|
├── lyon_geom v1.0.19 (*)
|
||||||
|
├── lyon_path v1.0.19 (*)
|
||||||
|
├── moxcms v0.8.1
|
||||||
|
│ └── image v0.25.10 (*)
|
||||||
|
└── slint v1.17.1 (*)
|
||||||
|
|
||||||
|
num-traits v0.2.19
|
||||||
|
├── euclid v0.22.14 (*)
|
||||||
|
├── lyon_geom v1.0.19 (*)
|
||||||
|
└── lyon_path v1.0.19 (*)
|
||||||
|
|
||||||
|
read-fonts v0.39.2 (*)
|
||||||
|
|
||||||
|
read-fonts v0.41.0 (*)
|
||||||
|
|
||||||
|
skrifa v0.42.1 (*)
|
||||||
|
|
||||||
|
skrifa v0.44.0 (*)
|
||||||
|
|
||||||
|
smallvec v1.15.2
|
||||||
|
└── idna v1.1.0
|
||||||
|
└── url v2.5.8
|
||||||
|
└── i-slint-compiler v1.17.1 (*)
|
||||||
|
|
||||||
|
smallvec v1.15.2
|
||||||
|
├── fixed_decimal v0.7.2
|
||||||
|
│ └── icu_decimal v2.2.0
|
||||||
|
│ └── i-slint-common v1.17.1 (*)
|
||||||
|
├── fontique v0.10.0 (*)
|
||||||
|
├── harfrust v0.8.4 (*)
|
||||||
|
├── icu_normalizer v2.2.0
|
||||||
|
│ ├── i-slint-compiler v1.17.1 (*)
|
||||||
|
│ ├── i-slint-core v1.17.1 (*)
|
||||||
|
│ ├── idna_adapter v1.2.2
|
||||||
|
│ │ └── idna v1.1.0 (*)
|
||||||
|
│ └── parley v0.10.0 (*)
|
||||||
|
├── kurbo v0.13.1 (*)
|
||||||
|
└── rustybuzz v0.20.1
|
||||||
|
└── usvg v0.47.0 (*)
|
||||||
|
|
||||||
|
syn v2.0.119
|
||||||
|
├── const-field-offset-macro v0.2.0 (proc-macro)
|
||||||
|
│ └── const-field-offset v0.2.0
|
||||||
|
│ ├── i-slint-core v1.17.1 (*)
|
||||||
|
│ ├── slint v1.17.1 (*)
|
||||||
|
│ └── vtable v0.4.0
|
||||||
|
│ ├── i-slint-core v1.17.1 (*)
|
||||||
|
│ └── slint v1.17.1 (*)
|
||||||
|
├── derive_more-impl v2.1.1 (proc-macro)
|
||||||
|
│ └── derive_more v2.1.1
|
||||||
|
│ ├── i-slint-common v1.17.1 (*)
|
||||||
|
│ ├── i-slint-common v1.17.1 (*)
|
||||||
|
│ ├── i-slint-compiler v1.17.1 (*)
|
||||||
|
│ ├── i-slint-core v1.17.1 (*)
|
||||||
|
│ └── i-slint-renderer-software v1.17.1 (*)
|
||||||
|
├── i-slint-core-macros v1.17.1 (proc-macro)
|
||||||
|
│ ├── i-slint-backend-selector v1.17.1 (*)
|
||||||
|
│ ├── i-slint-core v1.17.1 (*)
|
||||||
|
│ └── slint v1.17.1 (*)
|
||||||
|
├── num_enum_derive v0.7.6 (proc-macro) (*)
|
||||||
|
├── pin-project-internal v1.1.13 (proc-macro)
|
||||||
|
│ └── pin-project v1.1.13
|
||||||
|
│ └── i-slint-core v1.17.1 (*)
|
||||||
|
├── strum_macros v0.28.0 (proc-macro)
|
||||||
|
│ └── strum v0.28.0
|
||||||
|
│ ├── i-slint-compiler v1.17.1 (*)
|
||||||
|
│ └── i-slint-core v1.17.1 (*)
|
||||||
|
├── synstructure v0.13.2
|
||||||
|
│ ├── yoke-derive v0.8.2 (proc-macro)
|
||||||
|
│ │ └── yoke v0.8.3
|
||||||
|
│ │ ├── icu_collections v2.2.0
|
||||||
|
│ │ │ ├── icu_locale v2.2.0
|
||||||
|
│ │ │ │ ├── icu_decimal v2.2.0 (*)
|
||||||
|
│ │ │ │ └── icu_segmenter v2.2.0
|
||||||
|
│ │ │ │ └── parley v0.10.0 (*)
|
||||||
|
│ │ │ ├── icu_normalizer v2.2.0 (*)
|
||||||
|
│ │ │ ├── icu_properties v2.2.0
|
||||||
|
│ │ │ │ ├── idna_adapter v1.2.2 (*)
|
||||||
|
│ │ │ │ ├── parley v0.10.0 (*)
|
||||||
|
│ │ │ │ └── parley_data v0.10.0
|
||||||
|
│ │ │ │ └── parley v0.10.0 (*)
|
||||||
|
│ │ │ └── icu_segmenter v2.2.0 (*)
|
||||||
|
│ │ ├── icu_provider v2.2.0
|
||||||
|
│ │ │ ├── i-slint-common v1.17.1 (*)
|
||||||
|
│ │ │ ├── icu_decimal v2.2.0 (*)
|
||||||
|
│ │ │ ├── icu_locale v2.2.0 (*)
|
||||||
|
│ │ │ ├── icu_normalizer v2.2.0 (*)
|
||||||
|
│ │ │ ├── icu_properties v2.2.0 (*)
|
||||||
|
│ │ │ └── icu_segmenter v2.2.0 (*)
|
||||||
|
│ │ ├── zerotrie v0.2.4
|
||||||
|
│ │ │ ├── icu_properties v2.2.0 (*)
|
||||||
|
│ │ │ └── icu_provider v2.2.0 (*)
|
||||||
|
│ │ └── zerovec v0.11.6
|
||||||
|
│ │ ├── icu_collections v2.2.0 (*)
|
||||||
|
│ │ ├── icu_decimal v2.2.0 (*)
|
||||||
|
│ │ ├── icu_locale v2.2.0 (*)
|
||||||
|
│ │ ├── icu_locale_core v2.2.0
|
||||||
|
│ │ │ ├── i-slint-common v1.17.1 (*)
|
||||||
|
│ │ │ ├── icu_decimal v2.2.0 (*)
|
||||||
|
│ │ │ ├── icu_locale v2.2.0 (*)
|
||||||
|
│ │ │ ├── icu_properties v2.2.0 (*)
|
||||||
|
│ │ │ └── icu_provider v2.2.0 (*)
|
||||||
|
│ │ ├── icu_normalizer v2.2.0 (*)
|
||||||
|
│ │ ├── icu_properties v2.2.0 (*)
|
||||||
|
│ │ ├── icu_provider v2.2.0 (*)
|
||||||
|
│ │ ├── icu_segmenter v2.2.0 (*)
|
||||||
|
│ │ ├── potential_utf v0.1.5
|
||||||
|
│ │ │ ├── icu_collections v2.2.0 (*)
|
||||||
|
│ │ │ ├── icu_locale v2.2.0 (*)
|
||||||
|
│ │ │ └── icu_segmenter v2.2.0 (*)
|
||||||
|
│ │ └── tinystr v0.8.3
|
||||||
|
│ │ ├── icu_locale v2.2.0 (*)
|
||||||
|
│ │ └── icu_locale_core v2.2.0 (*)
|
||||||
|
│ └── zerofrom-derive v0.1.7 (proc-macro)
|
||||||
|
│ └── zerofrom v0.1.8
|
||||||
|
│ ├── icu_collections v2.2.0 (*)
|
||||||
|
│ ├── icu_provider v2.2.0 (*)
|
||||||
|
│ ├── yoke v0.8.3 (*)
|
||||||
|
│ ├── zerotrie v0.2.4 (*)
|
||||||
|
│ └── zerovec v0.11.6 (*)
|
||||||
|
├── vtable-macro v0.4.0 (proc-macro)
|
||||||
|
│ └── vtable v0.4.0 (*)
|
||||||
|
├── yoke-derive v0.8.2 (proc-macro) (*)
|
||||||
|
├── zerofrom-derive v0.1.7 (proc-macro) (*)
|
||||||
|
└── zerovec-derive v0.11.3 (proc-macro)
|
||||||
|
└── zerovec v0.11.6 (*)
|
||||||
|
|
||||||
|
syn v3.0.3
|
||||||
|
├── auto_enums v0.8.10 (proc-macro)
|
||||||
|
│ └── i-slint-core v1.17.1 (*)
|
||||||
|
├── bytemuck_derive v1.12.0 (proc-macro)
|
||||||
|
│ └── bytemuck v1.25.2
|
||||||
|
│ ├── font-types v0.11.3 (*)
|
||||||
|
│ ├── font-types v0.12.2 (*)
|
||||||
|
│ ├── harfrust v0.8.4 (*)
|
||||||
|
│ ├── i-slint-renderer-software v1.17.1 (*)
|
||||||
|
│ ├── image v0.25.10 (*)
|
||||||
|
│ ├── read-fonts v0.39.2 (*)
|
||||||
|
│ ├── read-fonts v0.41.0 (*)
|
||||||
|
│ ├── rgb v0.8.53
|
||||||
|
│ │ ├── i-slint-core v1.17.1 (*)
|
||||||
|
│ │ └── resvg v0.47.0 (*)
|
||||||
|
│ ├── rustybuzz v0.20.1 (*)
|
||||||
|
│ ├── skrifa v0.42.1 (*)
|
||||||
|
│ ├── skrifa v0.44.0 (*)
|
||||||
|
│ ├── tiny-skia v0.12.0 (*)
|
||||||
|
│ └── tiny-skia-path v0.12.0
|
||||||
|
│ ├── tiny-skia v0.12.0 (*)
|
||||||
|
│ └── usvg v0.47.0 (*)
|
||||||
|
├── derive_utils v0.16.0
|
||||||
|
│ └── auto_enums v0.8.10 (proc-macro) (*)
|
||||||
|
├── displaydoc v0.2.7 (proc-macro)
|
||||||
|
│ ├── fixed_decimal v0.7.2 (*)
|
||||||
|
│ ├── icu_collections v2.2.0 (*)
|
||||||
|
│ ├── icu_decimal v2.2.0 (*)
|
||||||
|
│ ├── icu_locale_core v2.2.0 (*)
|
||||||
|
│ ├── icu_provider v2.2.0 (*)
|
||||||
|
│ ├── tinystr v0.8.3 (*)
|
||||||
|
│ └── zerotrie v0.2.4 (*)
|
||||||
|
└── thiserror-impl v2.0.19 (proc-macro)
|
||||||
|
├── thiserror v2.0.19
|
||||||
|
│ └── lyon_extra v1.1.0 (*)
|
||||||
|
└── thiserror v2.0.19
|
||||||
|
└── lyon_extra v1.1.0 (*)
|
||||||
|
|
||||||
|
thiserror v2.0.19 (*)
|
||||||
|
|
||||||
|
thiserror v2.0.19 (*)
|
||||||
1
trekstor/.gitignore
vendored
Normal file
1
trekstor/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
4747
trekstor/Cargo.lock
generated
Normal file
4747
trekstor/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
10
trekstor/Cargo.toml
Normal file
10
trekstor/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "trekstor"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
slint.workspace = true
|
||||||
|
|
||||||
|
evdev = "0.13.2"
|
||||||
|
libc = "0.2.189"
|
||||||
74
trekstor/src/framebuffer/ffi.rs
Normal file
74
trekstor/src/framebuffer/ffi.rs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
//! Kernel ABI: framebuffer + Kindle EPDC structs and ioctl numbers.
|
||||||
|
|
||||||
|
// Standard Linux framebuffer ioctl numbers (see <linux/fb.h>).
|
||||||
|
// Typed as c_ulong (not libc::Ioctl) so the crate still type-checks on
|
||||||
|
// non-Linux dev hosts where libc::Ioctl isn't defined, like macos
|
||||||
|
pub(super) const FBIOGET_VSCREENINFO: libc::c_ulong = 0x4600;
|
||||||
|
pub(super) const FBIOGET_FSCREENINFO: libc::c_ulong = 0x4602;
|
||||||
|
|
||||||
|
// These structs mirror the kernel's `fb_var_screeninfo` and `fb_fix_screeninfo`.
|
||||||
|
// We only read from them, fields we care about are `xres`, `yres` (visible
|
||||||
|
// resolution) and `line_length` (stride in bytes per row, which may be larger
|
||||||
|
// than xres due to alignment padding).
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(super) struct FbBitfield {
|
||||||
|
pub(super) offset: u32,
|
||||||
|
pub(super) length: u32,
|
||||||
|
pub(super) msb_right: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(super) struct FbVarScreeninfo {
|
||||||
|
pub(super) xres: u32,
|
||||||
|
pub(super) yres: u32,
|
||||||
|
pub(super) xres_virtual: u32,
|
||||||
|
pub(super) yres_virtual: u32,
|
||||||
|
pub(super) xoffset: u32,
|
||||||
|
pub(super) yoffset: u32,
|
||||||
|
pub(super) bits_per_pixel: u32,
|
||||||
|
pub(super) grayscale: u32,
|
||||||
|
pub(super) red: FbBitfield,
|
||||||
|
pub(super) green: FbBitfield,
|
||||||
|
pub(super) blue: FbBitfield,
|
||||||
|
pub(super) transp: FbBitfield,
|
||||||
|
pub(super) nonstd: u32,
|
||||||
|
pub(super) activate: u32,
|
||||||
|
pub(super) height: u32,
|
||||||
|
pub(super) width: u32,
|
||||||
|
pub(super) accel_flags: u32,
|
||||||
|
pub(super) pixclock: u32,
|
||||||
|
pub(super) left_margin: u32,
|
||||||
|
pub(super) right_margin: u32,
|
||||||
|
pub(super) upper_margin: u32,
|
||||||
|
pub(super) lower_margin: u32,
|
||||||
|
pub(super) hsync_len: u32,
|
||||||
|
pub(super) vsync_len: u32,
|
||||||
|
pub(super) sync: u32,
|
||||||
|
pub(super) vmode: u32,
|
||||||
|
pub(super) rotate: u32,
|
||||||
|
pub(super) colorspace: u32,
|
||||||
|
pub(super) reserved: [u32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(super) struct FbFixScreeninfo {
|
||||||
|
pub(super) id: [u8; 16],
|
||||||
|
pub(super) smem_start: libc::c_ulong,
|
||||||
|
pub(super) smem_len: u32,
|
||||||
|
pub(super) type_: u32,
|
||||||
|
pub(super) type_aux: u32,
|
||||||
|
pub(super) visual: u32,
|
||||||
|
pub(super) xpanstep: u16,
|
||||||
|
pub(super) ypanstep: u16,
|
||||||
|
pub(super) ywrapstep: u16,
|
||||||
|
pub(super) line_length: u32,
|
||||||
|
pub(super) mmio_start: libc::c_ulong,
|
||||||
|
pub(super) mmio_len: u32,
|
||||||
|
pub(super) accel: u32,
|
||||||
|
pub(super) capabilities: u16,
|
||||||
|
pub(super) reserved: [u16; 2],
|
||||||
|
}
|
||||||
135
trekstor/src/framebuffer/mod.rs
Normal file
135
trekstor/src/framebuffer/mod.rs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
mod ffi;
|
||||||
|
|
||||||
|
use std::os::fd::AsRawFd;
|
||||||
|
|
||||||
|
use ffi::{FBIOGET_FSCREENINFO, FBIOGET_VSCREENINFO, FbFixScreeninfo, FbVarScreeninfo};
|
||||||
|
|
||||||
|
/// Memory-mapped handle to the Kindle's e-ink framebuffer.
|
||||||
|
///
|
||||||
|
/// Pixel format is 8-bit grayscale (one byte per pixel). The `stride` may be
|
||||||
|
/// wider than `width` due to hardware alignment requirements.
|
||||||
|
pub(crate) struct Framebuffer {
|
||||||
|
map: *mut u8,
|
||||||
|
len: usize,
|
||||||
|
pub(crate) width: u32,
|
||||||
|
pub(crate) height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: The mmap is process-wide and we only access it from the event loop thread.
|
||||||
|
unsafe impl Send for Framebuffer {}
|
||||||
|
|
||||||
|
impl Framebuffer {
|
||||||
|
/// Open the framebuffer device and query its geometry from the kernel.
|
||||||
|
///
|
||||||
|
/// This works on any Kindle model - the resolution and stride are read at
|
||||||
|
/// runtime rather than being hardcoded.
|
||||||
|
pub(crate) fn open() -> std::io::Result<Self> {
|
||||||
|
let file = std::fs::OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open("/dev/fb0")?;
|
||||||
|
|
||||||
|
let fd = file.as_raw_fd();
|
||||||
|
|
||||||
|
let mut vinfo = FbVarScreeninfo::default();
|
||||||
|
if unsafe {
|
||||||
|
libc::ioctl(
|
||||||
|
fd,
|
||||||
|
FBIOGET_VSCREENINFO as _,
|
||||||
|
&mut vinfo as *mut _ as *mut libc::c_void,
|
||||||
|
)
|
||||||
|
} == -1
|
||||||
|
{
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut finfo = FbFixScreeninfo::default();
|
||||||
|
if unsafe {
|
||||||
|
libc::ioctl(
|
||||||
|
fd,
|
||||||
|
FBIOGET_FSCREENINFO as _,
|
||||||
|
&mut finfo as *mut _ as *mut libc::c_void,
|
||||||
|
)
|
||||||
|
} == -1
|
||||||
|
{
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = vinfo.xres;
|
||||||
|
let height = vinfo.yres;
|
||||||
|
let stride = finfo.line_length as usize;
|
||||||
|
|
||||||
|
// The whole render path treats the mmap as four bytes per pixel. A
|
||||||
|
// different depth would silently produce garbled output, so reject it
|
||||||
|
// with a clear error instead.
|
||||||
|
if vinfo.bits_per_pixel != 32 {
|
||||||
|
return Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidData,
|
||||||
|
format!(
|
||||||
|
"unsupported framebuffer depth: {} bpp (expected 8-bit grayscale)",
|
||||||
|
vinfo.bits_per_pixel
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if width == 0 || height == 0 || stride < width as usize {
|
||||||
|
return Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidData,
|
||||||
|
format!("invalid framebuffer geometry: {width}x{height}, stride={stride}"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = stride * height as usize;
|
||||||
|
|
||||||
|
let map = unsafe {
|
||||||
|
libc::mmap(
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
len,
|
||||||
|
libc::PROT_READ | libc::PROT_WRITE,
|
||||||
|
libc::MAP_SHARED,
|
||||||
|
fd,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if map == libc::MAP_FAILED {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
map: map as *mut u8,
|
||||||
|
len,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn write_buffer(
|
||||||
|
&mut self,
|
||||||
|
pixels: &[slint::platform::software_renderer::Rgb565Pixel],
|
||||||
|
) {
|
||||||
|
let dst = unsafe { std::slice::from_raw_parts_mut(self.map as *mut u32, pixels.len()) };
|
||||||
|
|
||||||
|
for (dst, src) in dst.iter_mut().zip(pixels) {
|
||||||
|
let c = src.0; // assuming Rgb565Pixel(pub u16)
|
||||||
|
|
||||||
|
let r5 = ((c >> 11) & 0x1f) as u8;
|
||||||
|
let g6 = ((c >> 5) & 0x3f) as u8;
|
||||||
|
let b5 = (c & 0x1f) as u8;
|
||||||
|
|
||||||
|
// Expand to 8 bits.
|
||||||
|
let r = (r5 << 3) | (r5 >> 2);
|
||||||
|
let g = (g6 << 2) | (g6 >> 4);
|
||||||
|
let b = (b5 << 3) | (b5 >> 2);
|
||||||
|
|
||||||
|
// BGRA8888
|
||||||
|
*dst = u32::from_le_bytes([b, g, r, 0xff]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Framebuffer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { libc::munmap(self.map.cast::<libc::c_void>(), self.len) };
|
||||||
|
}
|
||||||
|
}
|
||||||
101
trekstor/src/input.rs
Normal file
101
trekstor/src/input.rs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use evdev::{AbsoluteAxisCode, Device, EventSummary, KeyCode};
|
||||||
|
|
||||||
|
pub struct InputState {
|
||||||
|
device: Option<Device>,
|
||||||
|
pub x: i32,
|
||||||
|
pub y: i32,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
keys_down: HashSet<KeyCode>,
|
||||||
|
keys_pressed_this_update: HashSet<KeyCode>,
|
||||||
|
keys_released_this_update: HashSet<KeyCode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputState {
|
||||||
|
pub fn new(w: u32, h: u32) -> Self {
|
||||||
|
let device = evdev::enumerate()
|
||||||
|
.find(|(_, device)| device.name() == Some("silead_ts"))
|
||||||
|
.map(|(_, d)| {
|
||||||
|
d.set_nonblocking(true).ok();
|
||||||
|
d
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
device,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: w.cast_signed(),
|
||||||
|
height: h.cast_signed(),
|
||||||
|
keys_down: HashSet::new(),
|
||||||
|
keys_pressed_this_update: HashSet::new(),
|
||||||
|
keys_released_this_update: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn finish(&mut self) {
|
||||||
|
self.keys_pressed_this_update.clear();
|
||||||
|
self.keys_released_this_update.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn handle_event(&mut self) {
|
||||||
|
let Some(device) = &mut self.device else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(events) = device.fetch_events() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
for event in events {
|
||||||
|
match event.destructure() {
|
||||||
|
EventSummary::AbsoluteAxis(_, AbsoluteAxisCode::ABS_X, value) => {
|
||||||
|
self.x = ((((value - 13) as f32 / 882.) * (self.width as f32)) as i32)
|
||||||
|
.max(0)
|
||||||
|
.min(self.width);
|
||||||
|
}
|
||||||
|
EventSummary::AbsoluteAxis(_, AbsoluteAxisCode::ABS_Y, value) => {
|
||||||
|
self.y = ((((value - 13) as f32 / 624.) * (self.height as f32)) as i32)
|
||||||
|
.max(0)
|
||||||
|
.min(self.width);
|
||||||
|
}
|
||||||
|
EventSummary::Key(_, key, value) => match value {
|
||||||
|
1 => {
|
||||||
|
if !self.keys_down.contains(&key) {
|
||||||
|
self.keys_pressed_this_update.insert(key);
|
||||||
|
}
|
||||||
|
self.keys_down.insert(key);
|
||||||
|
}
|
||||||
|
0 => {
|
||||||
|
if self.keys_down.contains(&key) {
|
||||||
|
self.keys_released_this_update.insert(key);
|
||||||
|
}
|
||||||
|
self.keys_down.remove(&key);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
impl InputState {
|
||||||
|
pub fn is_just_pressed(&self, key: KeyCode) -> bool {
|
||||||
|
self.keys_pressed_this_update.contains(&key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_down(&self, key: KeyCode) -> bool {
|
||||||
|
self.keys_down.contains(&key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_just_released(&self, key: KeyCode) -> bool {
|
||||||
|
self.keys_released_this_update.contains(&key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_up(&self, key: KeyCode) -> bool {
|
||||||
|
!self.is_down(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
153
trekstor/src/lib.rs
Normal file
153
trekstor/src/lib.rs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
#![allow(clippy::borrow_as_ptr)]
|
||||||
|
|
||||||
|
mod framebuffer;
|
||||||
|
mod input;
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use evdev::KeyCode;
|
||||||
|
use slint::platform::software_renderer::{MinimalSoftwareWindow, RepaintBufferType, Rgb565Pixel};
|
||||||
|
use slint::platform::{EventLoopProxy, Platform, WindowEvent};
|
||||||
|
use slint::{EventLoopError, LogicalPosition, PhysicalSize, PlatformError, WindowSize};
|
||||||
|
|
||||||
|
use self::framebuffer::Framebuffer;
|
||||||
|
use self::input::InputState;
|
||||||
|
|
||||||
|
pub struct Trekstor {
|
||||||
|
window: Rc<MinimalSoftwareWindow>,
|
||||||
|
queue: Queue,
|
||||||
|
quit_flag: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Trekstor {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Trekstor {
|
||||||
|
fn default() -> Self {
|
||||||
|
let window = MinimalSoftwareWindow::new(RepaintBufferType::NewBuffer);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
window,
|
||||||
|
queue: Arc::new(Mutex::new(Vec::new())),
|
||||||
|
quit_flag: Arc::new(AtomicBool::new(false)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Closure = Box<dyn FnOnce() + Send>;
|
||||||
|
type Queue = Arc<Mutex<Vec<Closure>>>;
|
||||||
|
|
||||||
|
struct TrekstorEventProxy {
|
||||||
|
queue: Queue,
|
||||||
|
quit_flag: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventLoopProxy for TrekstorEventProxy {
|
||||||
|
fn quit_event_loop(&self) -> Result<(), EventLoopError> {
|
||||||
|
self.quit_flag.store(true, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invoke_from_event_loop(&self, event: Closure) -> Result<(), EventLoopError> {
|
||||||
|
// Don't queue if the loop is shutting down, the closure would never
|
||||||
|
// run, and any caller blocking on a channel send inside it would hang.
|
||||||
|
if self.quit_flag.load(Ordering::SeqCst) {
|
||||||
|
return Err(EventLoopError::EventLoopTerminated);
|
||||||
|
}
|
||||||
|
self.queue
|
||||||
|
.lock()
|
||||||
|
.expect("event loop closure queue poisoned")
|
||||||
|
.push(event);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Platform for Trekstor {
|
||||||
|
fn create_window_adapter(
|
||||||
|
&self,
|
||||||
|
) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
|
||||||
|
Ok(self.window.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
|
||||||
|
let mut frame_buffer = Framebuffer::open()
|
||||||
|
.map_err(|e| PlatformError::Other(format!("failed to open /dev/fb0: {e}")))?;
|
||||||
|
|
||||||
|
let mut input = InputState::new(frame_buffer.width, frame_buffer.height);
|
||||||
|
|
||||||
|
#[allow(clippy::large_stack_arrays)] // causes stack overflow if rgba8
|
||||||
|
let mut buffer1 = [Rgb565Pixel(0); 614_400];
|
||||||
|
|
||||||
|
self.window.set_size(WindowSize::Physical(PhysicalSize::new(
|
||||||
|
frame_buffer.width,
|
||||||
|
frame_buffer.height,
|
||||||
|
)));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
input.handle_event();
|
||||||
|
|
||||||
|
slint::platform::update_timers_and_animations();
|
||||||
|
|
||||||
|
if input.is_just_pressed(KeyCode::BTN_TOUCH) {
|
||||||
|
self.window
|
||||||
|
.try_dispatch_event(WindowEvent::PointerPressed {
|
||||||
|
position: LogicalPosition::new(input.x as f32, input.y as f32),
|
||||||
|
button: slint::platform::PointerEventButton::Left,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.is_just_released(KeyCode::BTN_TOUCH) {
|
||||||
|
self.window
|
||||||
|
.try_dispatch_event(WindowEvent::PointerReleased {
|
||||||
|
position: LogicalPosition::new(input.x as f32, input.y as f32),
|
||||||
|
button: slint::platform::PointerEventButton::Left,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.is_down(KeyCode::BTN_TOUCH) {
|
||||||
|
self.window
|
||||||
|
.try_dispatch_event(WindowEvent::PointerMoved {
|
||||||
|
position: LogicalPosition::new(input.x as f32, input.y as f32),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.quit_flag.load(Ordering::SeqCst) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.window.draw_if_needed(|renderer| {
|
||||||
|
renderer.render(&mut buffer1, frame_buffer.width as usize);
|
||||||
|
frame_buffer.write_buffer(&buffer1);
|
||||||
|
});
|
||||||
|
|
||||||
|
input.finish();
|
||||||
|
|
||||||
|
let pending: Vec<_> = self
|
||||||
|
.queue
|
||||||
|
.lock()
|
||||||
|
.expect("event loop closure queue poisoned")
|
||||||
|
.drain(..)
|
||||||
|
.collect();
|
||||||
|
for c in pending {
|
||||||
|
c();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
|
||||||
|
Some(Box::new(TrekstorEventProxy {
|
||||||
|
queue: self.queue.clone(),
|
||||||
|
quit_flag: self.quit_flag.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user