use crate::*;
use crate::options_::*;
RB_GENERATE!(sessions, session, entry, discr_entry, session_cmp);
RB_GENERATE!(
session_groups,
session_group,
entry,
discr_entry,
session_group_cmp
);
pub static mut SESSIONS: sessions = unsafe { zeroed() };
pub static NEXT_SESSION_ID: AtomicU32 = AtomicU32::new(0);
pub static mut SESSION_GROUPS: session_groups = rb_initializer();
pub fn session_cmp(s1: &session, s2: &session) -> cmp::Ordering {
s1.name.cmp(&s2.name)
}
pub fn session_group_cmp(s1: &session_group, s2: &session_group) -> cmp::Ordering {
s1.name.cmp(&s2.name)
}
pub unsafe fn session_alive(s: *mut session) -> bool {
unsafe { rb_foreach(&raw mut SESSIONS).any(|s_loop| s_loop.as_ptr() == s) }
}
pub unsafe fn session_find(name: &str) -> *mut session {
let mut s = MaybeUninit::<session>::uninit();
let s = s.as_mut_ptr();
unsafe {
std::ptr::write(&raw mut (*s).name, Cow::Borrowed(std::mem::transmute::<&str, &'static str>(name)));
rb_find(&raw mut SESSIONS, s)
}
}
pub unsafe fn session_find_by_id_str(s: &str) -> *mut session {
unsafe {
if !s.starts_with('$') {
return null_mut();
}
let Ok(id) = strtonum_(&s[1..], 0, u32::MAX) else {
return null_mut();
};
transmute_ptr(session_find_by_id(id))
}
}
pub unsafe fn session_find_by_id(id: u32) -> Option<NonNull<session>> {
unsafe { rb_foreach(&raw mut SESSIONS).find(|s| (*s.as_ptr()).id == id) }
}
impl session {
unsafe fn create(
prefix: *const u8,
name: Option<&str>,
cwd: *const u8,
env: *mut environ,
oo: *mut options,
tio: *mut termios,
) -> Box<Self> {
unsafe {
let mut s: Box<session> = Box::new(zeroed());
s.references = 1;
s.flags = 0;
s.cwd = Some(std::ffi::CStr::from_ptr(cwd.cast()).to_owned());
tailq_init(&raw mut s.lastw);
rb_init(&raw mut s.windows);
s.environ = env;
s.options = oo;
status_update_cache(s.as_mut());
s.tio = null_mut();
if !tio.is_null() {
s.tio = Box::leak(Box::new(*tio)) as *mut termios;
}
if let Some(name) = name {
s.name = name.to_string().into();
s.id = NEXT_SESSION_ID.fetch_add(1, atomic::Ordering::Relaxed);
} else {
loop {
s.id = NEXT_SESSION_ID.fetch_add(1, atomic::Ordering::Relaxed);
s.name = if !prefix.is_null() {
format!("{}-{}", _s(prefix), s.id).into()
} else {
format!("{}", s.id).into()
};
if rb_find(&raw mut SESSIONS, s.as_mut()).is_null() {
break;
}
}
}
rb_insert(&raw mut SESSIONS, s.as_mut());
log_debug!("new session {} ${}", s.name, s.id);
if libc::gettimeofday(&raw mut s.creation_time, null_mut()) != 0 {
fatal("gettimeofday failed");
}
session_update_activity(s.as_mut(), &raw mut s.creation_time);
s
}
}
}
pub unsafe fn session_create(
prefix: *const u8,
name: Option<&str>,
cwd: *const u8,
env: *mut environ,
oo: *mut options,
tio: *mut termios,
) -> *mut session {
unsafe { Box::leak(session::create(prefix, name, cwd, env, oo, tio)) }
}
pub unsafe fn session_add_ref(s: *mut session, from: *const u8) {
let __func__ = "session_add_ref";
unsafe {
(*s).references += 1;
log_debug!(
"{}: {} {}, now {}",
__func__,
(*s).name,
_s(from),
(*s).references
);
}
}
pub unsafe fn session_remove_ref(s: *mut session, from: *const u8) {
let __func__ = "session_remove_ref";
unsafe {
(*s).references -= 1;
log_debug!(
"{}: {} {}, now {}",
__func__,
(*s).name,
_s(from),
(*s).references
);
if (*s).references == 0 {
event_once(-1, EV_TIMEOUT, Some(session_free), s.cast(), null_mut());
}
}
}
impl session {
#[inline]
pub(crate) fn cwd_ptr(&self) -> *const u8 {
match &self.cwd {
Some(c) => c.as_ptr().cast(),
None => std::ptr::null(),
}
}
}
pub unsafe extern "C-unwind" fn session_free(_fd: i32, _events: i16, arg: *mut c_void) {
unsafe {
let s = arg as *mut session;
log_debug!(
"session {} freed ({} references)",
(*s).name,
(*s).references
);
if (*s).references == 0 {
environ_free((*s).environ);
options_free((*s).options);
(*s).name = Cow::Borrowed("");
free_(s);
}
}
}
pub unsafe fn session_destroy(s: *mut session, notify: i32, from: *const u8) {
let __func__ = c!("session_destroy");
unsafe {
log_debug!("session {} destroyed ({})", (*s).name, _s(from));
if (*s).curw.is_null() {
return;
}
(*s).curw = null_mut();
rb_remove(&raw mut SESSIONS, s);
if notify != 0 {
notify_session(c"session-closed", s);
}
free_((*s).tio);
if event_initialized(&raw mut (*s).lock_timer) != 0 {
event_del(&raw mut (*s).lock_timer);
}
session_group_remove(s);
while !tailq_empty(&raw mut (*s).lastw) {
winlink_stack_remove(&raw mut (*s).lastw, tailq_first(&raw mut (*s).lastw));
}
while !rb_empty(&raw mut (*s).windows) {
let wl = rb_root(&raw mut (*s).windows);
notify_session_window(c"window-unlinked", s, (*wl).window);
winlink_remove(&raw mut (*s).windows, wl);
}
(*s).cwd = None;
session_remove_ref(s, __func__);
}
}
pub unsafe fn session_check_name(name: *const u8) -> Option<String> {
unsafe {
if *name == b'\0' {
return None;
}
let copy = xstrdup(name).as_ptr();
let mut cp = copy;
while *cp != b'\0' {
if *cp == b':' || *cp == b'.' {
*cp = b'_';
}
cp = cp.add(1);
}
let new_name = utf8_stravis_(
copy,
vis_flags::VIS_OCTAL | vis_flags::VIS_CSTYLE | vis_flags::VIS_TAB | vis_flags::VIS_NL,
);
free_(copy);
Some(String::from_utf8(new_name).unwrap())
}
}
pub unsafe extern "C-unwind" fn session_lock_timer(_fd: i32, _events: i16, s: NonNull<session>) {
unsafe {
if (*s.as_ptr()).attached == 0 {
return;
}
log_debug!(
"session {} locked, activity time {}",
(*s.as_ptr()).name,
(*s.as_ptr()).activity_time.tv_sec,
);
server_lock_session(s.as_ptr());
recalculate_sizes();
}
}
pub unsafe fn session_update_activity(s: *mut session, from: *mut timeval) {
unsafe {
let last = &raw mut (*s).last_activity_time;
memcpy__(last, &raw mut (*s).activity_time);
if from.is_null() {
libc::gettimeofday(&raw mut (*s).activity_time, null_mut());
} else {
memcpy__(&raw mut (*s).activity_time, from);
}
log_debug!(
"session ${} {} activity {}.{:06} (last {}.{:06})",
(*s).id,
(*s).name,
(*s).activity_time.tv_sec,
(*s).activity_time.tv_usec,
(*last).tv_sec,
(*last).tv_usec,
);
if evtimer_initialized(&raw mut (*s).lock_timer) {
evtimer_del(&raw mut (*s).lock_timer);
} else {
evtimer_set(
&raw mut (*s).lock_timer,
session_lock_timer,
NonNull::new(s).unwrap(),
);
}
if (*s).attached != 0 {
let tv = timeval {
tv_sec: options_get_number_((*s).options, "lock-after-time"),
tv_usec: 0,
};
if tv.tv_sec != 0 {
evtimer_add(&raw mut (*s).lock_timer, &tv);
}
}
}
}
pub unsafe fn session_next_session(s: *mut session, sort_crit: sort_criteria) -> *mut session {
unsafe {
if rb_empty(&raw mut SESSIONS) || !session_alive(s) {
return null_mut();
}
let l = sort_get_sessions(sort_crit);
let n = l.len();
let mut i = 0usize;
while i < n {
if l[i] == s {
break;
}
i += 1;
}
if i == n {
fatalx_!("session {} not found in sorted list", (*s).name);
}
i += 1;
if i == n {
i = 0;
}
l[i]
}
}
pub unsafe fn session_previous_session(s: *mut session, sort_crit: sort_criteria) -> *mut session {
unsafe {
if rb_empty(&raw mut SESSIONS) || !session_alive(s) {
return null_mut();
}
let l = sort_get_sessions(sort_crit);
let n = l.len();
let mut i = 0usize;
while i < n {
if l[i] == s {
break;
}
i += 1;
}
if i == n {
fatalx_!("session {} not found in sorted list", (*s).name);
}
if i == 0 {
i = n;
}
i -= 1;
l[i]
}
}
pub unsafe fn session_attach(
s: *mut session,
w: *mut window,
idx: i32,
cause: *mut *mut u8,
) -> *mut winlink {
unsafe {
let wl = winlink_add(&raw mut (*s).windows, idx);
if wl.is_null() {
*cause = format_nul!("index in use: {}", idx);
return null_mut();
}
(*wl).session = s;
winlink_set_window(wl, w);
notify_session_window(c"window-linked", s, w);
session_group_synchronize_from(s);
wl
}
}
pub unsafe fn session_detach(s: *mut session, wl: *mut winlink) -> i32 {
unsafe {
if (*s).curw == wl && session_last(s) != 0 && session_previous(s, false) != 0 {
session_next(s, false);
}
(*wl).flags &= !WINLINK_ALERTFLAGS;
notify_session_window(c"window-unlinked", s, (*wl).window);
winlink_stack_remove(&raw mut (*s).lastw, wl);
winlink_remove(&raw mut (*s).windows, wl);
session_group_synchronize_from(s);
if rb_empty(&raw mut (*s).windows) {
return 1;
}
0
}
}
pub unsafe fn session_has(s: *mut session, w: *mut window) -> bool {
unsafe {
tailq_foreach::<_, discr_wentry>(&raw mut (*w).winlinks)
.any(|wl| (*wl.as_ptr()).session == s)
}
}
pub unsafe fn session_is_linked(s: *mut session, w: *mut window) -> bool {
unsafe {
let sg = session_group_contains(s);
if !sg.is_null() {
return (*w).references != session_group_count(sg);
}
(*w).references != 1
}
}
pub unsafe fn session_next_alert(mut wl: *mut winlink) -> *mut winlink {
unsafe {
while !wl.is_null() {
if (*wl).flags.intersects(WINLINK_ALERTFLAGS) {
break;
}
wl = winlink_next(wl);
}
}
wl
}
pub unsafe fn session_next(s: *mut session, alert: bool) -> i32 {
unsafe {
if (*s).curw.is_null() {
return -1;
}
let mut wl = winlink_next((*s).curw);
if alert {
wl = session_next_alert(wl);
}
if wl.is_null() {
wl = rb_min(&raw mut (*s).windows);
if alert
&& ({
(wl = session_next_alert(wl));
wl.is_null()
})
{
return -1;
}
}
session_set_current(s, wl)
}
}
pub unsafe fn session_previous_alert(mut wl: *mut winlink) -> *mut winlink {
unsafe {
while !wl.is_null() {
if (*wl).flags.intersects(WINLINK_ALERTFLAGS) {
break;
}
wl = winlink_previous(wl);
}
wl
}
}
pub unsafe fn session_previous(s: *mut session, alert: bool) -> i32 {
unsafe {
if (*s).curw.is_null() {
return -1;
}
let mut wl = winlink_previous((*s).curw);
if alert {
wl = session_previous_alert(wl);
}
if wl.is_null() {
wl = rb_max(&raw mut (*s).windows);
if alert
&& ({
(wl = session_previous_alert(wl));
wl.is_null()
})
{
return -1;
}
}
session_set_current(s, wl)
}
}
pub unsafe fn session_select(s: *mut session, idx: i32) -> i32 {
unsafe {
let wl = winlink_find_by_index(&raw mut (*s).windows, idx);
session_set_current(s, wl)
}
}
pub unsafe fn session_last(s: *mut session) -> i32 {
unsafe {
let wl = tailq_first(&raw mut (*s).lastw);
if wl.is_null() {
return -1;
}
if wl == (*s).curw {
return 1;
}
session_set_current(s, wl)
}
}
pub unsafe fn session_set_current(s: *mut session, wl: *mut winlink) -> i32 {
unsafe {
let old: *mut winlink = (*s).curw;
if wl.is_null() {
return -1;
}
if wl == (*s).curw {
return 1;
}
winlink_stack_remove(&raw mut (*s).lastw, wl);
winlink_stack_push(&raw mut (*s).lastw, (*s).curw);
(*s).curw = wl;
if options_get_number_(GLOBAL_OPTIONS, "focus-events") != 0 {
if !old.is_null() {
window_update_focus((*old).window);
}
window_update_focus((*wl).window);
}
winlink_clear_flags(wl);
window_update_activity(NonNull::new_unchecked((*wl).window));
tty_update_window_offset((*wl).window);
notify_session(c"session-window-changed", s);
0
}
}
pub unsafe fn session_group_contains(target: *mut session) -> *mut session_group {
unsafe {
for sg in rb_foreach(&raw mut SESSION_GROUPS) {
for s in tailq_foreach(&raw mut (*sg.as_ptr()).sessions) {
if s.as_ptr() == target {
return sg.as_ptr();
}
}
}
null_mut()
}
}
pub unsafe fn session_group_find(name: &str) -> *mut session_group {
unsafe { rb_find_by(&raw mut SESSION_GROUPS, |sg| name.cmp(&sg.name)) }
}
pub unsafe fn session_group_new(name: &str) -> *mut session_group {
unsafe {
let mut sg = session_group_find(name);
if !sg.is_null() {
return sg;
}
sg = xcalloc1::<session_group>();
(*sg).name = name.to_string().into();
tailq_init(&raw mut (*sg).sessions);
rb_insert(&raw mut SESSION_GROUPS, sg);
sg
}
}
pub unsafe fn session_group_add(sg: *mut session_group, s: *mut session) {
unsafe {
if session_group_contains(s).is_null() {
tailq_insert_tail(&raw mut (*sg).sessions, s);
}
}
}
pub unsafe fn session_group_remove(s: *mut session) {
unsafe {
let sg = session_group_contains(s);
if sg.is_null() {
return;
}
tailq_remove(&raw mut (*sg).sessions, s);
if tailq_empty(&raw mut (*sg).sessions) {
rb_remove(&raw mut SESSION_GROUPS, sg);
(*sg).name = Cow::Borrowed("");
free_(sg);
}
}
}
pub unsafe fn session_group_count(sg: *mut session_group) -> u32 {
unsafe { tailq_foreach(&raw mut (*sg).sessions).count() as u32 }
}
pub unsafe fn session_group_attached_count(sg: *mut session_group) -> u32 {
unsafe {
tailq_foreach(&raw mut (*sg).sessions)
.map(|s| (*s.as_ptr()).attached)
.sum()
}
}
pub unsafe fn session_group_synchronize_to(s: *mut session) {
unsafe {
let sg = session_group_contains(s);
if sg.is_null() {
return;
}
let target = tailq_foreach(&raw mut (*sg).sessions)
.map(std::ptr::NonNull::as_ptr)
.find(|&target| target != s);
if let Some(target) = target {
session_group_synchronize1(target, s);
}
}
}
pub unsafe fn session_group_synchronize_from(target: *mut session) {
unsafe {
let sg = session_group_contains(target);
if sg.is_null() {
return;
}
for s in tailq_foreach(&raw mut (*sg).sessions).map(std::ptr::NonNull::as_ptr) {
if s != target {
session_group_synchronize1(target, s);
}
}
}
}
pub unsafe fn session_group_synchronize1(target: *mut session, s: *mut session) {
let mut old_windows = MaybeUninit::<winlinks>::uninit();
let mut old_lastw = MaybeUninit::<winlink_stack>::uninit();
unsafe {
let ww: *mut winlinks = &raw mut (*target).windows;
if rb_empty(ww) {
return;
}
if !(*s).curw.is_null()
&& winlink_find_by_index(ww, (*(*s).curw).idx).is_null()
&& session_last(s) != 0
&& session_previous(s, false) != 0
{
session_next(s, false);
}
memcpy__(old_windows.as_mut_ptr(), &raw mut (*s).windows);
rb_init(&raw mut (*s).windows);
for wl in rb_foreach(ww).map(std::ptr::NonNull::as_ptr) {
let wl2 = winlink_add(&raw mut (*s).windows, (*wl).idx);
(*wl2).session = s;
winlink_set_window(wl2, (*wl).window);
notify_session_window(c"window-linked", s, (*wl2).window);
(*wl2).flags |= (*wl).flags & WINLINK_ALERTFLAGS;
}
if !(*s).curw.is_null() {
(*s).curw = winlink_find_by_index(&raw mut (*s).windows, (*(*s).curw).idx);
} else {
(*s).curw = winlink_find_by_index(&raw mut (*s).windows, (*(*target).curw).idx);
}
memcpy__(old_lastw.as_mut_ptr(), &raw mut (*s).lastw);
tailq_init(&raw mut (*s).lastw);
for wl in tailq_foreach::<_, discr_sentry>(old_lastw.as_mut_ptr()).map(std::ptr::NonNull::as_ptr) {
if let Some(wl2) = NonNull::new(winlink_find_by_index(&raw mut (*s).windows, (*wl).idx))
{
tailq_insert_tail::<_, discr_sentry>(&raw mut (*s).lastw, wl2.as_ptr());
(*wl2.as_ptr()).flags |= winlink_flags::WINLINK_VISITED;
}
}
while !rb_empty(old_windows.as_mut_ptr()) {
let wl = rb_root(old_windows.as_mut_ptr());
let wl2 = winlink_find_by_window_id(&raw mut (*s).windows, (*(*wl).window).id);
if wl2.is_null() {
notify_session_window(c"window-unlinked", s, (*wl).window);
}
winlink_remove(old_windows.as_mut_ptr(), wl);
}
}
}
pub unsafe fn session_renumber_windows(s: *mut session) {
unsafe {
let mut old_wins = MaybeUninit::<winlinks>::uninit();
let mut old_lastw = MaybeUninit::<winlink_stack>::uninit();
let mut marked_idx = -1;
memcpy__(old_wins.as_mut_ptr(), &raw mut (*s).windows);
rb_init(&raw mut (*s).windows);
let mut new_idx = options_get_number_((*s).options, "base-index") as i32;
let mut new_curw_idx = 0;
for wl in rb_foreach(old_wins.as_mut_ptr()).map(std::ptr::NonNull::as_ptr) {
let wl_new = winlink_add(&raw mut (*s).windows, new_idx);
(*wl_new).session = s;
winlink_set_window(wl_new, (*wl).window);
(*wl_new).flags |= (*wl).flags & WINLINK_ALERTFLAGS;
if wl == MARKED_PANE.wl {
marked_idx = (*wl_new).idx;
}
if wl == (*s).curw {
new_curw_idx = (*wl_new).idx;
}
new_idx += 1;
}
memcpy__(old_lastw.as_mut_ptr(), &raw mut (*s).lastw);
tailq_init(&raw mut (*s).lastw);
for wl in tailq_foreach::<_, discr_sentry>(old_lastw.as_mut_ptr()).map(std::ptr::NonNull::as_ptr) {
(*wl).flags &= !winlink_flags::WINLINK_VISITED;
if let Some(wl_new) = winlink_find_by_window(&raw mut (*s).windows, (*wl).window) {
tailq_insert_tail::<_, discr_sentry>(&raw mut (*s).lastw, wl_new.as_ptr());
(*wl_new.as_ptr()).flags |= winlink_flags::WINLINK_VISITED;
}
}
if marked_idx != -1 {
MARKED_PANE.wl = winlink_find_by_index(&raw mut (*s).windows, marked_idx);
if MARKED_PANE.wl.is_null() {
server_clear_marked();
}
}
(*s).curw = winlink_find_by_index(&raw mut (*s).windows, new_curw_idx);
for wl in rb_foreach(old_wins.as_mut_ptr()).map(std::ptr::NonNull::as_ptr) {
winlink_remove(old_wins.as_mut_ptr(), wl);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_session_check_name() {
unsafe {
assert_eq!(session_check_name(crate::c!("")), None);
assert_eq!(
session_check_name(crate::c!("mysession")).as_deref(),
Some("mysession")
);
assert_eq!(
session_check_name(crate::c!("a.b:c")).as_deref(),
Some("a_b_c")
);
assert_eq!(
session_check_name(crate::c!("1.2.3")).as_deref(),
Some("1_2_3")
);
assert_eq!(session_check_name(crate::c!(":")).as_deref(), Some("_"));
}
}
#[test]
fn test_session_group_find() {
unsafe {
for name in ["mid", "alpha", "zulu"] {
assert!(!session_group_new(name).is_null());
}
for name in ["alpha", "mid", "zulu"] {
let sg = session_group_find(name);
assert!(!sg.is_null(), "group {name} should be found");
assert_eq!((*sg).name, name);
}
assert!(session_group_find("ggg").is_null());
assert!(session_group_find("").is_null());
assert_eq!(session_group_find("mid"), session_group_new("mid"));
}
}
}