diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..458144c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +batter_monitor +obj/* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bce361a --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) [year] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/Makefile b/Makefile index 63be530..5af3303 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,13 @@ all: $(TARGET) $(TARGET): $(OBJS) $(CC) -o $(TARGET) $(OBJS) $(LDFLAGS) +install: $(TARGET) + @echo "Installing $(TARGET) to /usr/local/bin" + cp $(TARGET) /usr/local/bin/ + chmod +x /usr/local/bin/$(TARGET) + clean: rm -rf $(OBJ_DIR) $(TARGET) -.PHONY: all clean +.PHONY: all clean install + diff --git a/README.md b/README.md index e099731..915576f 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,73 @@ -# Battery 0 Dameon -A battery daemon running the GTK framework built in C. Application monitors file structure /sys/class/power_suppply/BAT0/capacity file and if you have it its accounts for BAT1 (I'm pretty sure. In theory it does but I have not tested it as I dont have that file structure). +# Battery Monitor Daemon + +A battery monitor daemon built in C using the GTK framework. The application monitors your system's battery level and provides notifications when the battery is low or critically low. It also implements battery-saving features like reducing screen brightness and managing background processes to help preserve battery life. + +--- ## Table of Contents - [Features](#features) - [Installation](#installation) - [Dependencies](#dependencies) - - [Building the Application](#building-the-application) - - [Installing the Application](#installing-the-application) + - [Building and Installing the Application](#building-and-installing-the-application) +- [Configuration](#configuration) + - [Adjusting Battery Thresholds](#adjusting-battery-thresholds) + - [Configuring Process Management](#configuring-process-management) - [Uninstallation](#uninstallation) - [Contributing](#contributing) -## features +## Features -- GTK notification for 15 percent or less, and GTK notification for 5 percent or less. - - to change, go to src/battery_monitor.c, change #define THRESHOLD_LOW or #define THRESHOLD_CRITICAL to custom values. -- Dynamic battery percentage changing depending on last read value. - - When above THRESHOLD_HIGH, checks every 5 minutes. - - When Below critical, checks every 30 seconds. - - When low, checks every minute. -- Logging file created in /tmp/battery_monitor.log -- Battery saving mode changes brightness to help preserve battery -- Battery saving mode attempt to manage background procress is still in progress. +--- -## Installation +- **Configurable Battery Thresholds**: Receive notifications when the battery level falls below user-defined thresholds for low and critical levels. Adjust these thresholds easily via a configuration file. + +- **Dynamic Monitoring Interval**: The application adjusts its battery level check interval based on the current battery percentage to optimize performance and responsiveness. + +- **Battery Saving Mode**: + - Reduces screen brightness to 50% when the battery is low. + - Suspends high CPU-consuming processes and user daemons to conserve battery life. + - Allows users to specify which processes to ignore during suspension. + +- **Logging**: Activity is logged to `/tmp/battery_monitor.log` for debugging and monitoring purposes. + +- **Systemd Service**: Runs as a user-level systemd service, starting automatically upon login. + +- **Version Checking**: Supports version checking for both the application and the accompanying scripts. + +--- + +## Installation + +### Dependencies -### Dependencies Ensure you have the following dependencies installed on your system: + - **gcc**: GNU Compiler Collection for compiling the C code. - **make**: Utility for directing compilation. -- **GTK+**: Library for creating Graphical User Interfaces -- **Standard C Headers**: Standard Libraries found in glibc +- **pkg-config**: Helper tool used during compilation. +- **GTK+ 3 Development Libraries**: Library for creating graphical user interfaces. **On Debian/Ubuntu:** ```bash sudo apt-get update -sudo apt-get install build-essential libgtk-3-dev +sudo apt-get install build-essential pkg-config libgtk-3-dev ``` **On Fedora:** ```bash -sudo dnf install gcc make gtk3-devel +sudo dnf install gcc make pkgconf-pkg-config gtk3-devel ``` **On Arch Linux:** ```bash -sudo pacman -S base-devel gtk3 +sudo pacman -S base-devel pkgconf gtk3 ``` -### Building the Application +### Building and Installing the Application 1. **Clone the Repository** @@ -60,45 +76,124 @@ sudo pacman -S base-devel gtk3 cd bat0daemon ``` -2. **Build the Application** +2. **Run the Installation Script** ```bash - make + ./install.sh ``` - This will compile the source code and create the executable in the base directory. + The `install.sh` script will: -### Installing the Application + - Check for and install any missing dependencies. + - Build the application using `make`. + - Install the `battery_monitor` binary to `/usr/local/bin`. + - Install the `battery_daemon.sh` script to `/usr/local/bin`. + - Set up a user-level systemd service to run the application automatically on login. + - Create a default configuration file at `~/.config/battery_monitor/config.conf` if it does not exist. -Optionally, you can install the application system-wide: +**Note**: The `install.sh` script may prompt for your password to use `sudo` for installation. -```bash -make -./install.sh +--- + +## Configuration + +The application uses a configuration file located at `~/.config/battery_monitor/config.conf`. If the file does not exist, the `install.sh` script will create one with default values. + +### Adjusting Battery Thresholds + +Edit the configuration file to adjust battery thresholds: + +```ini +# ~/.config/battery_monitor/config.conf + +threshold_low=25 +threshold_critical=5 +threshold_high=80 ``` +- **threshold_low**: Battery percentage at which the application will send a low battery notification. +- **threshold_critical**: Battery percentage at which the application will send a critical battery notification. +- **threshold_high**: Battery percentage above which the application checks the battery level less frequently. + +### Configuring Process Management + +Specify processes to ignore when the application suspends high CPU-consuming processes or user daemons: + +```ini +ignore_processes_for_kill=process1, process2, process3 +ignore_processes_for_sleep=process4, process5, process6 +``` + +- **ignore_processes_for_kill**: List of processes to ignore when suspending high CPU-consuming processes. +- **ignore_processes_for_sleep**: List of processes to ignore when suspending user daemons. + +**Example**: + +```ini +ignore_processes_for_kill=firefox, code +ignore_processes_for_sleep=dropbox, slack +``` + +--- + ## Uninstallation To remove the application and its associated files: -1. **Remove the Executable** +1. **Stop and Disable the Systemd Service** - If installed system-wide: + ```bash + systemctl --user stop battery_monitor.service + systemctl --user disable battery_monitor.service + ``` + +2. **Remove Installed Files** + + ```bash + sudo rm /usr/local/bin/battery_monitor + sudo rm /usr/local/bin/battery_daemon.sh + ``` + +3. **Remove the Systemd Service File** + + ```bash + rm ~/.config/systemd/user/battery_monitor.service + ``` + +4. **Reload the Systemd Daemon** + + ```bash + systemctl --user daemon-reload + ``` + +5. **Remove Configuration and Log Files (Optional)** + + ```bash + rm -rf ~/.config/battery_monitor + rm /tmp/battery_monitor.log + ``` + +6. **Clean Up Build Files** ```bash make clean ``` -2. **Delete Configuration and Data Files** - - ```bash - sudo rm -rf ~/usr/local/bin/bat0daemon - *add more* - ``` - +--- ## Contributing -Contributions are welcome! If you have ideas for new features or improvements, feel free to fork the repository and submit a pull request. Let's make this application even better together. +Contributions are welcome! If you have ideas for new features, improvements, or bug fixes, feel free to fork the repository and submit a pull request. Let's make this application even better together. --- + +**Additional Notes**: + +- **Battery Monitoring**: The application dynamically finds the battery device path, supporting systems with different battery naming conventions (e.g., `BAT0`, `BAT1`). + +- **Process Suspension**: The application can suspend non-critical background processes to conserve battery life when in battery-saving mode. Critical system processes are automatically excluded. + +- **Customization**: Users can tailor the application's behavior extensively through the configuration file. + +--- + diff --git a/battery_daemon.sh b/battery_daemon.sh index 8dd1307..2622534 100755 --- a/battery_daemon.sh +++ b/battery_daemon.sh @@ -1,5 +1,14 @@ #!/usr/bin/env bash +# Version of the script +VERSION="1.0.0" + +# Check for version option +if [[ "$1" == "--version" ]]; then + echo "battery_daemon.sh version $VERSION" + exit 0 +fi + # Path to the battery monitor binary BINARY_PATH="/usr/local/bin/battery_monitor" diff --git a/battery_monitor b/battery_monitor index 30eaa37..f68a584 100755 Binary files a/battery_monitor and b/battery_monitor differ diff --git a/config.conf b/config.conf new file mode 100644 index 0000000..e3fb04a --- /dev/null +++ b/config.conf @@ -0,0 +1,7 @@ +# Default configuration for battery_monitor + +ignore_processes_for_sleep=vi,neovim,vim +threshold_low=25 +threshold_critical=5 +threshold_high=80 + diff --git a/include/battery_monitor.h b/include/battery_monitor.h index e7e52b0..31c1d47 100644 --- a/include/battery_monitor.h +++ b/include/battery_monitor.h @@ -13,4 +13,11 @@ void log_message(const char *message); // New function declaration for process monitoring int get_high_cpu_processes(char *process_list[], int max_processes); +extern int battery_saving_mode_active; +extern int THRESHOLD_LOW; +extern int THRESHOLD_CRITICAL; +extern int THRESHOLD_HIGH; + +void load_thresholds_from_config(); + #endif // BATTERY_MONITOR_H diff --git a/include/process_monitor.h b/include/process_monitor.h index 7b55b05..1cba37e 100644 --- a/include/process_monitor.h +++ b/include/process_monitor.h @@ -1,8 +1,21 @@ #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); +#include +#include + +extern bool dry_run; +extern pid_t suspended_pids[]; +extern int suspended_count; +extern pid_t suspended_high_cpu_pids[]; +extern int suspended_high_cpu_count; + +int run_battery_saving_mode(pid_t current_pid); +int get_ignore_processes(char *ignore_list[], int max_ignores, const char *config_key); +int is_process_critical(const char *process_name, char *ignore_list[], int ignore_count); + +int suspend_user_daemons(); +int resume_user_daemons(); +int resume_high_cpu_processes(); #endif // PROCESS_MONITOR_H - diff --git a/include/version.h b/include/version.h new file mode 100644 index 0000000..7d1c7b2 --- /dev/null +++ b/include/version.h @@ -0,0 +1,9 @@ +// version.h + +#ifndef VERSION_H +#define VERSION_H + +#define VERSION "1.0.0" + +#endif // VERSION_H + diff --git a/install.sh b/install.sh index 2f1658b..60ee1f4 100755 --- a/install.sh +++ b/install.sh @@ -10,43 +10,136 @@ check_dependency() { 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" +# Function to check if a package is installed (for libraries) +check_library() { + if ! dpkg -s "$1" &> /dev/null; then + echo "$1 is not installed. Installing..." + sudo apt-get install -y "$1" + else + echo "$1 is already installed." + fi +} -# 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") +# Step 1: Check for dependencies and install if not present +dependencies=("gcc" "make" "pkg-config") +libraries=("libgtk-3-dev") # Add other required development libraries here +echo "Checking for build dependencies..." for dep in "${dependencies[@]}"; do check_dependency "$dep" done -# Step 3: Copy battery_monitor.service to user systemd folder +echo "Checking for required libraries..." +for lib in "${libraries[@]}"; do + check_library "$lib" +done + +# Step 2: Build the application +echo "Building the application..." +make clean +make + +if [ $? -ne 0 ]; then + echo "Build failed. Exiting." + exit 1 +fi + +# Step 3: Find the current working directory and script location +SCRIPT_DIR=$(pwd) +SRC_SCRIPT="$SCRIPT_DIR/battery_monitor" +BASH_SCRIPT="$SCRIPT_DIR/battery_daemon.sh" + +# Step 4: Check if battery_monitor exists in common locations +INSTALL_PATH="/usr/local/bin/battery_monitor" +EXISTING_VERSION="" +NEW_VERSION="" + +if [ -f "$INSTALL_PATH" ]; then + echo "Existing installation of battery_monitor found at $INSTALL_PATH" + # Retrieve the version of the existing installation + EXISTING_VERSION=$("$INSTALL_PATH" --version | awk '{print $NF}') + echo "Existing battery_monitor version: $EXISTING_VERSION" +else + echo "No existing installation of battery_monitor found." +fi + +# Retrieve the version of the new build +if [ -f "$SRC_SCRIPT" ]; then + NEW_VERSION=$("$SRC_SCRIPT" --version | awk '{print $NF}') + echo "New battery_monitor version: $NEW_VERSION" +else + echo "New build of battery_monitor not found. Exiting." + exit 1 +fi + +# Function to compare versions +version_greater() { + # Returns 0 if $1 > $2 + [ "$(printf '%s\n' "$2" "$1" | sort -V | head -n1)" != "$1" ] +} + +# Step 5: Compare versions and install if new version is greater +if [ -z "$EXISTING_VERSION" ] || version_greater "$NEW_VERSION" "$EXISTING_VERSION"; then + echo "Installing new version of battery_monitor..." + + # Copy the binary to /usr/local/bin + sudo cp "$SRC_SCRIPT" "$INSTALL_PATH" + sudo chmod +x "$INSTALL_PATH" + + echo "battery_monitor installed successfully." +else + echo "Existing battery_monitor is up to date or newer. No installation needed." +fi + +# Step 6: Check and install battery_daemon.sh +DAEMON_INSTALL_PATH="/usr/local/bin/battery_daemon.sh" +EXISTING_DAEMON_VERSION="" +NEW_DAEMON_VERSION="" + +if [ -f "$DAEMON_INSTALL_PATH" ]; then + echo "Existing installation of battery_daemon.sh found at $DAEMON_INSTALL_PATH" + # Retrieve the version of the existing daemon + EXISTING_DAEMON_VERSION=$("$DAEMON_INSTALL_PATH" --version | awk '{print $NF}') + echo "Existing battery_daemon.sh version: $EXISTING_DAEMON_VERSION" +else + echo "No existing installation of battery_daemon.sh found." +fi + +# Retrieve the version of the new daemon script +if [ -f "$BASH_SCRIPT" ]; then + NEW_DAEMON_VERSION=$("$BASH_SCRIPT" --version | awk '{print $NF}') + echo "New battery_daemon.sh version: $NEW_DAEMON_VERSION" +else + echo "battery_daemon.sh script not found in the current directory. Exiting." + exit 1 +fi + +# Compare versions and install if new version is greater +if [ -z "$EXISTING_DAEMON_VERSION" ] || version_greater "$NEW_DAEMON_VERSION" "$EXISTING_DAEMON_VERSION"; then + echo "Installing new version of battery_daemon.sh..." + + # Copy the daemon script to /usr/local/bin + sudo cp "$BASH_SCRIPT" "$DAEMON_INSTALL_PATH" + sudo chmod +x "$DAEMON_INSTALL_PATH" + + echo "battery_daemon.sh installed successfully." +else + echo "Existing battery_daemon.sh is up to date or newer. No installation needed." +fi + +# Step 7: 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 @@ -54,7 +147,7 @@ else exit 1 fi -# Step 4: Reload the systemd daemon, enable and restart the service (user-level) +# Step 8: Reload the systemd daemon, enable and restart the service (user-level) echo "Reloading systemd user daemon..." systemctl --user daemon-reload @@ -71,3 +164,38 @@ else echo "Failed to start the service." exit 1 fi + +# Step 9: Create default configuration if not exists +CONFIG_DIR="$HOME/.config/battery_monitor" +CONFIG_FILE="$CONFIG_DIR/config.conf" +DEFAULT_CONFIG="$SCRIPT_DIR/config.conf" + +if [[ ! -d "$CONFIG_DIR" ]]; then + echo "Creating configuration directory at $CONFIG_DIR" + mkdir -p "$CONFIG_DIR" +fi + +if [[ ! -f "$CONFIG_FILE" ]]; then + if [[ -f "$DEFAULT_CONFIG" ]]; then + echo "Copying default config.conf to $CONFIG_FILE" + cp "$DEFAULT_CONFIG" "$CONFIG_FILE" + else + echo "Default config.conf not found!" + # Optionally, create a default config file here + cat < "$CONFIG_FILE" +# Default configuration for battery_monitor + +threshold_low=25 +threshold_critical=5 +threshold_high=80 + +# Add other configuration options as needed +EOF + echo "Created default configuration file." + fi +else + echo "Configuration file already exists at $CONFIG_FILE" +fi + +echo "Installation and setup complete." + diff --git a/obj/battery_monitor.o b/obj/battery_monitor.o deleted file mode 100644 index b5992b1..0000000 Binary files a/obj/battery_monitor.o and /dev/null differ diff --git a/obj/notification.o b/obj/notification.o deleted file mode 100644 index eedfec1..0000000 Binary files a/obj/notification.o and /dev/null differ diff --git a/obj/process_monitor.o b/obj/process_monitor.o deleted file mode 100644 index 0bc82e6..0000000 Binary files a/obj/process_monitor.o and /dev/null differ diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..75f3075 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +backups/ diff --git a/src/battery_monitor.c b/src/battery_monitor.c index 1e5fdc4..15dec02 100644 --- a/src/battery_monitor.c +++ b/src/battery_monitor.c @@ -1,26 +1,117 @@ +// Battery_monitor.c + #include #include #include #include "battery_monitor.h" +#include "process_monitor.h" +#include "version.h" +#include +#include +#include -// Define the battery thresholds -#define THRESHOLD_LOW 15 -#define THRESHOLD_CRITICAL 5 -#define THRESHOLD_HIGH 75 +// Global Variables for Thresholds +int THRESHOLD_LOW = 25; // Default values +int THRESHOLD_CRITICAL = 5; +int THRESHOLD_HIGH = 80; + +// Function to trim leading and trailing whitespace +static 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; +} + +void load_thresholds_from_config() { + char *home_dir = getenv("HOME"); + if (home_dir == NULL) { + log_message("Failed to get HOME environment variable"); + return; + } + + char config_file_path[PATH_MAX]; + snprintf(config_file_path, sizeof(config_file_path), "%s/.config/battery_monitor/config.config", home_dir); + + FILE *config_file = fopen(config_file_path, "r"); + if (config_file == NULL) { + log_message("Failed to open config file, using default thresholds"); + return; + } + + char buffer[256]; + + while (fgets(buffer, sizeof(buffer), config_file) != NULL) { + char *line = trim_whitespace(buffer); + + // Skip empty lines and comments + if (strlen(line) == 0 || line[0] == '#') { + continue; + } + + char key[64]; + char value[64]; + + if (sscanf(line, "%63[^=]=%63s", key, value) == 2) { + if (strcmp(key, "threshold_low") == 0) { + THRESHOLD_LOW = atoi(value); + } else if (strcmp(key, "threshold_critical") == 0) { + THRESHOLD_CRITICAL = atoi(value); + } else if (strcmp(key, "threshold_high") == 0) { + THRESHOLD_HIGH = atoi(value); + } + } + } + + fclose(config_file); + + // Log the loaded thresholds + char message[256]; + snprintf(message, sizeof(message), "Loaded thresholds: LOW=%d, CRITICAL=%d, HIGH=%d", + THRESHOLD_LOW, THRESHOLD_CRITICAL, THRESHOLD_HIGH); + log_message(message); +} // Track if notifications have been sent int notified_low = 0; int notified_critical = 0; +int battery_saving_mode_active = 0; // 0: inactive, 1: active -int main() { +int main(int argc, char *argv[]) { + if (argc > 1 && strcmp(argv[1], "--version") == 0) { + printf("Battery Monitor version %s\n", VERSION); + return 0; + } log_message("Battery monitor started"); + load_thresholds_from_config(); + 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; + + if (battery_saving_mode_active) { + // Resume suspended processes + log_message("Battery is charging, resuming suspended processes"); + resume_high_cpu_processes(); + resume_user_daemons(); + battery_saving_mode_active = 0; + } + sleep(300); // Sleep for 5 minutes while charging continue; } @@ -43,6 +134,14 @@ int main() { sleep_duration = 60; // Sleep for 1 minute when low } + // Check if battery-saving mode is active and battery level has surpassed THRESHOLD_LOW + if (battery_saving_mode_active && battery_level > THRESHOLD_LOW) { + log_message("Battery level above threshold, resuming suspended processes"); + resume_high_cpu_processes(); + resume_user_daemons(); + battery_saving_mode_active = 0; + } + // Check if the battery level is below the critical threshold if (battery_level <= THRESHOLD_CRITICAL && !notified_critical) { log_message("Battery critically low, showing notification"); diff --git a/src/notification.c b/src/notification.c index 09af4b6..fcc4015 100644 --- a/src/notification.c +++ b/src/notification.c @@ -1,3 +1,5 @@ +// Notification.c + #include #include #include @@ -9,6 +11,8 @@ #include "battery_monitor.h" #include "process_monitor.h" #include "log_message.h" +#include +#include #define CSS_STYLE "\ * { \ @@ -21,28 +25,67 @@ } \ " -// 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; +extern int battery_saving_mode_active; - for (int i = 0; i < sizeof(battery_paths) / sizeof(battery_paths[0]); i++) { - file = fopen(battery_paths[i], "r"); - if (file != NULL) { - break; +typedef enum { + INIT_SYSTEMD, + INIT_SYSVINIT, + INIT_OPENRC, + INIT_UNKNOWN +} init_system_t; + +init_system_t detect_init_system() { + struct stat sb; + + if (stat("/run/systemd/system", &sb) == 0) { + return INIT_SYSTEMD; + } else if (stat("/sbin/init", &sb) == 0) { + // Additional checks can be added here + return INIT_SYSVINIT; + } else if (stat("/run/openrc", &sb) == 0) { + return INIT_OPENRC; + } + + return INIT_UNKNOWN; +} + +// Function to find the battery path dynamically +char *get_battery_device_path(const char *file_name) { + glob_t glob_result; + char pattern[PATH_MAX]; + + snprintf(pattern, sizeof(pattern), "/sys/class/power_supply/BAT*/%s", file_name); + + if (glob(pattern, 0, NULL, &glob_result) == 0) { + if (glob_result.gl_pathc > 0) { + char *battery_path = strdup(glob_result.gl_pathv[0]); + globfree(&glob_result); + return battery_path; } } + globfree(&glob_result); + return NULL; +} + +// Function to get the battery level +int get_battery_level() { + char *capacity_path = get_battery_device_path("capacity"); + if (capacity_path == NULL) { + log_message("Failed to find battery capacity file"); + return -1; + } + + FILE *file = fopen(capacity_path, "r"); + free(capacity_path); + if (file == NULL) { perror("Failed to open capacity file"); log_message("Failed to open capacity file"); return -1; } + int battery_level; if (fscanf(file, "%d", &battery_level) != 1) { perror("Failed to read battery level"); log_message("Failed to read battery level"); @@ -78,10 +121,36 @@ void log_message(const char *message) { } } +char *get_backlight_device_path(const char *file_name) { + glob_t glob_result; + char pattern[PATH_MAX]; + + snprintf(pattern, sizeof(pattern), "/sys/class/backlight/*/%s", file_name); + + if (glob(pattern, 0, NULL, &glob_result) == 0) { + if (glob_result.gl_pathc > 0) { + char *backlight_path = strdup(glob_result.gl_pathv[0]); + globfree(&glob_result); + return backlight_path; + } + } + + globfree(&glob_result); + return NULL; +} + // 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"; + char *brightness_path = get_backlight_device_path("brightness"); + char *max_brightness_path = get_backlight_device_path("max_brightness"); + + if (brightness_path == NULL || max_brightness_path == NULL) { + log_message("Failed to find backlight brightness files"); + free(brightness_path); + free(max_brightness_path); + return -1; + } + int max_brightness = 100; int new_brightness = 0; char buffer[10]; @@ -124,6 +193,8 @@ int set_brightness(int brightness) { return -1; } + free(brightness_path); + free(max_brightness_path); close(fd); return 0; } @@ -135,58 +206,49 @@ int activate_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"); + // Suspend high CPU processes + log_message("Suspending high CPU processes"); + if (run_battery_saving_mode(current_pid) == -1) { + log_message("Failed to suspend 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; - } + // Suspend user daemons + log_message("Suspending user daemons"); + if (suspend_user_daemons() == -1) { + log_message("Failed to suspend user daemons"); + 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; } + // Set the battery-saving mode active flag + battery_saving_mode_active = 1; + return 0; } // Function to enter sleep mode int enter_sleep_mode() { log_message("Entering sleep mode"); - return system("systemctl suspend"); + + init_system_t init_sys = detect_init_system(); + + switch (init_sys) { + case INIT_SYSTEMD: + return system("systemctl suspend"); + case INIT_SYSVINIT: + return system("pm-suspend"); + case INIT_OPENRC: + return system("loginctl suspend"); + default: + log_message("Unknown init system, cannot enter sleep mode"); + return -1; + } } // Function to apply custom CSS styles to the GTK widgets @@ -292,26 +354,22 @@ void show_notification(const char *message, const char *title) { // 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; - } + char *status_path = get_battery_device_path("status"); + if (status_path == NULL) { + log_message("Failed to find battery status file"); + return -1; } + FILE *file = fopen(status_path, "r"); + free(status_path); + if (file == NULL) { perror("Failed to open status file"); log_message("Failed to open status file"); return -1; } + char status[16]; if (fscanf(file, "%15s", status) != 1) { perror("Failed to read battery status"); log_message("Failed to read battery status"); @@ -322,4 +380,3 @@ int is_charging() { fclose(file); return (strcmp(status, "Charging") == 0); } - diff --git a/src/notification.c.bak b/src/notification.c.bak deleted file mode 100644 index 58b0938..0000000 --- a/src/notification.c.bak +++ /dev/null @@ -1,307 +0,0 @@ -#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 index 3c168ae..51ecf01 100644 --- a/src/process_monitor.c +++ b/src/process_monitor.c @@ -1,18 +1,44 @@ +// process_monitor.c + #include #include #include #include #include #include +#include +#include +#include "process_monitor.h" #include "log_message.h" +#include +#include +#include #define BUFFER_SIZE 1024 -#define CONFIG_FILE "/.config/battery_monitor/config.config" +#define CONFIG_FILE "/.config/battery_monitor/config.conf" #define MAX_CRITICAL_PROCESSES 100 #define MAX_IGNORE_PROCESSES 100 +#define MAX_SUSPENDED_PROCESSES 1024 + +pid_t suspended_pids[MAX_SUSPENDED_PROCESSES]; +int suspended_count = 0; +pid_t suspended_high_cpu_pids[MAX_SUSPENDED_PROCESSES]; +int suspended_high_cpu_count = 0; + +bool dry_run = true; // Set to 'true' for dry run, 'false' for normal operation + +// CPU usage threshold to consider a process as high CPU-consuming +#define CPU_USAGE_THRESHOLD 1.0 // 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}; +const char *default_critical_processes[] = { + "systemd", "Xorg", "dbus-daemon", "NetworkManager", "dwm", "DWM", + "sddm", "gdm", "fprintd", "gnome-shell", "plasmashell", "kdeinit", + "kwin", "xfce4-session", "cinnamon", "mate-session", "pulseaudio", + "pipewire", "pipewire-pulse", "wireplumber", "lightdm", "udisksd", + "upowerd", "bash", "st", "picom", "python3", "gvfsd", "xdg-document-portal", + "at-spi-bus-launcher", "at-spi2-registr", "volumeicon", NULL +}; // Function to perform case-insensitive string comparison int case_insensitive_compare(const char *a, const char *b) { @@ -26,20 +52,17 @@ int case_insensitive_compare(const char *a, const char *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]); +// Function to output messages based on dry run mode +void output_message(const char *message) { + if (dry_run) { + printf("%s\n", message); + } else { + log_message(message); } - - *count = index; } // Helper function to remove leading/trailing whitespace -char *trim_whitespace(char *str) { +static char *trim_whitespace(char *str) { char *end; // Trim leading space @@ -57,15 +80,28 @@ char *trim_whitespace(char *str) { return str; } +// Function to dynamically build the list of critical processes +void build_critical_processes_list(char *critical_list[], int *count) { + int index = *count; + + // Add default critical processes + for (int i = 0; default_critical_processes[i] != NULL && index < MAX_IGNORE_PROCESSES; i++) { + critical_list[index++] = strdup(default_critical_processes[i]); + } + + *count = index; +} + // 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"); + output_message("Failed to get HOME environment variable"); return NULL; } - char *config_file_path = malloc(strlen(home_dir) + strlen(CONFIG_FILE) + 1); + size_t path_len = strlen(home_dir) + strlen(CONFIG_FILE) + 1; + char *config_file_path = malloc(path_len); if (config_file_path != NULL) { strcpy(config_file_path, home_dir); strcat(config_file_path, CONFIG_FILE); @@ -75,26 +111,32 @@ char *get_config_file_path() { } // Function to parse the ignore list from the config file -int get_ignore_processes(char *ignore_list[], int max_ignores) { +int get_ignore_processes(char *ignore_list[], int max_ignores, const char *config_key) { char *config_file_path = get_config_file_path(); if (config_file_path == NULL) { - log_message("Could not determine the config file path"); - return -1; + output_message("Could not determine the config file path"); + // Proceed with default critical processes + int ignore_count = 0; + build_critical_processes_list(ignore_list, &ignore_count); + return ignore_count; } FILE *config_file = fopen(config_file_path, "r"); free(config_file_path); + int ignore_count = 0; + if (config_file == NULL) { - log_message("Failed to open config file"); - return -1; + output_message("Failed to open config file"); + // Proceed with default critical processes + build_critical_processes_list(ignore_list, &ignore_count); + return ignore_count; } char buffer[BUFFER_SIZE]; - int ignore_count = 0; while (fgets(buffer, sizeof(buffer), config_file) != NULL) { - if (strstr(buffer, "ignore_processes_for_sleep") != NULL) { + if (strstr(buffer, config_key) != NULL) { char *token = strtok(buffer, "="); token = strtok(NULL, "="); // Get the processes list after '=' @@ -111,7 +153,7 @@ int get_ignore_processes(char *ignore_list[], int max_ignores) { fclose(config_file); - // Add default critical processes like dwm and Xorg if not already included + // Add default critical processes if not already included build_critical_processes_list(ignore_list, &ignore_count); return ignore_count; @@ -127,83 +169,237 @@ int is_process_critical(const char *process_name, char *ignore_list[], int ignor 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) { +// Main function to run battery saving mode +int run_battery_saving_mode(pid_t current_pid) { + output_message("Running battery saving mode in process_monitor"); + 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'"; + // Command to get high CPU-consuming processes excluding root processes + const char *command = "ps -eo user:32,pid,pcpu,comm --no-headers --sort=-pcpu"; fp = popen(command, "r"); if (fp == NULL) { - log_message("Failed to run command to get high CPU processes"); + output_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); + char *ignore_list[MAX_IGNORE_PROCESSES]; + int ignore_count = get_ignore_processes(ignore_list, MAX_IGNORE_PROCESSES, "ignore_processes_for_kill"); + if (ignore_count == -1) { + output_message("Failed to get ignore processes"); + pclose(fp); + return -1; + } - // Parse each line from the process list - while (fgets(buffer, sizeof(buffer), fp) != NULL && process_count < max_processes) { - char user[50], command_name[100]; + // Process the list and handle processes accordingly + while (fgets(buffer, sizeof(buffer), fp) != NULL) { + // Remove leading and trailing whitespace from the buffer + char *line = trim_whitespace(buffer); + + // Skip empty lines + if (strlen(line) == 0) { + continue; + } + + char user[33]; // +1 for null terminator + char command_name[256]; 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 + int items = sscanf(line, "%32s %d %f %255[^\n]", user, &pid, &cpu_usage, command_name); + + if (items == 4) { + // Exclude root processes + if (strcmp(user, "root") == 0) { + continue; } + + // Exclude processes with CPU usage below threshold + if (cpu_usage < CPU_USAGE_THRESHOLD) { + continue; + } + + if (pid == current_pid) { + output_message("Skipping suspending the current process."); + continue; + } + + if (!is_process_critical(command_name, ignore_list, ignore_count)) { + char message[512]; + snprintf(message, sizeof(message), "Process to be suspended: %s (PID: %d, CPU Usage: %.2f%%)", command_name, pid, cpu_usage); + output_message(message); + + if (dry_run) { + snprintf(message, sizeof(message), "Dry run mode active: Would suspend process %s (PID: %d)", command_name, pid); + output_message(message); + } else { + // Suspend the process + if (kill(pid, SIGSTOP) == -1) { + perror("Failed to suspend process"); + output_message("Failed to suspend process"); + } else { + // Add to the list of suspended processes + if (suspended_high_cpu_count < MAX_SUSPENDED_PROCESSES) { + suspended_high_cpu_pids[suspended_high_cpu_count++] = pid; + output_message("Process suspended successfully"); + } else { + output_message("Maximum suspended processes limit reached."); + } + } + } + } else { + char message[512]; + snprintf(message, sizeof(message), "Skipping critical process: %s (PID: %d)", command_name, pid); + output_message(message); + } + } else { + output_message("Failed to parse process information"); } } - // Free ignore list + // Clean up for (int i = 0; i < ignore_count; i++) { free(ignore_list[i]); } pclose(fp); - return process_count; + return 0; } -// 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]); +int resume_high_cpu_processes() { + for (int i = 0; i < suspended_high_cpu_count; i++) { + pid_t pid = suspended_high_cpu_pids[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"); + if (dry_run) { + printf("Dry run: Would resume high CPU process PID: %d\n", pid); } else { - log_message("Process killed successfully"); + if (kill(pid, SIGCONT) == 0) { + output_message("Resumed high CPU process"); + } else { + perror("Failed to resume high CPU process"); + } } } + suspended_high_cpu_count = 0; // Reset the count after resuming + return 0; } -// Free the process list -void free_process_list(char *process_list[], int count) { - for (int i = 0; i < count; i++) { - free(process_list[i]); +int suspend_user_daemons() { + DIR *proc_dir = opendir("/proc"); + if (proc_dir == NULL) { + perror("Failed to open /proc directory"); + return -1; } + + struct dirent *entry; + uid_t uid = getuid(); // Get the UID of the current user + + // Load ignore processes from config file for suspending daemons + char *ignore_list[MAX_IGNORE_PROCESSES]; + int ignore_count = get_ignore_processes(ignore_list, MAX_IGNORE_PROCESSES, "ignore_processes_for_sleep"); + if (ignore_count == -1) { + output_message("Failed to get ignore processes"); + closedir(proc_dir); + return -1; + } + + while ((entry = readdir(proc_dir)) != NULL) { + if (!isdigit(entry->d_name[0])) + continue; + + pid_t pid = atoi(entry->d_name); + if (pid == getpid()) // Skip current process + continue; + + char status_file[256]; + snprintf(status_file, sizeof(status_file), "/proc/%d/stat", pid); + + FILE *status_fp = fopen(status_file, "r"); + if (status_fp == NULL) + continue; + + char comm[256]; + char state; + int ppid, pgrp, session; + unsigned int tty_nr; + uid_t proc_uid = -1; + + // Read necessary fields from /proc/[pid]/stat + fscanf(status_fp, "%*d (%255[^)]) %c %d %d %d %u", comm, &state, &ppid, &pgrp, &session, &tty_nr); + fclose(status_fp); + + // Get UID of the process owner + char proc_status_file[256]; + snprintf(proc_status_file, sizeof(proc_status_file), "/proc/%d/status", pid); + + FILE *proc_status_fp = fopen(proc_status_file, "r"); + if (proc_status_fp == NULL) + continue; + + char line[256]; + while (fgets(line, sizeof(line), proc_status_fp)) { + if (strncmp(line, "Uid:", 4) == 0) { + sscanf(line, "Uid:\t%u", &proc_uid); + break; + } + } + fclose(proc_status_fp); + + // Check if process is owned by the user and has no controlling terminal + if (proc_uid == uid && tty_nr == 0) { + // Check if process is not critical + if (!is_process_critical(comm, ignore_list, ignore_count)) { + if (dry_run) { + printf("Dry run: Would suspend process PID: %d (%s)\n", pid, comm); + } else { + if (suspended_count < MAX_SUSPENDED_PROCESSES) { + if (kill(pid, SIGSTOP) == 0) { + suspended_pids[suspended_count++] = pid; + output_message("Suspended process"); + } else { + perror("Failed to suspend process"); + } + } else { + output_message("Maximum suspended processes limit reached."); + break; + } + } + } else { + // Log that we are skipping a critical process + if (dry_run) { + printf("Skipping critical process: PID: %d (%s)\n", pid, comm); + } + } + } + } + + // Free ignore_list + for (int i = 0; i < ignore_count; i++) { + free(ignore_list[i]); + } + + closedir(proc_dir); + return 0; } + +int resume_user_daemons() { + for (int i = 0; i < suspended_count; i++) { + pid_t pid = suspended_pids[i]; + + if (dry_run) { + printf("Dry run: Would resume process PID: %d\n", pid); + } else { + if (kill(pid, SIGCONT) == 0) { + output_message("Resumed process"); + } else { + perror("Failed to resume process"); + } + } + } + suspended_count = 0; // Reset the count after resuming + return 0; +} + diff --git a/src/process_monitor.c.bak b/src/process_monitor.c.bak deleted file mode 100644 index 404e7fb..0000000 --- a/src/process_monitor.c.bak +++ /dev/null @@ -1,177 +0,0 @@ -#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]); - } -}