mirror of
https://github.com/gabrielkheisa/auto_dark_light_windows.git
synced 2025-04-27 00:19:06 +07:00
first
This commit is contained in:
commit
56503c065d
46
.github/workflows/build.yml
vendored
Normal file
46
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
name: Build & Release ThemeChanger
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build and Upload Release ZIP
|
||||
runs-on: windows-latest
|
||||
|
||||
permissions:
|
||||
contents: write # Needed for creating/updating releases
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Tidy dependencies
|
||||
run: go mod tidy
|
||||
|
||||
- name: Build executable
|
||||
run: go build -ldflags="-H windowsgui" -o themechanger.exe
|
||||
|
||||
- name: Create ZIP with icon and binary
|
||||
run: Compress-Archive -Path themechanger.exe,icon.ico -DestinationPath themechanger.zip
|
||||
|
||||
- name: Create or update release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: latest
|
||||
name: Latest Build
|
||||
files: themechanger.zip
|
||||
body: |
|
||||
✅ Automatic build from latest commit to `main`
|
||||
🛠️ Includes `themechanger.exe` and `icon.ico`
|
||||
🔄 Ready to drop into `shell:startup`
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
# Go build artifacts
|
||||
*.exe
|
||||
*.exe~
|
||||
*.out
|
||||
*.test
|
||||
|
||||
# Go module files
|
||||
go.sum
|
||||
|
||||
# IDE/editor files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Windows files
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Archive files
|
||||
*.zip
|
||||
*.tar
|
||||
*.tar.gz
|
||||
|
||||
# Systray tray icon (you may want to ignore it if it's generated)
|
||||
# icon.ico
|
||||
|
||||
# GitHub Actions artifacts (if manually downloaded)
|
||||
*.7z
|
||||
*.rar
|
||||
|
||||
# Build folders (if any)
|
||||
bin/
|
||||
dist/
|
126
README.md
Normal file
126
README.md
Normal file
@ -0,0 +1,126 @@
|
||||
# Auto Theme + Wallpaper Switcher (Windows)
|
||||
|
||||
This is a lightweight Windows tray app that **automatically switches between light and dark mode** based on the time of day (6 AM to 6 PM), and optionally changes your desktop wallpaper. It also silently restarts `explorer.exe` after theme changes to force a full UI refresh (taskbar, Start menu, etc.).
|
||||
|
||||

|
||||
|
||||
Runs quietly in the background with a tray icon and auto-starts on login (via `shell:startup`).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- Automatically switches between **light** and **dark** mode
|
||||
- Optionally changes wallpaper to match the theme
|
||||
- Gracefully skips if wallpaper is missing (no crashes)
|
||||
- Runs silently with a **tray icon** and no console window
|
||||
- Uses native Windows APIs (registry + wallpaper)
|
||||
- Automatically **restarts `explorer.exe`** for full theme refresh
|
||||
|
||||
---
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
Place these files in the same directory:
|
||||
|
||||
```
|
||||
themechanger.exe ← compiled binary
|
||||
icon.ico ← tray icon (required)
|
||||
light.jpg/png/bmp/... ← optional day wallpaper
|
||||
dark.jpg/png/bmp/... ← optional night wallpaper
|
||||
```
|
||||
|
||||
Supported image formats: `.jpg`, `.jpeg`, `.png`, `.bmp`, `.webp`
|
||||
|
||||
The app will look for `light.*` and `dark.*` in its own folder. If no image is found, it simply skips the wallpaper change.
|
||||
|
||||
---
|
||||
|
||||
## Auto-Start on Login
|
||||
|
||||
To launch the app automatically when you log in:
|
||||
|
||||
1. Press `Win + R`
|
||||
2. Type: `shell:startup` and press Enter
|
||||
3. **Create a shortcut to `themechanger.exe`:**
|
||||
* Right-click on `themechanger.exe`.
|
||||
* Select "Create shortcut".
|
||||
4. **Move the shortcut you just created into the Startup folder.** The Startup folder is the one that opened in step 2.
|
||||
|
||||
Done! It will now auto-start silently every time.
|
||||
|
||||
> No need to edit the registry or install anything!
|
||||
|
||||
---
|
||||
|
||||
## Build It Yourself
|
||||
|
||||
If you'd like to compile from source:
|
||||
|
||||
### 1. Install Go
|
||||
|
||||
[https://go.dev/dl](https://go.dev/dl)
|
||||
|
||||
### 2. Initialize your Go module
|
||||
|
||||
```bash
|
||||
go mod init themechanger
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
### 3. Build the executable
|
||||
|
||||
```bash
|
||||
go build -ldflags="-H windowsgui" -o themechanger.exe
|
||||
```
|
||||
|
||||
This produces a GUI app (no console window).
|
||||
|
||||
---
|
||||
|
||||
## Technical Details
|
||||
|
||||
- Uses Go and the [`systray`](https://github.com/getlantern/systray) library for tray UI
|
||||
- Theme switching via registry keys:
|
||||
- `AppsUseLightTheme`
|
||||
- `SystemUsesLightTheme`
|
||||
- Wallpaper changes with `SystemParametersInfoW` API
|
||||
- Explorer restart via:
|
||||
```bash
|
||||
taskkill /f /im explorer.exe && start explorer.exe
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🖼 Tray Preview
|
||||
|
||||
You must provide your own `icon.ico` in the same folder — this will be used as the tray icon.
|
||||
|
||||
> Tip: You can create `.ico` files from PNGs using online converters like [icoconvert.com](https://icoconvert.com)
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- You can manually quit the app from the tray menu
|
||||
- Time check runs every 60 seconds
|
||||
- Changing the clock or timezone will trigger a switch automatically
|
||||
- No admin rights needed
|
||||
|
||||
---
|
||||
|
||||
## Optional Ideas
|
||||
|
||||
- Add GUI to configure start/stop time
|
||||
- Manual override options in tray menu
|
||||
- Geo-based sunrise/sunset switching
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT — free for personal and commercial use.
|
||||
|
||||
---
|
||||
|
||||
Made with Go, Windows quirks, and coffee ☕
|
19
go.mod
Normal file
19
go.mod
Normal file
@ -0,0 +1,19 @@
|
||||
module themechanger
|
||||
|
||||
go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/getlantern/systray v1.2.2
|
||||
golang.org/x/sys v0.32.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||
)
|
144
main.go
Normal file
144
main.go
Normal file
@ -0,0 +1,144 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/getlantern/systray"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
// Set Windows dark/light mode via registry
|
||||
func setMode(light bool) {
|
||||
key, _ := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.SET_VALUE)
|
||||
defer key.Close()
|
||||
val := uint32(0)
|
||||
if light {
|
||||
val = 1
|
||||
}
|
||||
key.SetDWordValue("AppsUseLightTheme", val)
|
||||
key.SetDWordValue("SystemUsesLightTheme", val)
|
||||
}
|
||||
|
||||
// Restart Explorer to fully apply theme
|
||||
func restartExplorer() {
|
||||
exec.Command("taskkill", "/f", "/im", "explorer.exe").Run()
|
||||
time.Sleep(1 * time.Second)
|
||||
exec.Command("explorer.exe").Start()
|
||||
}
|
||||
|
||||
// Get current theme mode from registry
|
||||
func getCurrentMode() string {
|
||||
key, _ := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.QUERY_VALUE)
|
||||
defer key.Close()
|
||||
val, _, _ := key.GetIntegerValue("AppsUseLightTheme")
|
||||
if val == 1 {
|
||||
return "light"
|
||||
}
|
||||
return "dark"
|
||||
}
|
||||
|
||||
// Find image in same dir with any supported extension
|
||||
func findImageFile(name string) string {
|
||||
exePath, _ := os.Executable()
|
||||
dir := filepath.Dir(exePath)
|
||||
matches, _ := filepath.Glob(filepath.Join(dir, name+".*"))
|
||||
for _, f := range matches {
|
||||
ext := strings.ToLower(filepath.Ext(f))
|
||||
if ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp" || ext == ".webp" {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Get current wallpaper path from registry
|
||||
func getCurrentWallpaper() string {
|
||||
key, _ := registry.OpenKey(registry.CURRENT_USER, `Control Panel\Desktop`, registry.QUERY_VALUE)
|
||||
defer key.Close()
|
||||
val, _, _ := key.GetStringValue("Wallpaper")
|
||||
return val
|
||||
}
|
||||
|
||||
// Set desktop wallpaper via Windows API
|
||||
func setWallpaper(path string) {
|
||||
user32 := syscall.NewLazyDLL("user32.dll")
|
||||
sysParam := user32.NewProc("SystemParametersInfoW")
|
||||
p, _ := syscall.UTF16PtrFromString(path)
|
||||
sysParam.Call(0x0014, 0, uintptr(unsafe.Pointer(p)), uintptr(0x01|0x02))
|
||||
}
|
||||
|
||||
// Main background loop
|
||||
func backgroundLoop() {
|
||||
lightImg := findImageFile("light")
|
||||
darkImg := findImageFile("dark")
|
||||
lastMode := getCurrentMode()
|
||||
|
||||
for {
|
||||
hour := time.Now().Hour()
|
||||
newMode := "dark"
|
||||
var targetImg string
|
||||
|
||||
if hour >= 6 && hour < 18 {
|
||||
newMode = "light"
|
||||
targetImg = lightImg
|
||||
} else {
|
||||
targetImg = darkImg
|
||||
}
|
||||
|
||||
if newMode != lastMode {
|
||||
setMode(newMode == "light")
|
||||
restartExplorer()
|
||||
lastMode = newMode
|
||||
}
|
||||
|
||||
if targetImg != "" && getCurrentWallpaper() != targetImg {
|
||||
setWallpaper(targetImg)
|
||||
}
|
||||
|
||||
time.Sleep(60 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// Load icon from local directory (icon.ico)
|
||||
func getIconBytes() []byte {
|
||||
exePath, _ := os.Executable()
|
||||
iconPath := filepath.Join(filepath.Dir(exePath), "icon.ico")
|
||||
data, err := ioutil.ReadFile(iconPath)
|
||||
if err != nil {
|
||||
fmt.Println("icon.ico not found or unreadable.")
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// Systray startup
|
||||
func onReady() {
|
||||
icon := getIconBytes()
|
||||
if icon != nil {
|
||||
systray.SetIcon(icon)
|
||||
}
|
||||
systray.SetTitle("Auto Theme")
|
||||
systray.SetTooltip("Auto Theme + Wallpaper Switcher")
|
||||
|
||||
mQuit := systray.AddMenuItem("Quit", "Exit the program")
|
||||
|
||||
go func() {
|
||||
go backgroundLoop()
|
||||
<-mQuit.ClickedCh
|
||||
systray.Quit()
|
||||
}()
|
||||
}
|
||||
|
||||
func onExit() {}
|
||||
|
||||
func main() {
|
||||
systray.Run(onReady, onExit)
|
||||
}
|
BIN
screenshot/cover.jpg
Normal file
BIN
screenshot/cover.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 280 KiB |
BIN
screenshot/dark_ss.png
Normal file
BIN
screenshot/dark_ss.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 711 KiB |
BIN
screenshot/light_ss.png
Normal file
BIN
screenshot/light_ss.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 782 KiB |
Loading…
x
Reference in New Issue
Block a user