Spice is a focus wallpaper manager for Windows and MacOS.
A deep-dive technical guide for implementing new image sources in Spice (v1.1.0+).
Spice uses a Registry Pattern to decouple providers. Providers are standalone packages in pkg/wallpaper/providers/<name>.
pkg/wallpaper/providers/bing/
├── bing.go # Implementation & Registration
├── const.go # Constants (API URL, Regex)
└── bing_test.go # Unit Tests
pkg/provider.ImageProvider)You must implement the following 6 methods.
Name() string:
Title() string:
ParseURL(webURL string) (string, error):
bing.com/images/search?q=foo).search:foo) or the input if it’s already compliant.const.go regex here to reject invalid domains.FetchImages(ctx context.Context, apiURL string, page int) ([]Image, error):
ctx.Done() for cancellation.page is 1-indexed. If the API uses offsets, calculate offset = (page-1) * limit.provider.Image.Image struct fields (Path, ID, Attribution, ViewURL).EnrichImage(ctx, img) (Image, error):
FileType, Path, etc.FetchImages, just return img, nil.GetProviderIcon() fyne.Resource:
fyne.NewStaticResource("Name", []byte{...}). Embed the PNG bytes in code or use //go:embed.Do NOT modify the global Config struct. Use fyne.Preferences.
CreateSettingsPanel)Constructs the “General” tab for your provider (e.g., API Keys).
Input: sm setting.SettingsManager.
returns: fyne.CanvasObject (usually a container.NewVBox).
Widget Types:
CreateTextEntrySetting: For strings (API Keys).
fyne.StringValidator (e.g., validator.NewRegexp(...)).func(s string) error for logic validation (e.g., “Key must start with ‘Bearer ‘”).CreateBoolSetting: For toggles.CreateSelectSetting: For dropdowns.CreateButtonWithConfirmationSetting: For dangerous actions (Reset, Clear Cache).CreateQueryPanel)Constructs the image source list. Pattern:
p.cfg.Preferences.QueryList("queries")? NO.p.cfg.Queries (the unified list). Filter by q.Provider == p.Name().Use Standardized Add Button: Use wallpaper.CreateAddQueryButton (in pkg/wallpaper/ui_add_query.go) to create the “Add” button. This helper handles validation, modal creation, and the critical “Apply” button wiring for you.
addBtn := wallpaper.CreateAddQueryButton(
"Add MyProvider Query",
sm,
wallpaper.AddQueryConfig{
Title: "New Query",
URLPlaceholder: "Search term or URL",
DescPlaceholder: "Description",
ValidateFunc: func(url, desc string) error {
if len(url) == 0 {
return errors.New("URL cannot be empty")
}
// Add provider-specific validation here (e.g., regex check)
return nil
},
AddHandler: func(desc, url string, active bool) (string, error) {
return p.cfg.AddMyProviderQuery(desc, url, active)
},
},
func() {
queryList.Refresh()
sm.SetRefreshFlag("queries")
},
)
Spice uses a deferred-save model. Changes are staged until “Apply” is clicked. You must implement this wiring:
Change Detected: Inside OnChanged:
sm.SetRefreshFlag("setting.id") // Enables "Apply" button
Queue Action:
sm.SetSettingChangedCallback("setting.id", func() {
// Logic to run ONLY when Apply is clicked
prefs.SetString("key", newValue)
})
Revert: If user cancels/reverts:
sm.UnsetRefreshFlag("setting.id")
sm.RemoveSettingChangedCallback("setting.id")
UI Update: Trigger visual update:
sm.GetCheckAndEnableApplyFunc()()
In myprovider.go, add:
func init() {
// Key "Bing" must match Name() return value
provider.RegisterProvider("Bing", func(cfg *wallpaper.Config, client *http.Client) provider.ImageProvider {
return NewBingProvider(cfg, client)
})
}
In cmd/spice/main.go:
import _ "github.com/dixieflatline76/Spice/pkg/wallpaper/providers/bing"
ParseURL with table-driven tests.http.Client or usage httptest.Server to test FetchImages without real network calls.If your provider supports “copy-pasting” URLs from the browser (like Wallhaven or Pexels), you can integrate with the Spice Safari/Chrome extension.
pkg/wallpaper/providers/<name>/const.go, define a constant for your URL pattern.
^https://bing.com/images/.*).cmd/spice/main.go (e.g., _ "github.com/.../providers/bing"). will automatically parse main.go, find your enabled provider, and extract the regex from your const.go to inject it into the extension's background.js`.make sync-extension
See pkg/wallpaper/providers/wikimedia/wikimedia.go or pkg/wallpaper/providers/pexels/pexels.go for the reference implementation of all these patterns.