Skip to main content

Automatically switches Windows desktop wallpapers based on configurable conditions (time, WiFi, location, day of week)

Project description

Wallpaper Auto

codecov

Automatically switches Windows desktop wallpapers based on configurable conditions (time, WiFi, day of week).

Features

  • Multi-condition triggers: Supports Windows session changes, network changes, and time changes as three types of triggers
  • Flexible rules: Supports AND/OR condition combinations, can evaluate multiple conditions simultaneously
  • Condition types:
    • network: Current connected WiFi name
    • day_of_week_is: Day-of-week check (0=Monday ... 6=Sunday)
    • time_range: Time range (supports crossing midnight)
  • System tray control: System tray menu for manual wallpaper switching and pause/resume auto-switching
  • Thread-safe: Each monitoring module runs independently without blocking others
  • At-shutdown wallpaper: Optionally apply a specific wallpaper when Windows shuts down or the user logs off

Quick Start

  1. Download the project to your local machine and enter the directory

  2. Install the package:

    pip install wallpaper-auto
    
  3. Generate a starter config file:

    wallpaper-auto init-config
    
  4. Edit config.yaml with your wallpaper paths and rules, then run:

    wallpaper-auto
    

Configuration

Generate a starter config with all options documented:

wallpaper-auto init-config
# Creates config.yaml in the current directory

Or specify a custom path and force-overwrite an existing file:

wallpaper-auto init-config myconfig.yaml -f

See wallpaper-auto init-config --help for all options. You can also create a config.yaml file manually (or specify the path via -c).

Configuration Structure

# 1. Wallpaper resource pool
resource:
  work_wallpaper:                         # Resource ID — referenced by rules & fallback
    name: static_wallpaper                # Single-image wallpaper
    config:
      path: "C:/path/to/wallpaper.jpg"
      style: fill                         # fill / fit / stretch / center / tile
      restore: false                      # restore original wallpaper on demount (default false)

  carousel:                               # Multi-image cycling wallpaper (resource carousel)
    name: resource_carousel
    config:
      resources:                          # Sub-resources to cycle through
        - name: static_wallpaper
          config: {path: "C:/Pictures/morning.jpg", style: fill}
        - name: static_wallpaper
          config: {path: "C:/Pictures/afternoon.jpg", style: fill}
      interval: 300                       # Seconds between switches (default 300)
      random: false                       # true = random order, false = sequential

# 2. Trigger configuration
trigger:
  - name: network                         # Monitor WiFi / network changes
    config: {}
  - name: time                            # Periodic time-based evaluation
    config: {}
  - name: windows_session                 # Monitor lock/unlock/logon/logoff
    config: {}

# 3. Rules (evaluated top-to-bottom; first match wins)
rule:
  - name: "At work"
    condition:
      wifi_ssid_is: "OfficeWiFi"          # Leaf condition — evaluator name + params
    target: "work_wallpaper"
  - name: "Night mode"
    condition:
      and:                                # AND/OR combinators supported
        - in_time_range: ["23:00", "06:00"]
        - day_of_week_is: [0, 1, 2, 3, 4] # Monday to Friday
    target: "dark_wallpaper"
# 4. Fallback wallpaper (used when no rule matches)
fallback: "default_wallpaper"

# 5. (Optional) At-shutdown wallpaper — applied when Windows shuts down
# at_shutdown: "work_wallpaper"

Running

# Generate a starter config file
wallpaper-auto init-config

# Use default config.yaml
wallpaper-auto

# Specify config file
wallpaper-auto -c /path/to/config.yaml

# Set log level
wallpaper-auto -l INFO

Or start programmatically from Python:

from wallpaper_auto import run_service
run_service("config.yaml")

Auto Start

To launch automatically at logon, create a Task Scheduler task with an At log on trigger. Use pythonw.exe to hide the console window:

How Config Parameters Flow to Components

Each component type can accept configuration via a config block in the YAML file. The key-value pairs are unpacked as keyword arguments to the component's constructor.

Triggers

trigger:
  - name: time
    config:
      interval: 60       # → TimeTrigger(interval=60)
      times:             # → TimeTrigger(times=["09:00", "18:00"])
        - "09:00"
        - "18:00"
Trigger Constructor Parameters Description
time interval (seconds), times (list of "HH:MM" strings) Periodic polling interval and/or fixed daily trigger times
network (none) Fires on WiFi SSID changes
windows_session (none) Fires on lock/unlock/resume

Resources

# Static — single image
resource:
  custom_name:
    name: static_wallpaper    # Resource type → constructor lookup
    config:
      path: "C:/img.jpg"     # → StaticWallpaper(path="C:/img.jpg", style="fill")
      style: fill

# Resource carousel — cycles through multiple sub-resources on a timer
  carousel:
    name: resource_carousel
    config:
      resources:
        - name: static_wallpaper
          config: {path: "C:/img1.jpg", style: fill}
        - name: static_wallpaper
          config: {path: "C:/img2.jpg", style: fill}
      interval: 300           # → ResourceCarousel(resources=[...], interval=300, random=False)
      random: false
Resource Constructor Parameters Description
static_wallpaper path (str), style (str), restore (bool, default False), cache_dir (str, optional) Static image wallpaper — restore=True restores original wallpaper on demount. cache_dir overrides the auto-created temp directory.
resource_carousel resources (list[dict]), interval (int, default 300), random (bool, default False) Cycles through sub-resources — each sub-resource is a full resource config dict with its own name and config.

The shorthand form (black: "C:/img.jpg") is expanded to static_wallpaper with the string as the path.

Evaluators

Evaluators don't use a config block — their parameters are defined inline in the condition:

condition:
  wifi_ssid_is: "Company_WiFi"             # param: SSID string
  in_time_range: ["09:00", "18:00"]         # param: [start, end]
  day_of_week_is: [5, 6]                    # param: list[int]  0=Mon ... 6=Sun

Custom Components

When registering a custom component, define __init__ parameters matching the keys you expect in the YAML config block:

class MyTrigger(BaseThreadTrigger):
    def __init__(self, poll_interval: int = 30, endpoint: str = "..."):
        ...

System Tray

After running, the app displays an icon in the system tray:

  • Auto mode: Resume auto-switching (hides the manual wallpaper options)
  • Pause mode: Stop automatic rule-based switching. Manual wallpaper selection becomes available.
  • Select wallpaper: Manually switch to the specified wallpaper (only available while paused)

Programmatic Usage

You can start the service from Python code using run_service(). This is the recommended way when registering custom components.

from wallpaper_auto import run_service

# Start with the default config.yaml
run_service("config.yaml")

# With custom components registered inline
run_service(
    "config.yaml",
    custom_triggers={"my_trigger": MyTrigger},
    custom_resources={"my_resource": MyResource},
    custom_evaluators={"my_evaluator": MyEvaluator()},
)

Custom Components

All three component types are extensible. The recommended way to register custom components is by passing them to run_service() via the custom_triggers, custom_resources, and custom_evaluators keyword arguments. All base classes are importable from the top-level wallpaper_auto package.

Custom Resource

Extend BaseResource and pass it through run_service().

from wallpaper_auto import BaseResource, run_service

class OnlineResource(BaseResource):
    def __init__(self, query: str = "nature", style: str = "fill"):
        self.query = query
        self.style = style

    def mount(self):
        # Download image, then set as wallpaper
        ...

    def demount(self):
        # Restore previous wallpaper
        ...

run_service("config.yaml", custom_resources={"online": OnlineResource})
resource:
  daily:
    name: online
    config:
      query: "mountain"
      style: fill

Custom Trigger

Extend BaseTrigger or BaseThreadTrigger and pass it through run_service().

from wallpaper_auto import BaseThreadTrigger, run_service

class UsbPlugTrigger(BaseThreadTrigger):
    def run(self):
        while not self._stop_event.is_set():
            # Poll for USB insertion/removal
            ...
            self.trigger()
            self._stop_event.wait(timeout=5)

run_service("config.yaml", custom_triggers={"usb_plug": UsbPlugTrigger})
trigger:
  - name: usb_plug
    config: {}

Custom Evaluator

Implement BaseEvaluator (a callable interface) and pass an instance through run_service().

For example, here is a geo-location evaluator that checks whether the current machine is within a given radius of a target location:

from wallpaper_auto import BaseEvaluator, run_service

class GeoEvaluator(BaseEvaluator):
    """Evaluate whether the current machine is within a given radius
    of a target location."""

    def __call__(self, param: dict) -> bool:
        # 1. Validate input: param must contain lat, lon, radius
        # 2. Resolve current location via IP geolocation API
        # 3. Compute distance between current location and target
        # 4. Return True if distance <= radius
        ...

run_service("config.yaml", custom_evaluators={"in_geo_range": GeoEvaluator()})

The example above resolves the machine's public IP via a geolocation API, computes the distance using the Haversine formula, and returns True when the machine is within the configured radius. The actual API call and distance calculation are left as an exercise for the reader -- the pattern shown here is the extensibility contract: subclass BaseEvaluator, implement __call__, and pass an instance to run_service().

rule:
  - name: "Near home"
    condition:
      in_geo_range:
        lat: 31.23
        lon: 121.47
        radius: 0.5
    target: "home_wallpaper"

Registering All Three Together

All custom component types can be registered in a single run_service() call:

from wallpaper_auto import (
    BaseResource,
    BaseThreadTrigger,
    BaseEvaluator,
    run_service,
)

class MyResource(BaseResource): ...
class MyTrigger(BaseThreadTrigger): ...
class MyEvaluator(BaseEvaluator): ...

run_service(
    "config.yaml",
    custom_triggers={"my_trigger": MyTrigger},
    custom_resources={"my_resource": MyResource},
    custom_evaluators={"in_geo_range": MyEvaluator()},
)

Alternative: Class-level Registration

As an alternative, you can register components directly on the manager classes before calling run_service(). This is useful when the registration must happen before the configuration is loaded (e.g., in a plugin system).

from wallpaper_auto import ResourceManager, RuleEngine, TriggerManager, run_service

ResourceManager.register_resource("online", OnlineResource)
TriggerManager.register_trigger("usb_plug", UsbPlugTrigger)
RuleEngine.register_evaluator("my_evaluator", MyEvaluator())

run_service("config.yaml")

Dependencies

  • Python 3.12+
  • PyYAML — config file parsing
  • Pydantic >=2.0 — config validation & data models
  • PySide6 — system tray UI
  • pywin32 — Windows wallpaper API (SystemParametersInfo) & session monitoring
  • Pillow — image resize/compress for wallpaper caching

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wallpaper_auto-1.0.0.tar.gz (75.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wallpaper_auto-1.0.0-py3-none-any.whl (42.7 kB view details)

Uploaded Python 3

File details

Details for the file wallpaper_auto-1.0.0.tar.gz.

File metadata

  • Download URL: wallpaper_auto-1.0.0.tar.gz
  • Upload date:
  • Size: 75.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for wallpaper_auto-1.0.0.tar.gz
Algorithm Hash digest
SHA256 0bc76a870921ded70245759c6b33baac78cf5e30b68e4d6c6163ce22b36979a2
MD5 5309b42ccd43a8569fbb0e8e48e2caa7
BLAKE2b-256 fd6aaa10c0fb4ba0c79643546cce471150c69a6a479fafb2bf9d0fa7b168851d

See more details on using hashes here.

Provenance

The following attestation bundles were made for wallpaper_auto-1.0.0.tar.gz:

Publisher: publish.yml on HS-Mike/wallpaper-auto

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file wallpaper_auto-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: wallpaper_auto-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 42.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for wallpaper_auto-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 899109b15d1b9401c911f59b55e408e057c7aa35c9e35ea335344c410b0092a4
MD5 2a79c791375ee59a34f7dff061d27f49
BLAKE2b-256 03aac7eb460115c6d19dec5f1b5f0da907cfa94cb4d67292ea4e79df9e09746e

See more details on using hashes here.

Provenance

The following attestation bundles were made for wallpaper_auto-1.0.0-py3-none-any.whl:

Publisher: publish.yml on HS-Mike/wallpaper-auto

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page