diff options
Diffstat (limited to 'widgets')
-rw-r--r-- | widgets/battery.go | 64 | ||||
-rw-r--r-- | widgets/cpu.go | 117 | ||||
-rw-r--r-- | widgets/disk.go | 162 | ||||
-rw-r--r-- | widgets/help.go | 82 | ||||
-rw-r--r-- | widgets/include/smc.c | 700 | ||||
-rw-r--r-- | widgets/include/smc.h | 254 | ||||
-rw-r--r-- | widgets/mem.go | 78 | ||||
-rw-r--r-- | widgets/mem_freebsd.go | 61 | ||||
-rw-r--r-- | widgets/mem_other.go | 22 | ||||
-rw-r--r-- | widgets/net.go | 138 | ||||
-rw-r--r-- | widgets/proc.go | 331 | ||||
-rw-r--r-- | widgets/proc_freebsd.go | 66 | ||||
-rw-r--r-- | widgets/proc_linux.go | 45 | ||||
-rw-r--r-- | widgets/proc_other.go | 57 | ||||
-rw-r--r-- | widgets/proc_windows.go | 44 | ||||
-rw-r--r-- | widgets/scalable.go | 8 | ||||
-rw-r--r-- | widgets/statusbar.go | 55 | ||||
-rw-r--r-- | widgets/temp.go | 102 | ||||
-rw-r--r-- | widgets/temp_darwin.go | 72 | ||||
-rw-r--r-- | widgets/temp_freebsd.go | 71 | ||||
-rw-r--r-- | widgets/temp_linux.go | 30 | ||||
-rw-r--r-- | widgets/temp_openbsd.go | 72 | ||||
-rw-r--r-- | widgets/temp_windows.go | 27 |
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); + + |