commit 5068895bd6f80f9e4686a851931d9d5b986397d5
Author: Jürg Hallenbarter
+ * This class is responsible for:
+ *
+ * By default, it prepares a {@link ShowProcesses} object displaying
+ * PID, process name, user, CPU usage, and memory usage.
+ *
+ * This method:
+ *
+ * This disables canonical input processing and echoing,
+ * allowing single keypress handling without pressing Enter.
+ *
+ *
+ */
+public class App {
+
+ /** Utility for determining terminal dimensions. */
+ private final TerminalSize terminalSize = new TerminalSize();
+
+ /** Holds configuration data for runtime settings. */
+ private final Config config = new Config();
+
+ /** Handles process retrieval and display logic. */
+ private final ShowProcesses showProcesses;
+
+ /** Shared flag for synchronizing display refreshes. */
+ private final AtomicBoolean refresh = new AtomicBoolean(true);
+
+ /**
+ * Constructs a new {@code App} instance and initializes the main process display.
+ *
+ *
+ * When the loop ends, terminal settings are restored and mouse reporting is disabled.
+ *
+ * @throws Exception if an I/O or threading error occurs
+ */
+ public void run() throws Exception {
+ enableRawMode();
+ enableMouseReporting();
+
+ try {
+ showProcesses.draw(); // initial draw
+
+ // Start background refresh
+ Thread refreshThread = new RefreshThread(showProcesses, refresh);
+ refreshThread.setDaemon(true);
+ refreshThread.start();
+
+ // Handle user input
+ new InputHandler(showProcesses, refresh, terminalSize).start();
+
+ } finally {
+ disableMouseReporting();
+ restoreTerminal();
+ }
+ }
+
+ /**
+ * Enables raw input mode on the terminal.
+ *
+ * Ensures that the terminal stops sending mouse event sequences + * once the program exits or cleans up. + *
+ */ + private void disableMouseReporting() { + System.out.print("\u001B[?1000l"); + System.out.flush(); + } +} \ No newline at end of file diff --git a/src/jtop/Isystem/IBatteryInfo.java b/src/jtop/Isystem/IBatteryInfo.java new file mode 100644 index 0000000..fdf0fb5 --- /dev/null +++ b/src/jtop/Isystem/IBatteryInfo.java @@ -0,0 +1,52 @@ +package jtop.Isystem; + +/** + * Interface for retrieving battery information. + *+ * Allows platform-specific implementations to provide battery status, charge, + * voltage, energy, and power readings. + */ +public interface IBatteryInfo { + + /** + * Gets the current battery charge percentage (0–100). + * + * @return battery percentage, or -1 if unavailable + */ + int getBatteryPercentage(); + + /** + * Gets the current battery status (e.g., Charging, Discharging, Full). + * + * @return battery status string, or null if unavailable + */ + String getBatteryStatus(); + + /** + * Gets the current battery voltage in volts. + * + * @return voltage in volts, or -1 if unavailable + */ + double getVoltage(); + + /** + * Gets the current battery energy in watt-hours. + * + * @return energy in Wh, or -1 if unavailable + */ + double getEnergy(); + + /** + * Gets the current power draw in watts. + * + * @return power in W, or -1 if unavailable + */ + double getPower(); + + /** + * Returns whether the system has a battery. + * + * @return true if battery is present and readable, false otherwise + */ + boolean hasBattery(); +} \ No newline at end of file diff --git a/src/jtop/Isystem/ICpuInfo.java b/src/jtop/Isystem/ICpuInfo.java new file mode 100644 index 0000000..4c56447 --- /dev/null +++ b/src/jtop/Isystem/ICpuInfo.java @@ -0,0 +1,32 @@ +package jtop.Isystem; + +/** + * Interface for retrieving CPU usage information in a cross-platform way. + *
+ * Implementations can provide system-specific logic for Linux, macOS, Windows, or FreeBSD. + */ +public interface ICpuInfo { + + /** + * Computes the CPU usage percentage of a specific process. + * + * @param pid the process ID + * @return CPU usage as a percentage, or -1 if unavailable + */ + double getCpuPercent(long pid); + + /** + * Retrieves the system load average. + * + * @return the load average string, or null if unavailable + */ + String getLoadAverage(); + + /** + * Computes the overall CPU usage percentage over a sample period. + * + * @param sampleMs the sample duration in milliseconds + * @return the CPU usage as a percentage over the sample period, or -1 if unavailable + */ + double getCpuUsage(long sampleMs); +} diff --git a/src/jtop/Isystem/IDiskInfo.java b/src/jtop/Isystem/IDiskInfo.java new file mode 100644 index 0000000..2e4f185 --- /dev/null +++ b/src/jtop/Isystem/IDiskInfo.java @@ -0,0 +1,31 @@ +package jtop.Isystem; + +import java.io.IOException; +import java.util.Map; + +/** + * Interface for providing information about disk usage and I/O statistics for mounted devices. + *
+ * Implementations should retrieve disk statistics and provide a map of device names to their + * read/write counts. + *
+ */ +public interface IDiskInfo { + + /** + * Retrieves disk I/O statistics for all block devices. + *+ * Each entry in the returned map contains the device name as the key, + * and an array of two long values as the value: + *
+ *
+ * Implementations typically read Linux /proc files to determine:
+ *
+ * Reads from the Linux file /proc/net/dev to retrieve
+ * the number of bytes received (RX) and transmitted (TX) per network interface.
+ *
+ * Implementations may use OS-specific mechanisms to fetch details about + * a running process, including its command (full path) and executable name. + *
+ */ +public interface IPathInfo { + + /** + * Returns the name of the executable for the given process ID. + *+ * For example, if the command is "/usr/bin/java", this should return "java". + *
+ * + * @param pid the process ID + * @return the executable name, or "Unknown" if the process does not exist + */ + String getName(long pid); + + /** + * Returns the full command path of the executable for the given process ID. + *+ * For example, "/usr/bin/java". + *
+ * + * @param pid the process ID + * @return the full command path, or "Unknown" if the process does not exist + */ + String getPath(long pid); +} diff --git a/src/jtop/Isystem/ITemperatureInfo.java b/src/jtop/Isystem/ITemperatureInfo.java new file mode 100644 index 0000000..564cf39 --- /dev/null +++ b/src/jtop/Isystem/ITemperatureInfo.java @@ -0,0 +1,19 @@ +package jtop.Isystem; + +import java.io.IOException; +import java.util.Map; + +/** + * Interface for providing system temperature information. + */ +public interface ITemperatureInfo { + + /** + * Retrieves a map of all detected temperatures on the system. + * + * @return Map where the key is a sensor name + * and the value is the temperature in °C. + * @throws IOException if the sensor directories cannot be read + */ + Map+ * Provides the uptime in various units such as seconds, minutes, hours, or days. + */ +public interface IUptime { + + /** + * Gets the system uptime in the specified format. + * + * @param timeFormat a character indicating the desired unit: + * 's' for seconds, + * 'm' for minutes, + * 'h' for hours, + * 'd' for days. + * @return the system uptime in the requested unit. + * @throws Exception if reading /proc/uptime fails or if the timeFormat is invalid. + */ + double getSystemUptime(char timeFormat) throws Exception; +} diff --git a/src/jtop/Main.java b/src/jtop/Main.java new file mode 100644 index 0000000..db5618c --- /dev/null +++ b/src/jtop/Main.java @@ -0,0 +1,23 @@ +package jtop; + +/** + * Entry point for the jtop system monitoring application. + *
+ * This class initializes the application and starts the main execution loop. + * All setup and execution logic is delegated to {@link App}. + */ +public class Main { + + /** + * Launches the jtop application. + *
+ * Initializes all necessary components, including process monitoring, + * terminal rendering, and input handling. + * + * @param args Command-line arguments (currently ignored) + * @throws Exception If system information cannot be read or if thread operations fail + */ + public static void main(String[] args) throws Exception { + new App().run(); + } +} \ No newline at end of file diff --git a/src/jtop/config/Config.java b/src/jtop/config/Config.java new file mode 100644 index 0000000..bc107c9 --- /dev/null +++ b/src/jtop/config/Config.java @@ -0,0 +1,141 @@ +package jtop.config; +import java.io.FileReader; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +/** + * Represents configuration options for the application. + *
+ * Handles reading, parsing, and providing configuration values + * such as refresh intervals, color modes, sorting options, and lists of items. + * Supports inline comments and ANSI escape sequences in configuration values. + *
+ */ +public class Config { + + /** Stores all loaded configuration properties. */ + private final Properties properties = new Properties(); + + /** + * Constructs a {@code Config} instance using the default configuration file. + * The default path is {@code config/default.conf}. + *+ * If the file cannot be loaded, an error message is printed to {@code stderr}. + *
+ */ + public Config() { + String filePath = "config/default.conf"; + try (FileReader reader = new FileReader(filePath)) { + properties.load(reader); + } catch (IOException e) { + System.err.println("Could not load config file: " + filePath); + e.printStackTrace(); + } + } + + /** + * Constructs a {@code Config} instance using a custom configuration file path. + * + * @param filePath the path to the configuration file + */ + public Config(String filePath) { + try (FileReader reader = new FileReader(filePath)) { + properties.load(reader); + } catch (IOException e) { + System.err.println("Could not load config file: " + filePath); + e.printStackTrace(); + } + } + + /** + * Cleans and normalizes a raw configuration value. + *+ * Cleaning includes: + *
+ *+ * Holds basic information about a process including its ID, name, executable path, + * owner, CPU usage, and memory usage. + *
+ */ +public class ProcessRow { + + /** Process ID (PID) */ + public long pid; + + /** Name of the executable (e.g., "java") */ + public String name; + + /** Full path of the executable (e.g., "/usr/bin/java") */ + public String path; + + /** User or owner of the process */ + public String user; + + /** CPU usage as a percentage string (e.g., "12.5") */ + public String cpu; + + /** Memory usage as a percentage string (e.g., "8.3") */ + public String memory; + + /** + * Constructs a ProcessRow instance. + * + * @param pid the process ID + * @param name the process executable name + * @param path the full path to the process executable + * @param user the owner of the process + * @param cpu the CPU usage as a string + * @param memory the memory usage as a string + */ + public ProcessRow(long pid, String name, String path, String user, String cpu, String memory) { + this.pid = pid; + this.name = name; + this.path = path; + this.user = user; + this.cpu = cpu; + this.memory = memory; + } +} \ No newline at end of file diff --git a/src/jtop/core/ProcessSorter.java b/src/jtop/core/ProcessSorter.java new file mode 100644 index 0000000..29144aa --- /dev/null +++ b/src/jtop/core/ProcessSorter.java @@ -0,0 +1,96 @@ +package jtop.core; + +import java.util.Comparator; +import java.util.Optional; + +import jtop.Isystem.ICpuInfo; +import jtop.Isystem.IMemoryInfo; +import jtop.Isystem.IPathInfo; +import jtop.system.Feature; +import jtop.system.SystemInfoFactory; + +/** + * Provides sorting utilities for processes. + *+ * Generates comparators to sort {@link ProcessHandle} instances based on + * PID, name, path, user, CPU usage, or memory usage. Supports ascending + * and descending order. + *
+ */ +public class ProcessSorter { + + /** + * Returns a comparator for processes based on the specified sort type. + * + * @param sortBy the {@link InfoType} to sort by (PID, NAME, CPU, MEMORY, etc.) + * @param ascending true for ascending order, false for descending + * @return a {@link Comparator} for {@link ProcessHandle} + */ + public static Comparator
+ * Reads the process status from /proc/[pid]/stat and maps
+ * the one-letter state code to a human-readable description.
+ *
+ * Typical Linux process states: + *
+ *
+ * Reads /proc/[pid]/stat and extracts the third field,
+ * which represents the process state as a single-character code.
+ *
"?" if unavailable
+ */
+ public static String getState(long pid) {
+ String path = "/proc/" + pid + "/stat";
+ try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
+ String[] parts = reader.readLine().split("\\s+");
+ // Field 3 is the process state (R, S, D, T, Z, etc.)
+ if (parts.length > 2) {
+ return parseState(parts[2]);
+ }
+ } catch (IOException e) {
+ return "?";
+ }
+ return "?";
+ }
+
+ /**
+ * Converts the short one-letter state code from /proc/[pid]/stat
+ * into a descriptive string.
+ *
+ * @param s the single-character state code (e.g. "R", "S", "Z")
+ * @return the full human-readable process state
+ */
+ private static String parseState(String s) {
+ switch (s) {
+ case "R": return "Running";
+ case "S": return "Sleeping";
+ case "D": return "Disk Sleep";
+ case "T": return "Stopped";
+ case "Z": return "Zombie";
+ case "X": return "Dead";
+ default: return s;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jtop/core/ProcessTableRenderer.java b/src/jtop/core/ProcessTableRenderer.java
new file mode 100644
index 0000000..381b503
--- /dev/null
+++ b/src/jtop/core/ProcessTableRenderer.java
@@ -0,0 +1,163 @@
+package jtop.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import jtop.config.Config;
+import jtop.terminal.Header;
+import jtop.terminal.TerminalSize;
+import jtop.system.linux.SystemSampler;
+
+/**
+ * Responsible for rendering the process table in the terminal.
+ * + * This class handles: + *
+ *+ * The thread wakes up at a fixed interval (2 seconds) and calls {@link IRefreshable#refresh()}. + * Refreshing only occurs if the {@link AtomicBoolean} flag is set to {@code true}. + *
+ * This thread runs as a daemon, allowing the application to exit gracefully. + */ +public class RefreshThread extends Thread { + private final IRefreshable refreshable; + private final AtomicBoolean refresh; + + /** + * Constructs a new RefreshThread. + * + * @param refreshable the component to refresh periodically + * @param refresh atomic boolean flag controlling whether a refresh should occur + */ + public RefreshThread(IRefreshable refreshable, AtomicBoolean refresh) { + this.refreshable = refreshable; + this.refresh = refresh; + setDaemon(true); + } + + /** + * Main loop of the thread. + *
+ * Sleeps for 2 seconds between updates and refreshes the target object + * if the {@code refresh} flag is set to {@code true}. + *
+ * Exits cleanly when interrupted.
+ */
+ @Override
+ public void run() {
+ while (!isInterrupted()) {
+ try {
+ Thread.sleep(2000);
+ if (refresh.get()) {
+ refreshable.refresh();
+ }
+ } catch (InterruptedException e) {
+ // Stop thread on interrupt
+ return;
+ } catch (Exception e) {
+ // Log or ignore other exceptions
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jtop/core/ShowProcesses.java b/src/jtop/core/ShowProcesses.java
new file mode 100644
index 0000000..7bbf8d8
--- /dev/null
+++ b/src/jtop/core/ShowProcesses.java
@@ -0,0 +1,167 @@
+package jtop.core;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jtop.Isystem.ICpuInfo;
+import jtop.Isystem.IMemoryInfo;
+import jtop.Isystem.IPathInfo;
+import jtop.Isystem.IUptime;
+import jtop.Isystem.ITemperatureInfo;
+import jtop.config.Config;
+import jtop.terminal.TerminalSize;
+import jtop.system.Feature;
+import jtop.system.SystemInfoFactory;
+import jtop.system.linux.SystemSampler;
+
+/**
+ * Core class responsible for managing, sorting, and displaying running processes.
+ */
+public class ShowProcesses implements IRefreshable {
+ private final List
+ * Each feature stores the name of its default implementation class, which is used
+ * by {@link SystemInfoFactory} to instantiate the appropriate OS-specific implementation.
+ *
+ * Provides a centralized way to query the supported {@link Feature}s for
+ * Linux, FreeBSD, and macOS without hardcoding OS-specific logic elsewhere.
+ *
+ * Provides a method to detect the current operating system at runtime.
+ *
+ * Uses the system property {@code os.name} to determine the OS.
+ * Returns the corresponding {@link OperatingSystem} enum value.
+ *
+ * Fully enum-driven: no need to manually add getters for each feature.
+ */
+public final class SystemInfoFactory {
+
+ private static final OperatingSystem OS = OperatingSystem.detect();
+ private static final Set
+ * Each operating system has its own feature class (e.g., {@link jtop.system.linux.LinuxFeatures})
+ * that lists which features are implemented and available.
+ *
+ * This is used by {@link jtop.system.FeatureResolver} to determine at runtime
+ * which features can be instantiated via {@link jtop.system.SystemInfoFactory}.
+ *
+ * Uses {@link ProcessHandle} to fetch details about a running process,
+ * including its command (full path) and executable name.
+ *
+ * For example, if the command is "/usr/bin/java", this will return "java".
+ *
+ * For example, "/usr/bin/java".
+ *
+ * Automatically detects the battery directory under
+ *
+ * Implements {@link IBatteryInfo} so it can be used in a cross-platform interface-based design.
+ */
+public class BatteryInfo implements IBatteryInfo {
+
+ /** Path to the power supply directory on Linux */
+ private static final Path POWER_SUPPLY_PATH = Path.of("/sys/class/power_supply");
+
+ /** Path to the detected battery directory (e.g., BAT0) */
+ private Path batteryPath;
+
+ /**
+ * Constructs a BatteryInfo instance and tries to detect the battery path.
+ */
+ public BatteryInfo() {
+ batteryPath = detectBatteryPath();
+ }
+
+ /**
+ * Detects the first available battery path under /sys/class/power_supply/.
+ *
+ * @return Path to the battery directory, or null if not found
+ */
+ private Path detectBatteryPath() {
+ if (!Files.isDirectory(POWER_SUPPLY_PATH)) return null;
+
+ try (var stream = Files.list(POWER_SUPPLY_PATH)) {
+ Optional
+ * Reads data from the
+ * Reads data from
+ * Each entry in the returned map contains the device name as the key,
+ * and an array of two long values as the value:
+ *
+ * Each operating system has its own feature class (e.g., {@link jtop.system.freebsd.FreeBsdFeatures})
+ * that lists which features are implemented and available.
+ *
+ * This is used by {@link jtop.system.FeatureResolver} to determine at runtime
+ * which features can be instantiated via {@link jtop.system.SystemInfoFactory}.
+ *
+ * Reads Linux /proc files to determine:
+ *
+ * Performance notes:
+ *
+ * Reads from the Linux file
+ * Uses {@link ProcessHandle} to fetch details about a running process,
+ * including its command (full path) and executable name.
+ *
+ * Performance notes:
+ *
+ * Temperature sources:
+ *
+ * Reads the uptime from
+ * Each operating system has its own feature class (e.g., {@link jtop.system.linux.LinuxFeatures})
+ * that lists which features are implemented and available.
+ *
+ * This is used by {@link jtop.system.FeatureResolver} to determine at runtime
+ * which features can be instantiated via {@link jtop.system.SystemInfoFactory}.
+ *
+ * Uses {@link ProcessHandle} to fetch details about a running process,
+ * including its command (full path) and executable name.
+ *
+ * For example, if the command is "/usr/bin/java", this will return "java".
+ *
+ * For example, "/usr/bin/java".
+ *
+ * Interprets key presses for:
+ *
+ * This method blocks and continuously interprets keyboard and mouse events
+ * until the user exits the application.
+ *
+ * Interprets:
+ *
+ * Provides methods to retrieve the number of rows and columns,
+ * allowing output to dynamically adjust to fit the screen.
+ */
+public class TerminalSize {
+
+ /**
+ * Retrieves the terminal size by executing the "stty size" command.
+ *
+ * @return an array of two integers: {rows, columns}.
+ * Defaults to {24, 80} if the size cannot be determined.
+ */
+ public static int[] getTerminalSize() {
+ try {
+ Process process = new ProcessBuilder("sh", "-c", "stty size < /dev/tty").start();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+ String line = reader.readLine();
+ if (line != null) {
+ String[] parts = line.trim().split("\\s+");
+ if (parts.length == 2) {
+ return new int[]{
+ Integer.parseInt(parts[0]),
+ Integer.parseInt(parts[1])
+ };
+ }
+ }
+ }
+ } catch (Exception e) {
+ // Ignore and fallback
+ }
+ return new int[]{24, 80}; // default fallback
+ }
+
+ /**
+ * Retrieves the number of terminal rows.
+ *
+ * @return the number of rows in the current terminal, or 24 if unknown
+ */
+ public static int getRows() {
+ return getTerminalSize()[0];
+ }
+
+ /**
+ * Retrieves the number of terminal columns.
+ *
+ * @return the number of columns in the current terminal, or 80 if unknown
+ */
+ public static int getColumns() {
+ return getTerminalSize()[1];
+ }
+}
\ No newline at end of file
/sys/class/power_supply/ (e.g. BAT0, BAT1)
+ * and exposes percentage, status, voltage, energy, and power readings.
+ * /proc filesystem on Linux:
+ *
+ *
+ */
+public class CpuInfo implements ICpuInfo {
+
+ /** Number of decimal places to round CPU percentage values. */
+ private static final int DECIMALS = 3;
+
+ /**
+ * Computes the CPU usage percentage of a specific process.
+ *
+ * @param pid the process ID
+ * @return CPU usage as a percentage, or -1 if unavailable
+ */
+ @Override
+ public double getCpuPercent(long pid) {
+ try {
+ String stat = Files.readString(Paths.get("/proc/" + pid + "/stat"));
+ String[] parts = stat.split("\\s+");
+
+ long utime = Long.parseLong(parts[13]);
+ long stime = Long.parseLong(parts[14]);
+ long totalTime = utime + stime;
+
+ double uptimeSeconds = new Uptime().getSystemUptime('s');
+
+ double percent = (100d * totalTime / uptimeSeconds) / Runtime.getRuntime().availableProcessors();
+
+ double factor = Math.pow(10, DECIMALS);
+ return Math.round(percent * factor) / factor;
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
+ /**
+ * Retrieves the system load average as reported by /proc/[pid]/stat for per-process CPU usage/proc/stat for overall CPU usage/proc/loadavg for system load average/proc/loadavg.
+ *
+ * @return the load average string, or null if unavailable
+ */
+ @Override
+ public String getLoadAverage() {
+ try {
+ return Files.readString(Path.of("/proc/loadavg")).trim();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Computes the overall CPU usage percentage over a sample period.
+ *
+ * @param sampleMs the sample duration in milliseconds
+ * @return the CPU usage as a percentage over the sample period, or -1 if unavailable
+ */
+ @Override
+ public double getCpuUsage(long sampleMs) {
+ try {
+ long[] first = readCpuStat();
+ Thread.sleep(sampleMs);
+ long[] second = readCpuStat();
+
+ long idle1 = first[3];
+ long idle2 = second[3];
+ long total1 = Arrays.stream(first).sum();
+ long total2 = Arrays.stream(second).sum();
+
+ long totalDelta = total2 - total1;
+ long idleDelta = idle2 - idle1;
+ return 100.0 * (totalDelta - idleDelta) / totalDelta;
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
+ /**
+ * Reads the system-wide CPU statistics from /proc/stat.
+ *
+ * @return an array of CPU time values (user, nice, system, idle, etc.), or null if unavailable
+ */
+ private long[] readCpuStat() {
+ try (BufferedReader br = Files.newBufferedReader(Path.of("/proc/stat"))) {
+ String[] parts = br.readLine().trim().split("\\s+");
+ long[] vals = new long[parts.length - 1];
+ for (int i = 1; i < parts.length; i++) {
+ vals[i - 1] = Long.parseLong(parts[i]);
+ }
+ return vals;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+}
diff --git a/src/jtop/system/linux/DiskInfo.java b/src/jtop/system/linux/DiskInfo.java
new file mode 100644
index 0000000..48a49c1
--- /dev/null
+++ b/src/jtop/system/linux/DiskInfo.java
@@ -0,0 +1,54 @@
+package jtop.system.linux;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import jtop.Isystem.IDiskInfo;
+
+/**
+ * Provides information about disk usage and I/O statistics for mounted devices.
+ * /proc/diskstats and parses the number of
+ * reads and writes for each block device. The results can be used to
+ * monitor disk I/O activity or calculate total/used storage if combined
+ * with filesystem information.
+ *
+ *
+ *
+ * @return a {@link Map} where the key is the device name and the value is a
+ * long array containing [reads, writes]
+ * @throws IOException if reading /proc/diskstats fails
+ */
+ @Override
+ public Map
+ *
+ *
+ *
+ *
+ * /proc/net/dev to retrieve
+ * the number of bytes received (RX) and transmitted (TX) per network interface.
+ *
+ *
+ * @throws IOException if /proc/net/dev cannot be read
+ */
+ @Override
+ public Map
+ *
+ *
+ *
+ * Each temperature is returned in degrees Celsius.
+ */
+public class TemperatureInfo implements ITemperatureInfo {
+
+ /**
+ * Retrieves a map of all detected temperatures on the system.
+ *
+ * @return Map where the key is a sensor name (e.g., "coretemp:Core 0")
+ * and the value is the temperature in °C.
+ * @throws IOException if the sensor directories cannot be read
+ */
+ @Override
+ public Map/proc/uptime and returns it
+ * in various units such as seconds, minutes, hours, or days.
+ */
+public class Uptime implements IUptime{
+
+ /**
+ * Gets the system uptime in the specified format.
+ *
+ * @param timeFormat a character indicating the desired unit:
+ * 's' for seconds,
+ * 'm' for minutes,
+ * 'h' for hours,
+ * 'd' for days.
+ * @return the system uptime in the requested unit.
+ * @throws Exception if reading /proc/uptime fails
+ * or if the timeFormat is invalid.
+ */
+ public double getSystemUptime(char timeFormat) throws Exception {
+ String content = Files.readString(Path.of("/proc/uptime"));
+ double seconds = Double.parseDouble(content.split(" ")[0]);
+
+ return switch (timeFormat) {
+ case 's' -> seconds;
+ case 'm' -> seconds / 60;
+ case 'h' -> seconds / 3600;
+ case 'd' -> seconds / 86400;
+ default -> throw new IllegalArgumentException("Invalid time format: " + timeFormat);
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/jtop/system/mac/MacFeatures.java b/src/jtop/system/mac/MacFeatures.java
new file mode 100644
index 0000000..39d5b32
--- /dev/null
+++ b/src/jtop/system/mac/MacFeatures.java
@@ -0,0 +1,28 @@
+package jtop.system.mac;
+
+import java.util.EnumSet;
+import jtop.system.Feature;
+
+/**
+ * Defines the set of system features supported on macOS.
+ *
+ *
+ */
+public class InputHandler {
+
+ /** The main process display manager. */
+ private final ShowProcesses showProcesses;
+
+ /** Atomic flag indicating whether the process table should be refreshed. */
+ private final AtomicBoolean refresh;
+
+ /** Provides the current terminal size. */
+ private final TerminalSize terminalSize;
+
+ /**
+ * Creates a new input handler for a given process table and terminal.
+ *
+ * @param showProcesses the {@link ShowProcesses} instance to control
+ * @param refresh atomic boolean controlling background refresh
+ * @param terminalSize the {@link TerminalSize} instance
+ */
+ public InputHandler(ShowProcesses showProcesses, AtomicBoolean refresh, TerminalSize terminalSize) {
+ this.showProcesses = showProcesses;
+ this.refresh = refresh;
+ this.terminalSize = terminalSize;
+ }
+
+ /**
+ * Starts reading and handling user input.
+ *
+ *
+ *
+ * @throws Exception if an I/O error occurs while reading mouse input
+ */
+ private void handleMouseEvent() throws Exception {
+ int cb = System.in.read() - 32; // button code
+ int cx = System.in.read() - 32; // column (X)
+ int cy = System.in.read() - 32; // row (Y)
+
+ switch (cb) {
+ case 0 -> { // Left click
+ if (cy == 1 + Header.getRowsCount()) {
+ showProcesses.changeSortByClick(cx - 1);
+ }
+ }
+ case 64 -> showProcesses.scrollUp(); // wheel up
+ case 65 -> showProcesses.scrollDown(); // wheel down
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jtop/terminal/TerminalSize.java b/src/jtop/terminal/TerminalSize.java
new file mode 100644
index 0000000..2e76bb3
--- /dev/null
+++ b/src/jtop/terminal/TerminalSize.java
@@ -0,0 +1,57 @@
+package jtop.terminal;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+/**
+ * Utility class to detect the current terminal window size.
+ *