Skip to content

Type Inference

Property definitions describe project.json; Wallpaper Engine callbacks deliver smaller runtime wrappers. The plugin entry exports two type utilities that connect those layers.

import type {
PropertyDefinitionToValue,
WallpaperUserPropertiesOf,
} from 'wallpaper-engine/plugin';

PropertyDefinitionToValue<T> maps a property discriminant to its host callback wrapper:

Definition Runtime wrapper
WallpaperColorProperty WallpaperColorValue ({ value: string })
WallpaperSliderProperty WallpaperSliderValue ({ value: number })
WallpaperBoolProperty WallpaperBoolValue ({ value: boolean })
WallpaperComboProperty WallpaperComboValue ({ value: string; text: string })
WallpaperTextInputProperty WallpaperTextValue ({ value: string })
WallpaperFileProperty WallpaperFileValue ({ value: string })
WallpaperDirectoryProperty WallpaperDirectoryValue ({ value: string })
WallpaperGroupProperty never

This is useful for generic helpers operating on one definition kind.

import type { WallpaperColorProperty } from 'wallpaper-engine';
import type { PropertyDefinitionToValue } from 'wallpaper-engine/plugin';
type RuntimeColor = PropertyDefinitionToValue<WallpaperColorProperty>;
// { value: string }

WallpaperUserPropertiesOf<T> maps every record key through PropertyDefinitionToValue and removes keys whose definition is a group.

import type { WallpaperUserPropertiesOf } from 'wallpaper-engine/plugin';
import type { properties } from './properties';
type UserProperties = WallpaperUserPropertiesOf<typeof properties>;

Given color accent, slider speed, and group appearance, the result is equivalent to:

interface UserProperties {
readonly accent: { value: string };
readonly speed: { value: number };
}

The group is layout metadata, so there is no appearance runtime key.

A definition includes editor configuration such as type, text, default value, range, options, ordering, and conditions. A callback wrapper contains the current host value and, for combos, its current display text. Do not type applyUserProperties with the definition record itself.

function applyProperties(values: Partial<UserProperties>): void {
if (values.accent) {
setAccent(values.accent.value);
}
if (values.speed) {
setSpeed(values.speed.value);
}
}
window.wallpaperPropertyListener = {
applyUserProperties(values) {
applyProperties(values as Partial<UserProperties>);
},
};

The ambient listener accepts the open-ended WallpaperUserProperties host type because it cannot know your build-time schema. The narrow assertion belongs at the boundary, after which key guards preserve safety.

Treat a missing key as “unchanged,” not “use the default.” A safe state reducer copies only values present in the callback:

interface State {
accent: string;
speed: number;
}
function updateState(state: State, values: Partial<UserProperties>): void {
if (values.accent)
state.accent = values.accent.value;
if (values.speed)
state.speed = values.speed.value;
}

This pattern survives startup delivery, one-property updates, and simulator replay without accidentally resetting unrelated values.

Combo values remain string because the builder return type follows the public Wallpaper Engine schema rather than retaining every literal option. Validate before narrowing:

type VisualStyle = 'bars' | 'wave' | 'off';
function isVisualStyle(value: string): value is VisualStyle {
return value === 'bars' || value === 'wave' || value === 'off';
}
if (values.visualStyle && isVisualStyle(values.visualStyle.value)) {
setVisualStyle(values.visualStyle.value);
}

Avoid a blind cast when the host or an older saved project could supply a value outside the current options.

The conditional and mapped types live in src/plugin/index.ts. Runtime wrapper contracts live in src/types/listeners.ts.

Register these callbacks at the correct time in Host Listeners, then handle native and simulated paths in Files & Directories.