Ready to build a cool City Hall Clock app for your Mac? Great! We're going to create an app that sits in your menu bar, chimes every 15 minutes, and even counts out the hours. Let's break it down step by step, and I'll explain every part of the code so you can understand what's going on.
Our City Hall Clock app will:
First things first, let's set up our project:
mkdir CityHallClock cd CityHallClock
go mod init cityhallclock
go get github.com/getlantern/systray go get github.com/faiface/beep
Now, let's create our main.go file and go through each function:
package main import ( "bytes" "log" "os" "path/filepath" "time" "github.com/faiface/beep" "github.com/faiface/beep/mp3" "github.com/faiface/beep/speaker" "github.com/getlantern/systray" ) var ( audioBuffer *beep.Buffer ) func main() { initAudio() systray.Run(onReady, onExit) } // ... (other functions will go here)
Let's break down each function:
func main() { initAudio() systray.Run(onReady, onExit) }
This is where our app starts. It does two important things:
func initAudio() { execPath, err := os.Executable() if err != nil { log.Fatal(err) } resourcesPath := filepath.Join(filepath.Dir(execPath), "..", "Resources") chimeFile := filepath.Join(resourcesPath, "chime.mp3") f, err := os.Open(chimeFile) if err != nil { log.Fatal(err) } defer f.Close() streamer, format, err := mp3.Decode(f) if err != nil { log.Fatal(err) } defer streamer.Close() audioBuffer = beep.NewBuffer(format) audioBuffer.Append(streamer) err = speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)) if err != nil { log.Fatal(err) } }
This function sets up our audio:
If anything goes wrong (like not finding the sound file), it'll log the error and quit.
func onReady() { systray.SetIcon(getIcon()) systray.SetTitle("City Hall Clock") systray.SetTooltip("City Hall Clock") mQuit := systray.AddMenuItem("Quit", "Quit the app") go func() {This function sets up our menu bar icon:
- Sets the icon (using getIcon()).
- Sets the title and tooltip.
- Adds a "Quit" option to the menu.
- Starts listening for when the "Quit" option is clicked.
- Starts running our clock (in a separate goroutine so it doesn't block).
4. onExit() Function
func onExit() { // Cleanup tasks go here }This function is called when the app is quitting. We're not doing anything here, but you could add cleanup tasks if needed.
5. runClock() Function
func runClock() { ticker := time.NewTicker(time.Minute) defer ticker.Stop() for { select { case t :=This is our clock's "heart":
- It creates a ticker that "ticks" every minute.
- In an infinite loop, it checks the time every minute.
- If it's the top of the hour or quarter past, it triggers the chime.
6. chime() Function
func chime(t time.Time) { hour := t.Hour() minute := t.Minute() var chimeTimes int if minute == 0 { chimeTimes = hour % 12 if chimeTimes == 0 { chimeTimes = 12 } } else { chimeTimes = 1 } for i := 0; iThis function plays our chimes:
- It figures out how many times to chime (once for quarter-hours, or the number of the hour at the top of the hour).
- It then plays the chime sound that many times, with a short pause between chimes.
7. getIcon() Function
func getIcon() []byte { execPath, err := os.Executable() if err != nil { log.Fatal(err) } iconPath := filepath.Join(filepath.Dir(execPath), "..", "Resources", "icon.png") // Read the icon file icon, err := os.ReadFile(iconPath) if err != nil { log.Fatal(err) } return icon }This function gets our menu bar icon:
- It finds where our app is running.
- Locates the icon file in the Resources directory.
- Reads the icon file and returns its contents.
Creating the macOS Application Bundle
To make our app a proper macOS citizen, we need to create an application bundle. This involves creating an Info.plist file:
CFBundleExecutable CityHallClock CFBundleIconFile AppIcon CFBundleIdentifier com.yourcompany.cityhallclock CFBundleName City Hall Clock CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 LSMinimumSystemVersion 10.12 LSUIElement NSHighResolutionCapable Save this as Info.plist in your project directory.
Adding Custom Icons
We need two icons:
- Menu Bar Icon: Create a 22x22 pixel PNG named icon.png.
- App Icon: Create an .icns file:
- Make images sized 16x16 to 1024x1024 pixels.
- Save them in AppIcon.iconset with names like icon_16x16.png.
- Run: iconutil -c icns AppIcon.iconset
Building and Packaging
Let's create a build script (build.sh):
#!/bin/bash # Build the Go application go build -o CityHallClock # Create the app bundle structure mkdir -p CityHallClock.app/Contents/MacOS mkdir -p CityHallClock.app/Contents/Resources # Move the executable to the app bundle mv CityHallClock CityHallClock.app/Contents/MacOS/ # Copy the Info.plist cp Info.plist CityHallClock.app/Contents/ # Copy the chime sound to Resources cp chime.mp3 CityHallClock.app/Contents/Resources/ # Copy the menu bar icon cp icon.png CityHallClock.app/Contents/Resources/ # Copy the application icon cp AppIcon.icns CityHallClock.app/Contents/Resources/ echo "Application bundle created: CityHallClock.app"Make it executable with chmod x build.sh, then run it with ./build.sh.
Conclusion
And there you have it! You've built a fully functional City Hall Clock app for macOS. You've learned about:
Feel free to expand on this. Maybe add preferences for custom chimes or different chiming intervals. The sky's the limit!
You can find full source code here https://github.com/rezmoss/citychime
Happy coding, and enjoy your new clock!
Disclaimer: All resources provided are partly from the Internet. If there is any infringement of your copyright or other rights and interests, please explain the detailed reasons and provide proof of copyright or rights and interests and then send it to the email: [email protected] We will handle it for you as soon as possible.
Copyright© 2022 湘ICP备2022001581号-3