Skip to content

Commit

Permalink
feat: add custom output example
Browse files Browse the repository at this point in the history
  • Loading branch information
Faervan committed Jan 9, 2025
1 parent 2a942f4 commit fdd3da6
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 74 deletions.
17 changes: 17 additions & 0 deletions examples/sctk_custom_output/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "sctk_custom_output"
version = "0.1.0"
edition.workspace = true
license.workspace = true

[dependencies]
iced = { path = "../..", features = [
"debug",
"lazy",
"wayland",
"winit",
"tokio",
"tiny-skia",
"advanced",
], default-features = false }
tokio = { workspace = true, features = ["time"] }
130 changes: 130 additions & 0 deletions examples/sctk_custom_output/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use std::env;
use std::time::Duration;

use iced::platform_specific::shell::commands::layer_surface::get_layer_surface;
use iced::platform_specific::shell::commands::output::{
get_output, get_output_info,
};
use iced::runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings;
use iced::widget::text;
use iced::{daemon, stream};
use iced::{
platform_specific::shell::commands::output::OutputInfo,
runtime::platform_specific::wayland::layer_surface::IcedOutput, Element,
Task,
};

use iced::window::Id;
use tokio::time::sleep;

fn main() -> iced::Result {
daemon("Custom Output", App::update, App::view).run_with(App::new)
}

#[derive(Debug)]
enum Message {
Output(Option<IcedOutput>),
OutputInfo(Option<OutputInfo>),
}

#[derive(Debug)]
struct App {
monitor: Option<String>,
output: IcedOutput,
logical_size: Option<(i32, i32)>,
}

impl App {
fn new() -> (App, Task<Message>) {
let app = App {
monitor: env::var("WL_OUTPUT").ok(),
output: IcedOutput::Active,
logical_size: None,
};

let task = match &app.monitor {
Some(_) => app.try_get_output(),
None => app.open(),
};

(app, task)
}

fn try_get_output(&self) -> Task<Message> {
let monitor = self.monitor.clone();
get_output(move |output_state| {
output_state
.outputs()
.find(|o| {
output_state
.info(o)
.map(|info| info.name == monitor)
.unwrap_or(false)
})
.clone()
})
.map(|optn| Message::Output(optn.map(IcedOutput::Output)))
}

fn try_get_output_info(&self) -> Task<Message> {
let monitor = self.monitor.clone();
get_output_info(move |output_state| {
output_state
.outputs()
.find(|o| {
output_state
.info(o)
.map(|info| info.name == monitor)
.unwrap_or(false)
})
.and_then(|o| output_state.info(&o))
.clone()
})
.map(Message::OutputInfo)
}

fn open(&self) -> Task<Message> {
get_layer_surface(SctkLayerSurfaceSettings {
output: self.output.clone(),
size: match self.logical_size {
Some(size) => {
Some((Some((size.0 / 2) as u32), Some((size.1 / 2) as u32)))
}
None => Some((Some(800), Some(500))),
},
..Default::default()
})
}

fn update(&mut self, msg: Message) -> Task<Message> {
match msg {
Message::Output(optn) => match optn {
Some(output) => {
self.output = output;
self.try_get_output_info()
}
None => Task::stream(stream::channel(1, |_| async {
sleep(Duration::from_millis(500)).await;
}))
.chain(self.try_get_output()),
},
Message::OutputInfo(optn) => match optn {
Some(info) => {
self.logical_size = info.logical_size;
self.open()
}
None => Task::stream(stream::channel(1, |_| async {
sleep(Duration::from_millis(500)).await;
}))
.chain(self.try_get_output_info()),
},
}
}

fn view(&self, _window_id: Id) -> Element<Message> {
match &self.monitor {
Some(monitor) => text!("Opened on monitor {monitor}\nSize: {:?}", self.logical_size),
None => text!("No output specified, try setting WL_OUTPUT=YourMonitor\nDefaulting to size 800x500"),
}.into()
}
}
1 change: 0 additions & 1 deletion examples/sctk_lazy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,3 @@ iced = { path = "../..", features = [
"tiny-skia",
"advanced",
], default-features = false }
tokio = { workspace = true, features = ["time"] }
38 changes: 3 additions & 35 deletions examples/sctk_lazy/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@ use iced::platform_specific::shell::commands::layer_surface::{
get_layer_surface, KeyboardInteractivity,
};

use iced::platform_specific::shell::commands::output::get_output;
use iced::runtime::platform_specific::wayland::layer_surface::IcedOutput;
use iced::widget::{
button, column, horizontal_space, lazy, pick_list, row, scrollable, text,
text_input,
};
use iced::window::Id;
use iced::{stream, Task};
use iced::Task;
use iced::{Element, Length};

use std::collections::HashSet;
use std::hash::Hash;
use std::sync::Arc;

pub fn main() -> iced::Result {
iced::daemon(App::title, App::update, App::view).run_with(App::new)
Expand Down Expand Up @@ -129,27 +126,10 @@ enum Message {
DeleteItem(Item),
AddItem(String),
ItemColorChanged(Item, Color),
GotOutput(Option<IcedOutput>),
}

impl App {
fn new() -> (App, Task<Message>) {
(Self::default(), App::try_get_output())
}

fn try_get_output() -> Task<Message> {
let monitor = std::env::var("WL_OUTPUT").expect("Provide an Output (Monitor name) by setting env WL_OUTPUT!");
get_output(move |output_state| {
output_state.outputs().find(|o|
output_state
.info(o)
.map(|info| info.name.as_ref() == Some(&monitor))
.unwrap_or(false)
).clone()
}).map(|optn| Message::GotOutput(optn.map(|output| IcedOutput::Output(output))))
}

fn open(output: IcedOutput) -> Task<Message> {
let mut initial_surface = SctkLayerSurfaceSettings::default();
initial_surface.keyboard_interactivity =
KeyboardInteractivity::OnDemand;
Expand All @@ -159,15 +139,14 @@ impl App {
.max_height(500.0)
.max_width(900.0);
initial_surface.size = Some((Some(500), Some(500)));
initial_surface.output = output;
get_layer_surface(initial_surface)
(Self::default(), get_layer_surface(initial_surface))
}

fn title(&self, _id: Id) -> String {
String::from("Lazy - Iced")
}

fn update(&mut self, message: Message) -> Task<Message> {
fn update(&mut self, message: Message) {
match message {
Message::InputChanged(input) => {
self.input = input;
Expand Down Expand Up @@ -197,18 +176,7 @@ impl App {
});
}
}
Message::GotOutput(optn) => {
return match optn {
Some(output) => App::open(output),
None => Task::stream(stream::channel(1, |_s| async {
tokio::time::sleep(std::time::Duration::from_secs(1)).await
}))
.chain(App::try_get_output()
)
}
}
}
Task::none()
}

fn view(&self, _: Id) -> Element<Message> {
Expand Down
22 changes: 6 additions & 16 deletions runtime/src/platform_specific/wayland/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
use std::fmt::Debug;

use cctk::{sctk::output::{OutputInfo, OutputState}, wayland_client::protocol::wl_output::WlOutput};
use iced_core::window::Id;

use crate::oneshot;
Expand All @@ -16,6 +15,8 @@ pub mod layer_surface;
pub mod popup;
/// session locks
pub mod session_lock;
/// Output (monitor) getters
pub mod output;

/// Platform specific actions defined for wayland
pub enum Action {
Expand All @@ -29,16 +30,8 @@ pub enum Action {
SessionLock(session_lock::Action),
/// Overlap Notify
OverlapNotify(Id, bool),
// WlOutput getter
GetOutput {
f: Box<dyn Fn(&OutputState) -> Option<WlOutput> + Send + Sync>,
channel: oneshot::Sender<Option<WlOutput>>,
},
// OutputInfo getter
GetOutputInfo {
f: Box<dyn Fn(&OutputState) -> Option<OutputInfo> + Send + Sync>,
channel: oneshot::Sender<Option<OutputInfo>>,
}
/// Output (monitor) getters
Output(output::Action),
}

impl Debug for Action {
Expand All @@ -57,11 +50,8 @@ impl Debug for Action {
Action::OverlapNotify(id, _) => {
f.debug_tuple("OverlapNotify").field(id).finish()
}
Action::GetOutput { .. } => {
f.debug_tuple("GetOutput").finish()
}
Action::GetOutputInfo { .. } => {
f.debug_tuple("GetOutputInfo").finish()
Action::Output(arg0) => {
f.debug_tuple("GetOutput").field(arg0).finish()
}
}
}
Expand Down
30 changes: 30 additions & 0 deletions runtime/src/platform_specific/wayland/output.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use std::fmt;

use cctk::{
sctk::output::{OutputInfo, OutputState},
wayland_client::protocol::wl_output::WlOutput,
};

use crate::oneshot;

pub enum Action {
// WlOutput getter
GetOutput {
f: Box<dyn Fn(&OutputState) -> Option<WlOutput> + Send + Sync>,
channel: oneshot::Sender<Option<WlOutput>>,
},
// OutputInfo getter
GetOutputInfo {
f: Box<dyn Fn(&OutputState) -> Option<OutputInfo> + Send + Sync>,
channel: oneshot::Sender<Option<OutputInfo>>,
},
}

impl fmt::Debug for Action {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Action::GetOutput { .. } => write!(f, "Action::GetOutput"),
Action::GetOutputInfo { .. } => write!(f, "Action::GetOutputInfo",),
}
}
}
49 changes: 32 additions & 17 deletions winit/src/platform_specific/wayland/commands/output.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
use cctk::sctk::output::{OutputInfo, OutputState};
use iced_runtime::{platform_specific::{self, wayland}, task, Action, Task};
use wayland_client::protocol::wl_output::WlOutput;
pub use cctk::sctk::output::{OutputInfo, OutputState};
use iced_runtime::{
platform_specific::{self, wayland},
task, Action, Task,
};
pub use wayland_client::protocol::wl_output::WlOutput;

pub fn get_output<F>(f: F) -> Task<Option<WlOutput>> where F: Fn(&OutputState) -> Option<WlOutput> + Send + Sync + 'static {
task::oneshot(|channel| Action::PlatformSpecific(
platform_specific::Action::Wayland(
wayland::Action::GetOutput {
/// Get a
/// [WlOutput](https://docs.rs/wayland-client/latest/wayland_client/protocol/wl_output/struct.WlOutput.html) by calling a closure on a
/// [&OutputState](https://docs.rs/smithay-client-toolkit/latest/smithay_client_toolkit/output/struct.OutputState.html)
pub fn get_output<F>(f: F) -> Task<Option<WlOutput>>
where
F: Fn(&OutputState) -> Option<WlOutput> + Send + Sync + 'static,
{
task::oneshot(|channel| {
Action::PlatformSpecific(platform_specific::Action::Wayland(
wayland::Action::Output(wayland::output::Action::GetOutput {
f: Box::new(f),
channel,
}
)
))
}),
))
})
}

pub fn get_output_info<F>(f: F) -> Task<Option<OutputInfo>> where F: Fn(&OutputState) -> Option<OutputInfo> + Send + Sync + 'static {
task::oneshot(|channel| Action::PlatformSpecific(
platform_specific::Action::Wayland(
wayland::Action::GetOutputInfo {
/// Get a
/// [OutputInfo](https://docs.rs/smithay-client-toolkit/latest/smithay_client_toolkit/output/struct.OutputInfo.html) by calling a closure on a
/// [&OutputState](https://docs.rs/smithay-client-toolkit/latest/smithay_client_toolkit/output/struct.OutputState.html)
pub fn get_output_info<F>(f: F) -> Task<Option<OutputInfo>>
where
F: Fn(&OutputState) -> Option<OutputInfo> + Send + Sync + 'static,
{
task::oneshot(|channel| {
Action::PlatformSpecific(platform_specific::Action::Wayland(
wayland::Action::Output(wayland::output::Action::GetOutputInfo {
f: Box::new(f),
channel,
}
)
))
}),
))
})
}
12 changes: 7 additions & 5 deletions winit/src/platform_specific/wayland/event_loop/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1209,11 +1209,13 @@ impl SctkState {
tracing::error!("Overlap notify subscription cannot be created for surface. No matching layer surface found.");
}
},
Action::GetOutput { f, channel } => {
let _ = channel.send(f(&self.output_state));
}
Action::GetOutputInfo { f, channel } => {
let _ = channel.send(f(&self.output_state));
Action::Output(action) => match action {
platform_specific::wayland::output::Action::GetOutput { f, channel } => {
let _ = channel.send(f(&self.output_state));
}
platform_specific::wayland::output::Action::GetOutputInfo { f, channel } => {
let _ = channel.send(f(&self.output_state));
}
}
};
Ok(())
Expand Down

0 comments on commit fdd3da6

Please sign in to comment.