1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Jokolink is a crate to deal with Mumble Link data exposed by other games/apps on windows via shared memory

//! Joko link is a windows only crate. designed to primarily get the MumbleLink or the window
//! size of the GW2 window for Jokolay (an crossplatform overlay for Guild Wars 2). It can also
//! expose the data through a server. can easily be modified to get data from other applications too.
//! on windows, you can use it to get the pointer. and on linux, you can run jokolink in wine,
//! so that you can easily request the data from a linux native application.
//! It can multiple accessing data of multiple MumbleLinks, and allows multiple clients
//! to request the data.

use log::{error, LevelFilter};
use serde::{Deserialize, Serialize};
use std::{
    fs::File,
    path::PathBuf,
    time::{Duration, Instant},
};

#[cfg(target_os = "windows")]
use crate::mlink::CMumbleLink;
use crate::mlink::MumbleLink;
pub mod mlink;

#[cfg(target_os = "windows")]
pub mod cli;
#[cfg(target_os = "linux")]
pub mod linux;
#[cfg(target_os = "windows")]
pub mod win;

/// initializes global logging backend that is used by log macros
/// Takes in a filter for stdout/stderr, a filter for logfile and finally the path to logfile
pub fn log_init(
    term_filter: LevelFilter,
    file_filter: LevelFilter,
    file_path: PathBuf,
) -> anyhow::Result<()> {
    use simplelog::*;

    CombinedLogger::init(vec![
        TermLogger::new(
            term_filter,
            Config::default(),
            TerminalMode::Mixed,
            ColorChoice::Auto,
        ),
        WriteLogger::new(file_filter, Config::default(), File::create(file_path)?),
    ])
    .map_err(|e| {
        eprintln!("failed to initialize logs due to error: {:?}", &e);
        e
    })?;
    Ok(())
}

/// This is used to update the link from the mumble source. when src is none, the link is usually a default. check if its valid before using
#[derive(Debug)]
pub struct MumbleManager {
    pub src: Option<MumbleSource>,
    pub link: MumbleLink,
    pub last_update: Instant,
    pub config: MumbleConfig,
}
/// The configuration that mumble needs. just a mumble link name
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MumbleConfig {
    /// This is used for identifying the shared memory of mumble link exposed by gw2
    pub link_name: String,
}

impl MumbleConfig {
    /// The default mumble link name. can only be changed by passing the `-mumble` options to gw2 for multiboxing
    pub const DEFAULT_MUMBLELINK_NAME: &'static str = "MumbleLink";
}

impl Default for MumbleConfig {
    /// Provides the default mumble_link_name as a string
    fn default() -> Self {
        Self {
            link_name: Self::DEFAULT_MUMBLELINK_NAME.to_string(),
        }
    }
}

impl MumbleManager {
    /// creates a mumble manager based on the config. it is upto users to check if its valid by checking the last instant and whether src is none
    pub fn new(config: MumbleConfig) -> anyhow::Result<MumbleManager> {
        let mut src = MumbleSource::new(&config.link_name);
        let mut link = MumbleLink::default();
        match src {
            Some(ref mut msrc) => link = msrc.get_link()?,
            None => {}
        }
        if link.ui_tick == 0 {
            error!("mumble link manager started with an uninitialized link");
        }
        let manager = MumbleManager {
            src,
            link,
            last_update: Instant::now(),
            config,
        };
        Ok(manager)
    }

    // just gets the already cached mumble link. call `tick()` to update
    pub fn get_link(&self) -> &MumbleLink {
        &self.link
    }

    // if src is none, it represents the last instant when we tried to update src. when src is some, it represents the last instant mumble's uitick changed
    pub fn last_updated(&self) -> Instant {
        self.last_update
    }

    /// this will check previous cache's uitick and if src is valid, will try to update mumble link. IF uitick is different, it will update the last_update instant
    /// if src is not valid, tries to create src again if it has been atleast a second from the last attempt to create. after creation attempt, we will update the last_update instant to now
    /// this keeps cpu usage low by not checking every frame. which is not that useful anyway.
    pub fn tick(&mut self) -> anyhow::Result<()> {
        let ui_tick = self.link.ui_tick;
        match self.src {
            Some(ref mut msrc) => {
                self.link = msrc.get_link()?;
                if ui_tick != self.link.ui_tick {
                    self.last_update = Instant::now();
                }
            }
            None => {
                if self.last_updated().elapsed() > Duration::from_secs(1) {
                    self.src = MumbleSource::new(&self.config.link_name);
                    log::warn!("mumble link is not initalized");
                    self.last_update = Instant::now();
                }
            }
        }
        Ok(())
    }
}
/// This source will be the used to abstract the linux/windows way of getting MumbleLink
/// on windows, this represents the shared memory pointer to mumblelink, and as long as one of gw2 or a client like us is alive, the shared memory will stay alive
/// on linux, this will be a File in /dev/shm that will only exist if jokolink created it at some point in time. this lives in ram, so reading from it is pretty much free.
#[derive(Debug)]
pub struct MumbleSource {
    #[cfg(target_os = "linux")]
    pub mumble_src: File,
    #[cfg(target_os = "windows")]
    pub mumble_src: *const CMumbleLink,
}

/// The Window dimensions struct used to represent the window position/sizes.
/// has lots of derives, so we don't have to update this again when requiring something like Hash
#[repr(C)]
#[derive(
    Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Clone, Copy,
)]
pub struct WindowDimensions {
    pub x: i32,
    pub y: i32,
    pub width: i32,
    pub height: i32,
}

unsafe impl bytemuck::Zeroable for WindowDimensions {
    fn zeroed() -> Self {
        unsafe { core::mem::zeroed() }
    }
}
unsafe impl bytemuck::Pod for WindowDimensions {}