commit 56503c065d0cfd3666972a02b5c72c91180f92c1 Author: gabrielkheisa Date: Sat Apr 19 20:05:02 2025 +0700 first diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..bd4e7b5 --- /dev/null +++ b/.github/workflows/build.yml @@ -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 }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81adb29 --- /dev/null +++ b/.gitignore @@ -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/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..8764d68 --- /dev/null +++ b/README.md @@ -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.). + +![cover](/screenshot/cover.jpg) + +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 ☕ diff --git a/dark.jpg b/dark.jpg new file mode 100644 index 0000000..42b48cd Binary files /dev/null and b/dark.jpg differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..115c6e7 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000..4a6e42e Binary files /dev/null and b/icon.ico differ diff --git a/light.jpg b/light.jpg new file mode 100644 index 0000000..83d272e Binary files /dev/null and b/light.jpg differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..65ee6aa --- /dev/null +++ b/main.go @@ -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) +} diff --git a/screenshot/cover.jpg b/screenshot/cover.jpg new file mode 100644 index 0000000..5363f94 Binary files /dev/null and b/screenshot/cover.jpg differ diff --git a/screenshot/dark_ss.png b/screenshot/dark_ss.png new file mode 100644 index 0000000..7954ebf Binary files /dev/null and b/screenshot/dark_ss.png differ diff --git a/screenshot/light_ss.png b/screenshot/light_ss.png new file mode 100644 index 0000000..504c08e Binary files /dev/null and b/screenshot/light_ss.png differ