├── .cspell.json ├── .github └── workflows │ ├── codeql-analysis.yml │ ├── goreleaser.yml │ └── mega-linter.yml ├── .gitignore ├── .goreleaser.yml ├── .jscpd.json ├── .mega-linter.yml ├── LICENSE ├── README.md ├── cool.go ├── go.mod ├── go.sum └── smc /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1", 3 | "language": "en", 4 | "ignorePaths": [ 5 | "**/node_modules/**", 6 | "**/vscode-extension/**", 7 | "**/.git/**", 8 | ".vscode", 9 | "package-lock.json", 10 | "report" 11 | ], 12 | "words": [] 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 3 * * 2' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['go'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | 42 | # codeql no longer needs this and instead recommends not to use it 43 | # - run: git checkout HEAD^2 44 | # if: ${{ github.event_name == 'pull_request' }} 45 | 46 | # Initializes the CodeQL tools for scanning. 47 | - name: Initialize CodeQL 48 | uses: github/codeql-action/init@v1 49 | with: 50 | languages: ${{ matrix.language }} 51 | # If you wish to specify custom queries, you can do so here or in a config file. 52 | # By default, queries listed here will override any specified in a config file. 53 | # Prefix the list here with "+" to use these queries and those in the config file. 54 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v1 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 https://git.io/JvXDl 63 | 64 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 65 | # and modify them (or add more) to build your code if your project 66 | # uses a compiled language 67 | 68 | #- run: | 69 | # make bootstrap 70 | # make release 71 | 72 | - name: Perform CodeQL Analysis 73 | uses: github/codeql-action/analyze@v1 74 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: Goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - 13 | name: Checkout 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - 18 | name: Set up Go 19 | uses: actions/setup-go@v2 20 | with: 21 | go-version: 1.16 22 | - 23 | name: Run GoReleaser 24 | uses: goreleaser/goreleaser-action@v2 25 | with: 26 | version: latest 27 | args: release --rm-dist 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /.github/workflows/mega-linter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Mega-Linter GitHub Action configuration file 3 | # More info at https://nvuillam.github.io/mega-linter 4 | name: Mega-Linter 5 | 6 | on: 7 | # Trigger mega-linter at every push. Action will also be visible from Pull Requests to master 8 | push: # Comment this line to trigger action only on pull-requests (not recommended if you don't pay for GH Actions) 9 | pull_request: 10 | branches: [master, main] 11 | 12 | env: # Comment env block if you do not want to apply fixes 13 | # Apply linter fixes configuration 14 | APPLY_FIXES: all # When active, APPLY_FIXES must also be defined as environment variable (in github/workflows/mega-linter.yml or other CI tool) 15 | APPLY_FIXES_EVENT: pull_request # Decide which event triggers application of fixes in a commit or a PR (pull_request, push, all) 16 | APPLY_FIXES_MODE: commit # If APPLY_FIXES is used, defines if the fixes are directly committed (commit) or posted in a PR (pull_request) 17 | 18 | jobs: 19 | # Cancel duplicate jobs: https://github.com/fkirc/skip-duplicate-actions#option-3-cancellation-only 20 | cancel_duplicates: 21 | name: Cancel duplicate jobs 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: fkirc/skip-duplicate-actions@master 25 | with: 26 | github_token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }} 27 | 28 | build: 29 | name: Mega-Linter 30 | runs-on: ubuntu-latest 31 | steps: 32 | # Git Checkout 33 | - name: Checkout Code 34 | uses: actions/checkout@v2 35 | with: 36 | token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }} 37 | fetch-depth: 0 38 | 39 | # Mega-Linter 40 | - name: Mega-Linter 41 | id: ml 42 | # You can override Mega-Linter flavor used to have faster performances 43 | # More info at https://nvuillam.github.io/mega-linter/flavors/ 44 | uses: nvuillam/mega-linter/flavors/go@latest 45 | env: 46 | # All available variables are described in documentation 47 | # https://nvuillam.github.io/mega-linter/configuration/ 48 | VALIDATE_ALL_CODEBASE: true # Set ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} to validate only diff with master branch 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | # ADD YOUR CUSTOM ENV VARIABLES HERE TO OVERRIDE VALUES OF .mega-linter.yml AT THE ROOT OF YOUR REPOSITORY 51 | 52 | # Upload Mega-Linter artifacts 53 | - name: Archive production artifacts 54 | if: ${{ success() }} || ${{ failure() }} 55 | uses: actions/upload-artifact@v2 56 | with: 57 | name: Mega-Linter reports 58 | path: | 59 | report 60 | mega-linter.log 61 | 62 | # Create pull request if applicable (for now works only on PR from same repository, not from forks) 63 | - name: Create Pull Request with applied fixes 64 | id: cpr 65 | if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && !contains(github.event.head_commit.message, 'skip fix') 66 | uses: peter-evans/create-pull-request@v3 67 | with: 68 | token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }} 69 | commit-message: "[Mega-Linter] Apply linters automatic fixes" 70 | title: "[Mega-Linter] Apply linters automatic fixes" 71 | labels: bot 72 | - name: Create PR output 73 | if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && !contains(github.event.head_commit.message, 'skip fix') 74 | run: | 75 | echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" 76 | echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}" 77 | 78 | # Push new commit if applicable (for now works only on PR from same repository, not from forks) 79 | - name: Prepare commit 80 | if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'commit' && github.ref != 'refs/heads/master' && github.event.pull_request.head.repo.full_name == github.repository && !contains(github.event.head_commit.message, 'skip fix') 81 | run: sudo chown -Rc $UID .git/ 82 | - name: Commit and push applied linter fixes 83 | if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'commit' && github.ref != 'refs/heads/master' && github.event.pull_request.head.repo.full_name == github.repository && !contains(github.event.head_commit.message, 'skip fix') 84 | uses: stefanzweifel/git-auto-commit-action@v4 85 | with: 86 | branch: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref }} 87 | commit_message: "[Mega-Linter] Apply linters fixes" 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | __debug_bin 3 | /cool 4 | .idea 5 | /dist 6 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example goreleaser.yaml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod download 7 | # you may remove this if you don't need go generate 8 | - go generate ./... 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - darwin 14 | goarch: 15 | - amd64 16 | - arm 17 | - arm64 18 | - 386 19 | ldflags: 20 | - -s -w 21 | ignore: # build problems 22 | - goos: windows 23 | goarch: arm64 24 | archives: 25 | - replacements: 26 | darwin: Darwin 27 | linux: Linux 28 | windows: Windows 29 | 386: 32-bit 30 | amd64: x86_64 31 | format_overrides: 32 | - goos: windows 33 | format: zip 34 | files: 35 | - 'smc' 36 | 37 | checksum: 38 | name_template: 'checksums.txt' 39 | snapshot: 40 | name_template: "{{ .Tag }}-next" 41 | changelog: 42 | sort: asc 43 | filters: 44 | exclude: 45 | - '^docs:' 46 | - '^test:' 47 | brews: 48 | - 49 | # Repository to push the tap to. 50 | tap: 51 | owner: quackduck 52 | name: homebrew-tap 53 | 54 | # Folder inside the repository to put the formula. 55 | # Default is the root folder. 56 | # folder: uniclip 57 | # Your app's homepage. 58 | # Default is empty. 59 | homepage: 'https://github.com/quackduck/cool' 60 | 61 | # Your app's description. 62 | # Default is empty. 63 | description: 'Never let kernel_task eat up CPU again.' 64 | install: | 65 | bin.install "cool" 66 | bin.install "smc" 67 | -------------------------------------------------------------------------------- /.jscpd.json: -------------------------------------------------------------------------------- 1 | { 2 | "threshold": 0, 3 | "reporters": [ 4 | "html", 5 | "markdown" 6 | ], 7 | "ignore": [ 8 | "**/node_modules/**", 9 | "**/.git/**", 10 | "**/.rbenv/**", 11 | "**/.venv'/**", 12 | "**/*cache*/**", 13 | "**/.github/**", 14 | "**/.idea/**", 15 | "**/report/**", 16 | "**/*.svg" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.mega-linter.yml: -------------------------------------------------------------------------------- 1 | # Configuration file for Mega-Linter 2 | # See all available variables at https://nvuillam.github.io/mega-linter/configuration/ and in linters documentation 3 | 4 | APPLY_FIXES: all # all, none, or list of linter keys 5 | DEFAULT_BRANCH: main # Usually master or main 6 | # ENABLE: # If you use ENABLE variable, all other languages/formats/tooling-formats will be disabled by default 7 | # ENABLE_LINTERS: # If you use ENABLE_LINTERS variable, all other linters will be disabled by default 8 | DISABLE: 9 | - COPYPASTE # Comment to disable checks of abusive copy-pastes 10 | - SPELL # Comment to disable checks of spelling mistakes # - SPELL # Uncomment to disable checks of spelling mistakes 11 | SHOW_ELAPSED_TIME: true 12 | FILEIO_REPORTER: false 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ishan Goel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cool 2 | 3 | Never let the heat slow your Mac down again. 4 | 5 | Note: Cool is still in beta because I've heard of some possible bugs. Cool is still perfectly safe and can cause no damage (the absolute worst case is that you have to reset the SMC). 6 | 7 | Cool is a fan control CLI that lets you cool your CPU to any temperature you'd like. Cool also displays a chart of the temperature changes, a chart of fan speed changes and much more. It reduced the CPU temperature from 97 to 75 in just 1 minute and 10 seconds on a MacBook Air 2017. 8 | 9 | [![asciicast](https://asciinema.org/a/400179.svg)](https://asciinema.org/a/400179?speed=2) 10 | 11 | ## Usage 12 | ```text 13 | Usage: sudo cool [-c/--no-chart] [] 14 | cool [-h/--help | -v/--version] 15 | ``` 16 | 17 | Be careful of commands that require sudo! Cool needs sudo to control fan speeds. 18 | 19 | You can specify a temperature to cool your Mac down to: 20 | ```shell 21 | sudo cool 57 22 | ``` 23 | or let Cool choose the default (75 C) 24 | ``` 25 | sudo cool 26 | ``` 27 | 28 | ## Install 29 | ```shell 30 | brew install quackduck/tap/cool 31 | ``` 32 | or get an executable from [releases](https://github.com/quackduck/cool/releases) 33 | 34 | ## FAQ 35 | 36 | **Isn't fan control bad for your Mac?** 37 | Only when done incorrectly. Cool only changes the minimum fan speed; macOS can decide the actual fan speed to set it to. This means that your fan speed will never be below the default. Likewise, the maximum fan speed Cool can set (this is hard-coded) is the maximum safe speed: 6500 RPM. This means that your fan speed is always in safe values! 38 | 39 | **How does this work?** 40 | Cool sets fan speeds, reads fan speeds and reads temperatures using the brilliant [smcFanControl CLI](https://github.com/hholtmann/smcFanControl/tree/master/smc-command). The `smc` binary is the compiled executable. If you're curious, Cool changes the value of the SMC key `F0Mn`. It reads the CPU 1 temperature sensor (`TC0E`). 41 | 42 | ## Thanks 43 | 44 | Thanks to [Sam](https://github.com/sampoder) and [Jubril](https://github.com/s1ntaxe770r) for their help with testing Cool. 45 | -------------------------------------------------------------------------------- /cool.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "math" 7 | "os" 8 | "os/exec" 9 | "os/signal" 10 | "os/user" 11 | "strconv" 12 | "strings" 13 | "syscall" 14 | "time" 15 | 16 | "github.com/fatih/color" 17 | ag "github.com/guptarohit/asciigraph" 18 | "github.com/muesli/termenv" 19 | "github.com/olekukonko/ts" 20 | ) 21 | 22 | var ( 23 | version = "dev" 24 | helpMsg = `Cool - Never let the heat slow your Mac down 25 | Usage: cool [-c/--no-chart] [] 26 | cool [-h/--help | -v/--version]` 27 | chart = true 28 | defaultTemp = 75.0 29 | 30 | fanKeyEncodingIsFLT = getFanIsFLT() 31 | ) 32 | 33 | func main() { 34 | if hasOption, i := argsHaveOption("no-chart", "c"); hasOption { 35 | chart = false 36 | os.Args = removeKeepOrder(os.Args, i) 37 | main() 38 | return 39 | } 40 | if len(os.Args) > 2 { 41 | handleErrStr("Too many arguments") 42 | fmt.Println(helpMsg) 43 | return 44 | } 45 | if hasOption, _ := argsHaveOption("help", "h"); hasOption { 46 | fmt.Println(helpMsg) 47 | return 48 | } 49 | if hasOption, _ := argsHaveOption("version", "v"); hasOption { 50 | fmt.Println("Cool " + version) 51 | return 52 | } 53 | currentUser, err := user.Current() 54 | if err != nil { 55 | handleErr(err) 56 | } 57 | if !(currentUser.Username == "root") { 58 | handleErrStr("Setting fan values needs root permissions. Try running with sudo.") 59 | return 60 | } 61 | if len(os.Args) == 1 { 62 | cool(defaultTemp) 63 | } 64 | temp, err := strconv.ParseFloat(os.Args[1], 32) 65 | if err != nil { 66 | handleErr(err) 67 | return 68 | } 69 | cool(temp) 70 | } 71 | 72 | func cool(target float64) { 73 | // Init: 74 | if !chart { 75 | fmt.Println("Cooling to", color.YellowString("%v C", target)) 76 | } else { 77 | termenv.AltScreen() 78 | termenv.HideCursor() 79 | } 80 | setupInterrupt() 81 | var ( 82 | fanSpeed int 83 | termsize, _ = ts.GetSize() 84 | temp = getTemp() 85 | timeTaken = "" 86 | alreadyReachedTarget = false 87 | green = color.New(color.FgHiGreen) 88 | greenDot = green.Sprint("·") 89 | cyan = color.New(color.FgCyan) 90 | cyanDown = cyan.Sprint("↓") 91 | yellow = color.New(color.FgHiYellow) 92 | yellowUp = yellow.Sprint("↑") 93 | start = time.Now() 94 | tplot []float64 95 | splot = []float64{float64(getFanSpeed())} // start at current because we'll change it soon 96 | lastTemp = temp 97 | arrLengthLim = 1000 // we'll keep an array limit so the values "scroll" 98 | screen string 99 | statusFmt = "Now at %.1f °C %v RPM Time: %v" 100 | ) 101 | 102 | setFanSpeed(1200 + int(math.Round(150*(temp-target)))) // quickly set it at the start 103 | for ; ; time.Sleep(time.Second * 2) { // fine tuning 104 | fanSpeed = getFanSpeed() 105 | lastTemp = temp 106 | temp = getTemp() 107 | 108 | if chart { 109 | screen = "" 110 | screen += "Target " + green.Sprintf("%v °C", target) + timeTaken + "\n" 111 | 112 | tplot = append(tplot, temp) 113 | if len(tplot) > arrLengthLim { 114 | tplot = tplot[len(tplot)-arrLengthLim:] // cut off the front so we have max 100 vals 115 | } 116 | screen += ag.Plot(tplot, ag.Height((termsize.Row()/2)-4), ag.Width(termsize.Col()-7), ag.Caption("Temperature (C)")) + "\n" 117 | 118 | splot = append(splot, float64(fanSpeed)) 119 | if len(splot) > arrLengthLim { 120 | splot = splot[len(splot)-arrLengthLim:] 121 | } 122 | screen += ag.Plot(splot, ag.Height((termsize.Row()/2)-4), ag.Width(termsize.Col()-7), ag.Offset(4), ag.Caption("Fan speed (RPM)")) + "\n" 123 | 124 | if math.Round(target) == math.Round(temp) { // nolint 125 | screen += greenDot + " Reached target!\n" 126 | if !alreadyReachedTarget { 127 | timeTaken = "reached in " + color.HiGreenString(time.Since(start).Round(time.Second).String()) 128 | alreadyReachedTarget = true 129 | } 130 | } else if target > temp { 131 | screen += cyanDown + " Cooler than target!\n" 132 | } else { 133 | screen += yellowUp + " Hotter than target\n" 134 | } 135 | 136 | if lastTemp == temp { // nolint 137 | screen += greenDot + " Temperature is stable\n" 138 | } else if lastTemp > temp { 139 | screen += cyanDown + " Temperature is decreasing\n" 140 | } else { 141 | screen += yellowUp + " Temperature is increasing\n" 142 | } 143 | screen += fmt.Sprintf(statusFmt, temp, fanSpeed, time.Since(start).Round(time.Second)) 144 | termsize, _ = ts.GetSize() 145 | termenv.ClearScreen() 146 | fmt.Print(screen) 147 | } else { 148 | screen += fmt.Sprintf(statusFmt, temp, fanSpeed, time.Since(start).Round(time.Second)) 149 | } 150 | setFanSpeed(fanSpeed + int(math.Round(temp-target))) // set current to current + the difference in temps. This will automatically correct when temp is too low. 151 | } 152 | } 153 | 154 | func setupInterrupt() { 155 | c := make(chan os.Signal) 156 | signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM) 157 | go func() { 158 | <-c 159 | setFanSpeed(1200) // default min fan speed 160 | if chart { 161 | termenv.ExitAltScreen() 162 | termenv.ShowCursor() 163 | } 164 | os.Exit(0) 165 | }() 166 | } 167 | 168 | func setFanSpeed(minSpeed int) { 169 | if minSpeed > 6500 { // max safe fan speed 170 | minSpeed = 6500 171 | } 172 | if minSpeed < 1200 { // min safe fan speed 173 | minSpeed = 1200 174 | } 175 | //math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x00, 0x40, 0x9c, 0x44})) // YES IT PRINTS 1250 176 | //math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x00, 0x40, 0x9c, 0x44})) 177 | if fanKeyEncodingIsFLT { 178 | setKey("F0Mn", fmt.Sprintf("%x", float32ToBytes(float32(minSpeed)))) 179 | return 180 | } 181 | setKey("F0Mn", strconv.FormatInt(int64(minSpeed<<2), 16)) // https://github.com/hholtmann/smcFanControl/tree/master/smc-command 182 | } 183 | 184 | func float32ToBytes(f float32) []byte { 185 | var buf [4]byte 186 | binary.LittleEndian.PutUint32(buf[:], math.Float32bits(f)) 187 | return buf[:] 188 | } 189 | 190 | 191 | func getFanSpeed() int { 192 | s, _ := strconv.ParseFloat(getKey("F0Mn"), 64) // get min fan speed (shouldn't change target) 193 | return int(s) 194 | } 195 | 196 | // getTemp returns the value of the SMC's CPU PECI Die filtered and adjusted temp for fan/power control in Celsius 197 | func getTemp() float64 { 198 | t, _ := strconv.ParseFloat(getKey("TC0F"), 64) 199 | return t 200 | } 201 | 202 | func getKey(key string) string { 203 | v := run("smc -r -k " + key) // v now has the format: " F0Mn [fpe2] 2400.00 (bytes 25 80)" 204 | v = strings.TrimSpace(v[strings.LastIndex(v, "]")+1:]) // cut it till the last bracket and trim so it's now "2400.00 (bytes 25 80)" 205 | return strings.Fields(v)[0] // split by whitespace to get ["2400.00", "(bytes", "25", "80)"] and then return the first value which is what we want 206 | } 207 | 208 | func getFanIsFLT() bool { 209 | v := run("smc -r -k F0Mn") 210 | return strings.Contains(v, "[flt ]") 211 | } 212 | 213 | func setKey(key string, value string) { 214 | run("smc -k " + key + " -w " + value) 215 | } 216 | 217 | func run(command string) string { 218 | cmdArr := strings.Fields(strings.TrimSpace(command)) 219 | cmd := exec.Command(cmdArr[0], cmdArr[1:]...) 220 | b, err := cmd.CombinedOutput() 221 | if err != nil { 222 | handleErr(err) 223 | return "" 224 | } 225 | return string(b) 226 | } 227 | 228 | func argsHaveOption(long string, short string) (hasOption bool, foundAt int) { 229 | for i, arg := range os.Args { 230 | if arg == "--"+long || arg == "-"+short { 231 | return true, i 232 | } 233 | } 234 | return false, 0 235 | } 236 | 237 | func handleErr(err error) { 238 | handleErrStr(err.Error()) 239 | } 240 | 241 | func handleErrStr(str string) { 242 | _, _ = fmt.Fprintln(os.Stderr, color.RedString("error: ")+str) 243 | } 244 | 245 | func removeKeepOrder(s []string, i int) []string { 246 | return append(s[:i], s[i+1:]...) 247 | } 248 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module cool 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/fatih/color v1.10.0 7 | github.com/guptarohit/asciigraph v0.5.1 8 | github.com/muesli/termenv v0.8.0 9 | github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 10 | golang.org/x/sys v0.1.0 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= 2 | github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 3 | github.com/guptarohit/asciigraph v0.5.1 h1:rzRUdibSt3ff75gVGtcUXQ0dEkNgG0A20fXkA8cOMsA= 4 | github.com/guptarohit/asciigraph v0.5.1/go.mod h1:9fYEfE5IGJGxlP1B+w8wHFy7sNZMhPtn59f0RLtpRFM= 5 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 6 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 7 | github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= 8 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 9 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 10 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 11 | github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= 12 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 13 | github.com/muesli/termenv v0.8.0 h1:HxhDOZbpf9nSUhOiMUNk5jsL3XaKWAbHucoGYZf+nag= 14 | github.com/muesli/termenv v0.8.0/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0= 15 | github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw= 16 | github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0= 17 | github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= 18 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 19 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 20 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 22 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 23 | -------------------------------------------------------------------------------- /smc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quackduck/cool/fb046c69e780d740264bcd613a37800b85349dd1/smc --------------------------------------------------------------------------------