Tray, fullscreen-only hiding, peeks, Ethernet details, wider maps

- tray.rs: the shell serves org.kde.StatusNotifierWatcher (only when no other
  watcher runs) and is its host; icons from the theme or from IconPixmap
  (png.rs, a stdlib png writer); left/middle/right click and scroll; dbusmenu
  menus drawn in the island's look; tray IPC verbs for testing
- edge.rs: the bar hides only on monitors showing a fullscreen window, driven
  by Hyprland's event socket; peeks of the workspaces (with the disc sliding)
  and the island (song change, new player), with a pop on the island
- prompt.rs: Ethernet details popup (addresses, gateway, DNS, MAC copyable)
- map.rs: 1100x560 maps, every other node a ring further in
- tools/fake_tray.py: a fake tray app, by bus name or by path
This commit is contained in:
server2
2026-10-13 04:58:08 +02:00
parent b14df3a435
commit 1e99d7264c
13 changed files with 1963 additions and 126 deletions
+121
View File
@@ -0,0 +1,121 @@
/*
* a png from raw rgba pixels, for tray icons that come as pixels (IconPixmap) rather
* than a name: amane draws images from files. stdlib only, so the deflate stream is
* stored, not compressed; tray icons are a few kB either way
*/
// the image, straight rgba (not premultiplied), rows top to bottom
pub fn encode(width: u32, height: u32, rgba: &[u8]) -> Vec<u8> {
let row = width as usize * 4;
// every row starts with its filter byte, 0: none
let mut raw = Vec::with_capacity((row + 1) * height as usize);
for line in rgba.chunks(row).take(height as usize) {
raw.push(0);
raw.extend_from_slice(line);
}
let mut header = Vec::with_capacity(13);
header.extend_from_slice(&width.to_be_bytes());
header.extend_from_slice(&height.to_be_bytes());
// 8 bits per channel, colour type 6 (rgba), deflate, adaptive filtering, no interlace
header.extend_from_slice(&[8, 6, 0, 0, 0]);
let mut png = vec![0x89, b'P', b'N', b'G', b'\r', b'\n', 0x1a, b'\n'];
chunk(&mut png, b"IHDR", &header);
chunk(&mut png, b"IDAT", &zlib_stored(&raw));
chunk(&mut png, b"IEND", &[]);
png
}
/*
* IconPixmap's pixels: argb32 in network byte order, so a, r, g, b per pixel; the
* same as rgba for amane
*/
pub fn argb_to_rgba(argb: &[u8]) -> Vec<u8> {
argb.chunks_exact(4).flat_map(|pixel| [pixel[1], pixel[2], pixel[3], pixel[0]]).collect()
}
fn chunk(png: &mut Vec<u8>, kind: &[u8; 4], data: &[u8]) {
png.extend_from_slice(&(data.len() as u32).to_be_bytes());
png.extend_from_slice(kind);
png.extend_from_slice(data);
let mut checked = kind.to_vec();
checked.extend_from_slice(data);
png.extend_from_slice(&crc32(&checked).to_be_bytes());
}
// zlib around deflate's stored blocks, at most 65535 bytes each
fn zlib_stored(data: &[u8]) -> Vec<u8> {
let mut out = vec![0x78, 0x01];
let blocks: Vec<&[u8]> = if data.is_empty() { vec![&[]] } else { data.chunks(65_535).collect() };
for (index, block) in blocks.iter().enumerate() {
let last = index + 1 == blocks.len();
let length = block.len() as u16;
out.push(u8::from(last));
out.extend_from_slice(&length.to_le_bytes());
out.extend_from_slice(&(!length).to_le_bytes());
out.extend_from_slice(block);
}
out.extend_from_slice(&adler32(data).to_be_bytes());
out
}
fn crc32(data: &[u8]) -> u32 {
let mut crc = 0xffff_ffff_u32;
for byte in data {
crc ^= u32::from(*byte);
for _ in 0..8 {
crc = if crc & 1 == 1 { (crc >> 1) ^ 0xedb8_8320 } else { crc >> 1 };
}
}
!crc
}
fn adler32(data: &[u8]) -> u32 {
let (mut a, mut b) = (1_u32, 0_u32);
for byte in data {
a = (a + u32::from(*byte)) % 65_521;
b = (b + a) % 65_521;
}
(b << 16) | a
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn checksums_and_layout() {
// the well-known values: IEND's crc, and adler32 of "Wikipedia"
assert_eq!(crc32(b"IEND"), 0xae42_6082);
assert_eq!(adler32(b"Wikipedia"), 0x11e6_0398);
let png = encode(2, 1, &[255, 0, 0, 255, 0, 0, 255, 128]);
assert_eq!(&png[..8], &[0x89, b'P', b'N', b'G', b'\r', b'\n', 0x1a, b'\n']);
assert_eq!(&png[12..16], b"IHDR");
assert_eq!(&png[png.len() - 12..], &[0, 0, 0, 0, b'I', b'E', b'N', b'D', 0xae, 0x42, 0x60, 0x82]);
// one row: filter byte, then the pixels, in one final stored block
let idat = &png[33 + 8..];
assert_eq!(&idat[..2], &[0x78, 0x01]);
assert_eq!(&idat[2..7], &[1, 9, 0, 0xf6, 0xff]);
assert_eq!(argb_to_rgba(&[128, 1, 2, 3]), [1, 2, 3, 128]);
}
}