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 {}