Imperator FreeGames
Polls a feed of games that are currently free to keep, notifies you when a new one shows up, and lists them all in a popover.
README.md
// Overview
A macOS menu bar watcher for game giveaways. It polls a public feed of games that are currently free to keep on Steam, Epic and GOG, notifies you when a new one shows up, and lists them all in a popover.
// Install
Download the latest zip from
Releases,
unzip, and move Imperator FreeGames.app to /Applications.
The app is signed with a self-signed certificate and is not notarized, so Gatekeeper blocks the first launch. Right-click the app and choose Open, or clear the quarantine flag:
xattr -dr com.apple.quarantine "/Applications/Imperator FreeGames.app"Requires macOS 13 or later, Apple silicon. Built and tested on macOS 26 only — older versions are expected to work but have not been verified.
Install at your own risk. The app is not notarized and carries no Apple Developer signature, so macOS cannot vouch for it. It is provided as is, with no warranty, under the MIT license.
// Permissions
No Accessibility, Input Monitoring, or Automation grants. The app declares no
NSUsage keys and reads nothing on your machine.
Two system integrations:
| Integration | What it does |
|---|---|
| Notifications | On first launch the app asks for notification permission through UNUserNotificationCenter. Decline it and everything still works — you just lose the alerts and keep the badge dot. |
| Open at Login | The footer toggle calls SMAppService.mainApp.register(), which adds the app to Login Items in System Settings. Turning it off unregisters it. |
Network access is one outbound HTTPS request every 30 minutes to
https://www.goranimperator.com/data/free-games.json. Nothing is sent — no
identifiers, no telemetry, no account. Clicking a game opens its store page in
your default browser.
// Use
Click the menu bar icon to open the popover. Games are grouped by platform with a count per section; each row shows the title, the regular price it was before the giveaway, release year, developer and a short description. Click a row to open the store page.
A red dot on the menu bar icon means something new arrived since you last looked. Opening the popover clears it, and the new titles keep a red NEW pill until then.
The footer holds the controls:
| Control | What it does |
|---|---|
| Open at Login | Register or unregister the login item |
| Open Website | Open the full free-games page in a browser |
| About | Version, build and copyright panel |
| Quit | Terminate the app |
The refresh arrow in the header forces a fetch instead of waiting for the next 30-minute tick.
State lives in
~/Library/Application Support/Imperator FreeGames/state.json: the IDs already
seen, capped at the 50 most recent per platform. The first fetch after a fresh
install seeds that list silently, so installing the app does not fire a
notification for every game already on offer.
// Build from source
make installBuilds release, bundles, codesigns, installs to /Applications, and launches.
Other targets:
make runmake cleanSigning uses the self-signed Imperator Dev identity by default. Override it:
make build CODESIGN_IDENTITY=-Ad-hoc signing (-) mints a new code hash on every build, which drops the
login-item registration on update. Fine for local iteration, wrong for a
release.
// Release
Build a zip without touching git or the remote:
make dist VERSION=1.0.0Cut a full release — bumps Info.plist, commits, tags v1.0.0, pushes, and
publishes a GitHub release with the zip attached:
make release VERSION=1.0.0Requires the GitHub CLI (brew install gh, then
gh auth login). The working tree must be clean. Tags are plain semver
(v1.0.0); the release title carries the app name. CFBundleVersion is set
from git rev-list --count HEAD and is never edited by hand.
// Implementation note
A SwiftPM executable with no dependencies. LSUIElement is true, so there is no
Dock icon and no menu — the status item is the entire interface. Because SwiftPM
does not compile asset catalogs, the popover and menu bar icons are inline SVG
strings decoded through NSImage(data:) with isTemplate = true, so they
follow the system appearance.