summaryrefslogtreecommitdiffstats
path: root/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'widgets')
-rw-r--r--widgets/battery.go64
-rw-r--r--widgets/cpu.go117
-rw-r--r--widgets/disk.go162
-rw-r--r--widgets/help.go82
-rw-r--r--widgets/include/smc.c700
-rw-r--r--widgets/include/smc.h254
-rw-r--r--widgets/mem.go78
-rw-r--r--widgets/mem_freebsd.go61
-rw-r--r--widgets/mem_other.go22
-rw-r--r--widgets/net.go138
-rw-r--r--widgets/proc.go331
-rw-r--r--widgets/proc_freebsd.go66
-rw-r--r--widgets/proc_linux.go45
-rw-r--r--widgets/proc_other.go57
-rw-r--r--widgets/proc_windows.go44
-rw-r--r--widgets/scalable.go8
-rw-r--r--widgets/statusbar.go55
-rw-r--r--widgets/temp.go102
-rw-r--r--widgets/temp_darwin.go72
-rw-r--r--widgets/temp_freebsd.go71
-rw-r--r--widgets/temp_linux.go30
-rw-r--r--widgets/temp_openbsd.go72
-rw-r--r--widgets/temp_windows.go27
23 files changed, 2658 insertions, 0 deletions
diff --git a/widgets/battery.go b/widgets/battery.go
new file mode 100644
index 0000000..89620bb
--- /dev/null
+++ b/widgets/battery.go
@@ -0,0 +1,64 @@
+package widgets
+
+import (
+ "fmt"
+ "log"
+ "math"
+ "strconv"
+ "time"
+
+ "github.com/distatus/battery"
+
+ ui "github.com/xxxserxxx/gotop/termui"
+)
+
+type BatteryWidget struct {
+ *ui.LineGraph
+ updateInterval time.Duration
+}
+
+func NewBatteryWidget(horizontalScale int) *BatteryWidget {
+ self := &BatteryWidget{
+ LineGraph: ui.NewLineGraph(),
+ updateInterval: time.Minute,
+ }
+ self.Title = " Battery Status "
+ self.HorizontalScale = horizontalScale
+
+ // intentional duplicate
+ // adds 2 datapoints to the graph, otherwise the dot is difficult to see
+ self.update()
+ self.update()
+
+ go func() {
+ for range time.NewTicker(self.updateInterval).C {
+ self.Lock()
+ self.update()
+ self.Unlock()
+ }
+ }()
+
+ return self
+}
+
+func makeId(i int) string {
+ return "Batt" + strconv.Itoa(i)
+}
+
+func (b *BatteryWidget) Scale(i int) {
+ b.LineGraph.HorizontalScale = i
+}
+
+func (self *BatteryWidget) update() {
+ batteries, err := battery.GetAll()
+ if err != nil {
+ log.Printf("failed to get battery info: %v", err)
+ return
+ }
+ for i, battery := range batteries {
+ id := makeId(i)
+ percentFull := math.Abs(battery.Current/battery.Full) * 100.0
+ self.Data[id] = append(self.Data[id], percentFull)
+ self.Labels[id] = fmt.Sprintf("%3.0f%% %.0f/%.0f", percentFull, math.Abs(battery.Current), math.Abs(battery.Full))
+ }
+}
diff --git a/widgets/cpu.go b/widgets/cpu.go
new file mode 100644
index 0000000..8e8819c
--- /dev/null
+++ b/widgets/cpu.go
@@ -0,0 +1,117 @@
+package widgets
+
+import (
+ "fmt"
+ "log"
+ "sync"
+ "time"
+
+ psCpu "github.com/shirou/gopsutil/cpu"
+
+ ui "github.com/xxxserxxx/gotop/termui"
+)
+
+type CpuWidget struct {
+ *ui.LineGraph
+ CpuCount int
+ ShowAverageLoad bool
+ ShowPerCpuLoad bool
+ updateInterval time.Duration
+ formatString string
+ updateLock sync.Mutex
+}
+
+func NewCpuWidget(updateInterval time.Duration, horizontalScale int, showAverageLoad bool, showPerCpuLoad bool) *CpuWidget {
+ cpuCount, err := psCpu.Counts(false)
+ if err != nil {
+ log.Printf("failed to get CPU count from gopsutil: %v", err)
+ }
+ formatString := "CPU%1d"
+ if cpuCount > 10 {
+ formatString = "CPU%02d"
+ }
+ self := &CpuWidget{
+ LineGraph: ui.NewLineGraph(),
+ CpuCount: cpuCount,
+ updateInterval: updateInterval,
+ ShowAverageLoad: showAverageLoad,
+ ShowPerCpuLoad: showPerCpuLoad,
+ formatString: formatString,
+ }
+ self.Title = " CPU Usage "
+ self.HorizontalScale = horizontalScale
+
+ if !(self.ShowAverageLoad || self.ShowPerCpuLoad) {
+ if self.CpuCount <= 8 {
+ self.ShowPerCpuLoad = true
+ } else {
+ self.ShowAverageLoad = true
+ }
+ }
+
+ if self.ShowAverageLoad {
+ self.Data["AVRG"] = []float64{0}
+ }
+
+ if self.ShowPerCpuLoad {
+ for i := 0; i < int(self.CpuCount); i++ {
+ key := fmt.Sprintf(formatString, i)
+ self.Data[key] = []float64{0}
+ }
+ }
+
+ self.update()
+
+ go func() {
+ for range time.NewTicker(self.updateInterval).C {
+ self.update()
+ }
+ }()
+
+ return self
+}
+
+func (b *CpuWidget) Scale(i int) {
+ b.LineGraph.HorizontalScale = i
+}
+
+func (self *CpuWidget) update() {
+ if self.ShowAverageLoad {
+ go func() {
+ percent, err := psCpu.Percent(self.updateInterval, false)
+ if err != nil {
+ log.Printf("failed to get average CPU usage percent from gopsutil: %v. self.updateInterval: %v. percpu: %v", err, self.updateInterval, false)
+ } else {
+ self.Lock()
+ defer self.Unlock()
+ self.updateLock.Lock()
+ defer self.updateLock.Unlock()
+ self.Data["AVRG"] = append(self.Data["AVRG"], percent[0])
+ self.Labels["AVRG"] = fmt.Sprintf("%3.0f%%", percent[0])
+ }
+ }()
+ }
+
+ if self.ShowPerCpuLoad {
+ go func() {
+ percents, err := psCpu.Percent(self.updateInterval, true)
+ if err != nil {
+ log.Printf("failed to get CPU usage percents from gopsutil: %v. self.updateInterval: %v. percpu: %v", err, self.updateInterval, true)
+ } else {
+ if len(percents) != int(self.CpuCount) {
+ log.Printf("error: number of CPU usage percents from gopsutil doesn't match CPU count. percents: %v. self.Count: %v", percents, self.CpuCount)
+ } else {
+ self.Lock()
+ defer self.Unlock()
+ self.updateLock.Lock()
+ defer self.updateLock.Unlock()
+ for i, percent := range percents {
+ key := fmt.Sprintf(self.formatString, i)
+ self.Data[key] = append(self.Data[key], percent)
+ self.Labels[key] = fmt.Sprintf("%3.0f%%", percent)
+ }
+ }
+ }
+ }()
+ }
+}
diff --git a/widgets/disk.go b/widgets/disk.go
new file mode 100644
index 0000000..d20b078
--- /dev/null
+++ b/widgets/disk.go
@@ -0,0 +1,162 @@
+package widgets
+
+import (
+ "fmt"
+ "log"
+ "sort"
+ "strings"
+ "time"
+
+ psDisk "github.com/shirou/gopsutil/disk"
+
+ ui "github.com/xxxserxxx/gotop/termui"
+ "github.com/xxxserxxx/gotop/utils"
+)
+
+type Partition struct {
+ Device string
+ MountPoint string
+ BytesRead uint64
+ BytesWritten uint64
+ BytesReadRecently string
+ BytesWrittenRecently string
+ UsedPercent uint32
+ Free string
+}
+
+type DiskWidget struct {
+ *ui.Table
+ updateInterval time.Duration
+ Partitions map[string]*Partition
+}
+
+func NewDiskWidget() *DiskWidget {
+ self := &DiskWidget{
+ Table: ui.NewTable(),
+ updateInterval: time.Second,
+ Partitions: make(map[string]*Partition),
+ }
+ self.Title = " Disk Usage "
+ self.Header = []string{"Disk", "Mount", "Used", "Free", "R/s", "W/s"}
+ self.ColGap = 2
+ self.ColResizer = func() {
+ self.ColWidths = []int{
+ utils.MaxInt(4, (self.Inner.Dx()-29)/2),
+ utils.MaxInt(5, (self.Inner.Dx()-29)/2),
+ 4, 5, 5, 5,
+ }
+ }
+
+ self.update()
+
+ go func() {
+ for range time.NewTicker(self.updateInterval).C {
+ self.Lock()
+ self.update()
+ self.Unlock()
+ }
+ }()
+
+ return self
+}
+
+func (self *DiskWidget) update() {
+ partitions, err := psDisk.Partitions(false)
+ if err != nil {
+ log.Printf("failed to get disk partitions from gopsutil: %v", err)
+ return
+ }
+
+ // add partition if it's new
+ for _, partition := range partitions {
+ // don't show loop devices
+ if strings.HasPrefix(partition.Device, "/dev/loop") {
+ continue
+ }
+ // don't show docker container filesystems
+ if strings.HasPrefix(partition.Mountpoint, "/var/lib/docker/") {
+ continue
+ }
+ // check if partition doesn't already exist in our list
+ if _, ok := self.Partitions[partition.Device]; !ok {
+ self.Partitions[partition.Device] = &Partition{
+ Device: partition.Device,
+ MountPoint: partition.Mountpoint,
+ }
+ }
+ }
+
+ // delete a partition if it no longer exists
+ toDelete := []string{}
+ for device := range self.Partitions {
+ exists := false
+ for _, partition := range partitions {
+ if device == partition.Device {
+ exists = true
+ break
+ }
+ }
+ if !exists {
+ toDelete = append(toDelete, device)
+ }
+ }
+ for _, device := range toDelete {
+ delete(self.Partitions, device)
+ }
+
+ // updates partition info
+ for _, partition := range self.Partitions {
+ usage, err := psDisk.Usage(partition.MountPoint)
+ if err != nil {
+ log.Printf("failed to get partition usage statistics from gopsutil: %v. partition: %v", err, partition)
+ continue
+ }
+ partition.UsedPercent = uint32(usage.UsedPercent)
+
+ bytesFree, magnitudeFree := utils.ConvertBytes(usage.Free)
+ partition.Free = fmt.Sprintf("%3d%s", uint64(bytesFree), magnitudeFree)
+
+ ioCounters, err := psDisk.IOCounters(partition.Device)
+ if err != nil {
+ log.Printf("failed to get partition read/write info from gopsutil: %v. partition: %v", err, partition)
+ continue
+ }
+ ioCounter := ioCounters[strings.Replace(partition.Device, "/dev/", "", -1)]
+ bytesRead, bytesWritten := ioCounter.ReadBytes, ioCounter.WriteBytes
+ if partition.BytesRead != 0 { // if this isn't the first update
+ bytesReadRecently := bytesRead - partition.BytesRead
+ bytesWrittenRecently := bytesWritten - partition.BytesWritten
+
+ readFloat, readMagnitude := utils.ConvertBytes(bytesReadRecently)
+ writeFloat, writeMagnitude := utils.ConvertBytes(bytesWrittenRecently)
+ bytesReadRecently, bytesWrittenRecently = uint64(readFloat), uint64(writeFloat)
+ partition.BytesReadRecently = fmt.Sprintf("%d%s", bytesReadRecently, readMagnitude)
+ partition.BytesWrittenRecently = fmt.Sprintf("%d%s", bytesWrittenRecently, writeMagnitude)
+ } else {
+ partition.BytesReadRecently = fmt.Sprintf("%d%s", 0, "B")
+ partition.BytesWrittenRecently = fmt.Sprintf("%d%s", 0, "B")
+ }
+ partition.BytesRead, partition.BytesWritten = bytesRead, bytesWritten
+ }
+
+ // converts self.Partitions into self.Rows which is a [][]String
+
+ sortedPartitions := []string{}
+ for seriesName := range self.Partitions {
+ sortedPartitions = append(sortedPartitions, seriesName)
+ }
+ sort.Strings(sortedPartitions)
+
+ self.Rows = make([][]string, len(self.Partitions))
+
+ for i, key := range sortedPartitions {
+ partition := self.Partitions[key]
+ self.Rows[i] = make([]string, 6)
+ self.Rows[i][0] = strings.Replace(strings.Replace(partition.Device, "/dev/", "", -1), "mapper/", "", -1)
+ self.Rows[i][1] = partition.MountPoint
+ self.Rows[i][2] = fmt.Sprintf("%d%%", partition.UsedPercent)
+ self.Rows[i][3] = partition.Free
+ self.Rows[i][4] = partition.BytesReadRecently
+ self.Rows[i][5] = partition.BytesWrittenRecently
+ }
+}
diff --git a/widgets/help.go b/widgets/help.go
new file mode 100644
index 0000000..9f78dd0
--- /dev/null
+++ b/widgets/help.go
@@ -0,0 +1,82 @@
+package widgets
+
+import (
+ "image"
+ "strings"
+
+ ui "github.com/gizak/termui/v3"
+)
+
+const KEYBINDS = `
+Quit: q or <C-c>
+
+Process navigation:
+ - k and <Up>: up
+ - j and <Down>: down
+ - <C-u>: half page up
+ - <C-d>: half page down
+ - <C-b>: full page up
+ - <C-f>: full page down
+ - gg and <Home>: jump to top
+ - G and <End>: jump to bottom
+
+Process actions:
+ - <Tab>: toggle process grouping
+ - dd: kill selected process or group of processes with SIGTERM (15)
+ - d3: kill selected process or group of processes with SIGQUIT (3)
+ - d9: kill selected process or group of processes with SIGKILL (9)
+
+Process sorting:
+ - c: CPU
+ - m: Mem
+ - p: PID
+
+Process filtering:
+ - /: start editing filter
+ - (while editing):
+ - <Enter>: accept filter
+ - <C-c> and <Escape>: clear filter
+
+CPU and Mem graph scaling:
+ - h: scale in
+ - l: scale out
+`
+
+type HelpMenu struct {
+ ui.Block
+}
+
+func NewHelpMenu() *HelpMenu {
+ return &HelpMenu{
+ Block: *ui.NewBlock(),
+ }
+}
+
+func (self *HelpMenu) Resize(termWidth, termHeight int) {
+ textWidth := 53
+ textHeight := strings.Count(KEYBINDS, "\n") + 1
+ x := (termWidth - textWidth) / 2
+ y := (termHeight - textHeight) / 2
+
+ self.Block.SetRect(x, y, textWidth+x, textHeight+y)
+}
+
+func (self *HelpMenu) Draw(buf *ui.Buffer) {
+ self.Block.Draw(buf)
+
+ for y, line := range strings.Split(KEYBINDS, "\n") {
+ for x, rune := range line {
+ buf.SetCell(
+ ui.NewCell(rune, ui.NewStyle(7)),
+ image.Pt(self.Inner.Min.X+x, self.Inner.Min.Y+y-1),
+ )
+ }
+ }
+}
+
+func maxInt(a int, b int) int {
+ if a > b {
+ return a
+ }
+ return b
+}
diff --git a/widgets/include/smc.c b/widgets/include/smc.c
new file mode 100644
index 0000000..0c200d6
--- /dev/null
+++ b/widgets/include/smc.c
@@ -0,0 +1,700 @@
+/*
+ * Apple System Management Controller (SMC) API from user space for Intel based
+ * Macs. Works by talking to the AppleSMC.kext (kernel extension), the driver
+ * for the SMC.
+ *
+ * smc.c
+ * libsmc
+ *
+ * Copyright (C) 2014 beltex <https://github.com/beltex>
+ *
+ * Based off of fork from:
+ * osx-cpu-temp <https://github.com/lavoiesl/osx-cpu-temp>
+ *
+ * With credits to:
+ *
+ * Copyright (C) 2006 devnull
+ * Apple System Management Control (SMC) Tool
+ *
+ * Copyright (C) 2006 Hendrik Holtmann
+ * smcFanControl <https://github.com/hholtmann/smcFanControl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "smc.h"
+
+
+//------------------------------------------------------------------------------
+// MARK: MACROS
+//------------------------------------------------------------------------------
+
+
+/**
+Name of the SMC IOService as seen in the IORegistry. You can view it either via
+command line with ioreg or through the IORegistryExplorer app (found on Apple's
+developer site - Hardware IO Tools for Xcode)
+*/
+#define IOSERVICE_SMC "AppleSMC"
+
+
+/**
+IOService for getting machine model name
+*/
+#define IOSERVICE_MODEL "IOPlatformExpertDevice"
+
+
+/**
+SMC data types - 4 byte multi-character constants
+
+Sources: See TMP SMC keys in smc.h
+
+http://stackoverflow.com/questions/22160746/fpe2-and-sp78-data-types
+*/
+#define DATA_TYPE_UINT8 "ui8 "
+#define DATA_TYPE_UINT16 "ui16"
+#define DATA_TYPE_UINT32 "ui32"
+#define DATA_TYPE_FLAG "flag"
+#define DATA_TYPE_FPE2 "fpe2"
+#define DATA_TYPE_SFDS "{fds"
+#define DATA_TYPE_SP78 "sp78"
+
+
+//------------------------------------------------------------------------------
+// MARK: GLOBAL VARS
+//------------------------------------------------------------------------------
+
+
+/**
+Our connection to the SMC
+*/
+static io_connect_t conn;
+
+
+/**
+Number of characters in an SMC key
+*/
+static const int SMC_KEY_SIZE = 4;
+
+
+/**
+Number of characters in a data type "key" returned from the SMC. See data type
+macros.
+*/
+static const int DATA_TYPE_SIZE = 4;
+
+
+//------------------------------------------------------------------------------
+// MARK: ENUMS
+//------------------------------------------------------------------------------
+
+
+/**
+Defined by AppleSMC.kext. See SMCParamStruct.
+
+These are SMC specific return codes
+*/
+typedef enum {
+ kSMCSuccess = 0,
+ kSMCError = 1,
+ kSMCKeyNotFound = 0x84
+} kSMC_t;
+
+
+/**
+Defined by AppleSMC.kext. See SMCParamStruct.
+
+Function selectors. Used to tell the SMC which function inside it to call.
+*/
+typedef enum {
+ kSMCUserClientOpen = 0,
+ kSMCUserClientClose = 1,
+ kSMCHandleYPCEvent = 2,
+ kSMCReadKey = 5,
+ kSMCWriteKey = 6,
+ kSMCGetKeyCount = 7,
+ kSMCGetKeyFromIndex = 8,
+ kSMCGetKeyInfo = 9
+} selector_t;
+
+
+//------------------------------------------------------------------------------
+// MARK: STRUCTS
+//------------------------------------------------------------------------------
+
+
+/**
+Defined by AppleSMC.kext. See SMCParamStruct.
+*/
+typedef struct {
+ unsigned char major;
+ unsigned char minor;
+ unsigned char build;
+ unsigned char reserved;
+ unsigned short release;
+} SMCVersion;
+
+
+/**
+Defined by AppleSMC.kext. See SMCParamStruct.
+*/
+typedef struct {
+ uint16_t version;
+ uint16_t length;
+ uint32_t cpuPLimit;
+ uint32_t gpuPLimit;
+ uint32_t memPLimit;
+} SMCPLimitData;
+
+
+/**
+Defined by AppleSMC.kext. See SMCParamStruct.
+
+- dataSize : How many values written to SMCParamStruct.bytes
+- dataType : Type of data written to SMCParamStruct.bytes. This lets us know how
+ to interpret it (translate it to human readable)
+*/
+typedef struct {
+ IOByteCount dataSize;
+ uint32_t dataType;
+ uint8_t dataColors;
+} SMCKeyInfoData;
+
+
+/**
+Defined by AppleSMC.kext.
+
+This is the predefined struct that must be passed to communicate with the
+AppleSMC driver. While the driver is closed source, the definition of this
+struct happened to appear in the Apple PowerManagement project at around
+version 211, and soon after disappeared. It can be seen in the PrivateLib.c
+file under pmconfigd.
+
+https://www.opensource.apple.com/source/PowerManagement/PowerManagement-211/
+*/
+typedef struct {
+ uint32_t key;
+ SMCVersion vers;
+ SMCPLimitData pLimitData;
+ SMCKeyInfoData keyInfo;
+ uint8_t result;
+ uint8_t status;
+ uint8_t data8;
+ uint32_t data32;
+ uint8_t bytes[32];
+} SMCParamStruct;
+
+
+/**
+Used for returning data from the SMC.
+*/
+typedef struct {
+ uint8_t data[32];
+ uint32_t dataType;
+ uint32_t dataSize;
+ kSMC_t kSMC;
+} smc_return_t;
+
+
+//------------------------------------------------------------------------------
+// MARK: HELPERS - TYPE CONVERSION
+//------------------------------------------------------------------------------
+
+
+/**
+Convert data from SMC of fpe2 type to human readable.
+
+:param: data Data from the SMC to be converted. Assumed data size of 2.
+:returns: Converted data
+*/
+static unsigned int from_fpe2(uint8_t data[32])
+{
+ unsigned int ans = 0;
+
+ // Data type for fan calls - fpe2
+ // This is assumend to mean floating point, with 2 exponent bits
+ // http://stackoverflow.com/questions/22160746/fpe2-and-sp78-data-types
+ ans += data[0] << 6;
+ ans += data[1] << 2;
+
+ return ans;
+}
+
+
+/**
+Convert to fpe2 data type to be passed to SMC.
+
+:param: val Value to convert
+:param: data Pointer to data array to place result
+*/
+static void to_fpe2(unsigned int val, uint8_t *data)
+{
+ data[0] = val >> 6;
+ data[1] = (val << 2) ^ (data[0] << 8);
+}
+
+
+/**
+Convert SMC key to uint32_t. This must be done to pass it to the SMC.
+
+:param: key The SMC key to convert
+:returns: uint32_t translation.
+ Returns zero if key is not 4 characters in length.
+*/
+static uint32_t to_uint32_t(char *key)
+{
+ uint32_t ans = 0;
+ uint32_t shift = 24;
+
+ // SMC key is expected to be 4 bytes - thus 4 chars
+ if (strlen(key) != SMC_KEY_SIZE) {
+ return 0;
+ }
+
+ for (int i = 0; i < SMC_KEY_SIZE; i++) {
+ ans += key[i] << shift;
+ shift -= 8;
+ }
+
+ return ans;
+}
+
+
+/**
+For converting the dataType return from the SMC to human readable 4 byte
+multi-character constant.
+*/
+static void to_string(uint32_t val, char *dataType)
+{
+ int shift = 24;
+
+ for (int i = 0; i < DATA_TYPE_SIZE; i++) {
+ // To get each char, we shift it into the lower 8 bits, and then & by
+ // 255 to insolate it
+ dataType[i] = (val >> shift) & 0xff;
+ shift -= 8;
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// MARK: HELPERS - TMP CONVERSION
+//------------------------------------------------------------------------------
+
+
+/**
+Celsius to Fahrenheit
+*/
+static double to_fahrenheit(double tmp)
+{
+ // http://en.wikipedia.org/wiki/Fahrenheit#Definition_and_conversions
+ return (tmp * 1.8) + 32;
+}
+
+
+/**
+Celsius to Kelvin
+*/
+static double to_kelvin(double tmp)
+{
+ // http://en.wikipedia.org/wiki/Kelvin
+ return tmp + 273.15;
+}
+
+
+//------------------------------------------------------------------------------
+// MARK: "PRIVATE" FUNCTIONS
+//------------------------------------------------------------------------------
+
+
+/**
+Make a call to the SMC
+
+:param: inputStruct Struct that holds data telling the SMC what you want
+:param: outputStruct Struct holding the SMC's response
+:returns: I/O Kit return code
+*/
+static kern_return_t call_smc(SMCParamStruct *inputStruct,
+ SMCParamStruct *outputStruct)
+{
+ kern_return_t result;
+ size_t inputStructCnt = sizeof(SMCParamStruct);
+ size_t outputStructCnt = sizeof(SMCParamStruct);
+
+ result = IOConnectCallStructMethod(conn, kSMCHandleYPCEvent,
+ inputStruct,
+ inputStructCnt,
+ outputStruct,
+ &outputStructCnt);
+
+ if (result != kIOReturnSuccess) {
+ // IOReturn error code lookup. See "Accessing Hardware From Applications
+ // -> Handling Errors" Apple doc
+ result = err_get_code(result);
+ }
+
+ return result;
+}
+
+
+/**
+Read data from the SMC
+
+:param: key The SMC key
+*/
+static kern_return_t read_smc(char *key, smc_return_t *result_smc)
+{
+ kern_return_t result;
+ SMCParamStruct inputStruct;
+ SMCParamStruct outputStruct;
+
+ memset(&inputStruct, 0, sizeof(SMCParamStruct));
+ memset(&outputStruct, 0, sizeof(SMCParamStruct));
+ memset(result_smc, 0, sizeof(smc_return_t));
+
+ // First call to AppleSMC - get key info
+ inputStruct.key = to_uint32_t(key);
+ inputStruct.data8 = kSMCGetKeyInfo;
+
+ result = call_smc(&inputStruct, &outputStruct);
+ result_smc->kSMC = outputStruct.result;
+
+ if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) {
+ return result;
+ }
+
+ // Store data for return
+ result_smc->dataSize = outputStruct.keyInfo.dataSize;
+ result_smc->dataType = outputStruct.keyInfo.dataType;
+
+
+ // Second call to AppleSMC - now we can get the data
+ inputStruct.keyInfo.dataSize = outputStruct.keyInfo.dataSize;
+ inputStruct.data8 = kSMCReadKey;
+
+ result = call_smc(&inputStruct, &outputStruct);
+ result_smc->kSMC = outputStruct.result;
+
+ if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) {
+ return result;
+ }
+
+ memcpy(result_smc->data, outputStruct.bytes, sizeof(outputStruct.bytes));
+
+ return result;
+}
+
+
+/**
+Write data to the SMC.
+
+:returns: IOReturn IOKit return code
+*/
+static kern_return_t write_smc(char *key, smc_return_t *result_smc)
+{
+ kern_return_t result;
+ SMCParamStruct inputStruct;
+ SMCParamStruct outputStruct;
+
+ memset(&inputStruct, 0, sizeof(SMCParamStruct));
+ memset(&outputStruct, 0, sizeof(SMCParamStruct));
+
+ // First call to AppleSMC - get key info
+ inputStruct.key = to_uint32_t(key);
+ inputStruct.data8 = kSMCGetKeyInfo;
+
+ result = call_smc(&inputStruct, &outputStruct);
+ result_smc->kSMC = outputStruct.result;
+
+ if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) {
+ return result;
+ }
+
+ // Check data is correct
+ if (result_smc->dataSize != outputStruct.keyInfo.dataSize ||
+ result_smc->dataType != outputStruct.keyInfo.dataType) {
+ return kIOReturnBadArgument;
+ }
+
+ // Second call to AppleSMC - now we can write the data
+ inputStruct.data8 = kSMCWriteKey;
+ inputStruct.keyInfo.dataSize = outputStruct.keyInfo.dataSize;
+
+ // Set data to write
+ memcpy(inputStruct.bytes, result_smc->data, sizeof(result_smc->data));
+
+ result = call_smc(&inputStruct, &outputStruct);
+ result_smc->kSMC = outputStruct.result;
+
+ return result;
+}
+
+
+/**
+Get the model name of the machine.
+*/
+static kern_return_t get_machine_model(io_name_t model)
+{
+ io_service_t service;
+ kern_return_t result;
+
+ service = IOServiceGetMatchingService(kIOMasterPortDefault,
+ IOServiceMatching(IOSERVICE_MODEL));
+
+ if (service == 0) {
+ printf("ERROR: %s NOT FOUND\n", IOSERVICE_MODEL);
+ return kIOReturnError;
+ }
+
+ // Get the model name
+ result = IORegistryEntryGetName(service, model);
+ IOObjectRelease(service);
+
+ return result;
+}
+
+
+//------------------------------------------------------------------------------
+// MARK: "PUBLIC" FUNCTIONS
+//------------------------------------------------------------------------------
+
+
+kern_return_t open_smc(void)
+{
+ kern_return_t result;
+ io_service_t service;
+
+ service = IOServiceGetMatchingService(kIOMasterPortDefault,
+ IOServiceMatching(IOSERVICE_SMC));
+
+ if (service == 0) {
+ // NOTE: IOServiceMatching documents 0 on failure
+ printf("ERROR: %s NOT FOUND\n", IOSERVICE_SMC);
+ return kIOReturnError;
+ }
+
+ result = IOServiceOpen(service, mach_task_self(), 0, &conn);
+ IOObjectRelease(service);
+
+ return result;
+}
+
+
+kern_return_t close_smc(void)
+{
+ return IOServiceClose(conn);
+}
+
+
+bool is_key_valid(char *key)
+{
+ bool ans = false;
+ kern_return_t result;
+ smc_return_t result_smc;
+
+ if (strlen(key) != SMC_KEY_SIZE) {
+ printf("ERROR: Invalid key size - must be 4 chars\n");
+ return ans;
+ }
+
+ // Try a read and see if it succeeds
+ result = read_smc(key, &result_smc);
+
+ if (result == kIOReturnSuccess && result_smc.kSMC == kSMCSuccess) {
+ ans = true;
+ }
+
+ return ans;
+}
+
+
+double get_tmp(char *key, tmp_unit_t unit)
+{
+ kern_return_t result;
+ smc_return_t result_smc;
+
+ result = read_smc(key, &result_smc);
+
+ if (!(result == kIOReturnSuccess &&
+ result_smc.dataSize == 2 &&
+ result_smc.dataType == to_uint32_t(DATA_TYPE_SP78))) {
+ // Error
+ return 0.0;
+ }
+
+ // TODO: Create from_sp78() convert function
+ double tmp = result_smc.data[0];
+
+ switch (unit) {
+ case CELSIUS:
+ break;
+ case FAHRENHEIT:
+ tmp = to_fahrenheit(tmp);
+ break;
+ case KELVIN:
+ tmp = to_kelvin(tmp);
+ break;
+ }
+
+ return tmp;
+}
+
+
+bool is_battery_powered(void)
+{
+ kern_return_t result;
+ smc_return_t result_smc;
+
+ result = read_smc(BATT_PWR, &result_smc);
+
+