use std::ffi::c_void;
use std::ptr;
type Handle = *mut c_void;
const WTS_ACTIVE: i32 = 0;
const WTS_USER_NAME: i32 = 5;
#[repr(C)]
struct WtsSessionInfoW {
session_id: u32,
p_win_station_name: *mut u16,
state: i32,
}
#[link(name = "wtsapi32")]
extern "system" {
fn WTSEnumerateSessionsW(
server: Handle,
reserved: u32,
version: u32,
pp_session_info: *mut *mut WtsSessionInfoW,
p_count: *mut u32,
) -> i32;
fn WTSQuerySessionInformationW(
server: Handle,
session_id: u32,
info_class: i32,
pp_buffer: *mut *mut u16,
p_bytes_returned: *mut u32,
) -> i32;
fn WTSFreeMemory(memory: *mut c_void);
}
pub fn active_user_session_count() -> usize {
let mut sessions: Vec<(i32, Option<String>)> = Vec::new();
unsafe {
let mut p_info: *mut WtsSessionInfoW = ptr::null_mut();
let mut count: u32 = 0;
if WTSEnumerateSessionsW(ptr::null_mut(), 0, 1, &mut p_info, &mut count) == 0
|| p_info.is_null()
{
return 0;
}
for i in 0..count as isize {
let session = &*p_info.offset(i);
let user = query_user_name(session.session_id);
sessions.push((session.state, user));
}
WTSFreeMemory(p_info as *mut c_void);
}
count_active_user_sessions(&sessions)
}
unsafe fn query_user_name(session_id: u32) -> Option<String> {
let mut buf: *mut u16 = ptr::null_mut();
let mut bytes: u32 = 0;
if WTSQuerySessionInformationW(
ptr::null_mut(),
session_id,
WTS_USER_NAME,
&mut buf,
&mut bytes,
) == 0
|| buf.is_null()
{
return None;
}
let len = (bytes as usize / 2).saturating_sub(1);
let name = if len == 0 {
String::new()
} else {
String::from_utf16_lossy(std::slice::from_raw_parts(buf, len))
.trim()
.to_string()
};
WTSFreeMemory(buf as *mut c_void);
if name.is_empty() {
None
} else {
Some(name)
}
}
fn count_active_user_sessions(sessions: &[(i32, Option<String>)]) -> usize {
sessions
.iter()
.filter(|(state, user)| {
*state == WTS_ACTIVE && user.as_deref().is_some_and(|n| !n.is_empty())
})
.count()
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::size_of;
#[test]
fn test_count_active_user_sessions() {
let sessions = vec![
(WTS_ACTIVE, None),
(WTS_ACTIVE, Some("kento".to_string())),
(4 , Some("remote".to_string())),
(WTS_ACTIVE, Some(String::new())),
];
assert_eq!(count_active_user_sessions(&sessions), 1);
assert_eq!(count_active_user_sessions(&[]), 0);
let two = vec![
(WTS_ACTIVE, Some("a".to_string())),
(WTS_ACTIVE, Some("b".to_string())),
];
assert_eq!(count_active_user_sessions(&two), 2);
}
#[test]
fn test_wts_session_info_layout() {
assert_eq!(size_of::<WtsSessionInfoW>(), 24);
}
}