tileme 0.0.3

(Not yet) A tiling window manager for Windows 10
use crate::KeyCode;
use crate::data_types::{KeyBindings, Config};
use crate::listeners::winevent::WinEventListener;
use crate::ring::{Ring, Selector};
use crate::virtual_desktop::VirtualDesktop;
use crate::windows::{get_all_windows, Window};
use crossbeam_channel::{Receiver, bounded, Sender};
use std::collections::HashMap;
use std::thread;
use winapi::shared::windef::HWND;
use winapi::um::winuser::GetForegroundWindow;
use winvd::helpers::{
    get_desktop_number_by_window,
    go_to_desktop_number,
    move_window_to_desktop_number,
    rename_desktop_number,
};
use winvd::{
    Desktop,
    VirtualDesktopEvent,
    create_desktop,
    get_desktops,
    get_event_receiver
};

pub enum Event {
    KeyPress(KeyCode),
    VirtualDesktopEvent(VirtualDesktopEvent),
    WinEvent(WinEvent),
}

#[derive(Debug)]
pub enum WinEvent {
    Foreground(usize),
    MinimizeStart(usize),
    MinimizeEnd(usize),
    MoveSizeStart(usize),
    MoveSizeEnd(usize),
}

pub struct WindowManager {
    event_sender: Sender<Event>,
    event_receiver: Receiver<Event>,
    threads: Vec<thread::JoinHandle<()>>,
    virtual_desktops: Ring<VirtualDesktop>,
    virtual_desktop_ids: Vec<Desktop>,
    hwnd_desktop: HashMap<HWND, usize>,
}

impl WindowManager {
    pub fn init(config: Config) -> WindowManager {
        let (sender, receiver) = bounded::<Event>(0);
        WindowManager {
            event_sender: sender,
            event_receiver: receiver,
            threads: Vec::new(),
            virtual_desktops: Ring::new(config.virtual_desktops.iter()
                .map(|name| VirtualDesktop::new(*name)).collect()),
            virtual_desktop_ids: get_desktops().unwrap(),
            hwnd_desktop: HashMap::new(),
        }
    }

    pub fn run(mut self, mut key_bindings: KeyBindings) {
        let hotkey_list = key_bindings.iter().map(|(key, _)| key.clone()).collect();
        self.init_virtual_desktops();
        self.init_windows();
        self.init_virtual_desktop_event_listener();
        self.init_keyboard_listener(hotkey_list);
        self.init_window_event_listener();

        loop {
            match self.event_receiver.recv().unwrap() {
                Event::KeyPress(key_code) =>
                    key_bindings.get_mut(&key_code).unwrap()(&mut self),
                Event::VirtualDesktopEvent(event) =>
                    self.handle_virtual_desktop_event(event),
                Event::WinEvent(event) => self.handle_win_event(event),
            };
        }
    }

    fn init_keyboard_listener(&mut self, hotkey_list: Vec<KeyCode>) {
        let event_sender = self.event_sender.clone();
        self.threads.push(thread::spawn(move || {
            let mut hk = hotkey::Listener::new();
            for keycode in hotkey_list {
                let sender = event_sender.clone();
                hk.register_hotkey(
                    keycode.mask, keycode.code,
                    move || sender.send(Event::KeyPress(keycode)).unwrap()
                ).unwrap();
            }
            hk.listen();
        }));
    }

    // Virtual desktops
    fn init_virtual_desktop_event_listener(&mut self) {
        let event_sender = self.event_sender.clone();
        self.threads.push(thread::spawn(move || {
            let receiver = get_event_receiver();
            loop {
                let event = receiver.recv()
                    .expect("Failed to receive a VirtualDesktopEvent");
                if let Err(_) = event_sender.send(Event::VirtualDesktopEvent(event)) {
                    println!("Failed to send a VirtualDesktopEvent");
                }
            }
        }));
    }

    fn init_window_event_listener(&mut self) {
        let sender = self.event_sender.clone();
        self.threads.push(thread::spawn(move || {
            WinEventListener::init(sender).listen();
        }));
    }

    fn handle_virtual_desktop_event(&mut self, event: VirtualDesktopEvent) {
        match event {
            VirtualDesktopEvent::DesktopChanged(_old, new) => self.handle_desktop_changed(&new),
            VirtualDesktopEvent::DesktopCreated(desk) =>
                println!("<- New desktop created {:?}", desk),
            VirtualDesktopEvent::DesktopDestroyed(desk) =>
                println!("<- Desktop destroyed {:?}", desk),
            VirtualDesktopEvent::WindowChanged(hwnd) => self.handle_window_changed(hwnd as HWND),
        };
    }

    fn handle_desktop_changed(&mut self, new: &Desktop) {
        match self.virtual_desktop_ids.iter().position(|desktop: &Desktop| desktop == new) {
            Some(vd_index) => {
                match self.virtual_desktops.focus(&Selector::Index(vd_index)) {
                    Some(vd) => vd.grab_focus(),
                    None => println!("Did not find a desktop to focus. Did you manipulate \
                                      virtual desktops manually after the start of the program?"),
                }
            }
            None => println!("Did not find a desktop to switch. Did you manipulate \
                              virtual desktops manually after the start of the program?"),
        }
    }

    /// Update window information, when a WindowChanged event is received for HWND
    fn handle_window_changed(&mut self, hwnd: HWND) {
        match self.hwnd_desktop.get(&hwnd) {
            Some(old_desktop_number) => {
                // Check if window didn't change desktop, if it did - move it.
                // If no new desktop -- remove window.
                match get_desktop_number_by_window(hwnd as u32) {
                    Ok(new_desktop_number) => {
                        println!("Window {:?} moved from desktop {} to desktop {}",
                                 hwnd, old_desktop_number, new_desktop_number);
                        if *old_desktop_number != new_desktop_number {
                            // Window moved to the new desktop
                            let window = self.virtual_desktops
                                .get_mut(*old_desktop_number)
                                .expect(format!("Internal error: no desktop number {}",
                                                old_desktop_number).as_str())
                                .pull_window_by_hwnd(hwnd)
                                .expect(format!(
                                    "Internal error: window {:?} is found in self.hwnd_desktop on \
                                     the desktop number {}, but the correspondent window is not \
                                     present in the virtual desktop ring.",
                                    hwnd, old_desktop_number).as_str());
                            self.virtual_desktops
                                .get_mut(new_desktop_number)
                                .expect(format!(
                                    "Internal error: no desktop number {}",
                                    new_desktop_number).as_str())
                                .push_window(window);
                            self.hwnd_desktop.insert(hwnd, new_desktop_number);
                        }
                    }
                    Err(_) => {
                        // No new desktop: window is probably destroyed
                        println!("Destroyed window {:?}", hwnd);
                        self.virtual_desktops
                            .get_mut(*old_desktop_number)
                            .expect(format!("Internal error: no desktop number {}",
                                            old_desktop_number).as_str())
                            .pull_window_by_hwnd(hwnd)
                            .expect(format!(
                                "Internal error: window {:?} is found in self.hwnd_desktop on \
                                     the desktop number {}, but the correspondent window is not \
                                     present in the virtual desktop ring.",
                                hwnd, old_desktop_number).as_str());
                        self.hwnd_desktop.remove(&hwnd);
                    }
                }
            }
            None => {
                // New window created, insert it into the virtual desktop ring
                match get_desktop_number_by_window(hwnd as u32) {
                    Ok(desktop_number) => {
                        println!("Created new window {:?} on desktop {}", hwnd, desktop_number);
                        let window = Window::init(hwnd);
                        let vd = self.virtual_desktops
                            .get_mut(desktop_number)
                            .expect(format!(
                                "Internal error: no desktop number {}", desktop_number).as_str());
                        vd.push_window(window);
                        // EVENT_SYSTEM_FOREGROUND arrives earlier than virtual desktop's
                        // window changed event, so at the moment of its arrival window manager
                        // won't have info about the window and won't handle the event.
                        // That's why we must check that after new window creation foreground
                        // window is set correctly.
                        vd.update_focus();
                        self.hwnd_desktop.insert(hwnd, desktop_number);

                    },
                    Err(error) => println!(
                        "Created new window {:?}, but could not determine its desktop: {:?}",
                        hwnd, error),
                }
            }
        }
    }

    fn handle_win_event(&mut self, event: WinEvent) {
        match event {
            WinEvent::Foreground(hwnd) => {
                let hwnd= hwnd as HWND;
                match self.hwnd_desktop.get(&hwnd) {
                    Some(desktop_number) => {
                        println!(
                            "Window {:?} went foreground", hwnd
                        );
                        self.virtual_desktops
                            .get_mut(*desktop_number)
                            .expect("Desktop is not present, try relaunching maybe?")
                            .set_focus_by_hwnd(hwnd);
                    }
                    None => println!(
                        "Window {:?} went foreground, but it is not present in \
                         self.hwnd_desktop mapping", hwnd
                    ),
                };
            }
            WinEvent::MinimizeStart(hwnd) => {
                let hwnd= hwnd as HWND;
                match self.hwnd_desktop.get_mut(&hwnd) {
                    Some(desktop_number) => {
                        println!(
                            "Window {:?} was minimized", hwnd
                        );
                        self.virtual_desktops
                            .get_mut(*desktop_number)
                            .expect("Desktop is not present, try relaunching maybe?")
                            .set_minimized(hwnd);
                    }
                    None => println!(
                        "Window {:?} was minimized, but it is not present in \
                         self.hwnd_desktop mapping", hwnd
                    ),
                }
            },
            WinEvent::MinimizeEnd(hwnd) => {
                let hwnd= hwnd as HWND;
                match self.hwnd_desktop.get_mut(&hwnd) {
                    Some(desktop_number) => {
                        println!(
                            "Window {:?} was unminimized", hwnd
                        );
                        let vd = self.virtual_desktops
                            .get_mut(*desktop_number)
                            .expect("Desktop is not present, try relaunching maybe?");
                        vd.set_unminimized(hwnd);
                        vd.update_focus();
                    }
                    None => println!(
                        "Window {:?} was minimized, but it is not present in \
                         self.hwnd_desktop mapping", hwnd
                    ),
                }
            },
            WinEvent::MoveSizeStart(_) => {}
            WinEvent::MoveSizeEnd(_) => {}
        }
    }

    fn init_virtual_desktops(&mut self) {
        let n_desktops_current = self.virtual_desktop_ids.len();
        let n_desktops = self.virtual_desktops.len();
        if n_desktops_current < n_desktops {
            for _ in 0..(n_desktops - n_desktops_current) {
                self.virtual_desktop_ids.push(create_desktop().unwrap());
            }
        }
        for (i, desktop) in self.virtual_desktops.iter().enumerate() {
            rename_desktop_number(i, &*desktop.name).unwrap();
        }
    }

    fn init_windows(&mut self) {
        let windows = get_all_windows().unwrap();
        for window in windows {
            match window.desktop_number() {
                Ok(n) => {
                    println!("Found window {:?} on desktop {}", window.hwnd(), n);
                    self.hwnd_desktop.insert(window.hwnd(), n);
                    self.virtual_desktops
                        .get_mut(n)
                        .expect(format!("Could not find a desktop number {}", n).as_str())
                        .push_window(window);
                },
                // Err(err) => println!("Failed to get desktop number for hwnd {}: error {:?}",
                //                            window.hwnd() as u32, err),
                Err(_) => {},
            }
        }
        let focused_window = unsafe { GetForegroundWindow() };
        let focused_window_desktop = self.hwnd_desktop
            .get(&focused_window)
            .expect("Focused window was not enumerated in all windows, try relaunching may be?");
        self.virtual_desktops
            .get_mut(*focused_window_desktop)
            .expect("Focused window desktop is larger than expected number of desktops")
            .set_focus_by_hwnd(focused_window);
    }

    // WINDOW MANAGER ACTIONS

    /// Shut down the WindowManager, running any required cleanup
    pub fn exit(&mut self) {
        std::process::exit(0)
    }

    /// Go to virtual desktop number
    pub fn go_to_virtual_desktop(&mut self, number: usize) {
        if go_to_desktop_number(number).is_err() {
            println!("Could not go to the desktop number {}", number)
        }
    }

    /// Move foreground window to desktop number
    pub fn move_window_to_desktop(&mut self, number: usize) {
        let foreground_window: HWND = unsafe { GetForegroundWindow() };
        if move_window_to_desktop_number(foreground_window as winvd::HWND, number).is_err() {
            println!("Could not move window {:?} to desktop number {}", foreground_window, number);
        }
    }

    /// Echo some string, for testing
    pub fn echo(&self, str: &str) {
        println!("{}", str);
    }
}