@@ -508,12 +508,25 @@ impl BorderMap { |
| 508 | 508 | |
| 509 | 509 | let old = self.focused_wid; |
| 510 | 510 | self.focused_wid = front; |
| 511 | + |
| 512 | + // tarmac-style workspace switching can swap focus to a window |
| 513 | + // that wasn't visible (and therefore not discovered) at ers |
| 514 | + // startup. Discover_windows only enumerates on-current-space |
| 515 | + // windows; tarmac stages other workspaces' windows in a hidden |
| 516 | + // state ers never picked up. If focus lands on such a wid, |
| 517 | + // create an overlay for it on demand. |
| 518 | + let new_target = !self.overlays.contains_key(&front); |
| 511 | 519 | debug!( |
| 512 | | - "[focus] {} -> {} (tracked targets: {:?})", |
| 520 | + "[focus] {} -> {} {}(tracked targets: {:?})", |
| 513 | 521 | old, |
| 514 | 522 | front, |
| 523 | + if new_target { "[NEW] " } else { "" }, |
| 515 | 524 | self.overlays.keys().collect::<Vec<_>>() |
| 516 | 525 | ); |
| 526 | + if new_target { |
| 527 | + self.add_fresh(front); |
| 528 | + self.subscribe_target(front); |
| 529 | + } |
| 517 | 530 | |
| 518 | 531 | // Pull both overlays' positions to the targets' current SLS bounds |
| 519 | 532 | // before un/hiding. AX-driven moves during a stack cycle frequently |
@@ -722,10 +735,31 @@ fn print_help() { |
| 722 | 735 | } |
| 723 | 736 | |
| 724 | 737 | fn main() { |
| 725 | | - tracing_subscriber::fmt() |
| 726 | | - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) |
| 727 | | - .with_writer(std::io::stderr) |
| 728 | | - .init(); |
| 738 | + // On the screenshot-exclusion research branch, default to file |
| 739 | + // logging at debug level so we can diagnose the NSWindow refactor |
| 740 | + // even when ers is spawned by tarmac (which inherits ers's stderr |
| 741 | + // to wherever tarmac was launched, often invisibly). |
| 742 | + let log_path = std::path::PathBuf::from("/tmp/ers-debug.log"); |
| 743 | + let log_file = std::fs::OpenOptions::new() |
| 744 | + .create(true) |
| 745 | + .append(true) |
| 746 | + .open(&log_path) |
| 747 | + .ok(); |
| 748 | + let env_filter = tracing_subscriber::EnvFilter::try_from_default_env() |
| 749 | + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("ers=debug")); |
| 750 | + if let Some(file) = log_file { |
| 751 | + tracing_subscriber::fmt() |
| 752 | + .with_env_filter(env_filter) |
| 753 | + .with_writer(std::sync::Mutex::new(file)) |
| 754 | + .with_ansi(false) |
| 755 | + .init(); |
| 756 | + } else { |
| 757 | + tracing_subscriber::fmt() |
| 758 | + .with_env_filter(env_filter) |
| 759 | + .with_writer(std::io::stderr) |
| 760 | + .init(); |
| 761 | + } |
| 762 | + debug!("[main] ers starting, pid={}", std::process::id()); |
| 729 | 763 | |
| 730 | 764 | let args: Vec<String> = std::env::args().collect(); |
| 731 | 765 | |
@@ -866,8 +900,8 @@ fn main() { |
| 866 | 900 | ptr::null(), |
| 867 | 901 | CFAbsoluteTimeGetCurrent() + 0.05, |
| 868 | 902 | 0.016, |
| 869 | | - 0, |
| 870 | | - 0, |
| 903 | + 0u64, |
| 904 | + 0i64, |
| 871 | 905 | timer_callback, |
| 872 | 906 | &mut ctx, |
| 873 | 907 | ); |
@@ -898,12 +932,22 @@ thread_local! { |
| 898 | 932 | |
| 899 | 933 | extern "C" fn timer_callback(_timer: *mut std::ffi::c_void, _info: *mut std::ffi::c_void) { |
| 900 | 934 | use std::time::{Duration, Instant}; |
| 935 | + use std::sync::atomic::AtomicUsize; |
| 936 | + static TICK_COUNT: AtomicUsize = AtomicUsize::new(0); |
| 937 | + let tick = TICK_COUNT.fetch_add(1, Ordering::Relaxed); |
| 938 | + if tick == 0 { |
| 939 | + debug!("[timer] first fire — main-thread event loop is alive"); |
| 940 | + } else if tick % 600 == 0 { |
| 941 | + // every ~10s if interval is 16ms |
| 942 | + debug!("[timer] tick {}", tick); |
| 943 | + } |
| 901 | 944 | MAIN_STATE.with(|cell| { |
| 902 | 945 | let mut state_opt = cell.borrow_mut(); |
| 903 | 946 | let s = match state_opt.as_mut() { |
| 904 | 947 | Some(s) => s, |
| 905 | 948 | None => return, |
| 906 | 949 | }; |
| 950 | + let mut received = 0usize; |
| 907 | 951 | loop { |
| 908 | 952 | match s.rx.try_recv() { |
| 909 | 953 | Ok(e) => { |
@@ -911,17 +955,38 @@ extern "C" fn timer_callback(_timer: *mut std::ffi::c_void, _info: *mut std::ffi |
| 911 | 955 | s.batch_first_seen = Some(Instant::now()); |
| 912 | 956 | } |
| 913 | 957 | s.batch_events.push(e); |
| 958 | + received += 1; |
| 914 | 959 | } |
| 915 | 960 | Err(mpsc::TryRecvError::Empty) => break, |
| 916 | 961 | Err(mpsc::TryRecvError::Disconnected) => break, |
| 917 | 962 | } |
| 918 | 963 | } |
| 919 | | - if let Some(first_seen) = s.batch_first_seen |
| 920 | | - && first_seen.elapsed() >= Duration::from_millis(100) |
| 921 | | - { |
| 964 | + if received > 0 { |
| 965 | + debug!( |
| 966 | + "[timer] received {} new events; batch size now {}", |
| 967 | + received, |
| 968 | + s.batch_events.len() |
| 969 | + ); |
| 970 | + } |
| 971 | + // Process the accumulated batch after a 16ms quiet window |
| 972 | + // (matches the old bg-thread behavior where it slept 16ms after |
| 973 | + // the first event then drained). Events keep arriving, the batch |
| 974 | + // grows; once 16ms passes without new events we flush. |
| 975 | + let should_flush = s.batch_first_seen.is_some_and(|t| { |
| 976 | + t.elapsed() >= Duration::from_millis(16) && received == 0 |
| 977 | + }) || s |
| 978 | + .batch_first_seen |
| 979 | + .is_some_and(|t| t.elapsed() >= Duration::from_millis(120)); |
| 980 | + if should_flush { |
| 922 | 981 | let events = std::mem::take(&mut s.batch_events); |
| 923 | 982 | s.batch_first_seen = None; |
| 983 | + debug!("[timer] processing batch of {}", events.len()); |
| 924 | 984 | process_event_batch(&mut s.borders, &mut s.pending, events); |
| 985 | + } else { |
| 986 | + // Even with no events, poll focus periodically so a missed |
| 987 | + // FrontChange notification doesn't strand the active border. |
| 988 | + // Cheap operation when focus hasn't changed. |
| 989 | + s.borders.update_focus(); |
| 925 | 990 | } |
| 926 | 991 | }); |
| 927 | 992 | } |
@@ -965,8 +1030,14 @@ fn process_event_batch( |
| 965 | 1030 | } |
| 966 | 1031 | Event::Hide(wid) => borders.hide(wid), |
| 967 | 1032 | Event::Unhide(wid) => { |
| 968 | | - if !borders.active_only || wid == borders.focused_wid { |
| 969 | | - borders.unhide(wid); |
| 1033 | + if !borders.is_overlay(wid) { |
| 1034 | + if !borders.overlays.contains_key(&wid) { |
| 1035 | + borders.add_fresh(wid); |
| 1036 | + borders.subscribe_target(wid); |
| 1037 | + } |
| 1038 | + if !borders.active_only || wid == borders.focused_wid { |
| 1039 | + borders.unhide(wid); |
| 1040 | + } |
| 970 | 1041 | } |
| 971 | 1042 | } |
| 972 | 1043 | Event::FrontChange => { |