feat: icon for app, and windows specific build code
This commit is contained in:
parent
307daaea7b
commit
090bea5805
971
Cargo.lock
generated
971
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
24
Cargo.toml
24
Cargo.toml
@ -21,18 +21,16 @@ rodio = "0.18.1"
|
||||
serde = { version = "1.0.171", features = ["derive"] }
|
||||
tokio = { version = "1.29.1", features = ["full"] }
|
||||
xxhash-rust = { version = "0.8.6", features = ["xxh3", "const_xxh3"] }
|
||||
strinto = { path = "./strinto" }
|
||||
eframe = "0.27.2"
|
||||
tray-item = "0.10"
|
||||
winit = "0.30"
|
||||
once_cell = "1.19.0"
|
||||
tray-icon = "0.14.3"
|
||||
raw-window-handle = "0.6.2"
|
||||
image = "0.25.1"
|
||||
|
||||
[build-dependencies]
|
||||
embed-resource = "2.3"
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winit = "0.30"
|
||||
|
||||
[dependencies.windows]
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
version = "0.57.0"
|
||||
features = [
|
||||
"Data_Xml_Dom",
|
||||
@ -40,4 +38,18 @@ features = [
|
||||
"Win32_Security",
|
||||
"Win32_System_Threading",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
|
||||
"Win32_UI_Controls",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_UI_Shell",
|
||||
]
|
||||
|
||||
#[target.'cfg(windows)'.dependencies.windows-sys]
|
||||
#version = "0.52.0"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
embed-resource = "2.3"
|
||||
|
5
build.rs
5
build.rs
@ -1,5 +1,8 @@
|
||||
#[cfg(windows)]
|
||||
extern crate embed_resource;
|
||||
|
||||
fn main() {
|
||||
embed_resource::compile("soundboard.rc", embed_resource::NONE);
|
||||
if cfg!(target_os = "windows") {
|
||||
embed_resource::compile("soundboard.rc", embed_resource::NONE);
|
||||
}
|
||||
}
|
||||
|
BIN
icons/soundboard.ico
Normal file
BIN
icons/soundboard.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 211 KiB |
@ -1 +1 @@
|
||||
icon-red ICON "icons/icon-red.ico"
|
||||
icon-soundboard ICON "icons/soundboard.ico"
|
@ -1,68 +0,0 @@
|
||||
///
|
||||
/// Trying to get around "".into() for String values.
|
||||
/// Or "".to_owned().
|
||||
/// Or String::from("").
|
||||
/// Or "".to_string().
|
||||
/// Choose your church.
|
||||
///
|
||||
/// This is as far as you will get declaratively.
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! strinto {
|
||||
($struct:ident { $($field:ident : $value:expr),* $(,)? }) => {
|
||||
$struct {
|
||||
$(
|
||||
$field: $crate::strinto!(@convert $value),
|
||||
)*
|
||||
}
|
||||
};
|
||||
|
||||
(@convert $value:literal) => {
|
||||
match () {
|
||||
_ if stringify!($value).starts_with("\"") => {
|
||||
$value.to_string()
|
||||
},
|
||||
_ => $value.into(), // <-- no getting rid of the into!
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
struct SomeStruct {
|
||||
first_name: String,
|
||||
//last_name: String, // NOPE because of @convert.
|
||||
//age: usize, // reason of .into() in the first place.
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let x = strinto!(SomeStruct {
|
||||
first_name: "First",
|
||||
//last_name: String::from("Last"), // NOPE 2.
|
||||
//age: 1, // NOPE 1. But I went further.
|
||||
});
|
||||
}
|
||||
|
||||
// while this compiles for only &str "", it is also useless compared to into!
|
||||
// the reason is, that you cannot type check in the declarative macros.
|
||||
// while yes, you would conditionally run stringify!($value) in the match, but the match expansion
|
||||
// will always lead you to type errors if you don't do an .into() in the other arm.
|
||||
// also in the end it fails to compile for anything but all members of the struct being Strings.
|
||||
|
||||
// the last idea was going with a helper trait, but that will bleed into the runtime code, and yet again only work on pure String structs.
|
||||
|
||||
// I guess I have to embrace String::from(), .to_string(), .to_owned(), .into() madness, for something every human reader can deduce in a second,
|
||||
// if you would just implicitly convert &str to String if literally written in the code.
|
||||
|
||||
// It is kinda amusing, that the other solution, to use new() kind of turns me off, because I cannot be explicit about the parameter name in the call.
|
||||
// I would literally love the option to be more explicit in function param names.
|
||||
|
||||
// while builder patterns feel a bit bloated for static runtime options, i will probably look into automations for that.
|
||||
|
||||
// this is probably the only aesthetic decision in rust I probably will hate forever.
|
||||
// It does not make sense, Strings as literals already are special in your code.
|
||||
// Because numbers are as well, you dont have to write 123.into() either.
|
||||
// I know I probably made some really harsh logical mistakes in my opinion here, and maybe it can be proven, that I am wrong, and I would love to hear that
|
||||
// However it kind of feels like an excuse to not simplify assigning declaratively written &str to Strings in the code.
|
||||
|
||||
// And it makes sense to be explicit about creating a String Buffer sometimes, but it does not make sense mostly.
|
||||
|
||||
// Anyway, I will still try a procedural macro for this, just for fun.
|
251
src/bin/ui_icon.rs
Normal file
251
src/bin/ui_icon.rs
Normal file
@ -0,0 +1,251 @@
|
||||
use eframe::{egui, NativeOptions};
|
||||
use std::{ffi::OsStr, os::windows::ffi::OsStrExt, ptr};
|
||||
use windows::{
|
||||
core::PCWSTR,
|
||||
Win32::{
|
||||
System::LibraryLoader::GetModuleHandleW,
|
||||
UI::WindowsAndMessaging::{LoadImageW, IMAGE_ICON, LR_DEFAULTCOLOR},
|
||||
},
|
||||
};
|
||||
|
||||
// https://github.com/emilk/egui/issues/920
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Run the UI on the main thread
|
||||
run_ui();
|
||||
}
|
||||
|
||||
fn run_ui() {
|
||||
let app = NativeApp {};
|
||||
|
||||
// Attempt to load the icon from resources
|
||||
let icon_handle = match load_icon_from_resource("icon-soundboard") {
|
||||
Ok(handle) => Some(handle as isize),
|
||||
Err(e) => {
|
||||
eprintln!("Failed to load icon: {}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// load from icon resource, cross platform.
|
||||
// let icon = load_icon_static();
|
||||
// load from exe resource
|
||||
let icon = from_windows::load_app_icon("icon-soundboard");
|
||||
|
||||
let native_options = NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default().with_icon(icon),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"Custom window frame", // unused title
|
||||
native_options,
|
||||
Box::new(|_cc| Box::new(app)),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
struct NativeApp {}
|
||||
|
||||
impl eframe::App for NativeApp {
|
||||
fn update(&mut self, ctx: &eframe::egui::Context, frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("Testing Software");
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Blah:");
|
||||
});
|
||||
|
||||
if ui.button("Apply").clicked() {
|
||||
eprintln!("Apply clicked");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn to_wstring(str: &str) -> Vec<u16> {
|
||||
OsStr::new(str)
|
||||
.encode_wide()
|
||||
.chain(Some(0).into_iter())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn load_icon_from_resource(resource_name: &str) -> Result<isize, String> {
|
||||
let icon = unsafe {
|
||||
let hmodule = if let Ok(hmodule) = GetModuleHandleW(None) {
|
||||
hmodule
|
||||
} else {
|
||||
return Err("Error getting windows module handle".to_owned());
|
||||
};
|
||||
let handle = if let Ok(handle) = LoadImageW(
|
||||
hmodule,
|
||||
PCWSTR(to_wstring(resource_name).as_ptr()),
|
||||
IMAGE_ICON,
|
||||
64,
|
||||
64,
|
||||
LR_DEFAULTCOLOR,
|
||||
) {
|
||||
handle
|
||||
} else {
|
||||
return Err("Error getting image handle".to_owned());
|
||||
};
|
||||
|
||||
handle
|
||||
};
|
||||
Ok(icon.0)
|
||||
}
|
||||
|
||||
fn load_icon(path: &str) -> egui::IconData {
|
||||
let (icon_rgba, icon_width, icon_height) = {
|
||||
let image = image::open(path)
|
||||
.expect("Failed to open icon path")
|
||||
.into_rgba8();
|
||||
let (width, height) = image.dimensions();
|
||||
let rgba = image.into_raw();
|
||||
(rgba, width, height)
|
||||
};
|
||||
|
||||
egui::IconData {
|
||||
rgba: icon_rgba,
|
||||
width: icon_width,
|
||||
height: icon_height,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn load_icon_static() -> egui::IconData {
|
||||
let (icon_rgba, icon_width, icon_height) = {
|
||||
let icon = include_bytes!("../../icons/soundboard.ico");
|
||||
let image = image::load_from_memory(icon)
|
||||
.expect("Failed to open icon path")
|
||||
.into_rgba8();
|
||||
let (width, height) = image.dimensions();
|
||||
let rgba = image.into_raw();
|
||||
(rgba, width, height)
|
||||
};
|
||||
|
||||
egui::IconData {
|
||||
rgba: icon_rgba,
|
||||
width: icon_width,
|
||||
height: icon_height,
|
||||
}
|
||||
}
|
||||
|
||||
mod from_windows {
|
||||
use std::{ffi::OsStr, os::windows::ffi::OsStrExt, ptr};
|
||||
|
||||
use eframe::egui;
|
||||
use windows::{
|
||||
core::PCWSTR,
|
||||
Win32::{
|
||||
Graphics::Gdi::{
|
||||
CreateCompatibleDC, DeleteDC, GetDIBits, GetObjectA, SelectObject, BITMAP,
|
||||
BITMAPINFO, BITMAPINFOHEADER, DIB_RGB_COLORS,
|
||||
},
|
||||
System::LibraryLoader::GetModuleHandleW,
|
||||
UI::WindowsAndMessaging::{
|
||||
GetIconInfo, LoadImageW, HICON, ICONINFO, IMAGE_ICON, LR_DEFAULTCOLOR,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
fn to_wstring(str: &str) -> Vec<u16> {
|
||||
OsStr::new(str)
|
||||
.encode_wide()
|
||||
.chain(Some(0).into_iter())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
// Grab the icon from the exe and hand it over to egui
|
||||
pub fn load_app_icon(icon_name: &str) -> egui::IconData {
|
||||
let (mut buffer, width, height) = unsafe {
|
||||
let resource_name = to_wstring(icon_name);
|
||||
let h_instance = GetModuleHandleW(None).unwrap();
|
||||
let icon = LoadImageW(
|
||||
h_instance,
|
||||
PCWSTR(resource_name.as_ptr()),
|
||||
IMAGE_ICON,
|
||||
512,
|
||||
512,
|
||||
LR_DEFAULTCOLOR,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut icon_info = ICONINFO::default();
|
||||
GetIconInfo(HICON(icon.0), &mut icon_info as *mut _).expect("Failed to load icon info");
|
||||
|
||||
let mut bitmap = BITMAP::default();
|
||||
GetObjectA(
|
||||
icon_info.hbmColor,
|
||||
std::mem::size_of::<BITMAP>() as i32,
|
||||
Some(&mut bitmap as *mut _ as *mut _),
|
||||
);
|
||||
|
||||
let width = bitmap.bmWidth;
|
||||
let height = bitmap.bmHeight;
|
||||
|
||||
let b_size = (width * height * 4) as usize;
|
||||
let mut buffer = Vec::<u8>::with_capacity(b_size);
|
||||
|
||||
let h_dc = CreateCompatibleDC(None);
|
||||
let h_bitmap = SelectObject(h_dc, icon_info.hbmColor);
|
||||
|
||||
let mut bitmap_info = BITMAPINFO::default();
|
||||
bitmap_info.bmiHeader.biSize = std::mem::size_of::<BITMAPINFOHEADER>() as u32;
|
||||
bitmap_info.bmiHeader.biWidth = width;
|
||||
bitmap_info.bmiHeader.biHeight = height;
|
||||
bitmap_info.bmiHeader.biPlanes = 1;
|
||||
bitmap_info.bmiHeader.biBitCount = 32;
|
||||
bitmap_info.bmiHeader.biCompression = 0;
|
||||
bitmap_info.bmiHeader.biSizeImage = 0;
|
||||
|
||||
let res = GetDIBits(
|
||||
h_dc,
|
||||
icon_info.hbmColor,
|
||||
0,
|
||||
height as u32,
|
||||
Some(buffer.spare_capacity_mut().as_mut_ptr() as *mut _),
|
||||
&mut bitmap_info as *mut _,
|
||||
DIB_RGB_COLORS,
|
||||
);
|
||||
if res == 0 {
|
||||
panic!("Failed to get RGB DI bits");
|
||||
}
|
||||
|
||||
SelectObject(h_dc, h_bitmap);
|
||||
let _ = DeleteDC(h_dc);
|
||||
|
||||
assert_eq!(
|
||||
bitmap_info.bmiHeader.biSizeImage as usize, b_size,
|
||||
"returned biSizeImage must equal to b_size"
|
||||
);
|
||||
|
||||
// set the new size
|
||||
buffer.set_len(bitmap_info.bmiHeader.biSizeImage as usize);
|
||||
|
||||
(buffer, width as u32, height as u32)
|
||||
};
|
||||
|
||||
// RGBA -> BGRA
|
||||
for pixel in buffer.as_mut_slice().chunks_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
}
|
||||
|
||||
// Flip the image vertically
|
||||
let row_size = width as usize * 4; // number of pixels in each row
|
||||
let row_count = buffer.len() as usize / row_size; // number of rows in the image
|
||||
for row in 0..row_count / 2 {
|
||||
// loop through half of the rows
|
||||
let start = row * row_size; // index of the start of the current row
|
||||
let end = (row_count - row - 1) * row_size; // index of the end of the current row
|
||||
for i in 0..row_size {
|
||||
buffer.swap(start + i, end + i);
|
||||
}
|
||||
}
|
||||
|
||||
egui::IconData {
|
||||
rgba: buffer,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,4 @@
|
||||
use axum::{
|
||||
routing::{self, get},
|
||||
Router,
|
||||
};
|
||||
use axum::{routing::get, Router};
|
||||
use eframe::egui;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
|
@ -1,7 +1,4 @@
|
||||
use axum::{
|
||||
routing::{self, get},
|
||||
Router,
|
||||
};
|
||||
use axum::{routing::get, Router};
|
||||
use eframe::egui;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
@ -56,7 +53,11 @@ async fn main() {
|
||||
|
||||
// Set up the system tray icon
|
||||
let (tray_tx, tray_rx) = std::sync::mpsc::channel();
|
||||
let mut tray = TrayItem::new("App Name", tray_item::IconSource::Resource("icon-red")).unwrap();
|
||||
let mut tray = TrayItem::new(
|
||||
"App Name",
|
||||
tray_item::IconSource::Resource("icon-soundboard"),
|
||||
)
|
||||
.unwrap();
|
||||
tray.add_label("Server Control").unwrap();
|
||||
tray.add_menu_item("Show/Hide", {
|
||||
let tray_tx = tray_tx.clone();
|
||||
|
144
src/icon.rs
Normal file
144
src/icon.rs
Normal file
@ -0,0 +1,144 @@
|
||||
// https://github.com/emilk/egui/issues/920
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn load_app_icon(_icon_name: &str) -> eframe::egui::IconData {
|
||||
let (icon_rgba, icon_width, icon_height) = {
|
||||
let icon = include_bytes!("../../icons/soundboard.ico");
|
||||
let image = image::load_from_memory(icon)
|
||||
.expect("Failed to open icon path")
|
||||
.into_rgba8();
|
||||
let (width, height) = image.dimensions();
|
||||
let rgba = image.into_raw();
|
||||
(rgba, width, height)
|
||||
};
|
||||
|
||||
eframe::egui::IconData {
|
||||
rgba: icon_rgba,
|
||||
width: icon_width,
|
||||
height: icon_height,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use from_windows::load_app_icon;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod from_windows {
|
||||
use std::{ffi::OsStr, os::windows::ffi::OsStrExt};
|
||||
|
||||
use eframe::egui;
|
||||
use windows::{
|
||||
core::PCWSTR,
|
||||
Win32::{
|
||||
Graphics::Gdi::{
|
||||
CreateCompatibleDC, DeleteDC, GetDIBits, GetObjectA, SelectObject, BITMAP,
|
||||
BITMAPINFO, BITMAPINFOHEADER, DIB_RGB_COLORS,
|
||||
},
|
||||
System::LibraryLoader::GetModuleHandleW,
|
||||
UI::WindowsAndMessaging::{
|
||||
GetIconInfo, LoadImageW, HICON, ICONINFO, IMAGE_ICON, LR_DEFAULTCOLOR,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
fn to_wstring(str: &str) -> Vec<u16> {
|
||||
OsStr::new(str)
|
||||
.encode_wide()
|
||||
.chain(Some(0).into_iter())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
// Grab the icon from the exe and hand it over to egui
|
||||
pub fn load_app_icon(icon_name: &str) -> egui::IconData {
|
||||
let (mut buffer, width, height) = unsafe {
|
||||
let resource_name = to_wstring(icon_name);
|
||||
let h_instance = GetModuleHandleW(None).unwrap();
|
||||
let icon = LoadImageW(
|
||||
h_instance,
|
||||
PCWSTR(resource_name.as_ptr()),
|
||||
IMAGE_ICON,
|
||||
512,
|
||||
512,
|
||||
LR_DEFAULTCOLOR,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut icon_info = ICONINFO::default();
|
||||
GetIconInfo(HICON(icon.0), &mut icon_info as *mut _).expect("Failed to load icon info");
|
||||
|
||||
let mut bitmap = BITMAP::default();
|
||||
GetObjectA(
|
||||
icon_info.hbmColor,
|
||||
std::mem::size_of::<BITMAP>() as i32,
|
||||
Some(&mut bitmap as *mut _ as *mut _),
|
||||
);
|
||||
|
||||
let width = bitmap.bmWidth;
|
||||
let height = bitmap.bmHeight;
|
||||
|
||||
let b_size = (width * height * 4) as usize;
|
||||
let mut buffer = Vec::<u8>::with_capacity(b_size);
|
||||
|
||||
let h_dc = CreateCompatibleDC(None);
|
||||
let h_bitmap = SelectObject(h_dc, icon_info.hbmColor);
|
||||
|
||||
let mut bitmap_info = BITMAPINFO::default();
|
||||
bitmap_info.bmiHeader.biSize = std::mem::size_of::<BITMAPINFOHEADER>() as u32;
|
||||
bitmap_info.bmiHeader.biWidth = width;
|
||||
bitmap_info.bmiHeader.biHeight = height;
|
||||
bitmap_info.bmiHeader.biPlanes = 1;
|
||||
bitmap_info.bmiHeader.biBitCount = 32;
|
||||
bitmap_info.bmiHeader.biCompression = 0;
|
||||
bitmap_info.bmiHeader.biSizeImage = 0;
|
||||
|
||||
let res = GetDIBits(
|
||||
h_dc,
|
||||
icon_info.hbmColor,
|
||||
0,
|
||||
height as u32,
|
||||
Some(buffer.spare_capacity_mut().as_mut_ptr() as *mut _),
|
||||
&mut bitmap_info as *mut _,
|
||||
DIB_RGB_COLORS,
|
||||
);
|
||||
if res == 0 {
|
||||
panic!("Failed to get RGB DI bits");
|
||||
}
|
||||
|
||||
SelectObject(h_dc, h_bitmap);
|
||||
let _ = DeleteDC(h_dc);
|
||||
|
||||
assert_eq!(
|
||||
bitmap_info.bmiHeader.biSizeImage as usize, b_size,
|
||||
"returned biSizeImage must equal to b_size"
|
||||
);
|
||||
|
||||
// set the new size
|
||||
buffer.set_len(bitmap_info.bmiHeader.biSizeImage as usize);
|
||||
|
||||
(buffer, width as u32, height as u32)
|
||||
};
|
||||
|
||||
// RGBA -> BGRA
|
||||
for pixel in buffer.as_mut_slice().chunks_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
}
|
||||
|
||||
// Flip the image vertically
|
||||
let row_size = width as usize * 4; // number of pixels in each row
|
||||
let row_count = buffer.len() as usize / row_size; // number of rows in the image
|
||||
for row in 0..row_count / 2 {
|
||||
// loop through half of the rows
|
||||
let start = row * row_size; // index of the start of the current row
|
||||
let end = (row_count - row - 1) * row_size; // index of the end of the current row
|
||||
for i in 0..row_size {
|
||||
buffer.swap(start + i, end + i);
|
||||
}
|
||||
}
|
||||
|
||||
egui::IconData {
|
||||
rgba: buffer,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ use dotenvy::dotenv;
|
||||
use vbplay::{example_handler, DeviceSelection, SoundDecoder};
|
||||
|
||||
mod handlers;
|
||||
mod icon;
|
||||
mod server;
|
||||
mod soundclips;
|
||||
mod state;
|
||||
@ -36,7 +37,7 @@ fn main() {
|
||||
let audio = Arc::new(Mutex::new(audio));
|
||||
|
||||
let server_settings = Arc::new(Mutex::new(server::ServerSettings {
|
||||
port: "3000".to_string(),
|
||||
port: "3311".to_string(),
|
||||
}));
|
||||
let should_run = Arc::new(Mutex::new(true));
|
||||
|
||||
|
68
src/ui.rs
68
src/ui.rs
@ -15,7 +15,13 @@ pub fn run_ui(
|
||||
static VISIBLE: once_cell::sync::Lazy<Mutex<bool>> =
|
||||
once_cell::sync::Lazy::new(|| Mutex::new(true));
|
||||
|
||||
let native_options = eframe::NativeOptions::default();
|
||||
let icon = crate::icon::load_app_icon("icon-soundboard");
|
||||
|
||||
let native_options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default().with_icon(icon),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"App",
|
||||
native_options,
|
||||
@ -23,38 +29,44 @@ pub fn run_ui(
|
||||
// Set up the tray icon event handler
|
||||
let window_handle = cc.window_handle().unwrap();
|
||||
let window_handle = window_handle.as_raw();
|
||||
let mut tray =
|
||||
TrayItem::new("Soundboard", tray_item::IconSource::Resource("icon-red")).unwrap();
|
||||
let mut tray = TrayItem::new(
|
||||
"Soundboard",
|
||||
tray_item::IconSource::Resource("icon-soundboard"),
|
||||
)
|
||||
.unwrap();
|
||||
if let RawWindowHandle::Win32(handle) = window_handle {
|
||||
// Windows Only.
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
ShowWindow,
|
||||
SW_HIDE,
|
||||
SW_RESTORE, // SW_SHOWDEFAULT, SW_SHOWNORMAL,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Windows Only.
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
ShowWindow,
|
||||
SW_HIDE,
|
||||
SW_RESTORE, // SW_SHOWDEFAULT, SW_SHOWNORMAL,
|
||||
};
|
||||
|
||||
tray.add_label("Server Control").unwrap();
|
||||
tray.add_label("Server Control").unwrap();
|
||||
|
||||
tray.add_menu_item("Show/Hide", {
|
||||
move || {
|
||||
let mut visible_lock = VISIBLE.lock().unwrap();
|
||||
let window_handle = HWND(handle.hwnd.into());
|
||||
tray.add_menu_item("Show/Hide", {
|
||||
move || {
|
||||
let mut visible_lock = VISIBLE.lock().unwrap();
|
||||
let window_handle = HWND(handle.hwnd.into());
|
||||
|
||||
if *visible_lock {
|
||||
unsafe {
|
||||
_ = ShowWindow(window_handle, SW_HIDE);
|
||||
if *visible_lock {
|
||||
unsafe {
|
||||
_ = ShowWindow(window_handle, SW_HIDE);
|
||||
}
|
||||
*visible_lock = false;
|
||||
} else {
|
||||
unsafe {
|
||||
_ = ShowWindow(window_handle, SW_RESTORE);
|
||||
}
|
||||
*visible_lock = true;
|
||||
}
|
||||
*visible_lock = false;
|
||||
} else {
|
||||
unsafe {
|
||||
_ = ShowWindow(window_handle, SW_RESTORE);
|
||||
}
|
||||
*visible_lock = true;
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
} else {
|
||||
println!("Unsupported platform for tray icon.");
|
||||
}
|
||||
@ -62,7 +74,7 @@ pub fn run_ui(
|
||||
let my_tx = tx.clone();
|
||||
tray.add_menu_item("Quit", move || {
|
||||
my_tx.try_send(ServerMessage::Shutdown).unwrap();
|
||||
//std::process::exit(0);
|
||||
std::process::exit(0);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
@ -200,7 +200,7 @@ pub fn audio_thread(
|
||||
error!("Mutex Lock Failure while trying to play sound.")
|
||||
}
|
||||
}
|
||||
Command::PlayWhilePressing(file_name, press_button) => {
|
||||
Command::PlayWhilePressing(file_name, _press_button) => {
|
||||
if let Ok(sink) = sink_mutex.lock() {
|
||||
play_file(&sink, file_name, &select_decoder);
|
||||
} else {
|
||||
@ -239,7 +239,7 @@ pub fn audio_thread(
|
||||
tx
|
||||
}
|
||||
|
||||
fn detect_decoder(file_name: &str, sound_decoder: &SoundDecoder) -> SoundDecoder {
|
||||
fn detect_decoder(_file_name: &str, sound_decoder: &SoundDecoder) -> SoundDecoder {
|
||||
// TODO: File detection via ending or whitelisting?
|
||||
// This function MUST NOT return SoundDecoder::Detect
|
||||
match sound_decoder {
|
||||
|
1
strinto/.gitignore
vendored
1
strinto/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/target
|
47
strinto/Cargo.lock
generated
47
strinto/Cargo.lock
generated
@ -1,47 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strinto"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "strinto"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
quote = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
proc-macro2 = "1.0"
|
@ -1,62 +0,0 @@
|
||||
extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, Expr, ExprStruct};
|
||||
|
||||
/// # literal strings in a struct insantiation are converted .into().
|
||||
///
|
||||
/// ```rust
|
||||
/// use strinto::strinto;
|
||||
/// #[derive(Debug)]
|
||||
/// struct TestStruct {
|
||||
/// name: String,
|
||||
/// title: String,
|
||||
/// description: String,
|
||||
/// age: usize,
|
||||
/// }
|
||||
///
|
||||
/// let descr = "description";
|
||||
///
|
||||
/// let output = strinto!(TestStruct {
|
||||
/// name: "John", // Literal string.
|
||||
/// title: String::from("Wicked"),
|
||||
/// description: descr.to_string(),
|
||||
/// age: 30,
|
||||
/// });
|
||||
///
|
||||
/// let output_string = format!("{:?}", output);
|
||||
/// assert_eq!(
|
||||
/// output_string,
|
||||
/// "TestStruct { name: \"John\", title: \"Wicked\", description: \"description\", age: 30 }"
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
#[proc_macro]
|
||||
pub fn strinto(input: TokenStream) -> TokenStream {
|
||||
let expr_struct = parse_macro_input!(input as ExprStruct);
|
||||
|
||||
// Extract struct name and fields
|
||||
let struct_name = &expr_struct.path;
|
||||
let fields = expr_struct.fields.iter().map(|field| {
|
||||
let field_name = field.member.clone();
|
||||
let field_value = &field.expr;
|
||||
// Determine if the field value is a string literal and transform it
|
||||
if let Expr::Lit(expr_lit) = field_value {
|
||||
if let syn::Lit::Str(_) = expr_lit.lit {
|
||||
quote! { #field_name: #field_value.into() }
|
||||
} else {
|
||||
quote! { #field_name: #field_value }
|
||||
}
|
||||
} else {
|
||||
quote! { #field_name: #field_value }
|
||||
}
|
||||
});
|
||||
|
||||
let expanded = quote! {
|
||||
#struct_name {
|
||||
#(#fields,)*
|
||||
}
|
||||
};
|
||||
|
||||
expanded.into()
|
||||
}
|
Loading…
Reference in New Issue
Block a user