Skip to content

Commit 4daea2a

Browse files
committed
feat(cli): add interactive menu for daemon control and GPU info
Add an interactive command-line interface to manage the daemon and display real-time GPU stats.
1 parent 1ed948d commit 4daea2a

4 files changed

Lines changed: 148 additions & 57 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
run: go build
2525

2626
- name: Create release archive
27-
run: Compress-Archive -Path hotamd.exe -DestinationPath $env:ZIP
27+
run: Compress-Archive -Path HotAMD.exe -DestinationPath $env:ZIP
2828

2929
- name: Create GitHub release
3030
env:

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module hotamd
1+
module HotAMD
22

33
go 1.25.0
44

main.go

Lines changed: 71 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,84 @@
11
package main
22

3-
import ("fmt"; "log"; "unsafe"; "time"; w "golang.org/x/sys/windows")
3+
import ("bufio"; "fmt"; "os"; "os/exec"; "strings"; "syscall"; "golang.org/x/sys/windows")
44

5-
type ADLTemperature struct {
6-
Size int32
7-
Temperature int32
5+
var eventPtr = utf16("Global\\HotAMD")
6+
7+
func main() {
8+
initialize()
9+
if len(os.Args) > 1 && os.Args[1] == "--daemon" {
10+
runDaemon()
11+
} else {
12+
runCLI()
13+
}
14+
close()
815
}
916

10-
type ADLFanSpeedValue struct {
11-
Size int32
12-
SpeedType int32
13-
FanSpeed int32
14-
Flags int32
17+
func runDaemon() {
18+
h, e := windows.CreateEvent(nil, 1, 0, eventPtr)
19+
if e != nil { return }
20+
defer windows.CloseHandle(h)
21+
go start()
22+
windows.WaitForSingleObject(h, windows.INFINITE)
1523
}
1624

17-
var (
18-
dll = w.NewLazySystemDLL("atiadlxx.dll")
19-
ADL_Main_Control_Create = dll.NewProc("ADL_Main_Control_Create").Call
20-
ADL_Main_Control_Destroy = dll.NewProc("ADL_Main_Control_Destroy").Call
21-
ADL_Adapter_NumberOfAdapters_Get = dll.NewProc("ADL_Adapter_NumberOfAdapters_Get").Call
22-
ADL_Overdrive5_Temperature_Get = dll.NewProc("ADL_Overdrive5_Temperature_Get").Call
23-
ADL_Overdrive5_FanSpeed_Get = dll.NewProc("ADL_Overdrive5_FanSpeed_Get").Call
24-
)
25-
26-
func utf16(s string) *uint16 {
27-
ptr, err := w.UTF16PtrFromString(s)
28-
if err != nil { panic(err) }; return ptr
25+
func runCLI() {
26+
reader := bufio.NewReader(os.Stdin)
27+
28+
for {
29+
cmd := exec.Command("cmd", "/c", "cls")
30+
cmd.Stdout = os.Stdout
31+
cmd.Run()
32+
33+
fmt.Println("♨️ HotAMD")
34+
35+
isRunning := isRunning()
36+
37+
38+
if isRunning {
39+
getAdapters()
40+
temp, fan := readGPU(0)
41+
fmt.Printf("\nTemperature: %d°C\n", temp)
42+
fmt.Printf("Fan: %d RPM", fan)
43+
}
44+
fmt.Print("\nDaemon: ")
45+
if isRunning { fmt.Println("running") } else { fmt.Println("not running") }
46+
47+
fmt.Print("\n1) ")
48+
if isRunning { fmt.Print("Stop ") } else { fmt.Print("Start ") }
49+
fmt.Println("daemon")
50+
fmt.Println("2) Exit")
51+
fmt.Print("\n> ")
52+
53+
input, _ := reader.ReadString('\n')
54+
input = strings.TrimSpace(input)
55+
56+
switch input {
57+
case "1": if isRunning { stopDaemon() } else { startDaemon() }
58+
case "2": return
59+
}
60+
}
2961
}
3062

31-
func alert(msg string) {
32-
w.MessageBox(0, utf16(msg), utf16("Hotamd"), w.MB_OK|w.MB_ICONWARNING)
63+
func isRunning() bool {
64+
handle, err := windows.OpenEvent(windows.SYNCHRONIZE, false, eventPtr)
65+
if err != nil { return false }
66+
windows.CloseHandle(handle)
67+
return true
3368
}
3469

35-
func main() {
36-
adlMalloc := w.NewCallback(func(size int32) uintptr {
37-
ptr, _ := w.LocalAlloc(0, uint32(size))
38-
return uintptr(ptr)
39-
})
40-
ADL_Main_Control_Create(adlMalloc, 1)
41-
defer ADL_Main_Control_Destroy()
42-
43-
var adapters int32
44-
ADL_Adapter_NumberOfAdapters_Get(uintptr(unsafe.Pointer(&adapters)))
45-
if adapters == 0 { log.Fatal("GPU adapters not found") }
46-
fmt.Println("Adapters found:", adapters)
47-
48-
temp := ADLTemperature{Size: int32(unsafe.Sizeof(ADLTemperature{}))}
49-
fan := ADLFanSpeedValue{Size: int32(unsafe.Sizeof(ADLFanSpeedValue{}))}
50-
51-
for {
52-
ADL_Overdrive5_Temperature_Get(0, 0, uintptr(unsafe.Pointer(&temp)))
53-
temp := temp.Temperature / 1000
54-
fmt.Printf("GPU Temperature: %d °C\n", temp)
55-
56-
ADL_Overdrive5_FanSpeed_Get(0, 0, uintptr(unsafe.Pointer(&fan)))
57-
rpm := fan.FanSpeed
58-
fmt.Printf("GPU Fan: %d RPM\n", rpm)
59-
60-
if temp > 40 && rpm == 0 {
61-
alert("GPU temperature > 40°C and fan is not spinning")
62-
} else if temp > 60 {
63-
alert("GPU temperature > 60°C")
64-
}
70+
func startDaemon() {
71+
if isRunning() { return }
72+
exe, _ := os.Executable()
73+
cmd := exec.Command(exe, "--daemon")
74+
cmd.SysProcAttr = &syscall.SysProcAttr{ CreationFlags: 0x00000008 }
75+
cmd.Start()
76+
}
6577

66-
time.Sleep(10 * time.Second)
67-
}
78+
func stopDaemon() {
79+
event, err := windows.OpenEvent(windows.EVENT_MODIFY_STATE, false, eventPtr)
80+
if err != nil { return }
81+
defer windows.CloseHandle(event)
82+
windows.SetEvent(event)
83+
fmt.Println("Stopping daemon...")
6884
}

monitor.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package main
2+
3+
import ("log"; "unsafe"; "time"; w "golang.org/x/sys/windows")
4+
5+
type ADLTemperature struct {
6+
Size int32
7+
Temperature int32
8+
}
9+
10+
type ADLFanSpeedValue struct {
11+
Size int32
12+
SpeedType int32
13+
FanSpeed int32
14+
Flags int32
15+
}
16+
17+
var (
18+
dll = w.NewLazySystemDLL("atiadlxx.dll")
19+
ADL_Main_Control_Create = dll.NewProc("ADL_Main_Control_Create").Call
20+
ADL_Main_Control_Destroy = dll.NewProc("ADL_Main_Control_Destroy").Call
21+
ADL_Adapter_NumberOfAdapters_Get = dll.NewProc("ADL_Adapter_NumberOfAdapters_Get").Call
22+
ADL_Overdrive5_Temperature_Get = dll.NewProc("ADL_Overdrive5_Temperature_Get").Call
23+
ADL_Overdrive5_FanSpeed_Get = dll.NewProc("ADL_Overdrive5_FanSpeed_Get").Call
24+
)
25+
26+
func utf16(s string) *uint16 {
27+
ptr, err := w.UTF16PtrFromString(s)
28+
if err != nil { panic(err) }; return ptr
29+
}
30+
31+
func alert(msg string) {
32+
w.MessageBox(0, utf16(msg), utf16("HotAMD"), w.MB_OK|w.MB_ICONWARNING)
33+
}
34+
35+
func getAdapters() int32 {
36+
var adapters int32
37+
ADL_Adapter_NumberOfAdapters_Get(uintptr(unsafe.Pointer(&adapters)))
38+
if adapters == 0 { log.Fatal("GPU adapters not found") }
39+
return adapters
40+
}
41+
42+
func readGPU(adapter uintptr) (temperature int32, fan int32) {
43+
temp := ADLTemperature{Size: int32(unsafe.Sizeof(ADLTemperature{}))}
44+
fanSpeed := ADLFanSpeedValue{Size: int32(unsafe.Sizeof(ADLFanSpeedValue{}))}
45+
46+
ADL_Overdrive5_Temperature_Get(0, adapter, uintptr(unsafe.Pointer(&temp)))
47+
ADL_Overdrive5_FanSpeed_Get(0, adapter, uintptr(unsafe.Pointer(&fanSpeed)))
48+
49+
return temp.Temperature / 1000, fanSpeed.FanSpeed
50+
}
51+
52+
func initialize() {
53+
adlMalloc := w.NewCallback(func(size int32) uintptr {
54+
ptr, _ := w.LocalAlloc(0, uint32(size))
55+
return uintptr(ptr)
56+
})
57+
ADL_Main_Control_Create(adlMalloc, 1)
58+
}
59+
60+
func close() {
61+
ADL_Main_Control_Destroy()
62+
}
63+
64+
func start() {
65+
for {
66+
getAdapters()
67+
temp, rpm := readGPU(0)
68+
if temp > 40 && rpm == 0 {
69+
alert("GPU temperature > 40°C and fan is not spinning")
70+
} else if temp > 60 {
71+
alert("GPU temperature > 60°C")
72+
}
73+
time.Sleep(10 * time.Second)
74+
}
75+
}

0 commit comments

Comments
 (0)