Spice

Spice is a focus wallpaper manager for Windows and MacOS.


Project maintained by dixieflatline76 Hosted on GitHub Pages — Theme by mattgraham

How to Create a New Image Provider

A deep-dive technical guide for implementing new image sources in Spice (v1.1.0+).

1. Provider Architecture

Spice uses a Registry Pattern to decouple providers. Providers are standalone packages in pkg/wallpaper/providers/<name>.

Directory Structure

pkg/wallpaper/providers/bing/
├── bing.go         # Implementation & Registration
├── const.go        # Constants (API URL, Regex)
└── bing_test.go    # Unit Tests

2. Interface Contract (pkg/provider.ImageProvider)

You must implement the following 6 methods.

2.1 Core Logic

2.2 UI Integration

3. Configuration & Settings Logic

Do NOT modify the global Config struct. Use fyne.Preferences.

3.1 Settings Panel (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:

3.2 Query Panel (CreateQueryPanel)

Constructs the image source list. Pattern:

  1. Iterate through p.cfg.Preferences.QueryList("queries")? NO.
  2. Use p.cfg.Queries (the unified list). Filter by q.Provider == p.Name().
  3. Render a list of queries with “Active” toggles.
  4. 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")
        },
    )
    

4. The “Apply” Lifecycle (Critical)

Spice uses a deferred-save model. Changes are staged until “Apply” is clicked. You must implement this wiring:

  1. Change Detected: Inside OnChanged:

     sm.SetRefreshFlag("setting.id") // Enables "Apply" button
    
  2. Queue Action:

     sm.SetSettingChangedCallback("setting.id", func() {
         // Logic to run ONLY when Apply is clicked
         prefs.SetString("key", newValue)
     })
    
  3. Revert: If user cancels/reverts:

     sm.UnsetRefreshFlag("setting.id")
     sm.RemoveSettingChangedCallback("setting.id")
    
  4. UI Update: Trigger visual update:

     sm.GetCheckAndEnableApplyFunc()()
    

5. Registration (The Hook)

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"

6. Testing

7. Browser Extension Integration

If your provider supports “copy-pasting” URLs from the browser (like Wallhaven or Pexels), you can integrate with the Spice Safari/Chrome extension.

  1. Define Regex: In your pkg/wallpaper/providers/<name>/const.go, define a constant for your URL pattern.
    • Naming Convention: ` URLRegexp` (e.g., `BingURLRegexp`).
    • Value: A regex string matching the URLs you want to intercept (e.g., ^https://bing.com/images/.*).
  2. Enable Discovery:
    • Ensure your provider is imported in cmd/spice/main.go (e.g., _ "github.com/.../providers/bing").
    • The build tool ` cmd/util/sync_regex 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`.
  3. Manual Sync: If you need to force a sync during development, run:
    make sync-extension
    

Reference

See pkg/wallpaper/providers/wikimedia/wikimedia.go or pkg/wallpaper/providers/pexels/pexels.go for the reference implementation of all these patterns.