Rebrand to Galaxy, major improvements to Bedrock support, still needs some TLC though

This commit is contained in:
Ryan Ward
2026-05-07 11:29:34 -05:00
parent f4e2475c60
commit a41cbd8cc7
2433 changed files with 14208 additions and 9409 deletions
@@ -0,0 +1,78 @@
use std::ops::Not;
use arboard::{self, Clipboard as WindowsClipboardInner};
use crate::{clipboard::ClipboardContent, Clipboard};
pub struct WindowsClipboard {
inner: WindowsClipboardInner,
}
impl WindowsClipboard {
pub fn new() -> Result<Self, arboard::Error> {
Ok(Self {
inner: WindowsClipboardInner::new()?,
})
}
}
impl Clipboard for WindowsClipboard {
fn write(&mut self, contents: ClipboardContent) {
let set_result = if let Some(html) = &contents.html {
self.inner.set().html(html, Some(&contents.plain_text))
} else {
self.inner.set().text(&contents.plain_text)
};
if let Err(err) = set_result {
if contents.html.is_some() {
log::warn!("Unable to set clipboard HTML: {err:?}");
} else {
log::warn!("Unable to set clipboard text: {err:?}");
}
}
}
fn read(&mut self) -> ClipboardContent {
let mut content = ClipboardContent {
plain_text: self.inner.get().text().unwrap_or_default(),
..Default::default()
};
// Try to get HTML content
if let Ok(html) = self.inner.get().html() {
content.html = html.is_empty().not().then_some(html);
}
// Some environments provide HTML but do not provide a plaintext representation.
// If that happens, derive a best-effort plaintext fallback from the HTML.
if content.plain_text.trim().is_empty() {
if let Some(html) = content.html.as_ref() {
let derived = crate::clipboard_utils::strip_html_to_plain_text(html);
if !derived.trim().is_empty() {
content.plain_text = derived;
}
}
}
// Get file paths.
content.paths = self.inner.get().file_list().ok().map(|list| {
list.into_iter()
.map(|p| p.to_string_lossy().to_string())
.collect()
});
// Try to get image content from clipboard
content.images = crate::clipboard_utils::read_images_from_clipboard(
&mut self.inner,
&content.html,
&content.plain_text,
);
content
}
}
#[cfg(test)]
#[path = "clipboard_tests.rs"]
mod tests;
@@ -0,0 +1,90 @@
/// Windows-specific clipboard tests.
///
/// Note: Most image processing functionality is tested in ui/src/clipboard_utils_tests.rs
/// to avoid duplication. These tests focus on Windows-specific clipboard behavior.
#[cfg(target_os = "windows")]
mod clipboard_tests {
use crate::windowing::winit::windows::clipboard::WindowsClipboard;
use crate::{clipboard::ClipboardContent, Clipboard};
fn create_test_clipboard() -> Option<WindowsClipboard> {
WindowsClipboard::new().ok()
}
#[test]
fn test_clipboard_round_trip() {
let mut clipboard = match create_test_clipboard() {
Some(clipboard) => clipboard,
None => {
eprintln!("Skipping test - no clipboard available (headless environment)");
return;
}
};
let test_content = ClipboardContent::plain_text("Windows clipboard test".to_string());
// Write content
clipboard.write(test_content.clone());
// Read it back
let read_content = clipboard.read();
// Should get the same text back (in environments where clipboard works)
if !read_content.plain_text.is_empty() {
assert_eq!(read_content.plain_text, test_content.plain_text);
}
}
#[test]
fn test_html_content_handling() {
let mut clipboard = match create_test_clipboard() {
Some(clipboard) => clipboard,
None => {
eprintln!("Skipping test - no clipboard available (headless environment)");
return;
}
};
let test_content = ClipboardContent {
plain_text: "Test text".to_string(),
html: Some("<div>Test HTML</div>".to_string()),
images: None,
paths: None,
};
// Write HTML content
clipboard.write(test_content.clone());
// Read it back
let read_content = clipboard.read();
// In environments where clipboard works, we should get content back
// (the exact HTML may not be preserved depending on the system)
if !read_content.is_empty() {
assert!(!read_content.plain_text.is_empty());
}
}
#[test]
fn test_empty_content_handling() {
let mut clipboard = match create_test_clipboard() {
Some(clipboard) => clipboard,
None => {
eprintln!("Skipping test - no clipboard available (headless environment)");
return;
}
};
let empty_content = ClipboardContent::plain_text("".to_string());
// Writing empty content should not panic
clipboard.write(empty_content);
// Reading should return valid ClipboardContent (may be empty or have previous content)
let read_content = clipboard.read();
// Should always return a valid ClipboardContent struct
// Test that the structure itself is valid, not the content
assert!(matches!(read_content.images, None | Some(_)));
}
}
@@ -0,0 +1,13 @@
pub mod clipboard;
mod network;
mod registry;
mod system_caption_buttons;
mod window_attribute;
mod window_ext;
pub use clipboard::*;
pub use network::*;
pub use registry::*;
pub use system_caption_buttons::*;
pub use window_attribute::*;
pub use window_ext::WindowExt;
@@ -0,0 +1,108 @@
use crate::windowing::winit::app::CustomEvent;
use anyhow::Context;
use windows::core::{implement, Interface};
use windows::Win32::Networking::NetworkListManager::{
INetworkListManager, INetworkListManagerEvents, INetworkListManagerEvents_Impl,
NetworkListManager, NLM_CONNECTIVITY, NLM_CONNECTIVITY_DISCONNECTED,
NLM_CONNECTIVITY_IPV4_INTERNET, NLM_CONNECTIVITY_IPV6_INTERNET,
};
use windows::Win32::System::Com::{
CoCreateInstance, CoInitializeEx, IConnectionPoint, IConnectionPointContainer, CLSCTX_ALL,
COINIT_APARTMENTTHREADED,
};
/// Implements the INetworkListManagerEvents trait so we can pass along connectivity events from Windows
/// OS to our winit event loop.
#[implement(INetworkListManagerEvents)]
#[allow(non_camel_case_types)]
struct WindowsNetworkListener {
event_loop: winit::event_loop::EventLoopProxy<CustomEvent>,
}
impl WindowsNetworkListener {
fn new(event_loop: winit::event_loop::EventLoopProxy<CustomEvent>) -> Self {
Self { event_loop }
}
}
#[allow(non_snake_case)]
impl INetworkListManagerEvents_Impl for WindowsNetworkListener_Impl {
fn ConnectivityChanged(&self, new_connectivity: NLM_CONNECTIVITY) -> windows::core::Result<()> {
// The NLM_CONNECTIVITY parameter is a bitmap. When it contains NLM_CONNECTIVITY_IPV4_INTERNET
// or NLM_CONNECTIVITY_IPV6_INTERNET, there's a connection. When it's equal to
// NLM_CONNECTIVITY_DISCONNECTED, it's a disconnection. Other arbitrary network events are ignored.
// https://learn.microsoft.com/en-us/windows/win32/api/netlistmgr/ne-netlistmgr-nlm_connectivity#syntax
// let connected = new_connectivity.eq(&NLM_CONNECTIVITY_IPV6_INTERNET) || new_connectivity.eq(&NLM_CONNECTIVITY_IPV4_INTERNET);
let connected = (new_connectivity.0
& (NLM_CONNECTIVITY_IPV6_INTERNET.0 | NLM_CONNECTIVITY_IPV4_INTERNET.0))
!= 0;
let disconnected = new_connectivity.eq(&NLM_CONNECTIVITY_DISCONNECTED);
if connected {
let _ = self.event_loop.send_event(CustomEvent::InternetConnected);
} else if disconnected {
let _ = self
.event_loop
.send_event(CustomEvent::InternetDisconnected);
}
Ok(())
}
}
pub struct WindowsNetworkConnectionPoint {
connection_point: IConnectionPoint,
cookie: u32,
#[allow(unused)]
/// We keep the events interface around for the duration of the program because
/// we're not sure we don't need it to keep living.
events_interface: INetworkListManagerEvents,
}
impl WindowsNetworkConnectionPoint {
pub fn clean_up(&self) {
unsafe {
if let Err(e) = self.connection_point.Unadvise(self.cookie) {
log::warn!("Failed to clean up network connection point: {e:?}");
}
}
}
}
pub fn add_network_connection_listener(
event_loop_proxy: winit::event_loop::EventLoopProxy<CustomEvent>,
) -> anyhow::Result<WindowsNetworkConnectionPoint> {
let network_listener = {
unsafe {
// This invocation matches winit exactly. We want to make sure we don't modify any winit invariants in the case that
// winit also calls CoInitializeEx.
// https://github.com/rust-windowing/winit/blob/953d9b426886749e2f88250f420c87db58080c97/src/platform_impl/windows/window.rs#L1386
CoInitializeEx(None, COINIT_APARTMENTTHREADED)
.ok()
.context("Failed to initialize COM")?;
let events_interface: INetworkListManagerEvents =
WindowsNetworkListener::new(event_loop_proxy).into();
let connection_point_container: IConnectionPointContainer =
CoCreateInstance(&NetworkListManager, None, CLSCTX_ALL)
.and_then(|network_manager: INetworkListManager| network_manager.cast())
.context("Failed to construct IConnectionPointContainer")?;
let connection_point: IConnectionPoint = connection_point_container
.FindConnectionPoint(&INetworkListManagerEvents::IID)
.context("Failed to construct IConnectionPoint")?;
let cookie = connection_point
.Advise(&events_interface)
.context("Failed to attach point and sink")?;
WindowsNetworkConnectionPoint {
connection_point,
cookie,
events_interface,
}
}
};
Ok(network_listener)
}
@@ -0,0 +1,22 @@
use crate::platform::SystemTheme;
use winreg::enums::HKEY_CURRENT_USER;
use winreg::RegKey;
const SYSTEM_THEME_SUBKEY_PATH: &str =
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
const LIGHT_MODE_SUBKEY_NAME: &str = "AppsUseLightTheme";
/// Retrieves the system theme from the Windows Registry.
/// https://github.com/wez/wezterm/blob/b8f94c474ce48ac195b51c1aeacf41ae049b774e/window/src/os/windows/connection.rs#L42
pub fn get_system_theme() -> Result<SystemTheme, std::io::Error> {
let theme_subkey = RegKey::predef(HKEY_CURRENT_USER).open_subkey(SYSTEM_THEME_SUBKEY_PATH)?;
let theme_value = theme_subkey.get_value::<u32, _>(LIGHT_MODE_SUBKEY_NAME)?;
match theme_value {
1 => Ok(SystemTheme::Light),
0 => Ok(SystemTheme::Dark),
_ => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("System theme value {theme_value:?} was invalid"),
)),
}
}
@@ -0,0 +1,42 @@
use super::window_attribute::get_window_attribute;
use super::WindowAttributeErr;
use windows::Win32::Foundation::RECT;
use windows::Win32::Graphics::Dwm;
use winit::window::Window as WinitWindow;
#[derive(Debug)]
pub struct SystemCaptionButtonData {
bounds: RECT,
}
#[derive(Debug)]
pub enum SystemCaptionButtonSide {
Left,
Right,
}
impl SystemCaptionButtonData {
pub fn total_width(&self) -> i32 {
self.bounds.right - self.bounds.left
}
pub fn side(&self) -> SystemCaptionButtonSide {
if self.bounds.left == 0 {
SystemCaptionButtonSide::Left
} else {
SystemCaptionButtonSide::Right
}
}
}
/// Retrieves the system caption button's bounds using the window's
/// CAPTION_BUTTON_BOUNDS attribute.
pub fn get_system_caption_button_bounds(
window: &WinitWindow,
) -> Result<SystemCaptionButtonData, WindowAttributeErr> {
let caption_button_bounds = get_window_attribute(window, Dwm::DWMWA_CAPTION_BUTTON_BOUNDS)?;
Ok(SystemCaptionButtonData {
bounds: caption_button_bounds,
})
}
@@ -0,0 +1,72 @@
use std::{ffi::c_void, mem::size_of};
use thiserror::Error;
use wgpu::rwh;
use windows::Win32::Foundation::HWND;
use windows::Win32::Graphics::Dwm::{self, DWMWINDOWATTRIBUTE};
use winit::raw_window_handle::HasWindowHandle;
use winit::raw_window_handle::RawWindowHandle;
use winit::window::Window as WinitWindow;
#[derive(Debug, Error)]
pub enum WindowAttributeErr {
#[error(transparent)]
HandleError(#[from] rwh::HandleError),
#[error(transparent)]
Win32Error(#[from] windows::core::Error),
}
/// Uses the `windows` crate to fetch a specific window attribute.
/// First, we translate the Winit window object to a native Windows HWND handle.
/// Then, we invoke the Device Window Manager (DWM)'s `DwmGetWindowAttribute`
/// function for the attribute in question.
pub fn get_window_attribute<T>(
window: &WinitWindow,
attribute_name: DWMWINDOWATTRIBUTE,
) -> Result<T, WindowAttributeErr>
where
T: Default,
{
let hwnd_handle = to_hwnd(window)?;
let mut result_destination: T = T::default();
let window_attribute_result = unsafe {
let result_address = core::ptr::addr_of_mut!(result_destination);
Dwm::DwmGetWindowAttribute(
hwnd_handle,
attribute_name,
result_address as *mut c_void,
size_of::<T>().try_into().unwrap(),
)
};
Ok(window_attribute_result.map(|_| result_destination)?)
}
/// Uses the `windows` crate to set a specific window attribute.
/// First, we translate the Winit window object to a native Windows HWND handle.
/// Then, we invoke the Device Window Manager (DWM)'s `DwmSetWindowAttribute`
/// function for the attribute in question.
pub fn set_window_attribute<T>(
window: &WinitWindow,
attribute_name: DWMWINDOWATTRIBUTE,
value: T,
) -> Result<(), WindowAttributeErr> {
let hwnd_handle = to_hwnd(window)?;
let window_attribute_result = unsafe {
Dwm::DwmSetWindowAttribute(
hwnd_handle,
attribute_name,
core::ptr::addr_of!(value) as *const c_void,
size_of::<T>().try_into().unwrap(),
)
};
Ok(window_attribute_result?)
}
fn to_hwnd(window: &WinitWindow) -> Result<HWND, rwh::HandleError> {
window
.window_handle()
.and_then(|handle| match handle.as_raw() {
RawWindowHandle::Win32(handle) => Ok(handle),
_ => Err(rwh::HandleError::NotSupported),
})
.map(|handle| HWND(handle.hwnd.get() as *mut c_void))
}
@@ -0,0 +1,42 @@
use windows::Win32::Foundation::{FALSE, HWND, TRUE};
use windows::Win32::Graphics::Dwm::{DwmSetWindowAttribute, DWMWA_CLOAK};
use windows_core::BOOL;
use winit::raw_window_handle::{HasWindowHandle, RawWindowHandle};
use winit::window::Window;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Invalid WindowHandle")]
InvalidWindowHandle,
#[error("Unknown error")]
Other(#[from] windows::core::Error),
}
/// Extension trait for Windows specific logic on a [`winit::window::Window`].
pub trait WindowExt {
/// "Cloaks" the window. A cloaked window is one that is invisible, but can still be drawn to.
fn set_cloaked(&self, cloaked: bool) -> Result<(), Error>;
}
impl WindowExt for Window {
fn set_cloaked(&self, cloaked: bool) -> Result<(), Error> {
let Ok(RawWindowHandle::Win32(handle)) = self
.window_handle()
.map(|window_handle| window_handle.as_raw())
else {
return Err(Error::InvalidWindowHandle);
};
let value = if cloaked { TRUE } else { FALSE };
unsafe {
DwmSetWindowAttribute(
HWND(handle.hwnd.get() as _),
DWMWA_CLOAK,
&value as *const BOOL as *const _,
size_of::<BOOL>() as u32,
)?
}
Ok(())
}
}