mirror of
https://github.com/gabrielkheisa/auto_dark_light_windows.git
synced 2025-04-27 08:29:14 +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