commit 55283e90df5bcfa1089e189b0fcb3bf6d3ea838c Author: klein panic Date: Sun Sep 29 02:33:20 2024 -0400 initial commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..63be530 --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +CC = gcc +CFLAGS = `pkg-config --cflags gtk+-3.0` -I$(INC_DIR) +LDFLAGS = `pkg-config --libs gtk+-3.0` +SRC_DIR = src +INC_DIR = include +OBJ_DIR = obj +OBJS = $(OBJ_DIR)/battery_monitor.o $(OBJ_DIR)/notification.o $(OBJ_DIR)/process_monitor.o +TARGET = battery_monitor + +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(OBJ_DIR) + $(CC) $(CFLAGS) -I$(INC_DIR) -o $@ -c $< + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) -o $(TARGET) $(OBJS) $(LDFLAGS) + +clean: + rm -rf $(OBJ_DIR) $(TARGET) + +.PHONY: all clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/battery_daemon.sh b/battery_daemon.sh new file mode 100755 index 0000000..8dd1307 --- /dev/null +++ b/battery_daemon.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +# Path to the battery monitor binary +BINARY_PATH="/usr/local/bin/battery_monitor" + +# Log file +LOG_FILE="/tmp/battery_monitor.log" + +# Create Log Directory if it doesn't exist +LOG_DIR=$(dirname "$LOG_FILE") + +if [ ! -d "$LOG_DIR" ]; then + mkdir -p "$LOG_DIR" +fi + +if [ ! -d "$LOG_FILE" ]; then + echo "Creating Find the log file" + touch "$LOG_FILE" +fi + +if [ ! -w "$LOG_FILE" ]; then + echo "Cannot Write to the Log: $LOG_FILE. Attempting to change perms..." + sudo chown $USER "$LOG_FILE" || { + echo "Failed to change perms. Exiting" + exit 1 + } +fi + +# Log starting message and environment variables +echo "Starting battery monitor script" >> "$LOG_FILE" +echo "DISPLAY=$DISPLAY" >> "$LOG_FILE" +echo "XAUTHORITY=$XAUTHORITY" >> "$LOG_FILE" + +# Check if binary exists and is executable +if [ ! -x "$BINARY_PATH" ]; then + echo "Binary not found or not executable: $BINARY_PATH" >> "$LOG_FILE" + exit 1 +fi + +check_x_server() { + if xset q > /dev/null 2>&1; then + echo "X Server is running." + return 0 + else + echo "X Server is not running yet." + return 1 + fi +} + +until check_x_server; do + sleep 1 +done + +while true; do + if pgrep -x "dwm" > /dev/null; then + echo "dwm running" + # Start the battery monitor and redirect output to log file + exec "$BINARY_PATH" >> "$LOG_FILE" 2>&1 + + while pgrep -x "dwm" > /dev/null; do + sleep 1 + done + else + echo "waiting for DWM to start" + sleep 1 + fi +done diff --git a/battery_monitor b/battery_monitor new file mode 100755 index 0000000..30eaa37 Binary files /dev/null and b/battery_monitor differ diff --git a/battery_monitor.service b/battery_monitor.service new file mode 100755 index 0000000..13ebf9f --- /dev/null +++ b/battery_monitor.service @@ -0,0 +1,16 @@ +[Unit] +Description=Battery Monitor Service +After=default.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/battery_daemon.sh +Restart=on-failure +Environment=DISPLAY=:0 +Environment=XAUTHORITY=%h/.Xauthority +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=default.target + diff --git a/include/battery_monitor.h b/include/battery_monitor.h new file mode 100644 index 0000000..e7e52b0 --- /dev/null +++ b/include/battery_monitor.h @@ -0,0 +1,16 @@ +#ifndef BATTERY_MONITOR_H +#define BATTERY_MONITOR_H + +void show_notification(const char *message, const char *title); +int get_battery_level(); +int is_charging(); +int activate_battery_saving_mode(); +int enter_sleep_mode(); +int kill_processes(const char *filename); +int set_brightness(int brightness); +void log_message(const char *message); + +// New function declaration for process monitoring +int get_high_cpu_processes(char *process_list[], int max_processes); + +#endif // BATTERY_MONITOR_H diff --git a/include/log_message.h b/include/log_message.h new file mode 100644 index 0000000..43bd751 --- /dev/null +++ b/include/log_message.h @@ -0,0 +1,7 @@ +#ifndef LOG_MESSAGE_H +#define LOG_MESSAGE_H + +void log_message(const char* msg); + +#endif + diff --git a/include/process_monitor.h b/include/process_monitor.h new file mode 100644 index 0000000..7b55b05 --- /dev/null +++ b/include/process_monitor.h @@ -0,0 +1,8 @@ +#ifndef PROCESS_MONITOR_H +#define PROCESS_MONITOR_H + +int get_high_cpu_processes(char *process_list[], int max_processes); +void free_process_list(char *process_list[], int count); + +#endif // PROCESS_MONITOR_H + diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..2f1658b --- /dev/null +++ b/install.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +# Function to check if a command exists +check_dependency() { + if ! command -v "$1" &> /dev/null; then + echo "$1 is not installed. Installing..." + sudo apt-get install -y "$1" + else + echo "$1 is already installed." + fi +} + +# Step 1: Finding the current working directory and script location +SCRIPT_DIR=$(pwd) +BASH_SCRIPT="$SCRIPT_DIR/battery_daemon.sh" +SRC_SCRIPT="$SCRIPT_DIR/battery_monitor" + +# Check if battery_daemon.sh exists +if [[ -f "$BASH_SCRIPT" ]]; then + echo "Found battery_daemon.sh. Moving to /usr/local/bin." + sudo cp "$BASH_SCRIPT" /usr/local/bin/battery_daemon.sh + sudo cp "$SRC_SCRIPT" /usr/local/bin/battery_monitor + sudo chmod +x /usr/local/bin/battery_monitor + sudo chmod +x /usr/local/bin/battery_daemon.sh +else + echo "battery_daemon.sh not found in the current directory!" + exit 1 +fi + +# Step 2: Check for dependencies and install if not present +dependencies=("gcc" "make" "brightnessctl") + +for dep in "${dependencies[@]}"; do + check_dependency "$dep" +done + +# Step 3: Copy battery_monitor.service to user systemd folder +SYSTEMD_SERVICE="$SCRIPT_DIR/battery_monitor.service" +USER_SYSTEMD_DIR="$HOME/.config/systemd/user" + +if [[ -f "$SYSTEMD_SERVICE" ]]; then + echo "Found battery_monitor.service." + + # Create the user systemd directory if it doesn't exist + if [[ ! -d "$USER_SYSTEMD_DIR" ]]; then + echo "Creating user systemd directory: $USER_SYSTEMD_DIR" + mkdir -p "$USER_SYSTEMD_DIR" + fi + + echo "Copying battery_monitor.service to $USER_SYSTEMD_DIR" + cp "$SYSTEMD_SERVICE" "$USER_SYSTEMD_DIR/" +else + echo "battery_monitor.service not found in the current directory!" + exit 1 +fi + +# Step 4: Reload the systemd daemon, enable and restart the service (user-level) +echo "Reloading systemd user daemon..." +systemctl --user daemon-reload + +echo "Enabling battery_monitor.service..." +systemctl --user enable battery_monitor.service + +echo "Restarting battery_monitor.service..." +systemctl --user restart battery_monitor.service + +# Check if the service was successfully started +if systemctl --user is-active --quiet battery_monitor.service; then + echo "Service started successfully!" +else + echo "Failed to start the service." + exit 1 +fi diff --git a/obj/battery_monitor.o b/obj/battery_monitor.o new file mode 100644 index 0000000..b5992b1 Binary files /dev/null and b/obj/battery_monitor.o differ diff --git a/obj/notification.o b/obj/notification.o new file mode 100644 index 0000000..eedfec1 Binary files /dev/null and b/obj/notification.o differ diff --git a/obj/process_monitor.o b/obj/process_monitor.o new file mode 100644 index 0000000..0bc82e6 Binary files /dev/null and b/obj/process_monitor.o differ diff --git a/src/battery_monitor.c b/src/battery_monitor.c new file mode 100644 index 0000000..1e5fdc4 --- /dev/null +++ b/src/battery_monitor.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include "battery_monitor.h" + +// Define the battery thresholds +#define THRESHOLD_LOW 15 +#define THRESHOLD_CRITICAL 5 +#define THRESHOLD_HIGH 75 + +// Track if notifications have been sent +int notified_low = 0; +int notified_critical = 0; + +int main() { + log_message("Battery monitor started"); + + while (1) { + if (is_charging()) { + // Reset notifications if the battery is charging + log_message("Battery is charging, notifications reset"); + notified_low = 0; + notified_critical = 0; + sleep(300); // Sleep for 5 minutes while charging + continue; + } + + int battery_level = get_battery_level(); + if (battery_level == -1) { + log_message("Battery level read failed, retrying in 1 minute"); + sleep(60); + continue; + } + + // Dynamic sleep interval based on battery level + int sleep_duration = 60; // Default 1 minute + + if (battery_level > THRESHOLD_HIGH) { + sleep_duration = 300; // Sleep for 5 minutes + } else if (battery_level <= THRESHOLD_CRITICAL) { + sleep_duration = 30; // Sleep for 30 seconds when critically low + } else if (battery_level <= THRESHOLD_LOW) { + sleep_duration = 60; // Sleep for 1 minute when low + } + + // Check if the battery level is below the critical threshold + if (battery_level <= THRESHOLD_CRITICAL && !notified_critical) { + log_message("Battery critically low, showing notification"); + show_notification("Battery is critically low, below 5%", "Critical Battery Warning"); + notified_critical = 1; + } else if (battery_level <= THRESHOLD_LOW && !notified_low) { + log_message("Battery low, showing notification"); + show_notification("Battery is low, below 15%", "Low Battery Warning"); + notified_low = 1; + } + + // Reset notifications if battery level goes back up + if (battery_level > THRESHOLD_LOW) { + notified_low = 0; + } + if (battery_level > THRESHOLD_CRITICAL) { + notified_critical = 0; + } + + // Wait for the dynamically determined duration before checking again + sleep(sleep_duration); + } + + return 0; +} diff --git a/src/notification.c b/src/notification.c new file mode 100644 index 0000000..09af4b6 --- /dev/null +++ b/src/notification.c @@ -0,0 +1,325 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "battery_monitor.h" +#include "process_monitor.h" +#include "log_message.h" + +#define CSS_STYLE "\ + * { \ + background-color: #333333; \ + color: white; \ + } \ + button { \ + background-color: #555555; \ + color: white; \ + } \ +" + +// Function to get the battery level +int get_battery_level() { + const char *battery_paths[] = { + "/sys/class/power_supply/BAT0/capacity", + "/sys/class/power_supply/BAT1/capacity" + }; + FILE *file; + int battery_level = -1; + + for (int i = 0; i < sizeof(battery_paths) / sizeof(battery_paths[0]); i++) { + file = fopen(battery_paths[i], "r"); + if (file != NULL) { + break; + } + } + + if (file == NULL) { + perror("Failed to open capacity file"); + log_message("Failed to open capacity file"); + return -1; + } + + if (fscanf(file, "%d", &battery_level) != 1) { + perror("Failed to read battery level"); + log_message("Failed to read battery level"); + fclose(file); + return -1; + } + + fclose(file); + return battery_level; +} + +// Function to get the base directory of the executable +char *get_base_directory() { + static char base_dir[PATH_MAX]; + ssize_t count = readlink("/proc/self/exe", base_dir, PATH_MAX); + if (count != -1) { + dirname(base_dir); + } + return base_dir; +} + +// Function to log messages to a file +void log_message(const char *message) { + char log_file[PATH_MAX]; + snprintf(log_file, PATH_MAX, "/tmp/battery_monitor.log"); + + FILE *log_file_ptr = fopen(log_file, "a"); + if (log_file_ptr) { + fprintf(log_file_ptr, "%s\n", message); + fclose(log_file_ptr); + } else { + perror("Failed to open log file"); + } +} + +// Function to set the screen brightness +int set_brightness(int brightness) { + const char *brightness_path = "/sys/class/backlight/intel_backlight/brightness"; + const char *max_brightness_path = "/sys/class/backlight/intel_backlight/max_brightness"; + int max_brightness = 100; + int new_brightness = 0; + char buffer[10]; + + // Open max brightness file + int fd = open(max_brightness_path, O_RDONLY); + if (fd == -1) { + perror("Failed to open max brightness file"); + log_message("Failed to open max brightness file"); + return -1; + } + + // Read max brightness value + if (read(fd, buffer, sizeof(buffer)) != -1) { + max_brightness = atoi(buffer); + } else { + perror("Failed to read max brightness"); + log_message("Failed to read max brightness"); + close(fd); + return -1; + } + close(fd); + + // Calculate the new brightness + new_brightness = max_brightness * brightness / 100; + + // Write the new brightness value to the brightness file + fd = open(brightness_path, O_WRONLY); + if (fd == -1) { + perror("Failed to open brightness file"); + log_message("Failed to open brightness file"); + return -1; + } + + snprintf(buffer, sizeof(buffer), "%d", new_brightness); + if (write(fd, buffer, strlen(buffer)) == -1) { + perror("Failed to write to brightness file"); + log_message("Failed to write to brightness file"); + close(fd); + return -1; + } + + close(fd); + return 0; +} + +// Function to activate battery saving mode +int activate_battery_saving_mode() { + log_message("Activating battery saving mode"); + + // Get the current PID of the running program + pid_t current_pid = getpid(); + + // Call the get_high_cpu_processes from process_monitor.c to get the list of high CPU-consuming processes + char *process_list[100]; + int process_count = get_high_cpu_processes(process_list, 100); + + if (process_count == -1) { + log_message("Failed to get high CPU processes"); + return -1; + } + + // Loop through each high CPU process and kill it, excluding this program's own PID + for (int i = 0; i < process_count; i++) { + char command[300]; + char pid[10]; + char process_name[100]; + sscanf(process_list[i], "%9s %99s", pid, process_name); + + pid_t process_pid = atoi(pid); + + if (process_pid == current_pid) { + char log_msg[200]; + snprintf(log_msg, sizeof(log_msg), "Skipping own process: %s (PID: %s)", process_name, pid); + log_message(log_msg); + continue; + } + + char log_msg[200]; + snprintf(log_msg, sizeof(log_msg), "Killing process: %s (PID: %s)", process_name, pid); + log_message(log_msg); + + snprintf(command, sizeof(command), "kill -9 %s", pid); + if (system(command) == -1) { + log_message("Failed to kill process"); + free_process_list(process_list, process_count); + return -1; + } + } + + free_process_list(process_list, process_count); + + // Set the brightness to 50% for battery saving + if (set_brightness(50) == -1) { + log_message("Failed to set brightness to 50%"); + return -1; + } + + return 0; +} + +// Function to enter sleep mode +int enter_sleep_mode() { + log_message("Entering sleep mode"); + return system("systemctl suspend"); +} + +// Function to apply custom CSS styles to the GTK widgets +void apply_css(GtkWidget *widget, const char *css) { + GtkCssProvider *provider = gtk_css_provider_new(); + gtk_css_provider_load_from_data(provider, css, -1, NULL); + GtkStyleContext *context = gtk_widget_get_style_context(widget); + gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_USER); + g_object_unref(provider); +} + +// Function to check the battery status and close the dialog if charging +gboolean check_battery_status(gpointer user_data) { + GtkWidget *dialog = GTK_WIDGET(user_data); + if (is_charging()) { + log_message("Battery started charging, closing notification"); + gtk_widget_destroy(dialog); + gtk_main_quit(); + return FALSE; + } + return TRUE; +} + +// Signal handler for SIGINT +void handle_sigint(int sig) { + log_message("SIGINT caught, ignoring Ctrl-C"); +} + +// Function to set up signal handling +void setup_signal_handling() { + signal(SIGINT, handle_sigint); +} + +// Function to ignore Enter key and other keyboard inputs +gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data) { + if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_Escape) { + return TRUE; // Prevent default behavior for Enter and Escape keys + } + return FALSE; // Allow other keys if needed +} + +// Function to handle dialog response +void on_dialog_response(GtkDialog *dialog, gint response_id, gpointer user_data) { + switch (response_id) { + case GTK_RESPONSE_OK: + log_message("User clicked OK"); + break; + case GTK_RESPONSE_APPLY: + log_message("User activated Battery Saving Mode"); + activate_battery_saving_mode(); + break; + case GTK_RESPONSE_CLOSE: + log_message("User triggered Sleep Mode"); + enter_sleep_mode(); + break; + default: + break; + } + gtk_widget_destroy(GTK_WIDGET(dialog)); + gtk_main_quit(); +} + +// Function to show the notification dialog +void show_notification(const char *message, const char *title) { + log_message("Showing notification"); + + GtkWidget *dialog; + gtk_init(0, NULL); + + dialog = gtk_message_dialog_new(NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_NONE, + "%s", message); + gtk_dialog_add_button(GTK_DIALOG(dialog), "OK", GTK_RESPONSE_OK); + gtk_dialog_add_button(GTK_DIALOG(dialog), "Battery Saving Mode", GTK_RESPONSE_APPLY); + + if (g_strcmp0(title, "Critical Battery Warning") == 0) { + gtk_dialog_add_button(GTK_DIALOG(dialog), "Sleep", GTK_RESPONSE_CLOSE); + } + + gtk_window_set_title(GTK_WINDOW(dialog), title); + + // Apply CSS styles + apply_css(dialog, CSS_STYLE); + + // Set up the callback to check battery status and close if charging + g_timeout_add(1000, check_battery_status, dialog); + + // Connect the dialog response to handle button clicks and ensure proper cleanup + g_signal_connect(dialog, "response", G_CALLBACK(on_dialog_response), NULL); + + // Connect key-press-event signal to disable Enter key behavior + g_signal_connect(dialog, "key-press-event", G_CALLBACK(on_key_press), NULL); + + // Set up signal handling to trap SIGINT + setup_signal_handling(); + + // Show the dialog and enter the GTK main loop + gtk_widget_show_all(dialog); + gtk_main(); +} + +// Function to check if the battery is charging +int is_charging() { + const char *status_paths[] = { + "/sys/class/power_supply/BAT0/status", + "/sys/class/power_supply/BAT1/status" + }; + FILE *file; + char status[16]; + + for (int i = 0; i < sizeof(status_paths) / sizeof(status_paths[0]); i++) { + file = fopen(status_paths[i], "r"); + if (file != NULL) { + break; + } + } + + if (file == NULL) { + perror("Failed to open status file"); + log_message("Failed to open status file"); + return -1; + } + + if (fscanf(file, "%15s", status) != 1) { + perror("Failed to read battery status"); + log_message("Failed to read battery status"); + fclose(file); + return -1; + } + + fclose(file); + return (strcmp(status, "Charging") == 0); +} + diff --git a/src/notification.c.bak b/src/notification.c.bak new file mode 100644 index 0000000..58b0938 --- /dev/null +++ b/src/notification.c.bak @@ -0,0 +1,307 @@ +#include +#include +#include +#include +#include +#include +#include +#include "battery_monitor.h" +#include "process_monitor.h" +#include "log_message.h" + +#define CSS_STYLE "\ + * { \ + background-color: #333333; \ + color: white; \ + } \ + button { \ + background-color: #555555; \ + color: white; \ + } \ +" + +// Function to get the battery level +int get_battery_level() { + const char *battery_paths[] = { + "/sys/class/power_supply/BAT0/capacity", + "/sys/class/power_supply/BAT1/capacity" + }; + FILE *file; + int battery_level = -1; + + for (int i = 0; i < sizeof(battery_paths) / sizeof(battery_paths[0]); i++) { + file = fopen(battery_paths[i], "r"); + if (file != NULL) { + break; + } + } + + if (file == NULL) { + perror("Failed to open capacity file"); + log_message("Failed to open capacity file"); + return -1; + } + + if (fscanf(file, "%d", &battery_level) != 1) { + perror("Failed to read battery level"); + log_message("Failed to read battery level"); + fclose(file); + return -1; + } + + fclose(file); + return battery_level; +} + +// Function to get the base directory of the executable +char *get_base_directory() { + static char base_dir[PATH_MAX]; + ssize_t count = readlink("/proc/self/exe", base_dir, PATH_MAX); + if (count != -1) { + dirname(base_dir); + } + return base_dir; +} + +// Function to log messages to a file +void log_message(const char *message) { + char log_file[PATH_MAX]; + snprintf(log_file, PATH_MAX, "/tmp/battery_monitor.log"); + + FILE *log_file_ptr = fopen(log_file, "a"); + if (log_file_ptr) { + fprintf(log_file_ptr, "%s\n", message); + fclose(log_file_ptr); + } else { + perror("Failed to open log file"); + } +} + +// Function to set the screen brightness +int set_brightness(int brightness) { + const char *brightness_path = "/sys/class/backlight/intel_backlight/brightness"; + const char *max_brightness_path = "/sys/class/backlight/intel_backlight/max_brightness"; + int max_brightness = 100; + int new_brightness = 0; + char buffer[10]; + + // Open max brightness file + int fd = open(max_brightness_path, O_RDONLY); + if (fd == -1) { + perror("Failed to open max brightness file"); + log_message("Failed to open max brightness file"); + return -1; // Return failure if the file can't be opened + } + + // Read max brightness value + if (read(fd, buffer, sizeof(buffer)) != -1) { + max_brightness = atoi(buffer); + } else { + perror("Failed to read max brightness"); + log_message("Failed to read max brightness"); + close(fd); + return -1; // Return failure if the file can't be read + } + close(fd); + + // Calculate the new brightness + new_brightness = max_brightness * brightness / 100; + + // Write the new brightness value to the brightness file + fd = open(brightness_path, O_WRONLY); + if (fd == -1) { + perror("Failed to open brightness file"); + log_message("Failed to open brightness file"); + return -1; // Return failure if the file can't be opened + } + + snprintf(buffer, sizeof(buffer), "%d", new_brightness); + if (write(fd, buffer, strlen(buffer)) == -1) { + perror("Failed to write to brightness file"); + log_message("Failed to write to brightness file"); + close(fd); + return -1; // Return failure if the write fails + } + + close(fd); + return 0; // Success +} + +// Function to activate battery saving mode +int activate_battery_saving_mode() { + log_message("Activating battery saving mode"); + + // Get the current PID of the running program + pid_t current_pid = getpid(); + + // Call the get_high_cpu_processes from process_monitor.c to get the list of high CPU-consuming processes + char *process_list[100]; // Assuming a maximum of 100 processes to handle + int process_count = get_high_cpu_processes(process_list, 100); + + if (process_count == -1) { + log_message("Failed to get high CPU processes"); + return -1; // Return failure if processes couldn't be killed + } + + // Loop through each high CPU process and kill it, excluding this program's own PID + for (int i = 0; i < process_count; i++) { + char command[300]; + + // Extract the PID and process name from process_list[i] + char pid[10]; + char process_name[100]; + sscanf(process_list[i], "%9s %99s", pid, process_name); // Assuming the format "PID ProcessName" in process_list + + // Convert the PID string to a number for comparison + pid_t process_pid = atoi(pid); + + // Check if the process PID matches the current program's PID, if so, skip it + if (process_pid == current_pid) { + char log_msg[200]; // Declare log_msg correctly + snprintf(log_msg, sizeof(log_msg), "Skipping own process: %s (PID: %s)", process_name, pid); + log_message(log_msg); + continue; // Skip killing the current program's own process + } + + // Log the process name and PID before killing the process + char log_msg[200]; + snprintf(log_msg, sizeof(log_msg), "Killing process: %s (PID: %s)", process_name, pid); + log_message(log_msg); + + // Kill the process by PID + snprintf(command, sizeof(command), "kill -9 %s", pid); + if (system(command) == -1) { + log_message("Failed to kill process"); + free_process_list(process_list, process_count); + return -1; // Return failure if the command fails + } + } + + // Free the dynamically allocated process list + free_process_list(process_list, process_count); + + // Set the brightness to 50% for battery saving + if (set_brightness(50) == -1) { + log_message("Failed to set brightness to 50%"); + return -1; // Return failure if brightness couldn't be set + } + + return 0; // Success +} + +// Function to enter sleep mode +int enter_sleep_mode() { + log_message("Entering sleep mode"); + return system("systemctl suspend"); // Return system command result +} + +// Function to apply custom CSS styles to the GTK widgets +void apply_css(GtkWidget *widget, const char *css) { + GtkCssProvider *provider = gtk_css_provider_new(); + gtk_css_provider_load_from_data(provider, css, -1, NULL); + GtkStyleContext *context = gtk_widget_get_style_context(widget); + gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_USER); + g_object_unref(provider); +} + +// Function to check the battery status and close the dialog if charging +gboolean check_battery_status(gpointer user_data) { + GtkWidget *dialog = GTK_WIDGET(user_data); + if (is_charging()) { + log_message("Battery started charging, closing notification"); + gtk_widget_destroy(dialog); + gtk_main_quit(); // Exit the GTK main loop + return FALSE; // Stop checking + } + return TRUE; // Continue checking +} + +// Function to handle dialog response +void on_dialog_response(GtkDialog *dialog, gint response_id, gpointer user_data) { + switch (response_id) { + case GTK_RESPONSE_OK: + log_message("User clicked OK"); + break; + case GTK_RESPONSE_APPLY: + log_message("User activated Battery Saving Mode"); + activate_battery_saving_mode(); + break; + case GTK_RESPONSE_CLOSE: + log_message("User triggered Sleep Mode"); + enter_sleep_mode(); + break; + default: + break; + } + gtk_widget_destroy(GTK_WIDGET(dialog)); + gtk_main_quit(); // Exit the GTK main loop +} + +// Function to show the notification dialog +void show_notification(const char *message, const char *title) { + log_message("Showing notification"); + + GtkWidget *dialog; + gtk_init(0, NULL); + + dialog = gtk_message_dialog_new(NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_NONE, + "%s", message); + gtk_dialog_add_button(GTK_DIALOG(dialog), "OK", GTK_RESPONSE_OK); + gtk_dialog_add_button(GTK_DIALOG(dialog), "Battery Saving Mode", GTK_RESPONSE_APPLY); + + if (g_strcmp0(title, "Critical Battery Warning") == 0) { + gtk_dialog_add_button(GTK_DIALOG(dialog), "Sleep", GTK_RESPONSE_CLOSE); + } + + gtk_window_set_title(GTK_WINDOW(dialog), title); + + // Apply CSS styles + apply_css(dialog, CSS_STYLE); + + // Set up the callback to check battery status and close if charging + g_timeout_add(1000, check_battery_status, dialog); + + // Connect the dialog response to handle button clicks and ensure proper cleanup + g_signal_connect(dialog, "response", G_CALLBACK(on_dialog_response), NULL); + + // Show the dialog and enter the GTK main loop + gtk_widget_show_all(dialog); + gtk_main(); // Start the GTK main loop +} + +// Function to check if the battery is charging +int is_charging() { + const char *status_paths[] = { + "/sys/class/power_supply/BAT0/status", + "/sys/class/power_supply/BAT1/status" + }; + FILE *file; + char status[16]; + + for (int i = 0; i < sizeof(status_paths) / sizeof(status_paths[0]); i++) { + file = fopen(status_paths[i], "r"); + if (file != NULL) { + break; + } + } + + if (file == NULL) { + perror("Failed to open status file"); + log_message("Failed to open status file"); + return -1; + } + + if (fscanf(file, "%15s", status) != 1) { + perror("Failed to read battery status"); + log_message("Failed to read battery status"); + fclose(file); + return -1; + } + + fclose(file); + return (strcmp(status, "Charging") == 0); +} + diff --git a/src/process_monitor.c b/src/process_monitor.c new file mode 100644 index 0000000..3c168ae --- /dev/null +++ b/src/process_monitor.c @@ -0,0 +1,209 @@ +#include +#include +#include +#include +#include +#include +#include "log_message.h" + +#define BUFFER_SIZE 1024 +#define CONFIG_FILE "/.config/battery_monitor/config.config" +#define MAX_CRITICAL_PROCESSES 100 +#define MAX_IGNORE_PROCESSES 100 + +// List of default critical processes (expanded with more essential processes) +const char *default_critical_processes[] = {"systemd", "Xorg", "dbus-daemon", "NetworkManager", "dwm", "DWM", "sddm", "gdm", "fprintd", NULL}; + +// Function to perform case-insensitive string comparison +int case_insensitive_compare(const char *a, const char *b) { + while (*a && *b) { + if (tolower((unsigned char)*a) != tolower((unsigned char)*b)) { + return 0; + } + a++; + b++; + } + return *a == *b; +} + +// Function to dynamically build the list of critical processes +void build_critical_processes_list(char *critical_list[], int *count) { + int index = 0; + + // Add default critical processes + for (int i = 0; default_critical_processes[i] != NULL; i++) { + critical_list[index++] = strdup(default_critical_processes[i]); + } + + *count = index; +} + +// Helper function to remove leading/trailing whitespace +char *trim_whitespace(char *str) { + char *end; + + // Trim leading space + while (isspace((unsigned char)*str)) str++; + + if (*str == 0) return str; // All spaces? + + // Trim trailing space + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) end--; + + // Write new null terminator character + end[1] = '\0'; + + return str; +} + +// Function to dynamically get the user's home directory and build the config file path +char *get_config_file_path() { + const char *home_dir = getenv("HOME"); + if (home_dir == NULL) { + log_message("Failed to get HOME environment variable"); + return NULL; + } + + char *config_file_path = malloc(strlen(home_dir) + strlen(CONFIG_FILE) + 1); + if (config_file_path != NULL) { + strcpy(config_file_path, home_dir); + strcat(config_file_path, CONFIG_FILE); + } + + return config_file_path; +} + +// Function to parse the ignore list from the config file +int get_ignore_processes(char *ignore_list[], int max_ignores) { + char *config_file_path = get_config_file_path(); + if (config_file_path == NULL) { + log_message("Could not determine the config file path"); + return -1; + } + + FILE *config_file = fopen(config_file_path, "r"); + free(config_file_path); + + if (config_file == NULL) { + log_message("Failed to open config file"); + return -1; + } + + char buffer[BUFFER_SIZE]; + int ignore_count = 0; + + while (fgets(buffer, sizeof(buffer), config_file) != NULL) { + if (strstr(buffer, "ignore_processes_for_sleep") != NULL) { + char *token = strtok(buffer, "="); + token = strtok(NULL, "="); // Get the processes list after '=' + + if (token != NULL) { + token = strtok(token, ","); + while (token != NULL && ignore_count < max_ignores) { + ignore_list[ignore_count] = strdup(trim_whitespace(token)); + ignore_count++; + token = strtok(NULL, ","); + } + } + } + } + + fclose(config_file); + + // Add default critical processes like dwm and Xorg if not already included + build_critical_processes_list(ignore_list, &ignore_count); + + return ignore_count; +} + +// Function to check if a process is critical (case-insensitive check) +int is_process_critical(const char *process_name, char *ignore_list[], int ignore_count) { + for (int i = 0; i < ignore_count; i++) { + if (case_insensitive_compare(process_name, ignore_list[i])) { + return 1; // Process is critical + } + } + return 0; +} + +// Get the list of high CPU processes excluding ignored and root processes +int get_high_cpu_processes(char *process_list[], int max_processes) { + FILE *fp; + char buffer[BUFFER_SIZE]; + int process_count = 0; + + // Command to get top CPU-consuming processes excluding root processes + const char *command = "ps -eo user,pid,comm,%cpu --sort=-%cpu | grep -vE '^root'"; + + fp = popen(command, "r"); + if (fp == NULL) { + log_message("Failed to run command to get high CPU processes"); + return -1; + } + + // Load ignore processes from config file + char *ignore_list[100]; + int ignore_count = get_ignore_processes(ignore_list, 100); + + // Parse each line from the process list + while (fgets(buffer, sizeof(buffer), fp) != NULL && process_count < max_processes) { + char user[50], command_name[100]; + int pid; + float cpu_usage; + + if (sscanf(buffer, "%49s %d %99s %f", user, &pid, command_name, &cpu_usage) == 4) { + if (!is_process_critical(command_name, ignore_list, ignore_count)) { + // Allocate memory for the process info and store the PID + process_list[process_count] = malloc(BUFFER_SIZE); + snprintf(process_list[process_count], BUFFER_SIZE, "%d", pid); // Only storing the PID + process_count++; + } else { + char log_msg[200]; + snprintf(log_msg, sizeof(log_msg), "Skipping critical process: %s (PID: %d)", command_name, pid); + log_message(log_msg); // Log critical processes skipped + } + } + } + + // Free ignore list + for (int i = 0; i < ignore_count; i++) { + free(ignore_list[i]); + } + + pclose(fp); + return process_count; +} + +// Function to handle killing high CPU processes +void kill_high_cpu_processes(char *process_list[], int process_count, pid_t current_pid) { + for (int i = 0; i < process_count; i++) { + pid_t process_pid = atoi(process_list[i]); + + if (process_pid == current_pid) { + log_message("Skipping killing the current process."); + continue; + } + + // Log the process PID before killing + char log_msg[200]; + snprintf(log_msg, sizeof(log_msg), "Killing process (PID: %d)", process_pid); + log_message(log_msg); + + // Kill the process by PID + char command[50]; + snprintf(command, sizeof(command), "kill -9 %d", process_pid); + if (system(command) == -1) { + log_message("Failed to kill process"); + } else { + log_message("Process killed successfully"); + } + } +} + +// Free the process list +void free_process_list(char *process_list[], int count) { + for (int i = 0; i < count; i++) { + free(process_list[i]); + } +} diff --git a/src/process_monitor.c.bak b/src/process_monitor.c.bak new file mode 100644 index 0000000..404e7fb --- /dev/null +++ b/src/process_monitor.c.bak @@ -0,0 +1,177 @@ +#include +#include +#include +#include +#include +#include +#include "log_message.h" + +#define BUFFER_SIZE 1024 +#define CONFIG_FILE "/.config/battery_monitor/config.config" +#define MAX_CRITICAL_PROCESSES 100 +#define MAX_IGNORE_PROCESSES 100 + +// List of default critical processes +const char *default_critical_processes[] = {"systemd", "Xorg", "dbus-daemon", "NetworkManager", NULL}; + +// Helper function to remove leading/trailing whitespace +char *trim_whitespace(char *str) { + char *end; + + // Trim leading space + while (isspace((unsigned char)*str)) str++; + + if (*str == 0) return str; // All spaces? + + // Trim trailing space + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) end--; + + // Write new null terminator character + end[1] = '\0'; + + return str; +} + +// Function to dynamically get the user's home directory and build the config file path +char *get_config_file_path() { + const char *home_dir = getenv("HOME"); + if (home_dir == NULL) { + log_message("Failed to get HOME environment variable"); + return NULL; + } + + char *config_file_path = malloc(strlen(home_dir) + strlen(CONFIG_FILE) + 1); + if (config_file_path != NULL) { + strcpy(config_file_path, home_dir); + strcat(config_file_path, CONFIG_FILE); + } + + return config_file_path; +} + +// Function to parse the ignore list from the config file +int get_ignore_processes(char *ignore_list[], int max_ignores) { + char *config_file_path = get_config_file_path(); + if (config_file_path == NULL) { + log_message("Could not determine the config file path"); + return -1; + } + + FILE *config_file = fopen(config_file_path, "r"); + free(config_file_path); + + if (config_file == NULL) { + log_message("Failed to open config file"); + return -1; + } + + char buffer[BUFFER_SIZE]; + int ignore_count = 0; + + while (fgets(buffer, sizeof(buffer), config_file) != NULL) { + if (strstr(buffer, "ignore_processes_for_sleep") != NULL) { + char *token = strtok(buffer, "="); + token = strtok(NULL, "="); // Get the processes list after '=' + + if (token != NULL) { + token = strtok(token, ","); + while (token != NULL && ignore_count < max_ignores) { + ignore_list[ignore_count] = strdup(trim_whitespace(token)); + ignore_count++; + token = strtok(NULL, ","); + } + } + } + } + + fclose(config_file); + return ignore_count; +} + +// Function to check if a process is critical +int is_process_critical(const char *process_name, char *ignore_list[], int ignore_count) { + for (int i = 0; i < ignore_count; i++) { + if (strcmp(process_name, ignore_list[i]) == 0) { + return 1; // Process is critical + } + } + return 0; +} + +// Get the list of high CPU processes excluding ignored and root processes +int get_high_cpu_processes(char *process_list[], int max_processes) { + FILE *fp; + char buffer[BUFFER_SIZE]; + int process_count = 0; + + // Command to get top CPU-consuming processes excluding root processes + const char *command = "ps -eo user,pid,comm,%cpu --sort=-%cpu | grep -vE '^root'"; + + fp = popen(command, "r"); + if (fp == NULL) { + log_message("Failed to run command to get high CPU processes"); + return -1; + } + + // Load ignore processes from config file + char *ignore_list[100]; + int ignore_count = get_ignore_processes(ignore_list, 100); + + // Parse each line from the process list + while (fgets(buffer, sizeof(buffer), fp) != NULL && process_count < max_processes) { + char user[50], command_name[100]; + int pid; + float cpu_usage; + + if (sscanf(buffer, "%49s %d %99s %f", user, &pid, command_name, &cpu_usage) == 4) { + if (!is_process_critical(command_name, ignore_list, ignore_count)) { + // Allocate memory for the process info and store the PID + process_list[process_count] = malloc(BUFFER_SIZE); + snprintf(process_list[process_count], BUFFER_SIZE, "%d", pid); // Only storing the PID + process_count++; + } + } + } + + // Free ignore list + for (int i = 0; i < ignore_count; i++) { + free(ignore_list[i]); + } + + pclose(fp); + return process_count; +} + +// Function to handle killing high CPU processes +void kill_high_cpu_processes(char *process_list[], int process_count, pid_t current_pid) { + for (int i = 0; i < process_count; i++) { + pid_t process_pid = atoi(process_list[i]); + + if (process_pid == current_pid) { + log_message("Skipping killing the current process."); + continue; + } + + // Log the process PID before killing + char log_msg[200]; + snprintf(log_msg, sizeof(log_msg), "Killing process (PID: %d)", process_pid); + log_message(log_msg); + + // Kill the process by PID + char command[50]; + snprintf(command, sizeof(command), "kill -9 %d", process_pid); + if (system(command) == -1) { + log_message("Failed to kill process"); + } else { + log_message("Process killed successfully"); + } + } +} + +// Free the process list +void free_process_list(char *process_list[], int count) { + for (int i = 0; i < count; i++) { + free(process_list[i]); + } +}