🌈️ apply a wallpaper based on the weather outside
0

Configure Feed

Select the types of activity you want to include in your feed.

style(treewide): deno fmt

Angel Wang (Jun 22, 2026, 10:17 PM -0600) 083ffc09 6a209504

+131 -103
+2 -2
.github/workflows/build.yml
··· 62 62 - name: Upload build artifacts 63 63 uses: actions/upload-artifact@v4 64 64 with: 65 - name: Build artifacts 66 - path: build/ 65 + name: Build artifacts 66 + path: build/
+39 -20
README.md
··· 1 1 # rainwall 2 + 2 3 ![screenshot](./.github/assets/screenshot.png) 3 4 4 5 **rainwall is a program to set your wallpaper based on the weather outside.** 5 6 6 7 ## but how? 7 - This application uses ImageMagick to identify the dominant colour of an image using the Kmeans algorithm in the Oklch colourspace. Once a dominant colour is found, a wallpaper is chosen by identifying the current shortwave radiation, cloud cover, and sun angle, which correspond to lightness, chroma, and hue respectively. 8 + 9 + This application uses ImageMagick to identify the dominant colour of an image using the Kmeans algorithm in the Oklch 10 + colourspace. Once a dominant colour is found, a wallpaper is chosen by identifying the current shortwave radiation, 11 + cloud cover, and sun angle, which correspond to lightness, chroma, and hue respectively. 8 12 9 13 ## dependencies 14 + 10 15 - [ImageMagick](https://imagemagick.org) 11 16 12 17 ## installing 13 - First, install [ImageMagick](https://imagemagick.org) using your method of choice. Then download the binary from [Releases on GitHub](https://github.com/weightedangelcube/rainwall/releases), or get it from [JSR](https://jsr.io/@angelcube/rainwall/). 18 + 19 + First, install [ImageMagick](https://imagemagick.org) using your method of choice. Then download the binary from 20 + [Releases on GitHub](https://github.com/weightedangelcube/rainwall/releases), or get it from 21 + [JSR](https://jsr.io/@angelcube/rainwall/). 14 22 15 23 Alternatively, if you are using Arch Linux, you can use the PKGBUILD in the repository root to do both for you. 16 24 17 25 ## usage 18 - 1. Give both `rainwall-analyze` and `rainwall-apply` an initial run to generate the config files, then edit the config files as needed. See below for configuration options. 19 - - Linux: `$XDG_CONFIG_HOME/rainwall` or `~/.config/rainwall` 20 - - Darwin (macOS/OSX): `~/Library/Preferences/rainwall` 21 - - Windows: `%LocalAppData%/rainwall` 26 + 27 + 1. Give both `rainwall-analyze` and `rainwall-apply` an initial run to generate the config files, then edit the config 28 + files as needed. See below for configuration options. 29 + - Linux: `$XDG_CONFIG_HOME/rainwall` or `~/.config/rainwall` 30 + - Darwin (macOS/OSX): `~/Library/Preferences/rainwall` 31 + - Windows: `%LocalAppData%/rainwall` 22 32 2. Run `rainwall-analyze` to generate the index of image colours. This will take a while, be patient! 23 33 3. Run `rainwall-apply` to set your wallpaper! Enjoy! 24 34 25 35 ## configuring 36 + 26 37 ### `analyze-config.json` 38 + 27 39 | option | typeof | default | description | 28 - |-----------------------|------------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 40 + | --------------------- | ---------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 29 41 | `imageDir` | `string` | `/home/<yourusername>/Pictures` | The directory where your image files are stored. | 30 42 | `preAnalysisCommands` | `string[]` | `[]` | An array of commands to perform before analysis begins. Examples include applying CLUTs, adjusting HSV, etc. All commands will be properly escaped. **Warning: commands are run directly with `eval`! Don't put anything potentially unsafe...** or do, we don't care. | 31 43 32 44 ### `apply-config.json` 45 + 33 46 | option | typeof | default | description | 34 - |-------------------------|----------------------------------|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| 47 + | ----------------------- | -------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 35 48 | `latitude` | `number` | `0` | The latitude used to discover the weather outside. | 36 49 | `longitude` | `number` | `0` | The longitude used to discover the weather outside. | 37 50 | `weatherModel` | `string` | `"best_match"` | The weather model to calculate cloud cover and shortwave radiation with. Find the options on the [Open-Meteo](https://open-meteo.com/en/docs) site. | 38 51 | `lightnessRange` | `{ start: number; end: number }` | `{ start: 0; end: 1 }` | The range of lightness values to map shortwave radiation to. Use this if your images are all exceptionally light or dark, due to post-processing or other reasons. | 39 52 | `chromaRange` | `{ start: number; end: number }` | `{ start: 0; end: 1 }` | The range of chroma values to map cloud cover to. Use this if your images are all exceptionally saturated or desaturated, due to post-processing or other reasons. | 40 - | `applyWallpaperCommand` | `string` | `hyprctl hyprpaper wallpaper , %s` | The command used to apply the wallpaper. See "applying the wallpaper" section below for more info. | 53 + | `applyWallpaperCommand` | `string` | `hyprctl hyprpaper wallpaper , %s` | The command used to apply the wallpaper. See "applying the wallpaper" section below for more info. | 41 54 42 55 ## applying the wallpaper 56 + 43 57 Put `%s` where the image path would normally go. 58 + 44 59 ### Linux 60 + 45 61 - GNOME (untested): 46 - - Light theme: `gsettings set org.gnome.desktop.background picture-uri file://%s` 47 - - Dark theme: `gsettings set org.gnome.desktop.background picture-uri-dark file://%s` 62 + - Light theme: `gsettings set org.gnome.desktop.background picture-uri file://%s` 63 + - Dark theme: `gsettings set org.gnome.desktop.background picture-uri-dark file://%s` 48 64 - KDE Plasma (untested): 49 - - `plasma-apply-wallpaperimage %s` 65 + - `plasma-apply-wallpaperimage %s` 50 66 - awww (untested) 51 - - `awww img %s` 67 + - `awww img %s` 52 68 - swaybg (untested) 53 - - `swaybg -i %s` 69 + - `swaybg -i %s` 54 70 - hyprpaper 55 - - `hyprctl hyprpaper wallpaper , %s` 71 + - `hyprctl hyprpaper wallpaper , %s` 56 72 57 73 ### Darwin (macOS/OSX) 74 + 58 75 <sub>(untested, [source](https://discussions.apple.com/thread/254859103))</sub> 59 76 60 - Launch Automator. Click New Document → Choose. From the "Files & Folders" library, drag and drop the "Set the Desktop Picture" action to the right onto the larger workflow window. Save it somewhere accessible. Then, put this into the config file: 61 - `automator -i "%s" /path/to/your.workflow` 77 + Launch Automator. Click New Document → Choose. From the "Files & Folders" library, drag and drop the "Set the Desktop 78 + Picture" action to the right onto the larger workflow window. Save it somewhere accessible. Then, put this into the 79 + config file: `automator -i "%s" /path/to/your.workflow` 62 80 63 81 ### Windows 82 + 64 83 ...who knows? 65 84 66 85 ## compatibility 86 + 67 87 - Linux ✅ 68 88 - Darwin (macOS/OSX) ❓ 69 89 - Windows ❌ 70 - - [ ] How does one set the wallpaper via CLI? 71 - - [ ] `apply/index.ts @ L80, analyze/analysis.ts @ L20`: equivalent of POSIX eval? 72 - 90 + - [ ] How does one set the wallpaper via CLI? 91 + - [ ] `apply/index.ts @ L80, analyze/analysis.ts @ L20`: equivalent of POSIX eval?
+23 -23
deno.json
··· 1 1 { 2 - "name": "@angelcube/rainwall", 3 - "version": "0.1.0-alpha.stardance", 4 - "license": "GPL-3.0-only", 5 - "exports": { 6 - "./analyze": "./analyze/index.ts", 7 - "./apply": "./apply/index.ts" 8 - }, 9 - "tasks": { 10 - "compile-analyze": "deno compile --output build/rainwall-analyze -A ./src/analyze/index.ts", 11 - "compile-apply": "deno compile --output build/rainwall-apply -A ./src/apply/index.ts", 12 - "analyze": "deno -A ./src/analyze/index.ts", 13 - "apply": "deno -A ./src/apply/index.ts" 14 - }, 15 - "imports": { 16 - "@std/assert": "jsr:@std/assert@1", 17 - "@std/path": "jsr:@std/path@^1.1.5" 18 - }, 19 - "fmt": { 20 - "semiColons": false, 21 - "indentWidth": 4, 22 - "useTabs": true, 23 - "lineWidth": 120 24 - } 2 + "name": "@angelcube/rainwall", 3 + "version": "0.1.0-alpha.stardance", 4 + "license": "GPL-3.0-only", 5 + "exports": { 6 + "./analyze": "./analyze/index.ts", 7 + "./apply": "./apply/index.ts" 8 + }, 9 + "tasks": { 10 + "compile-analyze": "deno compile --output build/rainwall-analyze -A ./src/analyze/index.ts", 11 + "compile-apply": "deno compile --output build/rainwall-apply -A ./src/apply/index.ts", 12 + "analyze": "deno -A ./src/analyze/index.ts", 13 + "apply": "deno -A ./src/apply/index.ts" 14 + }, 15 + "imports": { 16 + "@std/assert": "jsr:@std/assert@1", 17 + "@std/path": "jsr:@std/path@^1.1.5" 18 + }, 19 + "fmt": { 20 + "semiColons": false, 21 + "indentWidth": 4, 22 + "useTabs": true, 23 + "lineWidth": 120 24 + } 25 25 }
+48 -48
package-lock.json
··· 1 1 { 2 - "name": "rainwall", 3 - "lockfileVersion": 3, 4 - "requires": true, 5 - "packages": { 6 - "": { 7 - "dependencies": { 8 - "chalk": "^5.6.2", 9 - "color-diff": "^1.4.0", 10 - "suncalc": "^2.0.0", 11 - "zx": "^8.8.5" 12 - } 13 - }, 14 - "node_modules/chalk": { 15 - "version": "5.6.2", 16 - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", 17 - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", 18 - "license": "MIT", 19 - "engines": { 20 - "node": "^12.17.0 || ^14.13 || >=16.0.0" 21 - }, 22 - "funding": { 23 - "url": "https://github.com/chalk/chalk?sponsor=1" 24 - } 25 - }, 26 - "node_modules/color-diff": { 27 - "version": "1.4.0", 28 - "resolved": "https://registry.npmjs.org/color-diff/-/color-diff-1.4.0.tgz", 29 - "integrity": "sha512-4oDB/o78lNdppbaqrg0HjOp7pHmUc+dfCxWKWFnQg6AB/1dkjtBDop3RZht5386cq9xBUDRvDvSCA7WUlM9Jqw==", 30 - "license": "BSD-3-Clause" 31 - }, 32 - "node_modules/suncalc": { 33 - "version": "2.0.0", 34 - "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-2.0.0.tgz", 35 - "integrity": "sha512-RcO28GOMXNnB+sr+c7/+zv5UPMa9wxIsUVGE3HddjQykqCVEC2rc4t4IjBB7qaa7K7dx5Cxp8UZwexyXrX8JkA==" 36 - }, 37 - "node_modules/zx": { 38 - "version": "8.8.5", 39 - "resolved": "https://registry.npmjs.org/zx/-/zx-8.8.5.tgz", 40 - "integrity": "sha512-SNgDF5L0gfN7FwVOdEFguY3orU5AkfFZm9B5YSHog/UDHv+lvmd82ZAsOenOkQixigwH2+yyH198AwNdKhj+RA==", 41 - "license": "Apache-2.0", 42 - "bin": { 43 - "zx": "build/cli.js" 44 - }, 45 - "engines": { 46 - "node": ">= 12.17.0" 47 - } 48 - } 49 - } 2 + "name": "rainwall", 3 + "lockfileVersion": 3, 4 + "requires": true, 5 + "packages": { 6 + "": { 7 + "dependencies": { 8 + "chalk": "^5.6.2", 9 + "color-diff": "^1.4.0", 10 + "suncalc": "^2.0.0", 11 + "zx": "^8.8.5" 12 + } 13 + }, 14 + "node_modules/chalk": { 15 + "version": "5.6.2", 16 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", 17 + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", 18 + "license": "MIT", 19 + "engines": { 20 + "node": "^12.17.0 || ^14.13 || >=16.0.0" 21 + }, 22 + "funding": { 23 + "url": "https://github.com/chalk/chalk?sponsor=1" 24 + } 25 + }, 26 + "node_modules/color-diff": { 27 + "version": "1.4.0", 28 + "resolved": "https://registry.npmjs.org/color-diff/-/color-diff-1.4.0.tgz", 29 + "integrity": "sha512-4oDB/o78lNdppbaqrg0HjOp7pHmUc+dfCxWKWFnQg6AB/1dkjtBDop3RZht5386cq9xBUDRvDvSCA7WUlM9Jqw==", 30 + "license": "BSD-3-Clause" 31 + }, 32 + "node_modules/suncalc": { 33 + "version": "2.0.0", 34 + "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-2.0.0.tgz", 35 + "integrity": "sha512-RcO28GOMXNnB+sr+c7/+zv5UPMa9wxIsUVGE3HddjQykqCVEC2rc4t4IjBB7qaa7K7dx5Cxp8UZwexyXrX8JkA==" 36 + }, 37 + "node_modules/zx": { 38 + "version": "8.8.5", 39 + "resolved": "https://registry.npmjs.org/zx/-/zx-8.8.5.tgz", 40 + "integrity": "sha512-SNgDF5L0gfN7FwVOdEFguY3orU5AkfFZm9B5YSHog/UDHv+lvmd82ZAsOenOkQixigwH2+yyH198AwNdKhj+RA==", 41 + "license": "Apache-2.0", 42 + "bin": { 43 + "zx": "build/cli.js" 44 + }, 45 + "engines": { 46 + "node": ">= 12.17.0" 47 + } 48 + } 49 + } 50 50 }
+6 -6
package.json
··· 1 1 { 2 - "dependencies": { 3 - "chalk": "^5.6.2", 4 - "color-diff": "^1.4.0", 5 - "suncalc": "^2.0.0", 6 - "zx": "^8.8.5" 7 - } 2 + "dependencies": { 3 + "chalk": "^5.6.2", 4 + "color-diff": "^1.4.0", 5 + "suncalc": "^2.0.0", 6 + "zx": "^8.8.5" 7 + } 8 8 }
+13 -4
src/apply/application.ts
··· 41 41 current: "cloud_cover", 42 42 minutely_15: "shortwave_radiation_instant", 43 43 forecast_minutely_15: "1", 44 - past_minutely_15: "1" 44 + past_minutely_15: "1", 45 45 }), 46 46 }, 47 47 ) ··· 89 89 for (const image of imagesData.files) { 90 90 const targetAValue = image.oklch[1] * Math.cos(image.oklch[2]) 91 91 const targetBValue = image.oklch[1] * Math.sin(image.oklch[2]) 92 - 92 + 93 93 // then use the CIEDE2000 algorithm to calculate the difference between the colours 94 - const difference = colourDiff.diff({L: lightnessValue, a: aValue, b: bValue}, {L: image.oklch[0], a: targetAValue, b: targetBValue}) 94 + const difference = colourDiff.diff({ 95 + L: lightnessValue, 96 + a: aValue, 97 + b: bValue, 98 + }, { 99 + L: image.oklch[0], 100 + a: targetAValue, 101 + b: targetBValue, 102 + }) 103 + 95 104 if (difference <= targetDifference) { 96 105 matchingImages.push(image) 97 106 } 98 107 } 99 108 targetDifference += 0.01 100 - } 109 + } 101 110 return matchingImages 102 111 }