Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 31 additions & 179 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 3 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "winri"
description = "A window scrolling tiler for Windows 11"
version = "0.3.0"
version = "0.3.1"
edition = "2024"
license = "MIT"

Expand All @@ -12,8 +12,8 @@ log = "0"
log4rs = "1.4"
rdev = { version = "0", features = ["unstable_grab"] }
easy-ext = "1"
joy-vector = { git = "https://github.com/sub07/rust-utils", version = "0.2.1" }
joy-error = { git = "https://github.com/sub07/rust-utils", version = "0.6.0", features = ["log-crate", "anyhow-crate"] }
joy-vector = { git = "https://github.com/sub07/rust-utils", rev = "a7103d4dacf67412b9e85678581b1387649a2897" }
joy-error = { git = "https://github.com/sub07/rust-utils", rev = "a7103d4dacf67412b9e85678581b1387649a2897", features = ["log-crate", "anyhow-crate"] }
itertools = "0"
keyboard-types = { version = "0.8", features = ["std", "serde"] }
dirs = "6"
Expand All @@ -28,17 +28,13 @@ features = [
"Win32_System_Threading",
"Win32_System_ProcessStatus",
"Win32_Graphics_Dwm",
"Win32_UI_Controls",
"Win32_Graphics_Gdi"
]

[dependencies.iced]
git = "https://github.com/sub07/iced.git"
branch = "custom-winri"
features = [
"debug",
"time-travel",
"web-colors",
"wgpu",
"canvas",
"tokio",
Expand Down
26 changes: 10 additions & 16 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod action;
pub mod model;
mod service;
mod subscription;
pub mod task;
mod view;

use anyhow::Context;
Expand All @@ -10,6 +11,7 @@ use iced::{
theme::Palette,
window::{Settings, settings::PlatformSpecific},
};
use joy_error::ResultUtilityExt;

use crate::{
app::{
Expand All @@ -23,7 +25,7 @@ use crate::{
scroll_tiler::ScrollTiler,
system,
utils::math::Size,
window::{self, Window},
window::{self},
};

pub struct State {
Expand Down Expand Up @@ -72,19 +74,7 @@ fn create_overlay_window(screen_size: Size) -> (iced::window::Id, Task<Message>)
..Default::default()
});

(
id,
task.then(iced::window::enable_mouse_passthrough)
.then(|_: Message| {
// HACK: By default the overlay window steals focus when created, but should not be able to be focused.
// It causes weird behavior like keystroke not recorded until another window is focused.
// So we refocus the desktop window after creation.
if let Err(err) = system::get_desktop_window().and_then(Window::focus) {
log::error!("Could not focus desktop window after overlay creation: {err}");
}
Task::none()
}),
)
(id, task.then(iced::window::enable_mouse_passthrough))
}

impl State {
Expand Down Expand Up @@ -115,6 +105,12 @@ impl State {

pub fn handle_app_message(&mut self, message: Message) -> Task<Message> {
let mut task = Task::none();

// HACK: By default the overlay window steals focus when created, but should not be able to be focused.
// It causes weird behavior like keystroke not recorded until another window is focused.
// So we refocus the desktop window after creation.
task = task.chain(task::ensure_overlay_not_focused(self.overlay_window_id));

match message {
Message::Global(global_message) => {
task = task.chain(self.handle_global_message(global_message));
Expand Down Expand Up @@ -198,6 +194,4 @@ impl<T, E: std::fmt::Debug> Result<T, E> {
}
self
}

fn discard(self) {}
}
20 changes: 17 additions & 3 deletions src/app/subscription/global/input.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::thread;
use std::{thread, time::Duration};

use iced::futures::channel::mpsc::Sender;
use joy_error::ResultUtilityExt;
use keyboard_types::Modifiers;
use rdev::simulate;

use crate::app::subscription::global::GlobalMessage;
use crate::{app::subscription::global::GlobalMessage, system};

fn grab_event_processing(
event: rdev::Event,
Expand All @@ -22,6 +24,8 @@ fn grab_event_processing(
return Some(event);
}

log::debug!("{:?}", event.event_type);

match event.event_type {
rdev::EventType::KeyPress(key) => {
match key {
Expand All @@ -47,6 +51,16 @@ fn grab_event_processing(
}
_ => {
tx.try_send(GlobalMessage::Key(*modifiers, key)).unwrap();
if (*modifiers, key) == (Modifiers::META, Key::KeyL) {
// Win + L is a system shortcut to lock the screen
// We cannot override or block it
// It causes an unwanted behavior when triggering it causing the win key down to be registered but not the win up
// So when on the lock screen the win key is considered down despite the user not pressing it
// FIX: Simulate a win up event after the lock screen appeared by wait after a delay
thread::sleep(Duration::from_millis(300));
simulate(&EventType::KeyRelease(Key::MetaLeft)).discard();
simulate(&EventType::KeyRelease(Key::MetaRight)).discard();
}
return (!modifiers.contains(Modifiers::META)).then_some(event);
}
}
Expand Down Expand Up @@ -79,7 +93,7 @@ pub fn launch(tx: Sender<GlobalMessage>) {
let _ = thread::Builder::new()
.name("global-key-hook".into())
.spawn(move || {
let mut modifiers = Modifiers::default(); // TODO: Check initial state of modifiers
let mut modifiers = system::current_modifiers();
rdev::_grab(move |event| grab_event_processing(event, &mut modifiers, tx.clone()))
.unwrap();
});
Expand Down
8 changes: 4 additions & 4 deletions src/app/subscription/global/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use iced::futures::channel::mpsc::Sender;
use windows::Win32::UI::{
Accessibility::{SetWinEventHook, UnhookWinEvent},
WindowsAndMessaging::{
EVENT_OBJECT_CREATE, EVENT_OBJECT_FOCUS, GetMessageW, WINEVENT_OUTOFCONTEXT,
EVENT_OBJECT_CREATE, EVENT_OBJECT_LOCATIONCHANGE, GetMessageW, WINEVENT_OUTOFCONTEXT,
WINEVENT_SKIPOWNPROCESS,
},
};
Expand Down Expand Up @@ -60,7 +60,7 @@ impl WindowHookManager {

static WINDOW_HOOK_MANAGER: Mutex<Option<WindowHookManager>> = Mutex::new(None);

unsafe extern "system" fn hook_callback(
unsafe extern "system" fn win_event_hook_callback(
_hwineventhook: windows::Win32::UI::Accessibility::HWINEVENTHOOK,
_event: u32,
_hwnd: windows::Win32::Foundation::HWND,
Expand All @@ -86,9 +86,9 @@ pub fn launch(tx: Sender<GlobalMessage>) {
.spawn(move || unsafe {
let hook = SetWinEventHook(
EVENT_OBJECT_CREATE,
EVENT_OBJECT_FOCUS,
EVENT_OBJECT_LOCATIONCHANGE,
None,
Some(hook_callback),
Some(win_event_hook_callback),
0,
0,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS,
Expand Down
36 changes: 36 additions & 0 deletions src/app/task/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/// This module contains asynchronous `task`s for the iced runtime.
use anyhow::Context;
use iced::Task;
use joy_error::{ResultUtilityExt, log::ResultLogExt};

use crate::{
app::{self},
system,
window::Window,
};

pub fn ensure_overlay_not_focused(overlay_window_id: iced::window::Id) -> Task<app::Message> {
iced::window::raw_id::<app::Message>(overlay_window_id).then(|raw_id| {
unfocus_window(raw_id)
.context("unfocusing overlay window")
.error()
.log_err()
.discard();
Task::none()
})
}

fn unfocus_window(raw_id: u64) -> anyhow::Result<()> {
let focused_window = Window::focused().context("getting focused window")?;

let overlay_window = Window::from_safe_hwnd(raw_id).context(format!(
"given raw id ({raw_id}) is invalid: expected overlay raw id (aka. HWND)"
))?;

let desktop_window = system::get_desktop_window().context("getting desktop window")?;
if focused_window == overlay_window {
desktop_window.focus()?;
}

Ok(())
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ fn main() {
message_box::message_box_fatal_bug_report(info);
system::restore_windows();
default_hook(info);
std::process::exit(1);
}));

if let Err(e) = logger::setup()
Expand Down
59 changes: 37 additions & 22 deletions src/scroll_tiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,25 @@ use crate::{cast, utils::math::Size, window::Window};
#[derive(PartialEq)]
pub struct WindowItem {
pub inner: Window,
pub requested_width: Option<f32>,
pub width: f32,
}

impl WindowItem {
pub const fn new(inner: Window, width: f32) -> Self {
Self { inner, width }
Self {
inner,
requested_width: Some(width),
width,
}
}

fn request_width(&mut self, width: f32) {
self.requested_width = Some(width);
}

fn requested_width(&mut self) -> Option<f32> {
self.requested_width.take()
}
}

Expand Down Expand Up @@ -128,12 +141,12 @@ impl ScrollTiler {

pub fn set_current_window_fullscreen(&mut self) {
if let Some(focus_index) = self.focus_index() {
let screen_width = self.screen_size.width();
let width = self.max_screen_width();
// -1 to avoid occupying the whole screen and causing scroll issues
// TODO: find a better solution for this, the problem is that the scroll system
// doesn't handle windows that are equals or bigger than the screen size well.
// Fix for now: prevent windows width from being equal or bigger than screen size.
self.windows[focus_index].width = self.padding.mul_add(-2.0, screen_width) - 1.0;
self.windows[focus_index].request_width(width);
}
}

Expand All @@ -152,8 +165,12 @@ impl ScrollTiler {
self.resize_current_window_width_by_resize_increment(-1);
}

pub fn max_screen_width(&self) -> f32 {
self.padding.mul_add(-2.0, self.screen_size.width()) - 1.0
}

/// Resize the current window width by the resize increment in the given direction.
/// Direction should be 1 for increasing width and -1 for decreasing width.
/// Direction should be 1 for increasing width and -1 for decreasing width.ze
fn resize_current_window_width_by_resize_increment(&mut self, direction: i32) {
if let Some(focus_index) = self.focus_index() {
// TODO: check explanation in `set_current_window_fullscreen` about -1
Expand All @@ -167,7 +184,7 @@ impl ScrollTiler {
0.0,
self.padding.mul_add(-2.0, self.screen_size().width()) - 1.0,
);
self.windows[focus_index].width = new_width;
self.windows[focus_index].request_width(new_width);
}
}

Expand All @@ -184,6 +201,8 @@ impl ScrollTiler {
self.windows
.retain(|item| windows_snapshot.contains(&item.inner));

self.update_widths();

self.append_new_windows(windows_snapshot);

let windows_positions = self.windows_positions();
Expand All @@ -197,7 +216,6 @@ impl ScrollTiler {
);
}
self.layout_windows(&windows_positions);
self.reconciliate_window_widths();

if let Some(new_focused_window_index) = self
.focus_index()
Expand Down Expand Up @@ -261,23 +279,20 @@ impl ScrollTiler {
}
}

fn reconciliate_window_widths(&mut self) {
/// Width
fn update_widths(&mut self) {
let max_screen_width = self.max_screen_width();
for window in &mut self.windows {
let window_rect = match window.inner.desktop_manager_bounds() {
Ok(rect) => rect,
Err(e) => {
warn!(
"Error while reconciliating window, skipping to next one (window might have been closed just after enumeration): {e}"
);
continue;
}
};

let actual_size = window_rect.right - window_rect.left;

let expected_size = window.width;
if expected_size < actual_size {
window.width = self.padding.mul_add(2.0, actual_size);
if let Some(requested_width) = window.requested_width() {
window.width = requested_width;
} else if let Ok(bounds) = window
.inner
.desktop_manager_bounds()
.context("Updating widths")
.error()
.log_err()
{
window.width = bounds.size().width().min(max_screen_width);
}
}
}
Expand Down
Loading