Skip to content

Commit

Permalink
feat: allow to launch coco across multi monitors (#135)
Browse files Browse the repository at this point in the history
* feat: allow to launch coco across multi monitors

* chore: add missing commits

* fix: fix build and default style
  • Loading branch information
medcl authored Feb 11, 2025
1 parent 7d0274b commit e6afce6
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 31 deletions.
114 changes: 101 additions & 13 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use reqwest::Client;
use std::path::PathBuf;
#[cfg(target_os = "macos")]
use tauri::ActivationPolicy;
use tauri::{AppHandle, Emitter, Listener, Manager, Runtime, WebviewWindow, WindowEvent};
use tauri::{AppHandle, Emitter, Listener, Manager, PhysicalPosition, Runtime, WebviewWindow, Window, WindowEvent};
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_deep_link::DeepLinkExt;
use tokio::runtime::Runtime as RT;
Expand Down Expand Up @@ -106,8 +106,7 @@ pub fn run() {
dbg!("Async initialization tasks completed");
});


shortcut::enable_shortcut(app);
shortcut::enable_shortcut(&app);
enable_tray(app);
enable_autostart(app);

Expand Down Expand Up @@ -181,18 +180,30 @@ pub async fn init<R: Runtime>(app_handle: &AppHandle<R>) {
registry.register_source(source).await;
}

// Clone app_handle to move it into the background task
let app_handle_clone = app_handle.clone();

// Run the slow application directory search in the background
tokio::spawn(async move {
dbg!("Initializing application search source in background");
let dir = vec![
dirs::home_dir().map(|home| home.join("Applications")), // Resolve `~/Applications`
Some(PathBuf::from("/Applications")),
Some(PathBuf::from("/System/Applications")),
Some(PathBuf::from("/System/Applications/Utilities")),
];

// Remove any `None` values if `home_dir()` fails
let app_dirs: Vec<PathBuf> = dir.into_iter().flatten().collect();

let application_search = local::application::ApplicationSearchSource::new(1000f64, app_dirs);

let dir = vec![
dirs::home_dir().map(|home| home.join("Applications")), // Resolve `~/Applications`
Some(PathBuf::from("/Applications")),
Some(PathBuf::from("/System/Applications")),
Some(PathBuf::from("/System/Applications/Utilities")),
];
// Register the application search source
let registry = app_handle_clone.state::<SearchSourceRegistry>();
registry.register_source(application_search).await;

// Remove any `None` values if `home_dir()` fails
let app_dirs: Vec<PathBuf> = dir.into_iter().flatten().collect();
let application_search = local::application::ApplicationSearchSource::new(1000f64, app_dirs);
registry.register_source(application_search).await;
dbg!("Application search source initialized in background");
});

dbg!("Initialization completed");
}
Expand Down Expand Up @@ -226,6 +237,8 @@ fn handle_open_coco(app: &AppHandle) {
println!("Open Coco menu clicked!");

if let Some(window) = app.get_window(MAIN_WINDOW_LABEL) {
move_window_to_active_monitor(&window);

window.show().unwrap();
window.set_visible_on_all_workspaces(true).unwrap();
window.set_always_on_top(true).unwrap();
Expand All @@ -235,6 +248,81 @@ fn handle_open_coco(app: &AppHandle) {
}
}

fn move_window_to_active_monitor<R: Runtime>(window: &Window<R>) {
dbg!("Moving window to active monitor");
// Try to get the available monitors, handle failure gracefully
let available_monitors = match window.available_monitors() {
Ok(monitors) => monitors,
Err(e) => {
eprintln!("Failed to get monitors: {}", e);
return;
}
};

// Attempt to get the cursor position, handle failure gracefully
let cursor_position = match window.cursor_position() {
Ok(pos) => Some(pos),
Err(e) => {
eprintln!("Failed to get cursor position: {}", e);
None
}
};

// Find the monitor that contains the cursor or default to the primary monitor
let target_monitor = if let Some(cursor_position) = cursor_position {
// Convert cursor position to integers
let cursor_x = cursor_position.x.round() as i32;
let cursor_y = cursor_position.y.round() as i32;

// Find the monitor that contains the cursor
available_monitors.into_iter().find(|monitor| {
let monitor_position = monitor.position();
let monitor_size = monitor.size();

cursor_x >= monitor_position.x
&& cursor_x <= monitor_position.x + monitor_size.width as i32
&& cursor_y >= monitor_position.y
&& cursor_y <= monitor_position.y + monitor_size.height as i32
})
} else {
None
};

// Use the target monitor or default to the primary monitor
let monitor = match target_monitor.or_else(|| window.primary_monitor().ok().flatten()) {
Some(monitor) => monitor,
None => {
eprintln!("No monitor found!");
return;
}
};

let monitor_position = monitor.position();
let monitor_size = monitor.size();

// Get the current size of the window
let window_size = match window.inner_size() {
Ok(size) => size,
Err(e) => {
eprintln!("Failed to get window size: {}", e);
return;
}
};

let window_width = window_size.width as i32;
let window_height = window_size.height as i32;

// Calculate the new position to center the window on the monitor
let window_x = monitor_position.x + (monitor_size.width as i32 - window_width) / 2;
let window_y = monitor_position.y + (monitor_size.height as i32 - window_height) / 2;

// Move the window to the new position
if let Err(e) = window.set_position(PhysicalPosition::new(window_x, window_y)) {
eprintln!("Failed to move window: {}", e);
}
}


fn handle_hide_coco(app: &AppHandle) {
// println!("Hide Coco menu clicked!");

Expand Down
1 change: 0 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

fn main() {
coco_lib::run();
}
20 changes: 13 additions & 7 deletions src-tauri/src/shortcut.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::COCO_TAURI_STORE;
use crate::{move_window_to_active_monitor, COCO_TAURI_STORE};
use tauri::App;
use tauri::AppHandle;
use tauri::Manager;
Expand Down Expand Up @@ -99,18 +99,22 @@ pub fn change_shortcut<R: Runtime>(

/// Helper function to register a shortcut, used for shortcut updates.
fn _register_shortcut<R: Runtime>(app: &AppHandle<R>, shortcut: Shortcut) {
let main_window = app.get_webview_window("main").unwrap();
app.global_shortcut()
.on_shortcut(shortcut, move |_app, scut, event| {
.on_shortcut(shortcut, move |app, scut, event| {
if scut == &shortcut {
dbg!("shortcut pressed");
let main_window = app.get_window(MAIN_WINDOW_LABEL).unwrap();
if let ShortcutState::Pressed = event.state() {
if main_window.is_visible().unwrap() {
dbg!("hiding window");
main_window.hide().unwrap();
} else {
main_window.show().unwrap();
dbg!("showing window");
move_window_to_active_monitor(&main_window);
main_window.set_visible_on_all_workspaces(true).unwrap();
main_window.set_always_on_top(true).unwrap();
main_window.set_focus().unwrap();
main_window.show().unwrap();
}
}
}
Expand All @@ -123,21 +127,23 @@ use crate::common::MAIN_WINDOW_LABEL;

/// Helper function to register a shortcut, used to set up the shortcut up App's first start.
fn _register_shortcut_upon_start(app: &App, shortcut: Shortcut) {
let window = app.get_webview_window(MAIN_WINDOW_LABEL).unwrap();
app.handle()
let handler = app.app_handle();
handler
.plugin(
tauri_plugin_global_shortcut::Builder::new()
.with_handler(move |app, scut, event| {
if scut == &shortcut {
let window = app.get_window(MAIN_WINDOW_LABEL).unwrap();
if let ShortcutState::Pressed = event.state() {
if window.is_visible().unwrap() {
window.hide().unwrap();
} else {
dbg!("showing window");
window.show().unwrap();
move_window_to_active_monitor(&window);
window.set_visible_on_all_workspaces(true).unwrap();
window.set_always_on_top(true).unwrap();
window.set_focus().unwrap();
window.show().unwrap();
}
}
}
Expand Down
13 changes: 3 additions & 10 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,7 @@
},
"bundle": {
"active": true,
"targets": [
"nsis",
"dmg",
"app",
"appimage",
"deb",
"rpm"
],
"targets": "all",
"shortDescription": "Coco AI",
"icon": [
"icons/32x32.png",
Expand All @@ -103,11 +96,11 @@
"dmg": {
"appPosition": {
"x": 180,
"y": 140
"y": 180
},
"applicationFolderPosition": {
"x": 480,
"y": 140
"y": 180
}
}
},
Expand Down
1 change: 1 addition & 0 deletions src/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
--background: #ffffff;
--foreground: #09090b;
--border: #e3e3e7;
--coco-primary-color: rgb(149, 5, 153);
}

/* Light theme */
Expand Down

0 comments on commit e6afce6

Please # to comment.