first commit
This commit is contained in:
commit
827124d502
10 changed files with 3348 additions and 0 deletions
2526
Cargo.lock
generated
Normal file
2526
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
39
Cargo.toml
Normal file
39
Cargo.toml
Normal file
|
@ -0,0 +1,39 @@
|
|||
[package]
|
||||
name = "notifymylimits"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
notify-rust = { version = "4.10.0"}
|
||||
# heim = { version = "0.0.11", default-fatures = false, features = ["host", "sensors", "net"] }
|
||||
# heim-host = { version = "0.0.11" }
|
||||
# heim = { git = "https://github.com/andodeki/heim.git", rev = "5ed73cf", default-fatures = false, features = ["host", "sensors", "net"]}
|
||||
heim = { git = "https://github.com/heim-rs/heim.git", branch = "master", default-fatures = false, features = ["host", "sensors", "net"]}
|
||||
heim-host = { git = "https://github.com/heim-rs/heim.git", branch = "master"}
|
||||
battery = "0.7.8"
|
||||
libc = "0.2.152"
|
||||
futures = "0.3.30"
|
||||
pcap = "1.1.0"
|
||||
chrono = "0.4.31"
|
||||
objc = "0.2.7"
|
||||
psutil = { version = "3.3.0", optional = true }
|
||||
procfs = { version = "0.16.0", optional = true }
|
||||
cocoa = "0.25.0"
|
||||
core-graphics = "0.23.2"
|
||||
core-foundation = "0.9.4"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
linux = ["procfs"]
|
||||
android = ["procfs"]
|
||||
macos = ["psutil"]
|
||||
|
||||
[patch.crates-io]
|
||||
heim = { git = "https://github.com/andodeki/heim.git", rev = "5ed73cf"}
|
||||
heim-host = { git = "https://github.com/andodeki/heim.git", rev = "5ed73cf"}
|
||||
# heim = { git = "https://github.com/heim-rs/heim.git", rev = "b292f15"}
|
||||
# notify-rust = { git = "https://github.com/hoodie/notify-rust.git", rev = "996953c"}
|
||||
notify-rust = { git = "https://github.com/andodeki/notify-rust.git", rev = "175bd7c"}
|
0
README.md
Normal file
0
README.md
Normal file
14
com.example.notifymylimit.plist
Normal file
14
com.example.notifymylimit.plist
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.example.notifymylimit</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/notifymylimit</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
1
error.txt
Normal file
1
error.txt
Normal file
|
@ -0,0 +1 @@
|
|||
|
4
extra.txt
Normal file
4
extra.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
cargo build --release
|
||||
mv target/release/mbslimit /usr/local/bin
|
||||
create /Library/LaunchDaemons/com.example.notifymylimit.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.example.notifymylimit.plist
|
181
src/batterylibd.rs
Normal file
181
src/batterylibd.rs
Normal file
|
@ -0,0 +1,181 @@
|
|||
extern crate battery;
|
||||
extern crate futures;
|
||||
extern crate libc;
|
||||
extern crate tokio;
|
||||
|
||||
use std::error::Error;
|
||||
use std::ffi::CString;
|
||||
use std::io::Read;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::raw::{c_char, c_int, c_void};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::pin::Pin;
|
||||
use std::result::Result;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
|
||||
use tokio::io::unix::AsyncFd;
|
||||
use tokio::io::Interest;
|
||||
use tokio::io::ReadBuf;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||
|
||||
use futures::ready;
|
||||
|
||||
pub const KEY: &str = "com.apple.system.powersources.timeremaining";
|
||||
|
||||
struct NotifyFd {
|
||||
fd: RawFd,
|
||||
pub token: c_int,
|
||||
}
|
||||
|
||||
impl NotifyFd {
|
||||
pub fn new(key: &str) -> Result<Self, Box<dyn Error>> {
|
||||
let mut token = MaybeUninit::<c_int>::uninit();
|
||||
let mut nfd = MaybeUninit::<RawFd>::uninit();
|
||||
unsafe {
|
||||
let key = CString::new(key).unwrap();
|
||||
let r = notify_register_file_descriptor(
|
||||
key.as_ptr(),
|
||||
nfd.as_mut_ptr(),
|
||||
0,
|
||||
token.as_mut_ptr(),
|
||||
);
|
||||
if r != 0 {
|
||||
return Err("notify_register_file_descriptor failed".into());
|
||||
}
|
||||
}
|
||||
let token = unsafe { token.assume_init() };
|
||||
let nfd = unsafe { nfd.assume_init() };
|
||||
|
||||
return Ok(NotifyFd {
|
||||
fd: nfd,
|
||||
token: token,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for NotifyFd {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
unsafe {
|
||||
let r = libc::read(self.fd, buf.as_mut_ptr() as *mut c_void, buf.len());
|
||||
if r == -1 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
} else {
|
||||
return Ok(r as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NotifyFd {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let r = notify_cancel(self.token);
|
||||
if r != 0 {
|
||||
panic!("notify_cancel failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Needed for integration with Tokio
|
||||
impl AsRawFd for NotifyFd {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
return self.fd;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AsyncNotifyFd {
|
||||
inner: AsyncFd<NotifyFd>,
|
||||
pub token: c_int,
|
||||
}
|
||||
|
||||
impl AsyncNotifyFd {
|
||||
pub fn new(key: &str) -> Result<Self, Box<dyn Error>> {
|
||||
let mut nfd = NotifyFd::new(key)?;
|
||||
|
||||
// Suspend the events while we adjust the fd
|
||||
unsafe {
|
||||
let r = notify_suspend(nfd.token);
|
||||
if r != 0 {
|
||||
return Err("notify_suspend failed".into());
|
||||
}
|
||||
}
|
||||
|
||||
// Set the file descriptor in non blocking mode
|
||||
unsafe {
|
||||
let flags = libc::fcntl(nfd.fd, libc::F_GETFL);
|
||||
let r = libc::fcntl(nfd.fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
|
||||
if r != 0 {
|
||||
return Err("fcntl failed".into());
|
||||
}
|
||||
}
|
||||
|
||||
// Drain the file descriptor of all data before registering with Tokio
|
||||
loop {
|
||||
let mut buf = [0; 4];
|
||||
match nfd.read_exact(&mut buf) {
|
||||
Ok(_) => {
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() == std::io::ErrorKind::WouldBlock {
|
||||
break;
|
||||
} else {
|
||||
return Err(format!("unexpected read io error {}", e).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let t = nfd.token;
|
||||
|
||||
// Register the file descriptor with tokio
|
||||
let afd = AsyncFd::with_interest(nfd, Interest::READABLE)?;
|
||||
|
||||
// Resume events
|
||||
unsafe {
|
||||
let r = notify_resume(t);
|
||||
if r != 0 {
|
||||
return Err("notify_resume failed".into());
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Self {
|
||||
inner: afd,
|
||||
token: t,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for AsyncNotifyFd {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
loop {
|
||||
let mut guard = ready!(self.inner.poll_read_ready_mut(cx))?;
|
||||
let r = guard.try_io(|x| x.get_mut().read(buf.initialize_unfilled()));
|
||||
if r.is_ok() {
|
||||
return Poll::Ready(r.unwrap().map(|r| buf.advance(r)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
pub fn notify_register_file_descriptor(
|
||||
name: *const c_char,
|
||||
notify_fd: *mut c_int,
|
||||
flags: c_int,
|
||||
out_token: *mut c_int,
|
||||
) -> u32;
|
||||
|
||||
pub fn notify_cancel(token: c_int) -> u32;
|
||||
|
||||
// Added to allow safely setting the fd to non blocking mode
|
||||
pub fn notify_suspend(token: ::std::os::raw::c_int) -> u32;
|
||||
pub fn notify_resume(token: ::std::os::raw::c_int) -> u32;
|
||||
}
|
58
src/getforegroundapp.rs
Normal file
58
src/getforegroundapp.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
use std::collections::HashSet;
|
||||
use std::process::Command;
|
||||
|
||||
// Common function that delegates to the appropriate platform-specific implementation
|
||||
pub fn get_foreground_process_ids() -> HashSet<i32> {
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
return procfs_process_info::get_foreground_process_ids();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
return macos_process_info::get_foreground_process_ids();
|
||||
}
|
||||
|
||||
// Module containing platform-specific process information logic
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
mod procfs_process_info {
|
||||
use procfs::process::{FDInfo, FDTarget, Process};
|
||||
|
||||
// Function to get the set of foreground process IDs on Linux and Windows
|
||||
pub fn get_foreground_process_ids() -> HashSet<i32> {
|
||||
let terminal_fd_info = match Process::myself() {
|
||||
Ok(process) => process.fd,
|
||||
Err(_) => return HashSet::new(),
|
||||
};
|
||||
|
||||
let mut foreground_process_ids = HashSet::new();
|
||||
for fd_info in terminal_fd_info {
|
||||
if let FDTarget::Process(process_info) = fd_info.target {
|
||||
foreground_process_ids.insert(process_info.pid);
|
||||
}
|
||||
}
|
||||
|
||||
foreground_process_ids
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos_process_info {
|
||||
use std::collections::HashSet;
|
||||
use std::process::Command;
|
||||
// Function to get the set of foreground process IDs on macOS
|
||||
pub fn get_foreground_process_ids() -> HashSet<i32> {
|
||||
let output = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg("lsappinfo info -only pid $(lsappinfo front)")
|
||||
.output()
|
||||
.expect("Failed to execute command");
|
||||
|
||||
let pid: i32 = String::from_utf8(output.stdout)
|
||||
.unwrap()
|
||||
.trim()
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
let mut foreground_process_ids = HashSet::new();
|
||||
foreground_process_ids.insert(pid);
|
||||
|
||||
foreground_process_ids
|
||||
}
|
||||
}
|
2
src/lib.rs
Normal file
2
src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod batterylibd;
|
||||
pub mod getforegroundapp;
|
523
src/main.rs
Normal file
523
src/main.rs
Normal file
|
@ -0,0 +1,523 @@
|
|||
use cocoa::{
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSString},
|
||||
};
|
||||
use notify_rust::Notification;
|
||||
use objc::{
|
||||
declare::ClassDecl,
|
||||
msg_send,
|
||||
runtime::{Class, Object, Sel},
|
||||
sel, sel_impl,
|
||||
};
|
||||
use pcap::Capture;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{File, OpenOptions},
|
||||
io::{self, Write},
|
||||
process::Command,
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::{io::unix::AsyncFd, time::sleep};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// if let Err(mbs_error) = mbslimit().await {
|
||||
// eprintln!("MBS error: {:?}", mbs_error);
|
||||
// }
|
||||
//
|
||||
if let Err(foreground_app_error) = monitor_foreground_app_changes().await {
|
||||
eprintln!("Foreground App error: {:?}", foreground_app_error);
|
||||
}
|
||||
|
||||
// let (mbs_result, battery_result, monitor_result) =
|
||||
// tokio::join!(mbslimit(), batterylimit(), monitor_foreground_app_changes());
|
||||
|
||||
// if let Err(e) = mbs_result {
|
||||
// eprintln!("Error in mbslimit: {:?}", e);
|
||||
// }
|
||||
|
||||
// if let Err(e) = battery_result {
|
||||
// eprintln!("Error in batterylimit: {:?}", e);
|
||||
// }
|
||||
|
||||
// if let Err(e) = monitor_result {
|
||||
// eprintln!("Error in monitor_foreground_app_changes: {:?}", e);
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_foreground_app() -> Option<String> {
|
||||
unsafe {
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
let workspace: id = msg_send![Class::get("NSWorkspace").unwrap(), sharedWorkspace];
|
||||
let app: id = msg_send![workspace, frontmostApplication];
|
||||
let bundle_id: id = msg_send![app, bundleIdentifier];
|
||||
|
||||
if bundle_id != nil {
|
||||
let bundle_id_str: *const std::os::raw::c_char = msg_send![bundle_id, UTF8String];
|
||||
let app_name = std::ffi::CStr::from_ptr(bundle_id_str)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
pool.drain();
|
||||
Some(app_name)
|
||||
} else {
|
||||
pool.drain();
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn block_app(app_bundle_id: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let rule = format!(
|
||||
"block out quick proto tcp from any to any app \"{}\"",
|
||||
app_bundle_id
|
||||
);
|
||||
Command::new("sudo")
|
||||
.arg("pfctl")
|
||||
.arg("-a")
|
||||
.arg("foreground_app")
|
||||
.arg("-f")
|
||||
.arg(rule)
|
||||
.output()?;
|
||||
|
||||
Command::new("sudo").arg("pfctl").arg("-e").output()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn log_data_usage(app_bundle_id: &str, data_usage: usize) -> io::Result<()> {
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("data_usage_history.txt")?;
|
||||
|
||||
writeln!(file, "{}: {} bytes", app_bundle_id, data_usage)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn monitor_foreground_app_changes() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut current_app = get_foreground_app().unwrap_or_else(|| "Unknown".to_string());
|
||||
let mut app_data_usage: HashMap<String, usize> = HashMap::new();
|
||||
app_data_usage.insert(current_app.clone(), 0);
|
||||
|
||||
loop {
|
||||
if let Some(new_app) = get_foreground_app() {
|
||||
if new_app != current_app {
|
||||
println!("Foreground app changed from {} to {}", current_app, new_app);
|
||||
if let Some(&usage) = app_data_usage.get(¤t_app) {
|
||||
log_data_usage(¤t_app, usage)?;
|
||||
println!("{} used {} bytes", current_app, usage);
|
||||
}
|
||||
|
||||
block_app(¤t_app)?;
|
||||
current_app = new_app;
|
||||
app_data_usage.entry(current_app.clone()).or_insert(0);
|
||||
}
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
async fn mbslimit() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Entering mbslimit function");
|
||||
|
||||
let device = pcap::Device::lookup()?.ok_or("No device available")?;
|
||||
println!("Using device {}", device.name);
|
||||
|
||||
let mut cap = Capture::from_device(device)?.immediate_mode(true).open()?;
|
||||
|
||||
let mut total_size = 0;
|
||||
let mut current_app = get_foreground_app().unwrap_or_else(|| "Unknown".to_string());
|
||||
let mut app_data_usage: HashMap<String, usize> = HashMap::new();
|
||||
app_data_usage.insert(current_app.clone(), 0);
|
||||
|
||||
loop {
|
||||
while let Ok(packet) = cap.next_packet() {
|
||||
total_size += packet.len();
|
||||
*app_data_usage.entry(current_app.clone()).or_insert(0) += packet.len();
|
||||
|
||||
if total_size > 24 * 1024 * 1024 {
|
||||
total_size = 0;
|
||||
println!("Alert: Total data transferred exceeded 24MB");
|
||||
|
||||
notify_and_toggle_wifi().await?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(new_app) = get_foreground_app() {
|
||||
if new_app != current_app {
|
||||
println!("Foreground app changed from {} to {}", current_app, new_app);
|
||||
if let Some(&usage) = app_data_usage.get(¤t_app) {
|
||||
log_data_usage(¤t_app, usage)?;
|
||||
println!("{} used {} bytes", current_app, usage);
|
||||
}
|
||||
|
||||
block_app(¤t_app)?;
|
||||
current_app = new_app;
|
||||
app_data_usage.entry(current_app.clone()).or_insert(0);
|
||||
}
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn notify_and_toggle_wifi() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Command::new("osascript")
|
||||
.arg("-e")
|
||||
.arg("display notification \"Data transfer limit exceeded\" with title \"Alert\"")
|
||||
.output()?;
|
||||
|
||||
Command::new("networksetup")
|
||||
.arg("-setairportpower")
|
||||
.arg("en0")
|
||||
.arg("off")
|
||||
.output()?;
|
||||
|
||||
Notification::new()
|
||||
.summary("WiFi Off")
|
||||
.body("Data usage has exceeded 24MB. WiFi has been turned off.")
|
||||
.show()?;
|
||||
|
||||
println!("WiFi has been turned off.");
|
||||
sleep(Duration::from_secs(5 * 60)).await;
|
||||
|
||||
Command::new("networksetup")
|
||||
.arg("-setairportpower")
|
||||
.arg("en0")
|
||||
.arg("on")
|
||||
.output()?;
|
||||
|
||||
Notification::new()
|
||||
.summary("WiFi On")
|
||||
.body("WiFi has been turned back on.")
|
||||
.show()?;
|
||||
println!("WiFi has been turned on.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
async fn batterylimit() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Entering batterylimit function");
|
||||
|
||||
let manager = battery::Manager::new()?;
|
||||
let mut battery = manager.batteries()?.next().ok_or("No battery found")??;
|
||||
let mut nfd = AsyncNotifyFd::new(KEY)?;
|
||||
let mut buf = [0; 4];
|
||||
|
||||
loop {
|
||||
nfd.read_exact(&mut buf).await?;
|
||||
let v = c_int::from_be_bytes(buf);
|
||||
|
||||
if v == nfd.token {
|
||||
manager
|
||||
.refresh(&mut battery)
|
||||
.expect("Manager failed to refresh");
|
||||
} else {
|
||||
return Err("Unknown token in file descriptor!".into());
|
||||
}
|
||||
|
||||
let percentage = battery.state_of_charge().value * 100.0;
|
||||
|
||||
if percentage > 70.0 {
|
||||
notify_battery_level(percentage, "high").await?;
|
||||
} else if percentage < 25.0 {
|
||||
notify_battery_level(percentage, "low").await?;
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn notify_battery_level(
|
||||
percentage: f32,
|
||||
level: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Notification::new()
|
||||
.summary("Battery Level Alert")
|
||||
.body(&format!(
|
||||
"Your battery level is now at {:.0}%. Please monitor it.",
|
||||
percentage
|
||||
))
|
||||
.show()?;
|
||||
|
||||
println!("Battery charge: {:.0}%", percentage);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn block_all_except_foreground_app(app_bundle_id: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Load a custom pf.conf file that blocks all traffic by default
|
||||
// pfctl -f /etc/pf.conf
|
||||
Command::new("sudo")
|
||||
.arg("pfctl")
|
||||
.arg("-f")
|
||||
.arg("/etc/pf.conf")
|
||||
.output()?;
|
||||
|
||||
// Allow traffic for the foreground app (you might need to tailor this to your exact needs)
|
||||
let rule = format!(
|
||||
"pass out quick proto tcp from any to any app \"{}\" keep state",
|
||||
app_bundle_id
|
||||
);
|
||||
// sudo pfctl -a foreground_app -f
|
||||
Command::new("sudo")
|
||||
.arg("pfctl")
|
||||
.arg("-a")
|
||||
.arg("foreground_app")
|
||||
.arg("-f")
|
||||
.arg(rule)
|
||||
.output()?;
|
||||
|
||||
// Reload PF configuration
|
||||
Command::new("sudo").arg("pfctl").arg("-e").output()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// fn get_foreground_app() -> Option<String> {
|
||||
// unsafe {
|
||||
// let pool = NSAutoreleasePool::new(nil);
|
||||
// let workspace: id = msg_send![Class::get("NSWorkspace").unwrap(), sharedWorkspace];
|
||||
// let app: id = msg_send![workspace, frontmostApplication];
|
||||
// let bundle_id: id = msg_send![app, bundleIdentifier];
|
||||
|
||||
// if bundle_id != nil {
|
||||
// let bundle_id_str: *const std::os::raw::c_char = msg_send![bundle_id, UTF8String];
|
||||
// let app_name = std::ffi::CStr::from_ptr(bundle_id_str)
|
||||
// .to_string_lossy()
|
||||
// .into_owned();
|
||||
// pool.drain();
|
||||
// Some(app_name)
|
||||
// } else {
|
||||
// pool.drain();
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn block_app(app_bundle_id: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// // Block traffic for the app (you might need to tailor this to your exact needs)
|
||||
// let rule = format!(
|
||||
// "block out quick proto tcp from any to any app \"{}\"",
|
||||
// app_bundle_id
|
||||
// );
|
||||
// Command::new("sudo")
|
||||
// .arg("pfctl")
|
||||
// .arg("-a")
|
||||
// .arg("foreground_app")
|
||||
// .arg("-f")
|
||||
// .arg(rule)
|
||||
// .output()?;
|
||||
|
||||
// // Reload PF configuration
|
||||
// Command::new("sudo").arg("pfctl").arg("-e").output()?;
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
// fn log_data_usage(app_bundle_id: &str, data_usage: usize) -> io::Result<()> {
|
||||
// let mut file = OpenOptions::new()
|
||||
// .create(true)
|
||||
// .append(true)
|
||||
// .open("data_usage_history.txt")?;
|
||||
|
||||
// writeln!(file, "{}: {} bytes", app_bundle_id, data_usage)?;
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// async fn notify_and_toggle_wifi() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// // Notify user
|
||||
// Command::new("osascript")
|
||||
// .arg("-e")
|
||||
// .arg("display notification \"Data transfer limit exceeded\" with title \"Alert\"")
|
||||
// .output()?;
|
||||
|
||||
// // Turn off WiFi
|
||||
// Command::new("networksetup")
|
||||
// .arg("-setairportpower")
|
||||
// .arg("en0")
|
||||
// .arg("off")
|
||||
// .output()?;
|
||||
|
||||
// Notification::new()
|
||||
// .summary("WiFi Off")
|
||||
// .body("Data usage has exceeded 24MB. WiFi has been turned off.")
|
||||
// .show()?;
|
||||
|
||||
// // Wait for 5 minutes
|
||||
// println!("WiFi has been turned off.");
|
||||
// sleep(Duration::from_secs(5 * 60)).await;
|
||||
|
||||
// // Turn on WiFi
|
||||
// Command::new("networksetup")
|
||||
// .arg("-setairportpower")
|
||||
// .arg("en0")
|
||||
// .arg("on")
|
||||
// .output()?;
|
||||
|
||||
// Notification::new()
|
||||
// .summary("WiFi On")
|
||||
// .body("WiFi has been turned back on.")
|
||||
// .show()?;
|
||||
// println!("WiFi has been turned on.");
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
extern crate battery;
|
||||
extern crate futures;
|
||||
extern crate libc;
|
||||
extern crate tokio;
|
||||
|
||||
use futures::ready;
|
||||
use std::error::Error;
|
||||
use std::ffi::CString;
|
||||
use std::io::Read;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::raw::{c_char, c_int, c_void};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, Interest, ReadBuf};
|
||||
|
||||
pub const KEY: &str = "com.apple.system.powersources.timeremaining";
|
||||
|
||||
struct NotifyFd {
|
||||
fd: RawFd,
|
||||
token: c_int,
|
||||
}
|
||||
|
||||
impl NotifyFd {
|
||||
pub fn new(key: &str) -> Result<Self, Box<dyn Error>> {
|
||||
let mut token = MaybeUninit::<c_int>::uninit();
|
||||
let mut nfd = MaybeUninit::<RawFd>::uninit();
|
||||
|
||||
unsafe {
|
||||
let key = CString::new(key)?;
|
||||
let r = notify_register_file_descriptor(
|
||||
key.as_ptr(),
|
||||
nfd.as_mut_ptr(),
|
||||
0,
|
||||
token.as_mut_ptr(),
|
||||
);
|
||||
if r != 0 {
|
||||
return Err("notify_register_file_descriptor failed".into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(NotifyFd {
|
||||
fd: unsafe { nfd.assume_init() },
|
||||
token: unsafe { token.assume_init() },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for NotifyFd {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let r = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut c_void, buf.len()) };
|
||||
if r == -1 {
|
||||
Err(std::io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(r as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NotifyFd {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let r = notify_cancel(self.token);
|
||||
if r != 0 {
|
||||
panic!("notify_cancel failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for NotifyFd {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.fd
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AsyncNotifyFd {
|
||||
inner: AsyncFd<NotifyFd>,
|
||||
token: c_int,
|
||||
}
|
||||
|
||||
impl AsyncNotifyFd {
|
||||
pub fn new(key: &str) -> Result<Self, Box<dyn Error>> {
|
||||
let mut nfd = NotifyFd::new(key)?;
|
||||
|
||||
unsafe {
|
||||
let r = notify_suspend(nfd.token);
|
||||
if r != 0 {
|
||||
return Err("notify_suspend failed".into());
|
||||
}
|
||||
|
||||
let flags = libc::fcntl(nfd.fd, libc::F_GETFL);
|
||||
let r = libc::fcntl(nfd.fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
|
||||
if r != 0 {
|
||||
return Err("fcntl failed".into());
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
let mut buf = [0; 4];
|
||||
match nfd.read_exact(&mut buf) {
|
||||
Ok(_) => continue,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break,
|
||||
Err(e) => return Err(format!("unexpected read io error {}", e).into()),
|
||||
}
|
||||
}
|
||||
|
||||
let t = nfd.token;
|
||||
let afd = AsyncFd::with_interest(nfd, Interest::READABLE)?;
|
||||
|
||||
unsafe {
|
||||
let r = notify_resume(t);
|
||||
if r != 0 {
|
||||
return Err("notify_resume failed".into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
inner: afd,
|
||||
token: t,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for AsyncNotifyFd {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
loop {
|
||||
let mut guard = ready!(self.inner.poll_read_ready_mut(cx))?;
|
||||
let r = guard.try_io(|inner| inner.get_mut().read(buf.initialize_unfilled()));
|
||||
|
||||
if r.is_ok() {
|
||||
return Poll::Ready(r.unwrap().map(|n| buf.advance(n)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn notify_register_file_descriptor(
|
||||
name: *const c_char,
|
||||
notify_fd: *mut c_int,
|
||||
flags: c_int,
|
||||
out_token: *mut c_int,
|
||||
) -> u32;
|
||||
|
||||
fn notify_cancel(token: c_int) -> u32;
|
||||
|
||||
fn notify_suspend(token: c_int) -> u32;
|
||||
fn notify_resume(token: c_int) -> u32;
|
||||
}
|
Loading…
Reference in a new issue