Initial commit
This commit is contained in:
commit
3eedfa1eb5
11 changed files with 2453 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
1389
Cargo.lock
generated
Normal file
1389
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "multibg-sway"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Gergő Sályi <salyigergo94@gmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
description = "Set a different wallpaper for the background of each Sway workspace"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
keywords = ["wallpaper", "background", "desktop", "wayland", "sway"]
|
||||||
|
categories = ["command-line-utilities", "multimedia::images"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.2.1", features = ["derive"] }
|
||||||
|
env_logger = "0.10.0"
|
||||||
|
image = "0.24.6"
|
||||||
|
log = "0.4.17"
|
||||||
|
mio = { version = "0.8.6", features = ["os-ext", "os-poll"] }
|
||||||
|
swayipc = "3.0.1"
|
||||||
|
|
||||||
|
[dependencies.smithay-client-toolkit]
|
||||||
|
git = "https://github.com/Smithay/client-toolkit.git"
|
||||||
|
rev = "389a4f21872a99a3ba346cc3dabd55c4079ec191" # master branch as of 2023-04-07
|
||||||
177
LICENSE-APACHE
Normal file
177
LICENSE-APACHE
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
19
LICENSE-MIT
Normal file
19
LICENSE-MIT
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright 2023 Gergő Sályi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the “Software”), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
4
README.md
Normal file
4
README.md
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# multibg-sway
|
||||||
|
|
||||||
|
Set a different wallpaper for the background of each Sway workspace
|
||||||
|
|
||||||
8
src/cli.rs
Normal file
8
src/cli.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
pub struct Cli {
|
||||||
|
/// directory with: wallpaper_dir/output/workspace_name.{jpg|png|...}
|
||||||
|
pub wallpaper_dir: String,
|
||||||
|
}
|
||||||
147
src/image.rs
Normal file
147
src/image.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
use std::{
|
||||||
|
fs::read_dir,
|
||||||
|
path::Path
|
||||||
|
};
|
||||||
|
|
||||||
|
use log::error;
|
||||||
|
use image::{ImageBuffer, Rgb, ImageError};
|
||||||
|
use smithay_client_toolkit::shm::slot::{Buffer, SlotPool};
|
||||||
|
use smithay_client_toolkit::reexports::client::protocol::wl_shm;
|
||||||
|
|
||||||
|
use crate::wayland::WorkspaceBackground;
|
||||||
|
|
||||||
|
pub fn workspace_bgs_from_output_image_dir(
|
||||||
|
dir_path: impl AsRef<Path>,
|
||||||
|
slot_pool: &mut SlotPool,
|
||||||
|
format: wl_shm::Format,
|
||||||
|
)
|
||||||
|
-> Result<Vec<WorkspaceBackground>, String>
|
||||||
|
{
|
||||||
|
let mut buffers = Vec::new();
|
||||||
|
|
||||||
|
let dir = read_dir(&dir_path)
|
||||||
|
.map_err(|e| format!("Failed to open directory: {}", e))?;
|
||||||
|
|
||||||
|
for entry_result in dir {
|
||||||
|
|
||||||
|
let entry = match entry_result {
|
||||||
|
Ok(entry) => entry,
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"Skipping a directory entry in '{:?}' due to an error: {}",
|
||||||
|
dir_path.as_ref(), e
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Resolve symlinks
|
||||||
|
let path = match entry.path().canonicalize() {
|
||||||
|
Ok(file_type) => file_type,
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"Skipping '{:?}' in '{:?}' due to an error: {}",
|
||||||
|
entry.path(), dir_path.as_ref(), e
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Skip dirs
|
||||||
|
if path.is_dir() { continue }
|
||||||
|
|
||||||
|
// Use the file stem as the name of the workspace for this wallpaper
|
||||||
|
let workspace_name = path.file_stem().unwrap()
|
||||||
|
.to_string_lossy().into_owned();
|
||||||
|
|
||||||
|
let raw_image = match image::io::Reader::open(&path)
|
||||||
|
.map_err(ImageError::IoError)
|
||||||
|
.and_then(|r| r.with_guessed_format()
|
||||||
|
.map_err(ImageError::IoError)
|
||||||
|
)
|
||||||
|
.and_then(|r| r.decode())
|
||||||
|
{
|
||||||
|
Ok(raw_image) => raw_image,
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"Failed to open image '{:?}': {}",
|
||||||
|
path, e
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let image = raw_image
|
||||||
|
// It is possible to adjust the contrast and brightness here
|
||||||
|
// .adjust_contrast(-25.0)
|
||||||
|
// .brighten(-60)
|
||||||
|
.into_rgb8();
|
||||||
|
|
||||||
|
let buffer = match format {
|
||||||
|
wl_shm::Format::Xrgb8888 =>
|
||||||
|
buffer_xrgb8888_from_image(image, slot_pool),
|
||||||
|
wl_shm::Format::Bgr888 =>
|
||||||
|
buffer_bgr888_from_image(image, slot_pool),
|
||||||
|
_ => unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
buffers.push(WorkspaceBackground { workspace_name, buffer });
|
||||||
|
}
|
||||||
|
|
||||||
|
if buffers.is_empty() {
|
||||||
|
Err("Found 0 suitable images in the directory".to_string())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Ok(buffers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer_xrgb8888_from_image(
|
||||||
|
image: ImageBuffer<Rgb<u8>, Vec<u8>>,
|
||||||
|
slot_pool: &mut SlotPool,
|
||||||
|
)
|
||||||
|
-> Buffer
|
||||||
|
{
|
||||||
|
let (buffer, canvas) = slot_pool
|
||||||
|
.create_buffer(
|
||||||
|
image.width() as i32,
|
||||||
|
image.height() as i32,
|
||||||
|
image.width() as i32 * 4,
|
||||||
|
wl_shm::Format::Xrgb8888
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let canvas_len = image.len() / 3 * 4;
|
||||||
|
|
||||||
|
let image_pixels = image.pixels();
|
||||||
|
let canvas_pixels = canvas[..canvas_len].chunks_exact_mut(4);
|
||||||
|
|
||||||
|
for (image_pixel, canvas_pixel) in image_pixels.zip(canvas_pixels) {
|
||||||
|
canvas_pixel[0] = image_pixel.0[2];
|
||||||
|
canvas_pixel[1] = image_pixel.0[1];
|
||||||
|
canvas_pixel[2] = image_pixel.0[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer_bgr888_from_image(
|
||||||
|
image: ImageBuffer<Rgb<u8>, Vec<u8>>,
|
||||||
|
slot_pool: &mut SlotPool,
|
||||||
|
)
|
||||||
|
-> Buffer
|
||||||
|
{
|
||||||
|
let (buffer, canvas) = slot_pool
|
||||||
|
.create_buffer(
|
||||||
|
image.width() as i32,
|
||||||
|
image.height() as i32,
|
||||||
|
image.width() as i32 * 3,
|
||||||
|
wl_shm::Format::Bgr888
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
canvas[..image.len()].copy_from_slice(&image);
|
||||||
|
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
|
||||||
182
src/main.rs
Normal file
182
src/main.rs
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
mod cli;
|
||||||
|
mod image;
|
||||||
|
mod sway;
|
||||||
|
mod wayland;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
io,
|
||||||
|
os::fd::AsRawFd,
|
||||||
|
path::Path,
|
||||||
|
sync::{
|
||||||
|
Arc,
|
||||||
|
mpsc::{channel, Receiver},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use log::error;
|
||||||
|
use mio::{
|
||||||
|
Events, Interest, Poll, Token, Waker,
|
||||||
|
unix::SourceFd,
|
||||||
|
};
|
||||||
|
use smithay_client_toolkit::{
|
||||||
|
compositor::CompositorState,
|
||||||
|
output::OutputState,
|
||||||
|
registry::RegistryState,
|
||||||
|
shell::wlr_layer::LayerShell,
|
||||||
|
shm::{Shm, slot::SlotPool},
|
||||||
|
};
|
||||||
|
use smithay_client_toolkit::reexports::client::{
|
||||||
|
Connection, EventQueue,
|
||||||
|
backend::{ReadEventsGuard, WaylandError},
|
||||||
|
globals::registry_queue_init,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
cli::Cli,
|
||||||
|
sway::{SwayConnectionTask, WorkspaceVisible},
|
||||||
|
wayland::State,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main()
|
||||||
|
{
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
env_logger::Builder::from_env(
|
||||||
|
env_logger::Env::default().default_filter_or(
|
||||||
|
"warn,multibg_sway=trace"
|
||||||
|
)
|
||||||
|
).init();
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
env_logger::Builder::from_env(
|
||||||
|
env_logger::Env::default().default_filter_or("warn")
|
||||||
|
).init();
|
||||||
|
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let wallpaper_dir = Path::new(&cli.wallpaper_dir).canonicalize().unwrap();
|
||||||
|
|
||||||
|
// ********************************
|
||||||
|
// Initialize wayland client
|
||||||
|
// ********************************
|
||||||
|
|
||||||
|
let conn = Connection::connect_to_env().unwrap();
|
||||||
|
let (globals, mut event_queue) = registry_queue_init(&conn).unwrap();
|
||||||
|
let qh = event_queue.handle();
|
||||||
|
|
||||||
|
let compositor_state = CompositorState::bind(&globals, &qh).unwrap();
|
||||||
|
let layer_shell = LayerShell::bind(&globals, &qh).unwrap();
|
||||||
|
let shm = Shm::bind(&globals, &qh).unwrap();
|
||||||
|
|
||||||
|
// Initialize slot pool with a minimum size (0 is not allowed)
|
||||||
|
// it will be automatically resized later
|
||||||
|
let shm_slot_pool = SlotPool::new(1, &shm).unwrap();
|
||||||
|
|
||||||
|
// Sync tools for sway ipc tasks
|
||||||
|
let mut poll = Poll::new().unwrap();
|
||||||
|
let waker = Arc::new(Waker::new(poll.registry(), SWAY).unwrap());
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let mut state = State {
|
||||||
|
compositor_state,
|
||||||
|
registry_state: RegistryState::new(&globals),
|
||||||
|
output_state: OutputState::new(&globals, &qh),
|
||||||
|
shm,
|
||||||
|
shm_slot_pool,
|
||||||
|
layer_shell,
|
||||||
|
wallpaper_dir,
|
||||||
|
pixel_format: None,
|
||||||
|
background_layers: Vec::new(),
|
||||||
|
sway_connection_task: SwayConnectionTask::new(
|
||||||
|
tx.clone(), Arc::clone(&waker)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
event_queue.roundtrip(&mut state).unwrap();
|
||||||
|
|
||||||
|
// ********************************
|
||||||
|
// Main event loop
|
||||||
|
// ********************************
|
||||||
|
|
||||||
|
let mut events = Events::with_capacity(16);
|
||||||
|
|
||||||
|
const WAYLAND: Token = Token(0);
|
||||||
|
let read_guard = event_queue.prepare_read().unwrap();
|
||||||
|
let wayland_socket_fd = read_guard.connection_fd().as_raw_fd();
|
||||||
|
poll.registry().register(
|
||||||
|
&mut SourceFd(&wayland_socket_fd),
|
||||||
|
WAYLAND,
|
||||||
|
Interest::READABLE
|
||||||
|
).unwrap();
|
||||||
|
drop(read_guard);
|
||||||
|
|
||||||
|
const SWAY: Token = Token(1);
|
||||||
|
SwayConnectionTask::new(tx, waker).spawn_subscribe_event_loop();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
event_queue.flush().unwrap();
|
||||||
|
event_queue.dispatch_pending(&mut state).unwrap();
|
||||||
|
let mut read_guard_option = Some(event_queue.prepare_read().unwrap());
|
||||||
|
|
||||||
|
poll.poll(&mut events, None).unwrap();
|
||||||
|
|
||||||
|
for event in events.iter() {
|
||||||
|
match event.token() {
|
||||||
|
WAYLAND => handle_wayland_event(
|
||||||
|
&mut state,
|
||||||
|
&mut read_guard_option,
|
||||||
|
&mut event_queue
|
||||||
|
),
|
||||||
|
SWAY => handle_sway_event(&mut state, &rx),
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_wayland_event(
|
||||||
|
state: &mut State,
|
||||||
|
read_guard_option: &mut Option<ReadEventsGuard>,
|
||||||
|
event_queue: &mut EventQueue<State>,
|
||||||
|
) {
|
||||||
|
if let Some(read_guard) = read_guard_option.take() {
|
||||||
|
if let Err(e) = read_guard.read() {
|
||||||
|
// WouldBlock is normal here because of epoll false wakeups
|
||||||
|
if let WaylandError::Io(ref io_err) = e {
|
||||||
|
if io_err.kind() == io::ErrorKind::WouldBlock {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!("Failed to read Wayland events: {}", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = event_queue.dispatch_pending(state) {
|
||||||
|
panic!("Failed to dispatch pending Wayland events: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_sway_event(
|
||||||
|
state: &mut State,
|
||||||
|
rx: &Receiver<WorkspaceVisible>,
|
||||||
|
) {
|
||||||
|
while let Ok(workspace) = rx.try_recv()
|
||||||
|
{
|
||||||
|
// Find the background layer that of the output where the workspace is
|
||||||
|
if let Some(affected_bg_layer) = state.background_layers.iter_mut()
|
||||||
|
.find(|bg_layer| bg_layer.output_name == workspace.output)
|
||||||
|
{
|
||||||
|
affected_bg_layer.draw_workspace_bg(&workspace.workspace_name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error!(
|
||||||
|
"Workspace '{}' is on an unknown output '{}', known outputs were: {}",
|
||||||
|
workspace.workspace_name,
|
||||||
|
workspace.output,
|
||||||
|
state.background_layers.iter()
|
||||||
|
.map(|bg_layer| bg_layer.output_name.as_str())
|
||||||
|
.collect::<Vec<_>>().join(", ")
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
119
src/sway.rs
Normal file
119
src/sway.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
use std::{
|
||||||
|
sync::{Arc, mpsc::Sender},
|
||||||
|
thread::spawn,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mio::Waker;
|
||||||
|
use swayipc::{Connection, Event, EventType, WorkspaceChange};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WorkspaceVisible {
|
||||||
|
pub output: String,
|
||||||
|
pub workspace_name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SwayConnectionTask {
|
||||||
|
sway_conn: Connection,
|
||||||
|
tx: Sender<WorkspaceVisible>,
|
||||||
|
waker: Arc<Waker>,
|
||||||
|
}
|
||||||
|
impl SwayConnectionTask
|
||||||
|
{
|
||||||
|
pub fn new(tx: Sender<WorkspaceVisible>, waker: Arc<Waker>) -> Self {
|
||||||
|
SwayConnectionTask {
|
||||||
|
sway_conn: Connection::new()
|
||||||
|
.expect("Failed to connect to sway socket"),
|
||||||
|
tx,
|
||||||
|
waker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_visible_workspace(&mut self, output: &str) {
|
||||||
|
if let Some(workspace) = self.sway_conn.get_workspaces().unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|w| w.visible)
|
||||||
|
.find(|w| w.output == output)
|
||||||
|
{
|
||||||
|
self.tx.send(WorkspaceVisible {
|
||||||
|
output: workspace.output,
|
||||||
|
workspace_name: workspace.name,
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
self.waker.wake().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_visible_workspaces(&mut self) {
|
||||||
|
for workspace in self.sway_conn.get_workspaces().unwrap()
|
||||||
|
.into_iter().filter(|w| w.visible)
|
||||||
|
{
|
||||||
|
self.tx.send(WorkspaceVisible {
|
||||||
|
output: workspace.output,
|
||||||
|
workspace_name: workspace.name,
|
||||||
|
}).unwrap();
|
||||||
|
}
|
||||||
|
self.waker.wake().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_subscribe_event_loop(self) {
|
||||||
|
spawn(|| self.subscribe_event_loop());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscribe_event_loop(self) {
|
||||||
|
let event_stream = self.sway_conn.subscribe([EventType::Workspace])
|
||||||
|
.unwrap();
|
||||||
|
for event_result in event_stream {
|
||||||
|
let event = event_result.unwrap();
|
||||||
|
let Event::Workspace(workspace_event) = event else {continue};
|
||||||
|
if let WorkspaceChange::Focus = workspace_event.change {
|
||||||
|
let current_workspace = workspace_event.current.unwrap();
|
||||||
|
|
||||||
|
self.tx.send(WorkspaceVisible {
|
||||||
|
output: current_workspace.output.unwrap(),
|
||||||
|
workspace_name: current_workspace.name.unwrap(),
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
self.waker.wake().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn spawn_sway_events_loop_task(tx: Sender<WorkspaceVisible>, waker: Waker)
|
||||||
|
// {
|
||||||
|
// spawn(|| sway_events_loop_task(tx, waker));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// pub fn sway_events_loop_task(tx: Sender<WorkspaceVisible>, waker: Waker)
|
||||||
|
// {
|
||||||
|
// let mut sway_conn = Connection::new()
|
||||||
|
// .expect("Failed to connect to sway socket");
|
||||||
|
//
|
||||||
|
// // Send the initial workspaces
|
||||||
|
// for workspace in sway_conn.get_workspaces().unwrap().into_iter() {
|
||||||
|
// if workspace.visible {
|
||||||
|
// tx.send(WorkspaceVisible {
|
||||||
|
// output: workspace.output,
|
||||||
|
// workspace_name: workspace.name,
|
||||||
|
// }).unwrap();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// waker.wake().unwrap();
|
||||||
|
//
|
||||||
|
// // Event loop
|
||||||
|
// let event_stream = sway_conn.subscribe([EventType::Workspace]).unwrap();
|
||||||
|
// for event_result in event_stream {
|
||||||
|
// let event = event_result.unwrap();
|
||||||
|
// let Event::Workspace(workspace_event) = event else {continue};
|
||||||
|
// if let WorkspaceChange::Focus = workspace_event.change {
|
||||||
|
// let current_workspace = workspace_event.current.unwrap();
|
||||||
|
//
|
||||||
|
// tx.send(WorkspaceVisible {
|
||||||
|
// output: current_workspace.output.unwrap(),
|
||||||
|
// workspace_name: current_workspace.name.unwrap(),
|
||||||
|
// }).unwrap();
|
||||||
|
//
|
||||||
|
// waker.wake().unwrap();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
386
src/wayland.rs
Normal file
386
src/wayland.rs
Normal file
|
|
@ -0,0 +1,386 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use log::{debug, error};
|
||||||
|
use smithay_client_toolkit::{
|
||||||
|
delegate_compositor, delegate_layer, delegate_output, delegate_registry,
|
||||||
|
delegate_shm,
|
||||||
|
compositor::{CompositorHandler, CompositorState},
|
||||||
|
output::{OutputHandler, OutputState},
|
||||||
|
registry::{ProvidesRegistryState, RegistryState},
|
||||||
|
registry_handlers,
|
||||||
|
shell::{
|
||||||
|
WaylandSurface,
|
||||||
|
wlr_layer::{
|
||||||
|
KeyboardInteractivity, Layer, LayerShell,
|
||||||
|
LayerShellHandler, LayerSurface, LayerSurfaceConfigure,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shm::{
|
||||||
|
Shm, ShmHandler,
|
||||||
|
slot::{Buffer, SlotPool},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use smithay_client_toolkit::reexports::client::{
|
||||||
|
Connection, QueueHandle,
|
||||||
|
protocol::{wl_output, wl_shm, wl_surface},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
image::workspace_bgs_from_output_image_dir,
|
||||||
|
sway::SwayConnectionTask,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct State {
|
||||||
|
pub compositor_state: CompositorState,
|
||||||
|
pub registry_state: RegistryState,
|
||||||
|
pub output_state: OutputState,
|
||||||
|
pub shm: Shm,
|
||||||
|
pub shm_slot_pool: SlotPool,
|
||||||
|
pub layer_shell: LayerShell,
|
||||||
|
pub wallpaper_dir: PathBuf,
|
||||||
|
pub pixel_format: Option<wl_shm::Format>,
|
||||||
|
pub background_layers: Vec<BackgroundLayer>,
|
||||||
|
pub sway_connection_task: SwayConnectionTask,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
fn pixel_format(&mut self) -> wl_shm::Format {
|
||||||
|
*self.pixel_format.get_or_insert_with(|| {
|
||||||
|
// Consume less gpu memory by using Bgr888 if available,
|
||||||
|
// fall back to the always supported Xrgb8888 otherwise
|
||||||
|
for format in self.shm.formats() {
|
||||||
|
if let wl_shm::Format::Bgr888 = format {
|
||||||
|
debug!("Using pixel format: {:?}", format);
|
||||||
|
return *format
|
||||||
|
}
|
||||||
|
// XXX: One may add Rgb888 and HDR support here
|
||||||
|
}
|
||||||
|
debug!("Using default pixel format: Xrgb8888");
|
||||||
|
|
||||||
|
wl_shm::Format::Xrgb8888
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompositorHandler for State
|
||||||
|
{
|
||||||
|
fn scale_factor_changed(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
_surface: &wl_surface::WlSurface,
|
||||||
|
_new_factor: i32,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
_surface: &wl_surface::WlSurface,
|
||||||
|
_time: u32,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayerShellHandler for State
|
||||||
|
{
|
||||||
|
fn closed(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
_layer: &LayerSurface
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
layer: &LayerSurface,
|
||||||
|
_configure: LayerSurfaceConfigure,
|
||||||
|
_serial: u32,
|
||||||
|
) {
|
||||||
|
// The new layer is ready: request all the visible workspace from sway,
|
||||||
|
// it will get picked up by the main event loop and be drawn from there
|
||||||
|
let bg_layer = self.background_layers.iter_mut()
|
||||||
|
.find(|bg_layer| &bg_layer.layer == layer).unwrap();
|
||||||
|
|
||||||
|
if !bg_layer.configured {
|
||||||
|
bg_layer.configured = true;
|
||||||
|
self.sway_connection_task
|
||||||
|
.request_visible_workspace(&bg_layer.output_name);
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Background layer has been configured for output: {}",
|
||||||
|
bg_layer.output_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputHandler for State {
|
||||||
|
fn output_state(&mut self) -> &mut OutputState {
|
||||||
|
&mut self.output_state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_output(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
qh: &QueueHandle<Self>,
|
||||||
|
output: wl_output::WlOutput,
|
||||||
|
) {
|
||||||
|
let Some(info) = self.output_state.info(&output)
|
||||||
|
else {
|
||||||
|
error!("New output has no output info, skipping");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(output_name) = info.name
|
||||||
|
else {
|
||||||
|
error!("New output has no name, skipping");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some((width, height)) = info.modes.iter()
|
||||||
|
.find(|mode| mode.current)
|
||||||
|
.map(|mode| mode.dimensions)
|
||||||
|
else {
|
||||||
|
error!(
|
||||||
|
"New output '{}' has no current mode set, skipping",
|
||||||
|
output_name
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"New output, name: {}, resolution: {}x{}",
|
||||||
|
output_name, width, height
|
||||||
|
);
|
||||||
|
|
||||||
|
let surface = self.compositor_state.create_surface(qh);
|
||||||
|
|
||||||
|
let layer = self.layer_shell.create_layer_surface(
|
||||||
|
qh,
|
||||||
|
surface,
|
||||||
|
Layer::Background,
|
||||||
|
layer_surface_name(&output_name),
|
||||||
|
Some(&output)
|
||||||
|
);
|
||||||
|
|
||||||
|
layer.set_exclusive_zone(-1); // Don't let the status bar push it around
|
||||||
|
layer.set_keyboard_interactivity(KeyboardInteractivity::None);
|
||||||
|
layer.set_size(width as u32, height as u32);
|
||||||
|
|
||||||
|
layer.commit();
|
||||||
|
|
||||||
|
let pixel_format = self.pixel_format();
|
||||||
|
|
||||||
|
let output_wallpaper_dir = self.wallpaper_dir.join(&output_name);
|
||||||
|
|
||||||
|
let workspace_backgrounds = match workspace_bgs_from_output_image_dir(
|
||||||
|
&output_wallpaper_dir,
|
||||||
|
&mut self.shm_slot_pool,
|
||||||
|
pixel_format
|
||||||
|
) {
|
||||||
|
Ok(workspace_bgs) => {
|
||||||
|
debug!(
|
||||||
|
"Loaded {} wallpapers on new output for workspaces: {}",
|
||||||
|
workspace_bgs.len(),
|
||||||
|
workspace_bgs.iter()
|
||||||
|
.map(|workspace_bg| workspace_bg.workspace_name.as_str())
|
||||||
|
.collect::<Vec<_>>().join(", ")
|
||||||
|
);
|
||||||
|
workspace_bgs
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"Failed to get wallpapers for new output '{}' form '{:?}': {}",
|
||||||
|
output_name, output_wallpaper_dir, e
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.background_layers.push(BackgroundLayer {
|
||||||
|
output_name,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
layer,
|
||||||
|
configured: false,
|
||||||
|
workspace_backgrounds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_output(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
_output: wl_output::WlOutput,
|
||||||
|
) {
|
||||||
|
// This will only be needed if we implement scaling the wallpapers
|
||||||
|
// to the output resolution
|
||||||
|
//
|
||||||
|
// let Some(info) = self.output_state.info(&output)
|
||||||
|
// else {
|
||||||
|
// error!("Updated output has no output info, skipping");
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// let Some(name) = info.name
|
||||||
|
// else {
|
||||||
|
// error!("Updated output has no name, skipping");
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// let Some((width, height)) = info.modes.iter()
|
||||||
|
// .find(|mode| mode.current)
|
||||||
|
// .map(|mode| mode.dimensions)
|
||||||
|
// else {
|
||||||
|
// error!(
|
||||||
|
// "Updated output '{}' has no current mode set, skipping",
|
||||||
|
// name
|
||||||
|
// );
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// if let Some(bg_layer) = self.background_layers.iter()
|
||||||
|
// .find(|bg_layers| bg_layers.output_name == name)
|
||||||
|
// {
|
||||||
|
// if bg_layer.width == width && bg_layer.height == height {
|
||||||
|
// // if a known output has its resolution unchanged
|
||||||
|
// // then ignore this event
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // renew the output otherwise
|
||||||
|
// self.output_destroyed(conn, qh, output.clone());
|
||||||
|
// self.new_output(conn, qh, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_destroyed(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
output: wl_output::WlOutput,
|
||||||
|
) {
|
||||||
|
let Some(info) = self.output_state.info(&output)
|
||||||
|
else {
|
||||||
|
error!("Destroyed output has no output info, skipping");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(name) = info.name
|
||||||
|
else {
|
||||||
|
error!("Destroyed output has no name, skipping");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(bg_layer_index) = self.background_layers.iter()
|
||||||
|
.position(|bg_layers| bg_layers.output_name == name)
|
||||||
|
{
|
||||||
|
self.background_layers.swap_remove(bg_layer_index);
|
||||||
|
|
||||||
|
// Workspaces on the destroyed output may have been moved anywhere
|
||||||
|
// so reset the wallpaper on all the visible workspaces
|
||||||
|
self.sway_connection_task.request_visible_workspaces();
|
||||||
|
|
||||||
|
debug!("Destroyed output: {}", name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error!(
|
||||||
|
"Ignoring destroyed output with unknown name '{}', known outputs were: {}",
|
||||||
|
name,
|
||||||
|
self.background_layers.iter()
|
||||||
|
.map(|bg_layer| bg_layer.output_name.as_str())
|
||||||
|
.collect::<Vec<_>>().join(", ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProvidesRegistryState for State {
|
||||||
|
fn registry(&mut self) -> &mut RegistryState {
|
||||||
|
&mut self.registry_state
|
||||||
|
}
|
||||||
|
registry_handlers![OutputState];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShmHandler for State {
|
||||||
|
fn shm_state(&mut self) -> &mut Shm {
|
||||||
|
&mut self.shm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate_compositor!(State);
|
||||||
|
delegate_layer!(State);
|
||||||
|
delegate_output!(State);
|
||||||
|
delegate_registry!(State);
|
||||||
|
delegate_shm!(State);
|
||||||
|
|
||||||
|
pub struct BackgroundLayer {
|
||||||
|
pub output_name: String,
|
||||||
|
pub width: i32,
|
||||||
|
pub height: i32,
|
||||||
|
pub layer: LayerSurface,
|
||||||
|
pub configured: bool,
|
||||||
|
pub workspace_backgrounds: Vec<WorkspaceBackground>,
|
||||||
|
}
|
||||||
|
impl BackgroundLayer
|
||||||
|
{
|
||||||
|
pub fn draw_workspace_bg(&mut self, workspace_name: &str)
|
||||||
|
{
|
||||||
|
if !self.configured {
|
||||||
|
error!(
|
||||||
|
"Cannot draw wallpaper image on the not yet configured layer for output: {}",
|
||||||
|
self.output_name
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(workspace_bg) = self.workspace_backgrounds.iter()
|
||||||
|
.find(|workspace_bg| workspace_bg.workspace_name == workspace_name)
|
||||||
|
else {
|
||||||
|
error!(
|
||||||
|
"There is no wallpaper image on output '{}' for workspace '{}', only for: {}",
|
||||||
|
self.output_name,
|
||||||
|
workspace_name,
|
||||||
|
self.workspace_backgrounds.iter()
|
||||||
|
.map(|workspace_bg| workspace_bg.workspace_name.as_str())
|
||||||
|
.collect::<Vec<_>>().join(", ")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attach and commit to new workspace background
|
||||||
|
if let Err(e) = workspace_bg.buffer.attach_to(self.layer.wl_surface()) {
|
||||||
|
error!(
|
||||||
|
"Error attaching buffer of workspace '{}' on output '{}': {:#?}",
|
||||||
|
workspace_name,
|
||||||
|
self.output_name,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Damage the entire surface
|
||||||
|
self.layer.wl_surface().damage_buffer(0, 0, self.width, self.height);
|
||||||
|
|
||||||
|
self.layer.commit();
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Setting wallpaper on output '{}' for workspace: {}",
|
||||||
|
self.output_name, workspace_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WorkspaceBackground {
|
||||||
|
pub workspace_name: String,
|
||||||
|
pub buffer: Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layer_surface_name(output_name: &str) -> Option<String> {
|
||||||
|
Some([env!("CARGO_PKG_NAME"), "_wallpaper_", output_name].concat())
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue