Skip to content

Commit d22a0b4

Browse files
committed
feat: display transformations
refactors image loading/mode-setting to smoothly fall back to static mode, which is generally necessary when rotating a display.
1 parent 6937aef commit d22a0b4

File tree

6 files changed

+191
-109
lines changed

6 files changed

+191
-109
lines changed

src/daemon.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ pub trait Daemon {
1515

1616
fn handle_cmd(self: Arc<Self>, cmd: &CommandType);
1717

18-
fn load_image(self: Arc<Self>, path: &str) -> Result<(), DaemonError>;
19-
fn get_image_dimensions(self: Arc<Self>, img: &str) -> Result<(u32, u32), DaemonError>;
18+
fn load_image(self: Arc<Self>, path: &str) -> Result<(u32, u32), DaemonError>;
2019
fn read_img_to_file(
2120
self: Arc<Self>,
2221
img: &str,

src/pandora.rs

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ impl Daemon for Pandora {
8181
Anchor::Top | Anchor::Left | Anchor::Bottom | Anchor::Right,
8282
);
8383
layer_surface.set_exclusive_zone(conn, -1);
84-
84+
8585
wl_surface.commit(conn);
8686
conn.blocking_roundtrip().unwrap();
8787
}
@@ -94,22 +94,23 @@ impl Daemon for Pandora {
9494
};
9595
}
9696

97-
fn load_image(self: Arc<Self>, path: &str) -> Result<(), DaemonError> {
97+
fn load_image(self: Arc<Self>, path: &str) -> Result<(u32, u32), DaemonError> {
9898
let start = std::time::Instant::now();
9999
{
100100
// read lock
101101
match self.images.read() {
102102
Ok(images_check) => {
103-
if images_check.contains_key(&path.to_string()) {
103+
if let Some(img) = images_check.get(&path.to_string()) {
104104
self.debug("pandora", format!("file {} already loaded", path));
105-
return Ok(());
105+
return Ok((img.width(), img.height()));
106106
}
107107
}
108108
Err(e) => panic!("{e:?}"),
109109
}
110110
}
111111
let img = ImageReader::open(path)?.decode()?;
112112
let img_loaded = std::time::Instant::now();
113+
let dimensions = (img.width(), img.height());
113114
self.verbose(
114115
"pandora",
115116
format!("image loaded in {:?}", img_loaded - start),
@@ -118,7 +119,7 @@ impl Daemon for Pandora {
118119
//write lock
119120
match self.images.write() {
120121
Ok(mut images_table) => {
121-
if images_table.contains_key(&path.to_string()) {
122+
if let Some(existing_img) = images_table.get(&path.to_string()) {
122123
// written while we loaded the image! try to eliminate this case as much as we can.
123124
self.verbose(
124125
"pandora",
@@ -127,7 +128,7 @@ impl Daemon for Pandora {
127128
path
128129
),
129130
);
130-
return Ok(());
131+
return Ok((existing_img.width(), existing_img.height()));
131132
}
132133
images_table.insert(path.to_string(), img /*.into_rgba8() */);
133134
let img_inserted = std::time::Instant::now();
@@ -140,27 +141,13 @@ impl Daemon for Pandora {
140141
img_inserted - start
141142
),
142143
);
143-
Ok(())
144+
Ok(dimensions)
144145
}
145146
Err(e) => panic!("{e:?}"),
146147
}
147148
}
148149
}
149150

150-
fn get_image_dimensions(self: Arc<Self>, img: &str) -> Result<(u32, u32), DaemonError> {
151-
match self.images.read() {
152-
Ok(images_table) => {
153-
if images_table.contains_key(&img.to_string()) {
154-
let image = images_table.get(&img.to_string()).unwrap();
155-
Ok((image.width(), image.height()))
156-
} else {
157-
Err(CommandError::from_message("image not found in cache"))
158-
}
159-
}
160-
Err(_) => Err(DaemonError::PoisonError),
161-
}
162-
}
163-
164151
fn read_img_to_file(
165152
self: Arc<Self>,
166153
img: &str,
@@ -294,7 +281,9 @@ impl Pandora {
294281
if fs::exists(path.clone()).is_err() {
295282
return Err(format!("could not preload {path} during init"));
296283
}
297-
thread::spawn(move || self.load_image(&path));
284+
thread::spawn(move || {
285+
let _ = self.load_image(&path);
286+
});
298287
Ok(())
299288
}
300289

src/pithos/misc.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use fast_image_resize::{images::Image, pixels::{U8x3, U8x4}};
1+
use fast_image_resize::{
2+
images::Image,
3+
pixels::{U8x3, U8x4},
4+
};
25
use std::{
36
fs::File,
47
io::{BufWriter, Write},
@@ -16,15 +19,13 @@ pub fn img_into_buffer(img: &Image, buf: &mut BufWriter<&File>) {
1619
let (r, g, b, a) = (pixel.0[0], pixel.0[1], pixel.0[2], pixel.0[3]);
1720
buf.write_all(&[b, g, r, a]).unwrap();
1821
}
19-
} else {
20-
if let Some(typed) = img.typed_image::<U8x3>() {
21-
for pixel in typed.pixels() {
22-
let (r, g, b, a) = (pixel.0[0], pixel.0[1], pixel.0[2], u8::max_value());
23-
buf.write_all(&[b, g, r, a]).unwrap();
24-
}
25-
} else {
26-
panic!("image could not be coerced to U8x4");
22+
} else if let Some(typed) = img.typed_image::<U8x3>() {
23+
for pixel in typed.pixels() {
24+
let (r, g, b, a) = (pixel.0[0], pixel.0[1], pixel.0[2], u8::MAX);
25+
buf.write_all(&[b, g, r, a]).unwrap();
2726
}
27+
} else {
28+
panic!("image could not be coerced to U8x4");
2829
}
2930

3031
let loop_end = std::time::Instant::now();

src/threads/niri.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ fn run(
8787
let mut read_event = socket.read_events();
8888
loop {
8989
match read_event() {
90-
Ok(event ) => {
90+
Ok(event) => {
9191
processor.process(pandora.clone(), event);
9292
match cmd_queue.lock() {
9393
Ok(channel) => {
@@ -102,10 +102,11 @@ fn run(
102102
}
103103
}
104104
Err(e) => {
105-
pandora.log("niri-agent", format!("error acquiring channel lock: {e:?}"));
105+
pandora
106+
.log("niri-agent", format!("error acquiring channel lock: {e:?}"));
106107
}
107108
}
108-
},
109+
}
109110
Err(e) => {
110111
pandora.debug("niri-agent", format!("event read failed {e:?}"));
111112
}
@@ -130,24 +131,24 @@ struct NiriProcessor {
130131
}
131132

132133
impl NiriProcessor {
133-
fn update_config(
134-
&mut self,
135-
new_config: DaemonConfig,
136-
pandora: Arc<dyn Daemon + Send + Sync>,
137-
) {
134+
fn update_config(&mut self, new_config: DaemonConfig, pandora: Arc<dyn Daemon + Send + Sync>) {
138135
for new_output_conf in &new_config.outputs {
139136
let p = pandora.clone();
140137
let new_mode = new_output_conf.mode.unwrap_or(RenderMode::Static);
141138

142139
// First, find the output and check if we need to update
143-
let needs_update = if let Some((_, state)) = self.outputs.iter().find(|o| o.0 == new_output_conf.name) {
144-
state.current_image != new_output_conf.image || state.mode.unwrap_or(RenderMode::Static) != new_mode
140+
let needs_update = if let Some((_, state)) =
141+
self.outputs.iter().find(|o| o.0 == new_output_conf.name)
142+
{
143+
state.current_image != new_output_conf.image
144+
|| state.mode.unwrap_or(RenderMode::Static) != new_mode
145145
} else {
146146
continue; // Output not found
147147
};
148148

149149
if needs_update {
150-
let position = self.get_current_position_for_output(&new_output_conf.name, new_mode);
150+
let position =
151+
self.get_current_position_for_output(&new_output_conf.name, new_mode);
151152
let cmd = RenderCommand {
152153
output: new_output_conf.name.clone(),
153154
image: new_output_conf.image.clone(),
@@ -157,7 +158,11 @@ impl NiriProcessor {
157158
p.handle_cmd(&CommandType::Tc(RenderThreadCommand::Render(cmd)));
158159

159160
// Update state to reflect the change
160-
if let Some((_, state)) = self.outputs.iter_mut().find(|o| o.0 == new_output_conf.name) {
161+
if let Some((_, state)) = self
162+
.outputs
163+
.iter_mut()
164+
.find(|o| o.0 == new_output_conf.name)
165+
{
161166
state.current_image = new_output_conf.image.clone();
162167
state.mode = Some(new_mode);
163168
}
@@ -177,7 +182,8 @@ impl NiriProcessor {
177182
};
178183

179184
let active_workspace_idx = output.active_workspace_idx.unwrap_or(1);
180-
let mut scroll_percent = 100.0 * (active_workspace_idx - 1) as f64 / (output.max_workspace_idx - 1) as f64;
185+
let mut scroll_percent = 100.0 * (active_workspace_idx - 1) as f64
186+
/ (output.max_workspace_idx - 1) as f64;
181187
if scroll_percent.is_nan() {
182188
scroll_percent = 50.0;
183189
}
@@ -232,7 +238,6 @@ impl NiriProcessor {
232238
}
233239
}
234240
self.update_workspaces(&workspaces);
235-
236241
}
237242

238243
fn reseat_scroll_positions(&mut self, pandora: Arc<dyn Daemon + Send + Sync>) {

src/threads/render.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,25 @@ impl WallpaperThread {
9898
}
9999

100100
// Handle outputs that need re-initialization after transform changes
101-
for (_, output_state) in &mut state.outputs {
101+
let mut output_indices_to_reinit = Vec::new();
102+
103+
// Collect outputs that need reinitialization
104+
for (i, (_, output_state)) in state.outputs.iter().enumerate() {
102105
if output_state.needs_reinit {
103-
output_state.reinit();
106+
output_indices_to_reinit.push(i);
104107
}
105108
}
106109

110+
// Process each output by temporarily removing it from the state
111+
for &output_idx in &output_indices_to_reinit {
112+
let (mut output, mut output_state) = state.outputs.swap_remove(output_idx);
113+
114+
output_state.reinit(conn, &mut output, state);
115+
116+
// Put the output back
117+
state.outputs.insert(output_idx, (output, output_state));
118+
}
119+
107120
self.handle_inbound_commands(conn, state);
108121

109122
if received_events.is_err() {
@@ -223,11 +236,11 @@ impl WallpaperThread {
223236
done: output_state.done,
224237
transform: output_state.transform,
225238
render_state: crate::wayland::render_base::OutputRenderStateVariety::None,
239+
needs_reinit: false,
226240
};
227241

228242
// Update existing wallpaper state with new image
229-
if let OutputRenderStateVariety::Wallpaper(wallpaper_state) =
230-
&mut output_state.render_state
243+
if let OutputRenderStateVariety::Wallpaper(wallpaper_state) = &mut output_state.render_state
231244
{
232245
wallpaper_state.update_image(
233246
conn,
@@ -297,9 +310,7 @@ impl WallpaperThread {
297310
}
298311
};
299312

300-
if let OutputRenderStateVariety::Wallpaper(render_state) =
301-
&mut output_state.render_state
302-
{
313+
if let OutputRenderStateVariety::Wallpaper(render_state) = &mut output_state.render_state {
303314
render_state.scroll(conn, cmd.position_x, cmd.position_y);
304315
} else {
305316
self.debug("received scroll command, but no wallpaper state found on attached outputs. reseat pending/workspace change from output disconnect?".to_string());

0 commit comments

Comments
 (0)