├── test
├── GlobalUsings.cs
├── WinDynamicDesktop.Tests.csproj
└── SystemTests.cs
├── .env.example
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── feature_request.md
│ └── bug_report.md
├── dependabot.yml
└── workflows
│ ├── release-winget.yml
│ ├── release-choco.yml
│ ├── release.yml
│ ├── codeql.yml
│ └── build.yml
├── scripts
├── requirements.txt
├── tools
│ └── chocolateyInstall.ps1
├── version.ps1
├── i18n_validate.py
├── i18n_download.py
├── make_thumbnails.py
├── package.ps1
├── make_previews.py
├── publish_choco.py
├── i18n_gettext.py
└── windynamicdesktop.nuspec
├── src
├── locale
│ ├── am.mo
│ ├── ar.mo
│ ├── az.mo
│ ├── bg.mo
│ ├── bn.mo
│ ├── ca.mo
│ ├── cs.mo
│ ├── da.mo
│ ├── de.mo
│ ├── el.mo
│ ├── es.mo
│ ├── et.mo
│ ├── fa.mo
│ ├── fi.mo
│ ├── fr.mo
│ ├── gl.mo
│ ├── he.mo
│ ├── hi.mo
│ ├── hr.mo
│ ├── hu.mo
│ ├── id.mo
│ ├── is.mo
│ ├── it.mo
│ ├── ja.mo
│ ├── jv.mo
│ ├── kk.mo
│ ├── ko.mo
│ ├── lb.mo
│ ├── mk.mo
│ ├── my.mo
│ ├── nl.mo
│ ├── pl.mo
│ ├── pt.mo
│ ├── ro.mo
│ ├── ru.mo
│ ├── sk.mo
│ ├── sv.mo
│ ├── ta.mo
│ ├── th.mo
│ ├── tr.mo
│ ├── ug.mo
│ ├── uk.mo
│ ├── vi.mo
│ ├── pt-br.mo
│ ├── zh-TW.mo
│ ├── zh-Hans.mo
│ └── zh-Hant.mo
├── resources
│ ├── images
│ │ ├── Dome_day.jpg
│ │ ├── Peak_day.jpg
│ │ ├── Tree_day.jpg
│ │ ├── Dome_night.jpg
│ │ ├── Peak_night.jpg
│ │ ├── Tree_night.jpg
│ │ ├── Big_Sur_day.jpg
│ │ ├── Big_Sur_night.jpg
│ │ ├── Catalina_day.jpg
│ │ ├── The_Beach_day.jpg
│ │ ├── The_Lake_day.jpg
│ │ ├── Big_Sur_sunrise.jpg
│ │ ├── Big_Sur_sunset.jpg
│ │ ├── Catalina_night.jpg
│ │ ├── Catalina_sunset.jpg
│ │ ├── Dome_thumbnail.jpg
│ │ ├── Iridescence_day.jpg
│ │ ├── Peak_thumbnail.jpg
│ │ ├── The_Beach_night.jpg
│ │ ├── The_Cliffs_day.jpg
│ │ ├── The_Desert_day.jpg
│ │ ├── The_Lake_night.jpg
│ │ ├── The_Lake_sunset.jpg
│ │ ├── Tree_thumbnail.jpg
│ │ ├── Big_Sur_thumbnail.jpg
│ │ ├── Catalina_sunrise.jpg
│ │ ├── Catalina_thumbnail.jpg
│ │ ├── Iridescence_night.jpg
│ │ ├── Mojave_Desert_day.jpg
│ │ ├── Tahoe_Abstract_day.jpg
│ │ ├── The_Beach_sunrise.jpg
│ │ ├── The_Beach_sunset.jpg
│ │ ├── The_Cliffs_night.jpg
│ │ ├── The_Cliffs_sunrise.jpg
│ │ ├── The_Cliffs_sunset.jpg
│ │ ├── The_Desert_night.jpg
│ │ ├── The_Desert_sunrise.jpg
│ │ ├── The_Desert_sunset.jpg
│ │ ├── The_Lake_sunrise.jpg
│ │ ├── The_Lake_thumbnail.jpg
│ │ ├── fluent-search_dark.png
│ │ ├── Big_Sur_Abstract_day.jpg
│ │ ├── Mojave_Desert_night.jpg
│ │ ├── Mojave_Desert_sunset.jpg
│ │ ├── Sequoia_Abstract_day.jpg
│ │ ├── Solar_Gradients_day.jpg
│ │ ├── Sonoma_Abstract_day.jpg
│ │ ├── Tahoe_Abstract_night.jpg
│ │ ├── The_Beach_thumbnail.jpg
│ │ ├── The_Cliffs_thumbnail.jpg
│ │ ├── The_Desert_thumbnail.jpg
│ │ ├── Ventura_Abstract_day.jpg
│ │ ├── fluent-dismiss_dark.png
│ │ ├── fluent-dismiss_light.png
│ │ ├── fluent-search_light.png
│ │ ├── Big_Sur_Abstract_night.jpg
│ │ ├── Iridescence_thumbnail.jpg
│ │ ├── Mojave_Desert_sunrise.jpg
│ │ ├── Mojave_Desert_thumbnail.jpg
│ │ ├── Monterey_Abstract_day.jpg
│ │ ├── Monterey_Abstract_night.jpg
│ │ ├── Sequoia_Abstract_night.jpg
│ │ ├── Solar_Gradients_night.jpg
│ │ ├── Solar_Gradients_sunrise.jpg
│ │ ├── Solar_Gradients_sunset.jpg
│ │ ├── Sonoma_Abstract_night.jpg
│ │ ├── Ventura_Abstract_night.jpg
│ │ ├── Solar_Gradients_thumbnail.jpg
│ │ ├── Sonoma_Abstract_thumbnail.jpg
│ │ ├── Tahoe_Abstract_thumbnail.jpg
│ │ ├── Big_Sur_Abstract_thumbnail.jpg
│ │ ├── Monterey_Abstract_thumbnail.jpg
│ │ ├── Sequoia_Abstract_thumbnail.jpg
│ │ └── Ventura_Abstract_thumbnail.jpg
│ ├── WinDynamicDesktop.ico
│ ├── fonts
│ │ └── fontawesome-webfont.ttf
│ └── default_themes.yaml
├── FodyWeavers.xml
├── .editorconfig
├── MessageDialog.cs
├── RestResponse.cs
├── ThemeResult.cs
├── LaunchSequence.cs
├── UwpDesktop.cs
├── WallpaperApi.cs
├── DesktopHelper.cs
├── LockScreenChanger.cs
├── ThemeJsonValidator.cs
├── COM
│ ├── PowerSetting.cs
│ ├── TaskbarProgress.cs
│ └── DesktopWallpaper.cs
├── Program.cs
├── UwpLocation.cs
├── DefaultThemes.cs
├── WinDynamicDesktop.csproj
├── LanguageDialog.cs
├── ImportDialog.cs
├── HiddenForm.cs
├── LocationManager.cs
├── ImportDialog.Designer.cs
├── AboutDialog.cs
├── FodyWeavers.xsd
├── AboutDialog.Designer.cs
├── ThemeError.cs
├── UwpHelper.cs
├── AppContext.cs
├── ScriptManager.cs
├── FullScreenApi.cs
├── Skia
│ └── ImageCache.cs
├── JsonConfig.cs
├── LoggingHandler.cs
└── Properties
│ └── Resources.Designer.cs
├── images
├── choco_icon.png
├── select_theme.png
├── download_github.png
└── configure_schedule.png
├── uwp
├── Images
│ ├── StoreLogo.backup.png
│ ├── LargeTile.scale-100.png
│ ├── LargeTile.scale-125.png
│ ├── LargeTile.scale-150.png
│ ├── LargeTile.scale-200.png
│ ├── LargeTile.scale-400.png
│ ├── SmallTile.scale-100.png
│ ├── SmallTile.scale-125.png
│ ├── SmallTile.scale-150.png
│ ├── SmallTile.scale-200.png
│ ├── SmallTile.scale-400.png
│ ├── StoreLogo.scale-100.png
│ ├── StoreLogo.scale-125.png
│ ├── StoreLogo.scale-150.png
│ ├── StoreLogo.scale-200.png
│ ├── StoreLogo.scale-400.png
│ ├── SplashScreen.scale-100.png
│ ├── SplashScreen.scale-125.png
│ ├── SplashScreen.scale-150.png
│ ├── SplashScreen.scale-200.png
│ ├── SplashScreen.scale-400.png
│ ├── LockScreenLogo.scale-200.png
│ ├── Square44x44Logo.scale-100.png
│ ├── Square44x44Logo.scale-125.png
│ ├── Square44x44Logo.scale-150.png
│ ├── Square44x44Logo.scale-200.png
│ ├── Square44x44Logo.scale-400.png
│ ├── Wide310x150Logo.scale-100.png
│ ├── Wide310x150Logo.scale-125.png
│ ├── Wide310x150Logo.scale-150.png
│ ├── Wide310x150Logo.scale-200.png
│ ├── Wide310x150Logo.scale-400.png
│ ├── Square150x150Logo.scale-100.png
│ ├── Square150x150Logo.scale-125.png
│ ├── Square150x150Logo.scale-150.png
│ ├── Square150x150Logo.scale-200.png
│ ├── Square150x150Logo.scale-400.png
│ ├── Square44x44Logo.targetsize-16.png
│ ├── Square44x44Logo.targetsize-24.png
│ ├── Square44x44Logo.targetsize-256.png
│ ├── Square44x44Logo.targetsize-32.png
│ ├── Square44x44Logo.targetsize-48.png
│ ├── Square44x44Logo.altform-unplated_targetsize-16.png
│ ├── Square44x44Logo.altform-unplated_targetsize-256.png
│ ├── Square44x44Logo.altform-unplated_targetsize-32.png
│ ├── Square44x44Logo.altform-unplated_targetsize-48.png
│ └── Square44x44Logo.targetsize-24_altform-unplated.png
├── WinDynamicDesktop.Package_TemporaryKey.pfx
└── Package.appxmanifest
├── .gitignore
├── CONTRIBUTING.md
└── README.md
/test/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using Xunit;
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | LOCATIONIQ_API_KEY=
2 | POEDITOR_API_TOKEN=
3 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: ["https://paypal.me/t1m0thyj"]
2 |
--------------------------------------------------------------------------------
/scripts/requirements.txt:
--------------------------------------------------------------------------------
1 | Pillow
2 | poeditor
3 | polib
4 | python-dotenv
5 | requests
6 |
--------------------------------------------------------------------------------
/src/locale/am.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/am.mo
--------------------------------------------------------------------------------
/src/locale/ar.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/ar.mo
--------------------------------------------------------------------------------
/src/locale/az.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/az.mo
--------------------------------------------------------------------------------
/src/locale/bg.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/bg.mo
--------------------------------------------------------------------------------
/src/locale/bn.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/bn.mo
--------------------------------------------------------------------------------
/src/locale/ca.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/ca.mo
--------------------------------------------------------------------------------
/src/locale/cs.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/cs.mo
--------------------------------------------------------------------------------
/src/locale/da.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/da.mo
--------------------------------------------------------------------------------
/src/locale/de.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/de.mo
--------------------------------------------------------------------------------
/src/locale/el.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/el.mo
--------------------------------------------------------------------------------
/src/locale/es.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/es.mo
--------------------------------------------------------------------------------
/src/locale/et.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/et.mo
--------------------------------------------------------------------------------
/src/locale/fa.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/fa.mo
--------------------------------------------------------------------------------
/src/locale/fi.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/fi.mo
--------------------------------------------------------------------------------
/src/locale/fr.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/fr.mo
--------------------------------------------------------------------------------
/src/locale/gl.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/gl.mo
--------------------------------------------------------------------------------
/src/locale/he.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/he.mo
--------------------------------------------------------------------------------
/src/locale/hi.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/hi.mo
--------------------------------------------------------------------------------
/src/locale/hr.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/hr.mo
--------------------------------------------------------------------------------
/src/locale/hu.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/hu.mo
--------------------------------------------------------------------------------
/src/locale/id.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/id.mo
--------------------------------------------------------------------------------
/src/locale/is.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/is.mo
--------------------------------------------------------------------------------
/src/locale/it.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/it.mo
--------------------------------------------------------------------------------
/src/locale/ja.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/ja.mo
--------------------------------------------------------------------------------
/src/locale/jv.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/jv.mo
--------------------------------------------------------------------------------
/src/locale/kk.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/kk.mo
--------------------------------------------------------------------------------
/src/locale/ko.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/ko.mo
--------------------------------------------------------------------------------
/src/locale/lb.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/lb.mo
--------------------------------------------------------------------------------
/src/locale/mk.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/mk.mo
--------------------------------------------------------------------------------
/src/locale/my.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/my.mo
--------------------------------------------------------------------------------
/src/locale/nl.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/nl.mo
--------------------------------------------------------------------------------
/src/locale/pl.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/pl.mo
--------------------------------------------------------------------------------
/src/locale/pt.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/pt.mo
--------------------------------------------------------------------------------
/src/locale/ro.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/ro.mo
--------------------------------------------------------------------------------
/src/locale/ru.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/ru.mo
--------------------------------------------------------------------------------
/src/locale/sk.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/sk.mo
--------------------------------------------------------------------------------
/src/locale/sv.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/sv.mo
--------------------------------------------------------------------------------
/src/locale/ta.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/ta.mo
--------------------------------------------------------------------------------
/src/locale/th.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/th.mo
--------------------------------------------------------------------------------
/src/locale/tr.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/tr.mo
--------------------------------------------------------------------------------
/src/locale/ug.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/ug.mo
--------------------------------------------------------------------------------
/src/locale/uk.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/uk.mo
--------------------------------------------------------------------------------
/src/locale/vi.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/vi.mo
--------------------------------------------------------------------------------
/src/locale/pt-br.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/pt-br.mo
--------------------------------------------------------------------------------
/src/locale/zh-TW.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/zh-TW.mo
--------------------------------------------------------------------------------
/images/choco_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/images/choco_icon.png
--------------------------------------------------------------------------------
/images/select_theme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/images/select_theme.png
--------------------------------------------------------------------------------
/src/locale/zh-Hans.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/zh-Hans.mo
--------------------------------------------------------------------------------
/src/locale/zh-Hant.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/locale/zh-Hant.mo
--------------------------------------------------------------------------------
/images/download_github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/images/download_github.png
--------------------------------------------------------------------------------
/images/configure_schedule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/images/configure_schedule.png
--------------------------------------------------------------------------------
/src/resources/images/Dome_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Dome_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/Peak_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Peak_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/Tree_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Tree_day.jpg
--------------------------------------------------------------------------------
/uwp/Images/StoreLogo.backup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/StoreLogo.backup.png
--------------------------------------------------------------------------------
/src/resources/WinDynamicDesktop.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/WinDynamicDesktop.ico
--------------------------------------------------------------------------------
/src/resources/images/Dome_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Dome_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/Peak_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Peak_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/Tree_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Tree_night.jpg
--------------------------------------------------------------------------------
/uwp/Images/LargeTile.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/LargeTile.scale-100.png
--------------------------------------------------------------------------------
/uwp/Images/LargeTile.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/LargeTile.scale-125.png
--------------------------------------------------------------------------------
/uwp/Images/LargeTile.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/LargeTile.scale-150.png
--------------------------------------------------------------------------------
/uwp/Images/LargeTile.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/LargeTile.scale-200.png
--------------------------------------------------------------------------------
/uwp/Images/LargeTile.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/LargeTile.scale-400.png
--------------------------------------------------------------------------------
/uwp/Images/SmallTile.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/SmallTile.scale-100.png
--------------------------------------------------------------------------------
/uwp/Images/SmallTile.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/SmallTile.scale-125.png
--------------------------------------------------------------------------------
/uwp/Images/SmallTile.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/SmallTile.scale-150.png
--------------------------------------------------------------------------------
/uwp/Images/SmallTile.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/SmallTile.scale-200.png
--------------------------------------------------------------------------------
/uwp/Images/SmallTile.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/SmallTile.scale-400.png
--------------------------------------------------------------------------------
/uwp/Images/StoreLogo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/StoreLogo.scale-100.png
--------------------------------------------------------------------------------
/uwp/Images/StoreLogo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/StoreLogo.scale-125.png
--------------------------------------------------------------------------------
/uwp/Images/StoreLogo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/StoreLogo.scale-150.png
--------------------------------------------------------------------------------
/uwp/Images/StoreLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/StoreLogo.scale-200.png
--------------------------------------------------------------------------------
/uwp/Images/StoreLogo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/StoreLogo.scale-400.png
--------------------------------------------------------------------------------
/src/resources/images/Big_Sur_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Big_Sur_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/Big_Sur_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Big_Sur_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/Catalina_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Catalina_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Beach_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Beach_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Lake_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Lake_day.jpg
--------------------------------------------------------------------------------
/uwp/Images/SplashScreen.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/SplashScreen.scale-100.png
--------------------------------------------------------------------------------
/uwp/Images/SplashScreen.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/SplashScreen.scale-125.png
--------------------------------------------------------------------------------
/uwp/Images/SplashScreen.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/SplashScreen.scale-150.png
--------------------------------------------------------------------------------
/uwp/Images/SplashScreen.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/SplashScreen.scale-200.png
--------------------------------------------------------------------------------
/uwp/Images/SplashScreen.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/SplashScreen.scale-400.png
--------------------------------------------------------------------------------
/src/resources/images/Big_Sur_sunrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Big_Sur_sunrise.jpg
--------------------------------------------------------------------------------
/src/resources/images/Big_Sur_sunset.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Big_Sur_sunset.jpg
--------------------------------------------------------------------------------
/src/resources/images/Catalina_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Catalina_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/Catalina_sunset.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Catalina_sunset.jpg
--------------------------------------------------------------------------------
/src/resources/images/Dome_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Dome_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/Iridescence_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Iridescence_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/Peak_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Peak_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Beach_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Beach_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Cliffs_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Cliffs_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Desert_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Desert_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Lake_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Lake_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Lake_sunset.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Lake_sunset.jpg
--------------------------------------------------------------------------------
/src/resources/images/Tree_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Tree_thumbnail.jpg
--------------------------------------------------------------------------------
/uwp/Images/LockScreenLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/LockScreenLogo.scale-200.png
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.scale-100.png
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.scale-125.png
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.scale-150.png
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.scale-200.png
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.scale-400.png
--------------------------------------------------------------------------------
/uwp/Images/Wide310x150Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Wide310x150Logo.scale-100.png
--------------------------------------------------------------------------------
/uwp/Images/Wide310x150Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Wide310x150Logo.scale-125.png
--------------------------------------------------------------------------------
/uwp/Images/Wide310x150Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Wide310x150Logo.scale-150.png
--------------------------------------------------------------------------------
/uwp/Images/Wide310x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Wide310x150Logo.scale-200.png
--------------------------------------------------------------------------------
/uwp/Images/Wide310x150Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Wide310x150Logo.scale-400.png
--------------------------------------------------------------------------------
/src/resources/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/src/resources/images/Big_Sur_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Big_Sur_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/Catalina_sunrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Catalina_sunrise.jpg
--------------------------------------------------------------------------------
/src/resources/images/Catalina_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Catalina_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/Iridescence_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Iridescence_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/Mojave_Desert_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Mojave_Desert_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/Tahoe_Abstract_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Tahoe_Abstract_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Beach_sunrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Beach_sunrise.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Beach_sunset.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Beach_sunset.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Cliffs_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Cliffs_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Cliffs_sunrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Cliffs_sunrise.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Cliffs_sunset.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Cliffs_sunset.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Desert_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Desert_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Desert_sunrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Desert_sunrise.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Desert_sunset.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Desert_sunset.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Lake_sunrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Lake_sunrise.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Lake_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Lake_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/fluent-search_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/fluent-search_dark.png
--------------------------------------------------------------------------------
/uwp/Images/Square150x150Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square150x150Logo.scale-100.png
--------------------------------------------------------------------------------
/uwp/Images/Square150x150Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square150x150Logo.scale-125.png
--------------------------------------------------------------------------------
/uwp/Images/Square150x150Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square150x150Logo.scale-150.png
--------------------------------------------------------------------------------
/uwp/Images/Square150x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square150x150Logo.scale-200.png
--------------------------------------------------------------------------------
/uwp/Images/Square150x150Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square150x150Logo.scale-400.png
--------------------------------------------------------------------------------
/src/resources/images/Big_Sur_Abstract_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Big_Sur_Abstract_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/Mojave_Desert_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Mojave_Desert_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/Mojave_Desert_sunset.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Mojave_Desert_sunset.jpg
--------------------------------------------------------------------------------
/src/resources/images/Sequoia_Abstract_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Sequoia_Abstract_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/Solar_Gradients_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Solar_Gradients_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/Sonoma_Abstract_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Sonoma_Abstract_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/Tahoe_Abstract_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Tahoe_Abstract_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Beach_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Beach_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Cliffs_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Cliffs_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/The_Desert_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/The_Desert_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/Ventura_Abstract_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Ventura_Abstract_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/fluent-dismiss_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/fluent-dismiss_dark.png
--------------------------------------------------------------------------------
/src/resources/images/fluent-dismiss_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/fluent-dismiss_light.png
--------------------------------------------------------------------------------
/src/resources/images/fluent-search_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/fluent-search_light.png
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.targetsize-16.png
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.targetsize-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.targetsize-24.png
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.targetsize-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.targetsize-256.png
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.targetsize-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.targetsize-32.png
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.targetsize-48.png
--------------------------------------------------------------------------------
/src/resources/images/Big_Sur_Abstract_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Big_Sur_Abstract_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/Iridescence_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Iridescence_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/Mojave_Desert_sunrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Mojave_Desert_sunrise.jpg
--------------------------------------------------------------------------------
/src/resources/images/Mojave_Desert_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Mojave_Desert_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/Monterey_Abstract_day.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Monterey_Abstract_day.jpg
--------------------------------------------------------------------------------
/src/resources/images/Monterey_Abstract_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Monterey_Abstract_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/Sequoia_Abstract_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Sequoia_Abstract_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/Solar_Gradients_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Solar_Gradients_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/Solar_Gradients_sunrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Solar_Gradients_sunrise.jpg
--------------------------------------------------------------------------------
/src/resources/images/Solar_Gradients_sunset.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Solar_Gradients_sunset.jpg
--------------------------------------------------------------------------------
/src/resources/images/Sonoma_Abstract_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Sonoma_Abstract_night.jpg
--------------------------------------------------------------------------------
/src/resources/images/Ventura_Abstract_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Ventura_Abstract_night.jpg
--------------------------------------------------------------------------------
/uwp/WinDynamicDesktop.Package_TemporaryKey.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/WinDynamicDesktop.Package_TemporaryKey.pfx
--------------------------------------------------------------------------------
/src/resources/images/Solar_Gradients_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Solar_Gradients_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/Sonoma_Abstract_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Sonoma_Abstract_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/Tahoe_Abstract_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Tahoe_Abstract_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/Big_Sur_Abstract_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Big_Sur_Abstract_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/Monterey_Abstract_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Monterey_Abstract_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/Sequoia_Abstract_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Sequoia_Abstract_thumbnail.jpg
--------------------------------------------------------------------------------
/src/resources/images/Ventura_Abstract_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/src/resources/images/Ventura_Abstract_thumbnail.jpg
--------------------------------------------------------------------------------
/src/FodyWeavers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.altform-unplated_targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.altform-unplated_targetsize-16.png
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.altform-unplated_targetsize-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.altform-unplated_targetsize-256.png
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.altform-unplated_targetsize-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.altform-unplated_targetsize-32.png
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.altform-unplated_targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.altform-unplated_targetsize-48.png
--------------------------------------------------------------------------------
/uwp/Images/Square44x44Logo.targetsize-24_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/t1m0thyj/WinDynamicDesktop/HEAD/uwp/Images/Square44x44Logo.targetsize-24_altform-unplated.png
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Question
4 | url: https://github.com/t1m0thyj/WinDynamicDesktop/discussions
5 | about: Please ask and answer questions here.
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | ---
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 |
8 | - package-ecosystem: "nuget"
9 | directory: "src"
10 | schedule:
11 | interval: "daily"
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | ---
5 |
6 |
7 |
8 | - WDD Version:
9 | - OS Version:
10 |
11 | Steps to Reproduce:
12 |
13 | 1.
14 | 2.
15 |
16 | Are you using the Microsoft Store version of WDD?: Yes/No
17 |
--------------------------------------------------------------------------------
/src/.editorconfig:
--------------------------------------------------------------------------------
1 | # To learn more about .editorconfig see https://aka.ms/editorconfigdocs
2 | root = true
3 |
4 | # All files
5 | [*]
6 | indent_style = space
7 | guidelines = 80
8 |
9 | # C# files
10 | [*.cs]
11 | file_header_template = This Source Code Form is subject to the terms of the Mozilla Public\nLicense, v. 2.0. If a copy of the MPL was not distributed with this\nfile, You can obtain one at http://mozilla.org/MPL/2.0/.
12 | guidelines = 80, 120
13 | propertychanged.first_letter_capitalization = lower_case
14 |
15 | # Xml files
16 | [*.xml]
17 | indent_size = 2
18 |
--------------------------------------------------------------------------------
/.github/workflows/release-winget.yml:
--------------------------------------------------------------------------------
1 | name: Publish to WinGet
2 | on:
3 | release:
4 | types: [released]
5 | workflow_dispatch:
6 | inputs:
7 | release_tag:
8 | required: true
9 | description: Tag of release you want to publish
10 | type: string
11 | jobs:
12 | publish:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: vedantmgoyal9/winget-releaser@main
16 | with:
17 | identifier: t1m0thyj.WinDynamicDesktop
18 | installers-regex: 'Setup\.exe$'
19 | release-tag: ${{ inputs.release_tag || github.event.release.tag_name }}
20 | token: ${{ secrets.WINGET_TOKEN }}
21 |
--------------------------------------------------------------------------------
/.github/workflows/release-choco.yml:
--------------------------------------------------------------------------------
1 | name: Publish to Chocolatey
2 | on:
3 | release:
4 | types: [released]
5 | workflow_dispatch:
6 | inputs:
7 | release_tag:
8 | required: true
9 | description: Tag of release you want to publish
10 | type: string
11 | jobs:
12 | publish:
13 | runs-on: windows-latest # choco can only be run on windows
14 | steps:
15 | - uses: actions/checkout@v6
16 | - run: pip install -r scripts/requirements.txt
17 | - run: python scripts/publish_choco.py ${{ inputs.release_tag || github.event.release.tag_name }}
18 | env:
19 | CHOCO_API_KEY: ${{ secrets.CHOCO_APIKEY }}
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | themes/
3 | uwp/AppPackages/
4 | uwp/BundleArtifacts/
5 | uwp/*.assets.cache
6 | uwp/Package.StoreAssociation.xml
7 | .env
8 | *.pot
9 |
10 | *.swp
11 | *.*~
12 | project.lock.json
13 | .DS_Store
14 | *.pyc
15 | nupkg/
16 |
17 | # Visual Studio Code
18 | .vscode
19 |
20 | # Rider
21 | .idea
22 |
23 | # User-specific files
24 | *.suo
25 | *.user
26 | *.userosscache
27 | *.sln.docstates
28 |
29 | # Build results
30 | [Dd]ebug/
31 | [Dd]ebugPublic/
32 | [Rr]elease/
33 | [Rr]eleases/
34 | x64/
35 | x86/
36 | build/
37 | bld/
38 | [Bb]in/
39 | [Oo]bj/
40 | [Oo]ut/
41 | msbuild.log
42 | msbuild.err
43 | msbuild.wrn
44 |
45 | # Visual Studio 2015
46 | .vs/
47 |
--------------------------------------------------------------------------------
/scripts/tools/chocolateyInstall.ps1:
--------------------------------------------------------------------------------
1 |
2 | $ErrorActionPreference = 'Stop';
3 | $toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
4 | $url = '{{installerUrl}}'
5 | $url64 = '{{installerUrl64}}'
6 |
7 | $packageArgs = @{
8 | packageName = $env:ChocolateyPackageName
9 | unzipLocation = $toolsDir
10 | fileType = 'EXE'
11 | url = $url
12 | url64 = $url64
13 |
14 | softwareName = 'WinDynamicDesktop*'
15 |
16 | checksum = '{{installerChecksum}}'
17 | checksumType = 'sha256'
18 | checksum64 = '{{installerChecksum64}}'
19 | checksumType64= 'sha256'
20 |
21 | silentArgs = '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-'
22 | validExitCodes= @(0)
23 | }
24 |
25 | Install-ChocolateyPackage @packageArgs
26 |
--------------------------------------------------------------------------------
/scripts/version.ps1:
--------------------------------------------------------------------------------
1 | param (
2 | [Parameter(Position=0)][string]$newVersion
3 | )
4 |
5 | $appxmanifest = "uwp\Package.appxmanifest"
6 | $csproj = "src\WinDynamicDesktop.csproj"
7 |
8 | $xmlDoc = [XML](Get-Content -Path $csproj)
9 | $oldVersion = $xmlDoc.Project.PropertyGroup[0].Version
10 |
11 | if (!$newVersion) {
12 | $newVersion = "$oldVersion-g$(git rev-parse --short HEAD)"
13 | } elseif ($newVersion -eq $oldVersion) {
14 | Write-Host "Version is already $newVersion"
15 | exit
16 | }
17 |
18 | $xmlDoc.Project.PropertyGroup[0].Version = $newVersion
19 | $xmlDoc.Save("$pwd\$csproj")
20 |
21 | $xmlDoc = [XML](Get-Content -Path $appxmanifest)
22 | $newVersion4 = $newVersion -Replace '^(\d+\.\d+\.\d+).*', '$1.0'
23 | $xmlDoc.Package.Identity.setAttribute("Version", $newVersion4)
24 | $xmlDoc.Save("$pwd\$appxmanifest")
25 |
--------------------------------------------------------------------------------
/scripts/i18n_validate.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import os
3 |
4 | import polib
5 |
6 | os.chdir(os.path.dirname(os.path.realpath(__file__)))
7 |
8 | errors = 0
9 | with open("../src/Localization.cs", 'r', encoding="utf-8") as fileobj:
10 | l10n_src = fileobj.read()
11 | for filename in os.listdir("../src/locale"):
12 | if not filename.endswith(".mo"):
13 | continue
14 | language_code = filename[:-3]
15 | if f"\"{language_code}\"" not in l10n_src[:l10n_src.index("};")]:
16 | print(f"[{language_code}] ERROR: Language missing in Localization.cs")
17 | errors += 1
18 | for entry in polib.mofile(f"../src/locale/{filename}"):
19 | i = 0
20 | while f"{{{i}}}" in entry.msgid:
21 | if f"{{{i}}}" not in entry.msgstr:
22 | print(f"[{language_code}] {entry.msgid}\n\t{entry.msgstr}")
23 | errors += 1
24 | break
25 | i += 1
26 | print(f"\n❌ {errors} problem(s) found" if errors else "✅ No problems found")
27 | input()
28 |
--------------------------------------------------------------------------------
/scripts/i18n_download.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import os
3 | import shutil
4 | import sys
5 |
6 | from dotenv import load_dotenv
7 | from poeditor import POEditorAPI
8 |
9 | os.chdir(os.path.dirname(os.path.realpath(__file__)))
10 | load_dotenv()
11 |
12 | file_type = sys.argv[1] if len(sys.argv) > 1 else "mo"
13 | output_dir = sys.argv[2] if len(sys.argv) > 2 else "../src/locale"
14 |
15 | if len(sys.argv) > 2 and os.path.isdir(output_dir):
16 | shutil.rmtree(output_dir)
17 | os.makedirs(output_dir, exist_ok=True)
18 |
19 | client = POEditorAPI(os.getenv("POEDITOR_API_TOKEN"))
20 | projects = client.list_projects()
21 | project_id = [proj for proj in projects if proj["name"] == "WinDynamicDesktop"][0]["id"]
22 | languages = client.list_project_languages(project_id)
23 |
24 | for lang in languages:
25 | language_code = lang["code"]
26 | output_file = f"{output_dir}/{language_code}.{file_type}"
27 | if lang["percentage"] < 50 and not os.path.isfile(output_file):
28 | continue
29 | print(f"Downloading translation for {language_code}")
30 | client.export(project_id, language_code, file_type, local_file=output_file)
31 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Pull requests are welcome. For major changes, please [open an issue](https://github.com/t1m0thyj/WinDynamicDesktop/issues/new?assignees=&labels=&projects=&template=feature_request.md) to discuss them first.
4 |
5 | ## How to Build
6 |
7 | 1. Install [Visual Studio](https://visualstudio.microsoft.com/vs/) (Community edition is fine) with the following workloads:
8 | * .NET desktop deployment
9 | * Universal Windows Platform deployment
10 |
11 | 2. Create an env file by copying `.env.example` to `.env`. Define the following variables in it:
12 | * `LOCATIONIQ_API_KEY` (optional) - [LocationIQ API key](https://help.locationiq.com/support/solutions/articles/36000172496-how-do-i-get-the-api-key-access-token-) used to get latitude and longitude based on city name
13 | * `POEDITOR_API_TOKEN` (optional) - [POEditor API token](https://poeditor.com/account/api) used to test new translations from the POEditor website
14 |
15 | 3. Open [the solution file](./WinDynamicDesktop.sln) in Visual Studio and build one of the projects:
16 | * `WinDynamicDesktop` - Main project to run the app
17 | * `WinDynamicDesktop.Package` - UWP project for Microsoft Store app
18 | * `WinDynamicDesktop.Tests` - Unit tests
19 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | version:
7 | description: Version number for release
8 | required: true
9 | type: string
10 |
11 | jobs:
12 | release:
13 | runs-on: windows-2022
14 |
15 | steps:
16 | - uses: actions/checkout@v6
17 |
18 | - uses: actions/setup-dotnet@v5
19 | with:
20 | dotnet-version: 10.0.x
21 |
22 | - uses: microsoft/setup-msbuild@v2
23 |
24 | - run: echo ${{ secrets.ENV_FILE }} | base64 --decode > .env
25 | shell: bash
26 |
27 | - run: scripts\version.ps1 ${{ github.event.inputs.version }}
28 |
29 | - run: scripts\package.ps1
30 |
31 | - run: gh release create $env:TAG_NAME --draft --title "$env:RELEASE_NAME" (Get-Item dist\*.exe) dist\checksums.txt
32 | env:
33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34 | RELEASE_NAME: WinDynamicDesktop ${{ github.event.inputs.version }}
35 | TAG_NAME: v${{ github.event.inputs.version }}
36 |
37 | - uses: actions/upload-artifact@v6
38 | with:
39 | name: msixupload # To download/extract: gh run download -n msixupload
40 | path: dist\*.msixupload
41 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: CodeQL
2 |
3 | on:
4 | push:
5 | branches: [ main, stable ]
6 | paths:
7 | - '**.cs'
8 | - '**.csproj'
9 | pull_request:
10 | branches: [ main, stable ]
11 | paths:
12 | - '**.cs'
13 | - '**.csproj'
14 |
15 | jobs:
16 | analyze:
17 | runs-on: windows-latest
18 |
19 | permissions:
20 | actions: read
21 | contents: read
22 | security-events: write
23 |
24 | strategy:
25 | fail-fast: false
26 | matrix:
27 | language: [ 'csharp' ]
28 |
29 | steps:
30 | - name: Checkout repository
31 | uses: actions/checkout@v6
32 |
33 | - name: Setup .NET
34 | uses: actions/setup-dotnet@v5
35 | with:
36 | dotnet-version: 10.0.x
37 |
38 | - name: Initialize CodeQL
39 | uses: github/codeql-action/init@v4
40 | with:
41 | languages: ${{ matrix.language }}
42 | queries: +security-extended
43 |
44 | - name: Autobuild
45 | run: |
46 | cp .env.example .env
47 | dotnet build src\WinDynamicDesktop.csproj
48 |
49 | - name: Perform CodeQL Analysis
50 | uses: github/codeql-action/analyze@v4
51 | with:
52 | category: "/language:${{matrix.language}}"
53 |
--------------------------------------------------------------------------------
/test/WinDynamicDesktop.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net10.0-windows10.0.19041.0
5 | enable
6 | enable
7 |
8 | false
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 | all
20 |
21 |
22 | runtime; build; native; contentfiles; analyzers; buildtransitive
23 | all
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/MessageDialog.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System.Windows.Forms;
6 |
7 | namespace WinDynamicDesktop
8 | {
9 | class MessageDialog
10 | {
11 | public static DialogResult ShowError(string message, string title = null)
12 | {
13 | return MessageBox.Show(message, title ?? "WinDynamicDesktop", MessageBoxButtons.OK, MessageBoxIcon.Error);
14 | }
15 |
16 | public static DialogResult ShowInfo(string message, string title = null, bool cancelButton = false)
17 | {
18 | return MessageBox.Show(message, title ?? "WinDynamicDesktop",
19 | cancelButton ? MessageBoxButtons.OKCancel : MessageBoxButtons.OK, MessageBoxIcon.Information);
20 | }
21 |
22 | public static DialogResult ShowQuestion(string message, string title = null,
23 | MessageBoxIcon questionIcon = MessageBoxIcon.Question)
24 | {
25 | return MessageBox.Show(message, title ?? "WinDynamicDesktop", MessageBoxButtons.YesNo, questionIcon);
26 | }
27 |
28 | public static DialogResult ShowWarning(string message, string title = null)
29 | {
30 | return MessageBox.Show(message, title ?? "WinDynamicDesktop", MessageBoxButtons.OK, MessageBoxIcon.Warning);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/scripts/make_thumbnails.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import glob
3 | import json
4 | import os
5 | import sys
6 |
7 | from PIL import Image
8 |
9 | os.chdir(os.path.dirname(os.path.realpath(__file__)))
10 |
11 | # Thumbnails for theme repo: width=384, quality=95
12 | # Thumbnails for MS Store: width=1920, quality=-1 (png)
13 | img_width = int(sys.argv[1]) if len(sys.argv) > 1 else 256
14 | img_height = int(img_width * 9 / 16)
15 | jpeg_quality = int(sys.argv[2]) if len(sys.argv) > 2 else 95
16 |
17 | input_dir = "..\\themes"
18 | output_dir = sys.argv[3] if len(sys.argv) > 3 else "../src/resources/images"
19 |
20 | get_middle_item = lambda image_list: image_list[len(image_list) // 2]
21 |
22 | for theme_dir in glob.glob(f"{input_dir}/**"):
23 | print(f"<- {theme_dir}")
24 |
25 | with open(f"{theme_dir}/theme.json", 'r') as fileobj:
26 | theme_config = json.load(fileobj)
27 | theme_name = os.path.basename(theme_dir)
28 |
29 | light_image_id = theme_config.get("dayHighlight") or get_middle_item(theme_config["dayImageList"])
30 | light_image_filename = theme_config["imageFilename"].replace("*", str(light_image_id))
31 | dark_image_id = theme_config.get("nightHighlight") or get_middle_item(theme_config["nightImageList"])
32 | dark_image_filename = theme_config["imageFilename"].replace("*", str(dark_image_id))
33 |
34 | img1 = Image.open(f"{theme_dir}/{light_image_filename}")
35 | img2 = Image.open(f"{theme_dir}/{dark_image_filename}")
36 |
37 | img2.paste(img1.crop((0, 0, img1.width // 2, img1.height)))
38 | img2.thumbnail((img_width, img_height))
39 | if jpeg_quality >= 0:
40 | img2.save(f"{output_dir}/{theme_name}_thumbnail.jpg", quality=jpeg_quality)
41 | else:
42 | img2.save(f"{output_dir}/{theme_name}_thumbnail.png")
43 |
44 | print(f"-> {output_dir}")
45 |
--------------------------------------------------------------------------------
/scripts/package.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = "Stop"
2 |
3 | if (-Not (Get-Command msbuild -ErrorAction SilentlyContinue)) {
4 | Import-Module "$env:ProgramFiles\Microsoft Visual Studio\2022\Community\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
5 | Enter-VsDevShell -VsInstallPath "$env:ProgramFiles\Microsoft Visual Studio\2022\Community"
6 | }
7 |
8 | Set-Location "$(Split-Path $MyInvocation.MyCommand.Path)\.."
9 |
10 | $appPlatforms = if ($args) { $args } else { @("x86", "x64", "arm64") }
11 | $appVersion = ([Xml](Get-Content -Path "src\WinDynamicDesktop.csproj")).Project.PropertyGroup[0].Version
12 |
13 | Remove-Item "dist" -ErrorAction Ignore -Recurse
14 | New-Item -ItemType Directory -Force -Path "dist"
15 |
16 | dotnet restore WinDynamicDesktop.sln
17 |
18 | foreach ($platform in $appPlatforms) {
19 | dotnet publish src\WinDynamicDesktop.csproj -a $platform -c Release --no-restore --self-contained -p:EnableCompressionInSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:PublishSingleFile=true
20 | Copy-Item -Path "src\bin\Release\net10.0-windows10.0.19041.0\win-$platform\publish\WinDynamicDesktop.exe" -Destination "dist\WinDynamicDesktop_$appVersion`_$platform`_Portable.exe"
21 |
22 | iscc scripts\installer.iss /dMyAppPlatform="$platform" /dMyAppVersion="$appVersion"
23 | }
24 |
25 | msbuild uwp\WinDynamicDesktop.Package.wapproj /v:m /p:AppxBundle=Always /p:AppxBundlePlatforms=$($appPlatforms -join '|') /p:AppxPackageSigningEnabled=false /p:Configuration=Release /p:Platform=$($appPlatforms[0]) /p:UapAppxPackageBuildMode=StoreOnly
26 | Copy-Item -Path "uwp\AppPackages\WinDynamicDesktop.Package_$appVersion`*.msixupload" -Destination "dist\WinDynamicDesktop_$appVersion`.msixupload"
27 |
28 | Get-ChildItem "dist" -Filter *.exe | ForEach-Object {
29 | $checksum = (Get-FileHash -Path $_.FullName).Hash.ToLower()
30 | Add-Content -Path "dist\checksums.txt" "$checksum $($_.BaseName + $_.Extension)"
31 | }
32 |
--------------------------------------------------------------------------------
/src/RestResponse.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 |
8 | namespace WinDynamicDesktop
9 | {
10 | public class GitHubApiData
11 | {
12 | public string url { get; set; }
13 | public string html_url { get; set; }
14 | public string assets_url { get; set; }
15 | public string upload_url { get; set; }
16 | public string tarball_url { get; set; }
17 | public string zipball_url { get; set; }
18 | public int id { get; set; }
19 | public string node_id { get; set; }
20 | public string tag_name { get; set; }
21 | public string target_commitish { get; set; }
22 | public string name { get; set; }
23 | public string body { get; set; }
24 | public bool draft { get; set; }
25 | public bool prerelease { get; set; }
26 | public DateTime created_at { get; set; }
27 | public DateTime published_at { get; set; }
28 | }
29 |
30 | public class LocationIQData
31 | {
32 | public string place_id { get; set; }
33 | public string licence { get; set; }
34 | public string osm_type { get; set; }
35 | public string osm_id { get; set; }
36 | public List boundingbox { get; set; }
37 | public string lat { get; set; }
38 | public string lon { get; set; }
39 | public string display_name { get; set; }
40 | public string @class { get; set; }
41 | public string type { get; set; }
42 | public double importance { get; set; }
43 | public string icon { get; set; }
44 | }
45 |
46 | public class PoEditorApiResult
47 | {
48 | public string url { get; set; }
49 | }
50 |
51 | public class PoEditorApiData
52 | {
53 | public PoEditorApiResult result { get; set; }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/ThemeResult.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 |
7 | namespace WinDynamicDesktop
8 | {
9 | // Code based on https://mikhail.io/2016/01/validation-with-either-data-type-in-csharp/
10 | public class ThemeResult
11 | {
12 | private readonly ThemeError left;
13 | private readonly ThemeConfig right;
14 | private readonly bool isLeft;
15 |
16 | public ThemeResult(ThemeError left)
17 | {
18 | this.left = left;
19 | this.isLeft = true;
20 | }
21 |
22 | public ThemeResult(ThemeConfig right)
23 | {
24 | this.right = right;
25 | this.isLeft = false;
26 | }
27 |
28 | public T Match(Func leftFunc, Func rightFunc)
29 | {
30 | if (leftFunc == null)
31 | {
32 | throw new ArgumentNullException(nameof(leftFunc));
33 | }
34 |
35 | if (rightFunc == null)
36 | {
37 | throw new ArgumentNullException(nameof(rightFunc));
38 | }
39 |
40 | return this.isLeft ? leftFunc(this.left) : rightFunc(this.right);
41 | }
42 |
43 | public void Match(Action leftAction, Action rightAction)
44 | {
45 | if (leftAction == null)
46 | {
47 | throw new ArgumentNullException(nameof(leftAction));
48 | }
49 |
50 | if (rightAction == null)
51 | {
52 | throw new ArgumentNullException(nameof(rightAction));
53 | }
54 |
55 | if (this.isLeft)
56 | {
57 | leftAction(this.left);
58 | }
59 | else
60 | {
61 | rightAction(this.right);
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/LaunchSequence.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 |
7 | namespace WinDynamicDesktop
8 | {
9 | class LaunchSequence
10 | {
11 | private static readonly Func _ = Localization.GetTranslation;
12 |
13 | public static bool IsLocationReady()
14 | {
15 | if (JsonConfig.settings.locationMode >= 0)
16 | {
17 | return (JsonConfig.settings.latitude.HasValue && JsonConfig.settings.longitude.HasValue);
18 | }
19 | else
20 | {
21 | return (JsonConfig.settings.sunriseTime != null && JsonConfig.settings.sunsetTime != null);
22 | }
23 | }
24 |
25 | public static bool IsThemeReady()
26 | {
27 | return (!JsonConfig.IsNullOrEmpty(JsonConfig.settings.activeThemes) && ThemeManager.importPaths.Count == 0);
28 | }
29 |
30 | public static void NextStep(bool themeReadyOverride = false)
31 | {
32 | if (!IsLocationReady())
33 | {
34 | LocationManager.ChangeLocation();
35 |
36 | if (JsonConfig.firstRun)
37 | {
38 | AppContext.ShowPopup(_("Welcome! Please enter your location so the app can determine sunrise and " +
39 | "sunset times."));
40 | }
41 | }
42 | else if (!IsThemeReady() && !themeReadyOverride) // Override if theme=None chosen
43 | {
44 | ThemeManager.SelectTheme();
45 | }
46 | else if (JsonConfig.firstRun)
47 | {
48 | AppContext.ShowPopup(_("The app is still running in the background. You can access it at any time by " +
49 | "clicking on the icon in the system tray."));
50 |
51 | JsonConfig.firstRun = false; // Don't show this message again
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/UwpDesktop.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using DesktopBridge;
6 | using System;
7 |
8 | namespace WinDynamicDesktop
9 | {
10 | abstract class PlatformHelper
11 | {
12 | public abstract string GetLocalFolder();
13 |
14 | public abstract void CheckStartOnBoot();
15 |
16 | public abstract void ToggleStartOnBoot();
17 |
18 | public abstract void OpenUpdateLink();
19 |
20 | public abstract void SetWallpaper(string imagePath, int displayIndex);
21 |
22 | public abstract void SetLockScreen(string imagePath);
23 | }
24 |
25 | class UwpDesktop
26 | {
27 | private static bool? _isRunningAsUwp;
28 | private static PlatformHelper helper;
29 |
30 | public static bool IsDarkMode()
31 | {
32 | #pragma warning disable SYSLIB5002
33 | return System.Drawing.SystemColors.UseAlternativeColorSet;
34 | #pragma warning restore SYSLIB5002
35 | }
36 |
37 | public static bool IsRunningAsUwp()
38 | {
39 | if (!_isRunningAsUwp.HasValue)
40 | {
41 | Helpers helpers = new Helpers();
42 | _isRunningAsUwp = helpers.IsRunningAsUwp();
43 | }
44 |
45 | return _isRunningAsUwp.Value;
46 | }
47 |
48 | public static bool IsUwpSupported()
49 | {
50 | return Environment.OSVersion.Version.Major >= 10;
51 | }
52 |
53 | public static bool IsWinRtSupported()
54 | {
55 | return Environment.OSVersion.Version.Major >= 8;
56 | }
57 |
58 | public static PlatformHelper GetHelper()
59 | {
60 | if (helper == null)
61 | {
62 | if (!IsRunningAsUwp())
63 | {
64 | helper = new DesktopHelper();
65 | }
66 | else
67 | {
68 | helper = new UwpHelper();
69 | }
70 | }
71 |
72 | return helper;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/WallpaperApi.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Runtime.InteropServices;
7 | using System.Threading;
8 | using WinDynamicDesktop.COM;
9 |
10 | namespace WinDynamicDesktop
11 | {
12 | public class WallpaperApi
13 | {
14 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
15 | private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
16 |
17 | [DllImport("user32.dll", CharSet = CharSet.Auto)]
18 | private static extern int SendMessageTimeout(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam, uint fuFlags,
19 | uint uTimeout, out IntPtr result);
20 |
21 | public static void EnableTransitions()
22 | {
23 | IntPtr result = IntPtr.Zero;
24 | SendMessageTimeout(FindWindow("Progman", null), 0x52c, IntPtr.Zero, IntPtr.Zero, 0, 500, out result);
25 | }
26 |
27 | public static void SetWallpaper(string imagePath, int displayIndex = -1)
28 | {
29 | EnableTransitions();
30 |
31 | if (displayIndex != -1)
32 | {
33 | IDesktopWallpaper desktopWallpaper = DesktopWallpaperFactory.Create();
34 | string monitorId = desktopWallpaper.GetMonitorDevicePathAt((uint)displayIndex);
35 | desktopWallpaper.SetWallpaper(monitorId, imagePath);
36 | }
37 | else
38 | {
39 | ThreadStart threadStarter = () =>
40 | {
41 | IActiveDesktop _activeDesktop = ActiveDesktopWrapper.GetActiveDesktop();
42 | _activeDesktop.SetWallpaper(imagePath, 0);
43 | _activeDesktop.ApplyChanges(AD_Apply.ALL | AD_Apply.FORCE);
44 |
45 | Marshal.ReleaseComObject(_activeDesktop);
46 | };
47 | Thread thread = new Thread(threadStarter);
48 | thread.SetApartmentState(ApartmentState.STA); // Set the thread to STA (required!)
49 | thread.Start();
50 | thread.Join(2000);
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/DesktopHelper.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using Microsoft.Win32;
6 | using System.Diagnostics;
7 | using System.Windows.Forms;
8 |
9 | namespace WinDynamicDesktop
10 | {
11 | class DesktopHelper : PlatformHelper
12 | {
13 | private const string registryStartupLocation = @"Software\Microsoft\Windows\CurrentVersion\Run";
14 | private const string updateLink = "https://github.com/t1m0thyj/WinDynamicDesktop/releases";
15 |
16 | private bool startOnBoot;
17 |
18 | public override string GetLocalFolder()
19 | {
20 | return Application.StartupPath;
21 | }
22 |
23 | public override void CheckStartOnBoot()
24 | {
25 | using (RegistryKey startupKey = Registry.CurrentUser.OpenSubKey(registryStartupLocation))
26 | {
27 | startOnBoot = startupKey.GetValue("WinDynamicDesktop") != null;
28 | }
29 |
30 | TrayMenu.startOnBootItem.Checked = startOnBoot;
31 | }
32 |
33 | public override void ToggleStartOnBoot()
34 | {
35 | using (RegistryKey startupKey = Registry.CurrentUser.OpenSubKey(registryStartupLocation, true))
36 | {
37 | if (!startOnBoot)
38 | {
39 | startupKey.SetValue("WinDynamicDesktop", Application.ExecutablePath);
40 | }
41 | else
42 | {
43 | startupKey.DeleteValue("WinDynamicDesktop");
44 | }
45 | }
46 |
47 | startOnBoot = !startOnBoot;
48 | TrayMenu.startOnBootItem.Checked = startOnBoot;
49 | }
50 |
51 | public override void OpenUpdateLink()
52 | {
53 | Process.Start(new ProcessStartInfo(updateLink) { UseShellExecute = true });
54 | }
55 |
56 | public override void SetWallpaper(string imagePath, int displayIndex)
57 | {
58 | WallpaperApi.SetWallpaper(imagePath, displayIndex);
59 | }
60 |
61 | public override void SetLockScreen(string imagePath)
62 | {
63 | if (UwpDesktop.IsUwpSupported())
64 | {
65 | LockScreenChanger.UpdateImage(imagePath);
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/LockScreenChanger.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Windows.Forms;
7 |
8 | namespace WinDynamicDesktop
9 | {
10 | class LockScreenChanger
11 | {
12 | private static readonly Func _ = Localization.GetTranslation;
13 | private static ToolStripMenuItem menuItem;
14 | private static int activeDisplayIndex;
15 |
16 | public static ToolStripItem[] GetMenuItems()
17 | {
18 | if ((JsonConfig.settings.lockScreenDisplayIndex != -1 || JsonConfig.settings.lockScreenTheme != null) &&
19 | !UwpDesktop.IsUwpSupported())
20 | {
21 | JsonConfig.settings.lockScreenDisplayIndex = -1;
22 | JsonConfig.settings.lockScreenTheme = null;
23 | }
24 |
25 | menuItem = new ToolStripMenuItem(_("Sync &lockscreen image with {0}"), null, OnLockScreenItemClick);
26 | menuItem.Enabled = UwpDesktop.IsUwpSupported();
27 |
28 | return new ToolStripItem[] {
29 | menuItem
30 | };
31 | }
32 |
33 | public static async void UpdateImage(string imagePath)
34 | {
35 | var imageFile = await Windows.Storage.StorageFile.GetFileFromPathAsync(imagePath);
36 | await Windows.System.UserProfile.LockScreen.SetImageFileAsync(imageFile);
37 | }
38 |
39 | public static void UpdateMenuItems(int? selectedDisplayIndex = null)
40 | {
41 | activeDisplayIndex = Math.Max(0, selectedDisplayIndex ?? JsonConfig.settings.lockScreenDisplayIndex);
42 | menuItem.Checked = menuItem.Enabled && activeDisplayIndex == JsonConfig.settings.lockScreenDisplayIndex;
43 | menuItem.Text = string.Format(_("Sync &lock screen image with {0}"), GetDisplayName());
44 | }
45 |
46 | public static string GetDisplayName()
47 | {
48 | return activeDisplayIndex == 0 ? _("All Displays") : string.Format(_("Display {0}"), activeDisplayIndex);
49 | }
50 |
51 | private static void OnLockScreenItemClick(object sender, EventArgs e)
52 | {
53 | JsonConfig.settings.lockScreenDisplayIndex = JsonConfig.settings.lockScreenDisplayIndex == -1 ?
54 | activeDisplayIndex : -1;
55 | UpdateMenuItems();
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/ThemeJsonValidator.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Linq;
9 |
10 | namespace WinDynamicDesktop
11 | {
12 | class ThemeJsonValidator
13 | {
14 | public static ThemeResult ValidateQuick(ThemeConfig theme)
15 | {
16 | if (string.IsNullOrEmpty(theme.imageFilename) || JsonConfig.IsNullOrEmpty(theme.dayImageList) ||
17 | JsonConfig.IsNullOrEmpty(theme.nightImageList))
18 | {
19 | return new ThemeResult(new MissingFieldsInThemeJSON(theme.themeId));
20 | }
21 |
22 | return new ThemeResult(theme);
23 | }
24 |
25 | public static ThemeResult ValidateFull(ThemeConfig theme)
26 | {
27 | string themePath = Path.Combine("themes", theme.themeId);
28 |
29 | if (Directory.GetFiles(themePath, theme.imageFilename).Length == 0)
30 | {
31 | return new ThemeResult(new NoImagesMatchingPattern(theme.themeId, theme.imageFilename));
32 | }
33 |
34 | foreach (int imageId in GetThemeImageList(theme))
35 | {
36 | string imageFilename = theme.imageFilename.Replace("*", imageId.ToString());
37 | if (!File.Exists(Path.Combine(themePath, imageFilename)))
38 | {
39 | return new ThemeResult(new InvalidImageInThemeJSON(theme.themeId, imageId, imageFilename));
40 | }
41 | }
42 |
43 | return new ThemeResult(theme);
44 | }
45 |
46 | private static List GetThemeImageList(ThemeConfig theme)
47 | {
48 | List imageList = new List();
49 |
50 | if (theme.sunriseImageList != null && !theme.sunriseImageList.SequenceEqual(theme.dayImageList))
51 | {
52 | imageList.AddRange(theme.sunriseImageList);
53 | }
54 |
55 | imageList.AddRange(theme.dayImageList);
56 |
57 | if (theme.sunsetImageList != null && !theme.sunsetImageList.SequenceEqual(theme.dayImageList))
58 | {
59 | imageList.AddRange(theme.sunsetImageList);
60 | }
61 |
62 | imageList.AddRange(theme.nightImageList);
63 | return imageList;
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/COM/PowerSetting.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Runtime.InteropServices;
7 | using System.Windows.Forms;
8 |
9 | namespace WinDynamicDesktop.COM
10 | {
11 | [StructLayout(LayoutKind.Sequential)]
12 | public struct POWERBROADCAST_SETTING
13 | {
14 | public Guid PowerSetting;
15 | public int DataLength;
16 | // Variable length data follows
17 | }
18 |
19 | internal class PowerSetting
20 | {
21 | private static readonly Guid GUID_CONSOLE_DISPLAY_STATE =
22 | new Guid("6fe69556-704a-47a0-8f24-c28d936fda47");
23 |
24 | private static IntPtr hPowerNotify;
25 | private static int? lastDisplayState = null;
26 |
27 | [DllImport("user32.dll")]
28 | private static extern IntPtr RegisterPowerSettingNotification(
29 | IntPtr hRecipient, ref Guid PowerSettingGuid, int Flags);
30 |
31 | [DllImport("user32.dll")]
32 | private static extern bool UnregisterPowerSettingNotification(IntPtr Handle);
33 |
34 | public static void RegisterWindow(IntPtr windowHandle)
35 | {
36 | Guid guid = GUID_CONSOLE_DISPLAY_STATE;
37 | hPowerNotify = RegisterPowerSettingNotification(windowHandle, ref guid, 0);
38 |
39 | if (hPowerNotify != IntPtr.Zero)
40 | {
41 | Application.ApplicationExit += (object sender, EventArgs e) =>
42 | {
43 | UnregisterPowerSettingNotification(hPowerNotify);
44 | };
45 | }
46 | else
47 | {
48 | LoggingHandler.LogMessage("Failed to register for power notifications");
49 | }
50 | }
51 |
52 | public static bool IsExitingStandby(nint msgPtr)
53 | {
54 | var powerSetting = Marshal.PtrToStructure(msgPtr);
55 |
56 | if (powerSetting.PowerSetting.Equals(GUID_CONSOLE_DISPLAY_STATE))
57 | {
58 | IntPtr dataPtr = new(msgPtr.ToInt64() + Marshal.SizeOf());
59 | int displayState = Marshal.ReadInt32(dataPtr);
60 |
61 | bool stateChanged = lastDisplayState.HasValue && lastDisplayState.Value != displayState;
62 | lastDisplayState = displayState;
63 |
64 | // If state changed and display is turning on, assume we are exiting standby
65 | return stateChanged && displayState == 1;
66 | }
67 |
68 | return false;
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/COM/TaskbarProgress.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Runtime.InteropServices;
7 |
8 | namespace WinDynamicDesktop.COM
9 | {
10 | // Code from https://stackoverflow.com/a/24187171/5504760
11 | public static class TaskbarProgress
12 | {
13 | public enum TaskbarStates
14 | {
15 | NoProgress = 0,
16 | Indeterminate = 0x1,
17 | Normal = 0x2,
18 | Error = 0x4,
19 | Paused = 0x8
20 | }
21 |
22 | [ComImport()]
23 | [Guid("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf")]
24 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
25 | private interface ITaskbarList3
26 | {
27 | // ITaskbarList
28 | [PreserveSig]
29 | void HrInit();
30 | [PreserveSig]
31 | void AddTab(IntPtr hwnd);
32 | [PreserveSig]
33 | void DeleteTab(IntPtr hwnd);
34 | [PreserveSig]
35 | void ActivateTab(IntPtr hwnd);
36 | [PreserveSig]
37 | void SetActiveAlt(IntPtr hwnd);
38 |
39 | // ITaskbarList2
40 | [PreserveSig]
41 | void MarkFullscreenWindow(IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool fFullscreen);
42 |
43 | // ITaskbarList3
44 | [PreserveSig]
45 | void SetProgressValue(IntPtr hwnd, UInt64 ullCompleted, UInt64 ullTotal);
46 | [PreserveSig]
47 | void SetProgressState(IntPtr hwnd, TaskbarStates state);
48 | }
49 |
50 | [ComImport()]
51 | [Guid("56fdf344-fd6d-11d0-958a-006097c9a090")]
52 | [ClassInterface(ClassInterfaceType.None)]
53 | private class TaskbarInstance
54 | {
55 | }
56 |
57 | private static ITaskbarList3 taskbarInstance = (ITaskbarList3)new TaskbarInstance();
58 | private static bool taskbarSupported = Environment.OSVersion.Version >= new Version(6, 1);
59 |
60 | public static void SetState(IntPtr windowHandle, TaskbarStates taskbarState)
61 | {
62 | if (taskbarSupported)
63 | {
64 | taskbarInstance.SetProgressState(windowHandle, taskbarState);
65 | }
66 | }
67 |
68 | public static void SetValue(IntPtr windowHandle, double progressValue, double progressMax)
69 | {
70 | if (taskbarSupported)
71 | {
72 | taskbarInstance.SetProgressValue(windowHandle, (ulong)progressValue, (ulong)progressMax);
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/uwp/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | WinDynamicDesktop
6 | Timothy Johnson
7 | Images\StoreLogo.png
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | .ddw
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/scripts/make_previews.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import glob
3 | import json
4 | import os
5 | import sys
6 | from io import BytesIO
7 |
8 | from PIL import Image, ImageFilter
9 |
10 | HAS_MOZJPEG = False
11 | try:
12 | import mozjpeg_lossless_optimization as mozjpeg
13 | HAS_MOZJPEG = True
14 | except ImportError:
15 | pass
16 |
17 | os.chdir(os.path.dirname(os.path.realpath(__file__)))
18 |
19 | img_width = int(sys.argv[1]) if len(sys.argv) > 1 else 1920
20 | img_height = int(img_width * 9 / 16)
21 | jpeg_quality = int(sys.argv[2]) if len(sys.argv) > 2 else 75
22 |
23 | input_dir = "..\\themes"
24 | output_dir = sys.argv[3] if len(sys.argv) > 3 else "../src/resources/images"
25 |
26 | get_middle_item = lambda image_list: image_list[len(image_list) // 2] if image_list else -1
27 |
28 | for theme_dir in glob.glob(f"{input_dir}/**"):
29 | print(f"<- {theme_dir}")
30 |
31 | with open(f"{theme_dir}/theme.json", 'r') as fileobj:
32 | theme_config = json.load(fileobj)
33 | theme_name = os.path.basename(theme_dir)
34 |
35 | sunrise_image_id = get_middle_item(theme_config.get("sunriseImageList"))
36 | day_image_id = theme_config.get("dayHighlight") or get_middle_item(theme_config["dayImageList"])
37 | sunset_image_id = get_middle_item(theme_config.get("sunsetImageList"))
38 | night_image_id = theme_config.get("nightHighlight") or get_middle_item(theme_config["nightImageList"])
39 |
40 | image_filenames = {
41 | "day": theme_config["imageFilename"].replace("*", str(day_image_id)),
42 | "night": theme_config["imageFilename"].replace("*", str(night_image_id))
43 | }
44 | if sunrise_image_id != -1 and sunrise_image_id != day_image_id and sunrise_image_id != night_image_id:
45 | image_filenames["sunrise"] = theme_config["imageFilename"].replace("*", str(sunrise_image_id))
46 | if sunset_image_id != -1 and sunset_image_id != day_image_id and sunset_image_id != night_image_id:
47 | image_filenames["sunset"] = theme_config["imageFilename"].replace("*", str(sunset_image_id))
48 |
49 | for phase, filename in image_filenames.items():
50 | img = Image.open(f"{theme_dir}/{filename}")
51 | img.thumbnail((img_width, img_height))
52 | if jpeg_quality >= 0:
53 | if HAS_MOZJPEG:
54 | jpeg_io = BytesIO()
55 | img.save(jpeg_io, format="JPEG", quality=jpeg_quality)
56 | jpeg_io.seek(0)
57 | with open(f"{output_dir}/{theme_name}_{phase}.jpg", 'wb') as fileobj:
58 | fileobj.write(mozjpeg.optimize(jpeg_io.read()))
59 | else:
60 | img.save(f"{output_dir}/{theme_name}_{phase}.jpg", quality=jpeg_quality)
61 | else:
62 | img.save(f"{output_dir}/{theme_name}_{phase}.png")
63 |
64 | print(f"-> {output_dir}")
65 |
--------------------------------------------------------------------------------
/src/Program.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.IO;
7 | using System.Text;
8 | using System.Windows.Forms;
9 |
10 | namespace WinDynamicDesktop
11 | {
12 | internal static class Program
13 | {
14 | ///
15 | /// The main entry point for the application.
16 | ///
17 | [STAThread]
18 | static void Main(string[] args)
19 | {
20 | string localFolder = UwpDesktop.GetHelper().GetLocalFolder();
21 | Application.ThreadException += (sender, e) => LoggingHandler.LogError(localFolder, e.Exception);
22 | AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
23 | LoggingHandler.LogError(localFolder, e.ExceptionObject as Exception);
24 | Directory.SetCurrentDirectory(FindCwd(localFolder));
25 | LoadDotEnv();
26 |
27 | // To customize application configuration such as set high DPI settings or default font,
28 | // see https://aka.ms/applicationconfiguration.
29 | ApplicationConfiguration.Initialize();
30 | Application.SetColorMode(SystemColorMode.System);
31 | Application.Run(new AppContext(args));
32 | }
33 |
34 | static string FindCwd(string localFolder)
35 | {
36 | string cwd = localFolder;
37 | string pathFile = Path.Combine(localFolder, "WinDynamicDesktop.pth");
38 |
39 | if (File.Exists(pathFile))
40 | {
41 | cwd = Environment.ExpandEnvironmentVariables(File.ReadAllText(pathFile).Trim());
42 |
43 | if (!Directory.Exists(cwd))
44 | {
45 | Directory.CreateDirectory(cwd);
46 | }
47 | }
48 |
49 | return cwd;
50 | }
51 |
52 | static void LoadDotEnv()
53 | {
54 | string envText = Encoding.UTF8.GetString(Properties.Resources.DotEnv);
55 | string[] envLines = envText.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
56 |
57 | foreach (string line in envLines)
58 | {
59 | string[] parts = line.Split('=', 2, StringSplitOptions.RemoveEmptyEntries);
60 |
61 | if (parts.Length == 2)
62 | {
63 | string rawValue = parts[1].StartsWith("base64:") ?
64 | Encoding.UTF8.GetString(Convert.FromBase64String(parts[1].Substring(7))) : parts[1];
65 | Environment.SetEnvironmentVariable(parts[0], rawValue);
66 | }
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/scripts/publish_choco.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import os
3 | import subprocess
4 | import sys
5 | import tempfile
6 |
7 | import requests
8 | from dotenv import load_dotenv
9 |
10 | os.chdir(os.path.dirname(os.path.realpath(__file__)))
11 | load_dotenv()
12 |
13 | chocolatey_repo = "https://push.chocolatey.org/"
14 | nuspec_filename = "windynamicdesktop.nuspec"
15 | script_filename = "tools/chocolateyInstall.ps1"
16 |
17 | def installer_checksum(tag_name, filename):
18 | checksums = {}
19 | r = requests.get(f"https://github.com/t1m0thyj/WinDynamicDesktop/releases/download/{tag_name}/checksums.txt",
20 | allow_redirects=True)
21 | for line in r.text.splitlines():
22 | fst, snd = line.split()
23 | checksums[snd] = fst
24 | return checksums[filename]
25 |
26 | def render_template(filename, replacers):
27 | with open(filename, 'r', encoding="utf-8") as fileobj:
28 | old_text = fileobj.read()
29 | new_text = old_text
30 | for key, value in replacers.items():
31 | new_text = new_text.replace("{{" + key + "}}", value)
32 | write_file(filename, new_text)
33 | return old_text
34 |
35 | def write_file(filename, contents):
36 | with open(filename, 'w', encoding="utf-8") as fileobj:
37 | fileobj.write(contents)
38 |
39 | release_tag = f"tags/{sys.argv[1]}" if len(sys.argv) > 1 else "latest"
40 | r = requests.get(f"https://api.github.com/repos/t1m0thyj/WinDynamicDesktop/releases/{release_tag}")
41 | response = r.json()
42 | installer_url = next(a for a in response["assets"] if a["name"].endswith("x86_Setup.exe"))["browser_download_url"]
43 | installer_url64 = next(a for a in response["assets"] if a["name"].endswith("x64_Setup.exe"))["browser_download_url"]
44 | package_version = response["tag_name"].removeprefix("v")
45 | replacers = {
46 | "installerChecksum": installer_checksum(response["tag_name"], os.path.basename(installer_url)),
47 | "installerUrl": installer_url,
48 | "installerChecksum64": installer_checksum(response["tag_name"], os.path.basename(installer_url64)),
49 | "installerUrl64": installer_url64,
50 | "packageVersion": package_version,
51 | "releaseNotes": "\n".join(response["body"].splitlines()),
52 | "releaseTagName": response["tag_name"]
53 | }
54 |
55 | old_nuspec = render_template(nuspec_filename, replacers)
56 | old_script = render_template(script_filename, replacers)
57 |
58 | subprocess.run(["choco", "pack", "--out", tempfile.gettempdir()])
59 |
60 | write_file(nuspec_filename, old_nuspec)
61 | write_file(script_filename, old_script)
62 |
63 | nupkg_filename = f"windynamicdesktop.{package_version}.nupkg"
64 | if os.getenv("CI") or input(f"Push {nupkg_filename}? (y/N) ").lower() == "y":
65 | subprocess.run(["choco", "push", os.path.join(tempfile.gettempdir(), nupkg_filename), "-s", chocolatey_repo, "-k",
66 | os.getenv("CHOCO_API_KEY")])
67 |
--------------------------------------------------------------------------------
/src/UwpLocation.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Threading.Tasks;
7 |
8 | namespace WinDynamicDesktop
9 | {
10 | class UwpLocation
11 | {
12 | private static readonly Func _ = Localization.GetTranslation;
13 | public static Exception lastUpdateError;
14 |
15 | private static async Task UnsafeRequestAccess()
16 | {
17 | var accessStatus = await Windows.Devices.Geolocation.Geolocator.RequestAccessAsync();
18 |
19 | return (accessStatus == Windows.Devices.Geolocation.GeolocationAccessStatus.Allowed);
20 | }
21 |
22 | public static bool HasAccess()
23 | {
24 | bool hasAccess = false;
25 |
26 | try
27 | {
28 | hasAccess = Task.Run(() => UnsafeRequestAccess()).Result;
29 | }
30 | catch // Error when attempting to show UWP location prompt in WinForms app
31 | {
32 | hasAccess = false;
33 | }
34 |
35 | return hasAccess;
36 | }
37 |
38 | public static async Task RequestAccess()
39 | {
40 | bool hasAccess = HasAccess();
41 |
42 | if (!hasAccess)
43 | {
44 | AppContext.ShowPopup(_("In Windows location settings, grant location access to WinDynamicDesktop. " +
45 | "Then return to the app and click \"Check for Permission\"."));
46 |
47 | await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings:privacy-location"));
48 | }
49 |
50 | return hasAccess;
51 | }
52 |
53 | private static async Task UnsafeUpdateGeoposition()
54 | {
55 | var geolocator = new Windows.Devices.Geolocation.Geolocator();
56 | var geoposition = await geolocator.GetGeopositionAsync(maximumAge: TimeSpan.FromMinutes(1),
57 | timeout: TimeSpan.FromSeconds(10));
58 |
59 | return geoposition.Coordinate.Point.Position;
60 | }
61 |
62 | public static async Task UpdateGeoposition()
63 | {
64 | try
65 | {
66 | var pos = await UnsafeUpdateGeoposition();
67 | JsonConfig.settings.latitude = pos.Latitude;
68 | JsonConfig.settings.longitude = pos.Longitude;
69 |
70 | return true;
71 | }
72 | catch (Exception exc)
73 | {
74 | lastUpdateError = exc;
75 | }
76 |
77 | return false;
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches: [ '**' ]
6 | tags-ignore: [ '**' ]
7 | pull_request:
8 |
9 | jobs:
10 | build:
11 | runs-on: windows-2022
12 | if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
13 |
14 | strategy:
15 | fail-fast: false
16 | matrix:
17 | platform: [x86, x64, arm64]
18 |
19 | steps:
20 | - uses: actions/checkout@v6
21 |
22 | - uses: actions/setup-dotnet@v5
23 | with:
24 | dotnet-version: 10.0.x
25 |
26 | - uses: microsoft/setup-msbuild@v2
27 |
28 | - run: echo ${{ secrets.ENV_FILE }} | base64 --decode > .env
29 | shell: bash
30 |
31 | - run: scripts\version.ps1
32 |
33 | - run: dotnet restore WinDynamicDesktop.sln
34 |
35 | - run: dotnet publish src\WinDynamicDesktop.csproj -a ${{ matrix.platform }} -c Release --no-restore --self-contained -p:EnableCompressionInSingleFile=$env:EnableCompressionInSingleFile -p:IncludeNativeLibrariesForSelfExtract=$env:IncludeNativeLibrariesForSelfExtract -p:PublishSingleFile=$env:PublishSingleFile
36 | env:
37 | EnableCompressionInSingleFile: true
38 | IncludeNativeLibrariesForSelfExtract: true
39 | PublishSingleFile: true
40 |
41 | - uses: actions/upload-artifact@v6
42 | with:
43 | name: exe-${{ matrix.platform }}
44 | path: src\bin\Release\net10.0-windows10.0.19041.0\win-${{ matrix.platform }}\publish\*.exe
45 |
46 | - run: msbuild uwp\WinDynamicDesktop.Package.wapproj /v:m /p:AppxBundle=$env:AppxBundle /p:Configuration=$env:Configuration /p:Platform=$env:Platform /p:UapAppxPackageBuildMode=$env:UapAppxPackageBuildMode /p:UseTemporarySignCert=$env:UseTemporarySignCert
47 | env:
48 | AppxBundle: Never
49 | Configuration: Release
50 | Platform: ${{ matrix.platform }}
51 | UapAppxPackageBuildMode: SideloadOnly
52 | UseTemporarySignCert: true
53 |
54 | - uses: actions/upload-artifact@v6
55 | with:
56 | name: msix-${{ matrix.platform }}
57 | path: uwp\AppPackages\*\*.msix
58 |
59 | test:
60 | runs-on: windows-2025
61 | if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
62 | needs: build
63 |
64 | steps:
65 | - uses: actions/checkout@v6
66 |
67 | - uses: actions/setup-dotnet@v5
68 | with:
69 | dotnet-version: 10.0.x
70 |
71 | - run: |
72 | cp .env.example .env
73 | dotnet restore WinDynamicDesktop.sln
74 |
75 | - name: Unit Tests
76 | run: dotnet test --filter type!=system --verbosity normal
77 | working-directory: test
78 |
79 | - run: |
80 | npm install -g appium
81 | Start-Process -FilePath "C:\Program Files (x86)\Windows Application Driver\WinAppDriver.exe"
82 |
83 | - uses: actions/download-artifact@v7
84 | with:
85 | name: exe-x64
86 | path: test\bin
87 |
88 | - name: System Tests
89 | run: dotnet test --filter type=system --verbosity normal
90 | working-directory: test
91 |
92 | - uses: actions/upload-artifact@v6
93 | if: ${{ failure() }}
94 | with:
95 | name: test-dir
96 | path: |
97 | test\bin
98 | !test\bin\*.exe
99 |
--------------------------------------------------------------------------------
/src/DefaultThemes.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using Newtonsoft.Json;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.IO;
9 | using System.Linq;
10 |
11 | namespace WinDynamicDesktop
12 | {
13 | class DefaultThemes
14 | {
15 | private static string[] yamlLines = Array.Empty();
16 | public static string windowsLockScreenFolder = Environment.ExpandEnvironmentVariables(
17 | @"%SystemRoot%\Web\Screen");
18 | public static string windowsWallpaperFolder = Environment.ExpandEnvironmentVariables(
19 | @"%SystemRoot%\Web\Wallpaper\Windows");
20 |
21 | public static string[] GetDefaultThemes()
22 | {
23 | string yamlText = Properties.Resources.DefaultThemesYaml;
24 | yamlLines = yamlText.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
25 | .Select((line) => line.Trim())
26 | .Where((line) => !line.StartsWith('#')).ToArray();
27 |
28 | return yamlLines.Where((line) => !line.StartsWith('-'))
29 | .Select((line) => line.TrimEnd(':')).ToArray();
30 | }
31 |
32 | public static ThemeConfig GetWindowsTheme()
33 | {
34 | if (Environment.OSVersion.Version.Build >= 22000 && Directory.Exists(windowsWallpaperFolder))
35 | {
36 | return new ThemeConfig
37 | {
38 | themeId = "Windows_11",
39 | imageFilename = "img*.jpg",
40 | imageCredits = "Microsoft",
41 | dayImageList = new[] { 0 },
42 | nightImageList = new[] { 19 }
43 | };
44 | }
45 |
46 | return null;
47 | }
48 |
49 | public static Uri[] GetThemeUriList(string themeId)
50 | {
51 | int startIndex = Array.FindIndex(yamlLines, (line) => line == themeId + ":") + 1;
52 | List uriList = new List();
53 |
54 | while ((startIndex < yamlLines.Length) && yamlLines[startIndex].StartsWith("- "))
55 | {
56 | uriList.Add(new Uri(yamlLines[startIndex].Substring(2)));
57 | startIndex++;
58 | }
59 |
60 | return uriList.ToArray();
61 | }
62 |
63 | public static void InstallWindowsTheme(ThemeConfig theme)
64 | {
65 | string themePath = Path.Combine("themes", theme.themeId);
66 | Directory.CreateDirectory(themePath);
67 | string jsonText = JsonConvert.SerializeObject(theme, Formatting.Indented, new JsonSerializerSettings
68 | {
69 | NullValueHandling = NullValueHandling.Ignore,
70 | });
71 | File.WriteAllText(Path.Combine(themePath, "theme.json"), jsonText);
72 | string[] imagePaths = Directory.GetFiles(windowsWallpaperFolder, theme.imageFilename);
73 | foreach (string imagePath in imagePaths)
74 | {
75 | File.Copy(imagePath, Path.Combine(themePath, Path.GetFileName(imagePath)), true);
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | # WinDynamicDesktop
10 | Port of macOS Mojave Dynamic Desktop feature to Windows 10 and 11. Available on GitHub and the Microsoft Store.
11 |
12 |
13 |
14 |
15 | ## Themes
16 |
17 | Pick from bundled macOS themes, browse hundreds of themes [available online](https://windd.info/themes/), or [create your own](https://github.com/t1m0thyj/WinDynamicDesktop/wiki/Creating-custom-themes)
18 |
19 | 
20 |
21 | ## Schedule
22 |
23 | Choose a schedule for cycling through wallpaper images over 24 hours
24 |
25 | 
26 |
27 | ## Scripts
28 |
29 | Extend the behavior of WinDynamicDesktop with PowerShell scripts, and share them with other users [here](https://github.com/t1m0thyj/WDD-scripts#readme)
30 |
31 | ## Supported Devices
32 |
33 | WinDynamicDesktop is developed primarily for Windows 11, but should run on any device with Windows 7 or newer. Windows Insider builds are not officially supported.
34 |
35 | ## Resources
36 |
37 | * [Get Help](https://github.com/t1m0thyj/WinDynamicDesktop/wiki)
38 | * [Translate on POEditor](https://poeditor.com/join/project/DEgfVpyuiK)
39 | * [.ddw Theme Creator](https://ddw-theme-creator.vercel.app/) (thanks @gdstewart)
40 |
41 | ## Known Issues
42 |
43 | * [Wallpaper fit not remembered in Microsoft Store app](https://github.com/t1m0thyj/WinDynamicDesktop/wiki/Known-issues#wallpaper-fit-not-saved-with-multiple-monitors)
44 | * [Wallpaper gets stuck and won't update](https://github.com/t1m0thyj/WinDynamicDesktop/wiki/Known-issues#wallpaper-gets-stuck-and-wont-update)
45 |
46 | ## Limitations
47 | * [Can't show separate images on multiple virtual desktops](https://github.com/t1m0thyj/WinDynamicDesktop/issues/299)
48 |
49 | ## Disclaimers
50 |
51 | * Wallpaper images are not owned by me, they belong to Apple
52 | * [LocationIQ API](https://locationiq.org/) is used when your enter your location, to convert it to latitude and longitude
53 | * Microsoft Store app uses the Windows location API if permission is granted
54 | * App icon made by [Roundicons](https://www.flaticon.com/authors/roundicons) from [flaticon.com](https://www.flaticon.com/) and is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/)
55 |
--------------------------------------------------------------------------------
/src/WinDynamicDesktop.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net10.0-windows10.0.19041.0
4 | net10.0
5 | win-x86;win-x64;win-arm64
6 | AnyCPU;x86;x64;ARM64
7 | WinExe
8 | WinDynamicDesktop
9 | WinDynamicDesktop
10 | WinDynamicDesktop
11 | WinDynamicDesktop
12 | Copyright © 2025 Timothy Johnson
13 | 5.6.1
14 | resources\WinDynamicDesktop.ico
15 | true
16 |
17 |
18 | true
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | all
36 | runtime; build; native; contentfiles; analyzers; buildtransitive
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | True
48 | True
49 | Resources.resx
50 |
51 |
52 |
53 |
54 | ResXFileCodeGenerator
55 | Resources.Designer.cs
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/LanguageDialog.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Globalization;
8 | using System.Linq;
9 | using System.Windows.Forms;
10 |
11 | namespace WinDynamicDesktop
12 | {
13 | public partial class LanguageDialog : Form
14 | {
15 | private static readonly Func _ = Localization.GetTranslation;
16 | private const string translateLink = "https://poeditor.com/join/project/DEgfVpyuiK";
17 | private List languageNames = new List();
18 |
19 | public LanguageDialog()
20 | {
21 | InitializeComponent();
22 | Localization.TranslateForm(this);
23 | this.linkLabel1.LinkColor = System.Drawing.SystemColors.HotTrack;
24 | }
25 |
26 | private void LanguageDialog_Load(object sender, EventArgs e)
27 | {
28 | foreach (string langCode in Localization.languageCodes)
29 | {
30 | try
31 | {
32 | languageNames.Add(new CultureInfo(langCode).NativeName);
33 | }
34 | catch (CultureNotFoundException)
35 | {
36 | languageNames.Add(null);
37 | }
38 | }
39 | comboBox1.Items.AddRange(languageNames.Where(x => x != null).ToArray());
40 | comboBox1.Sorted = true;
41 |
42 | int langIndex = Array.IndexOf(Localization.languageCodes, Localization.currentLocale);
43 | if (langIndex != -1)
44 | {
45 | comboBox1.SelectedItem = languageNames[langIndex];
46 | }
47 | }
48 |
49 | private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
50 | {
51 | System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(translateLink) { UseShellExecute = true });
52 | }
53 |
54 | private void okButton_Click(object sender, EventArgs e)
55 | {
56 | int langIndex = languageNames.IndexOf((string)comboBox1.SelectedItem);
57 | string languageCode = Localization.languageCodes[langIndex];
58 |
59 | if (languageCode != Localization.currentLocale)
60 | {
61 | Localization.currentLocale = languageCode;
62 |
63 | if (AppContext.notifyIcon == null) // Has UI been loaded yet?
64 | {
65 | Localization.LoadLocaleFromFile();
66 | }
67 |
68 | JsonConfig.settings.language = languageCode;
69 |
70 | if (AppContext.notifyIcon != null)
71 | {
72 | DialogResult result = MessageDialog.ShowQuestion(_("WinDynamicDesktop needs to restart for the " +
73 | "language to change. Do you want to restart the app now?"), _("Question"));
74 |
75 | if (result == DialogResult.Yes)
76 | {
77 | JsonConfig.ReloadConfig();
78 | }
79 | }
80 | }
81 |
82 | this.Close();
83 | }
84 |
85 | private void cancelButton_Click(object sender, EventArgs e)
86 | {
87 | this.Close();
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/ImportDialog.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Threading.Tasks;
9 | using System.Windows.Forms;
10 | using WinDynamicDesktop.COM;
11 |
12 | namespace WinDynamicDesktop
13 | {
14 | public partial class ImportDialog : Form
15 | {
16 | private static readonly Func _ = Localization.GetTranslation;
17 | private Queue importQueue;
18 | private int numJobs;
19 | public bool thumbnailsLoaded = false;
20 |
21 | public ImportDialog()
22 | {
23 | InitializeComponent();
24 | Localization.TranslateForm(this);
25 |
26 | this.FormClosing += OnFormClosing;
27 | ThemeLoader.taskbarHandle = this.Handle;
28 | }
29 |
30 | public void InitImport(List themePaths)
31 | {
32 | ThemeManager.importMode = true;
33 | importQueue = new Queue(themePaths);
34 | numJobs = importQueue.Count;
35 |
36 | Task.Run(() => ImportNext());
37 | }
38 |
39 | private void ImportNext()
40 | {
41 | if (ThemeManager.importPaths.Count > 0)
42 | {
43 | foreach (string themePath in ThemeManager.importPaths)
44 | {
45 | importQueue.Enqueue(themePath);
46 | }
47 |
48 | numJobs += ThemeManager.importPaths.Count;
49 | ThemeManager.importPaths.Clear();
50 | }
51 |
52 | this.Invoke(new Action(() => UpdateTotalPercentage(0)));
53 |
54 | if (importQueue.Count > 0)
55 | {
56 | string themePath = importQueue.Peek();
57 | this.Invoke(new Action(() =>
58 | label1.Text = string.Format(_("Importing theme from {0}..."), Path.GetFileName(themePath))));
59 |
60 | ThemeResult result = ThemeManager.ImportTheme(themePath);
61 | result.Match(e => this.Invoke(new Action(() => ThemeLoader.HandleError(e))),
62 | theme => ThemeManager.importedThemes.Add(theme));
63 |
64 | importQueue.Dequeue();
65 | ImportNext();
66 | }
67 | else
68 | {
69 | this.Invoke(new Action(() =>
70 | {
71 | label1.Text = _("Generating thumbnails, please wait...");
72 | this.Close();
73 | }));
74 | }
75 | }
76 |
77 | private void UpdateTotalPercentage(int themePercentage)
78 | {
79 | int percentage = ((numJobs - importQueue.Count) * 100 + themePercentage) / numJobs;
80 |
81 | progressBar1.Invoke(new Action(() =>
82 | {
83 | progressBar1.Value = percentage;
84 | progressBar1.Refresh();
85 | TaskbarProgress.SetValue(this.Handle, percentage, 100);
86 | }));
87 | }
88 |
89 | private void OnFormClosing(object sender, FormClosingEventArgs e)
90 | {
91 | ThemeManager.importMode = false;
92 | if (thumbnailsLoaded)
93 | {
94 | ThemeLoader.taskbarHandle = IntPtr.Zero;
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/HiddenForm.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Runtime.InteropServices;
7 | using System.Windows.Forms;
8 | using WinDynamicDesktop.COM;
9 |
10 | namespace WinDynamicDesktop
11 | {
12 | // https://github.com/specshell/specshell.software.winform.hiddenform/blob/main/src/Specshell.WinForm.HiddenForm/HiddenForm.cs
13 | internal class HiddenForm : Form
14 | {
15 | private const int ENDSESSION_CLOSEAPP = 0x1;
16 | private const int WM_QUERYENDSESSION = 0x11;
17 | private const int WM_ENDSESSION = 0x16;
18 | private const int WM_SETTINGCHANGE = 0x1A;
19 | private const int WM_POWERBROADCAST = 0x0218;
20 | private const int PBT_POWERSETTINGCHANGE = 0x8013;
21 |
22 | [DllImport("user32.dll")]
23 | private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hwndNewParent);
24 |
25 | public HiddenForm()
26 | {
27 | this.Load += this.HiddenForm_Load;
28 | }
29 |
30 | protected override CreateParams CreateParams
31 | {
32 | get
33 | {
34 | const int WS_POPUP = unchecked((int)0x80000000);
35 | const int WS_EX_TOOLWINDOW = 0x80;
36 |
37 | CreateParams cp = base.CreateParams;
38 | cp.ExStyle = WS_EX_TOOLWINDOW;
39 | cp.Style = WS_POPUP;
40 | cp.Height = 0;
41 | cp.Width = 0;
42 | return cp;
43 | }
44 | }
45 |
46 | protected override void WndProc(ref Message m)
47 | {
48 | // https://github.com/rocksdanister/lively/blob/9142f6a4cfc222cd494f205a5daaa1a0238282e3/src/Lively/Lively/Views/WindowMsg/WndProcMsgWindow.xaml.cs#L41
49 | switch (m.Msg)
50 | {
51 | case WM_QUERYENDSESSION:
52 | if ((m.LParam & ENDSESSION_CLOSEAPP) != 0)
53 | {
54 | UpdateChecker.RegisterApplicationRestart(null, (int)RestartFlags.RESTART_NO_CRASH |
55 | (int)RestartFlags.RESTART_NO_HANG | (int)RestartFlags.RESTART_NO_REBOOT);
56 | }
57 | m.Result = new IntPtr(1);
58 | break;
59 | case WM_ENDSESSION:
60 | if ((m.LParam & ENDSESSION_CLOSEAPP) != 0)
61 | {
62 | Application.Exit();
63 | }
64 | m.Result = IntPtr.Zero;
65 | break;
66 | case WM_SETTINGCHANGE:
67 | if (Marshal.PtrToStringUni(m.LParam) == "ImmersiveColorSet")
68 | {
69 | AppContext.HandleThemeChange();
70 | }
71 | break;
72 | case WM_POWERBROADCAST:
73 | if (m.WParam.ToInt32() == PBT_POWERSETTINGCHANGE && PowerSetting.IsExitingStandby(m.LParam))
74 | {
75 | AppContext.scheduler.HandleResumeFromStandby();
76 | }
77 | break;
78 | }
79 |
80 | base.WndProc(ref m);
81 | }
82 |
83 | private void HiddenForm_Load(object source, EventArgs e)
84 | {
85 | const int HWND_MESSAGE = -1;
86 | SetParent(this.Handle, new IntPtr(HWND_MESSAGE));
87 |
88 | if (UwpDesktop.IsWinRtSupported())
89 | {
90 | PowerSetting.RegisterWindow(this.Handle);
91 | }
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/COM/DesktopWallpaper.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Runtime.InteropServices;
7 |
8 | namespace WinDynamicDesktop.COM
9 | {
10 | // https://github.com/Grabacr07/SylphyHorn/blob/79e200cf7aec5b797b0cd0146ad4972150bd79db/source/SylphyHorn/Interop/IDesktopWallpaper.cs
11 | [StructLayout(LayoutKind.Sequential)]
12 | public struct COLORREF
13 | {
14 | public byte R;
15 | public byte G;
16 | public byte B;
17 | }
18 |
19 | [StructLayout(LayoutKind.Sequential)]
20 | public struct RECT
21 | {
22 | public int Left;
23 | public int Top;
24 | public int Right;
25 | public int Bottom;
26 | }
27 |
28 | public enum DesktopSlideshowOptions
29 | {
30 | DSO_SHUFFLEIMAGES = 0x01,
31 | }
32 |
33 | public enum DesktopSlideshowState
34 | {
35 | DSS_ENABLED = 0x01,
36 | DSS_SLIDESHOW = 0x02,
37 | DSS_DISABLED_BY_REMOTE_SESSION = 0x04,
38 | }
39 |
40 | public enum DesktopSlideshowDirection
41 | {
42 | DSD_FORWARD = 0,
43 | DSD_BACKWARD,
44 | }
45 |
46 | public enum DesktopWallpaperPosition
47 | {
48 | DWPOS_CENTER = 0,
49 | DWPOS_TILE,
50 | DWPOS_STRETCH,
51 | DWPOS_FIT,
52 | DWPOS_FILL,
53 | DWPOS_SPAN,
54 | }
55 |
56 | [ComImport]
57 | [Guid("B92B56A9-8B55-4E14-9A89-0199BBB6F93B")]
58 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
59 | public interface IDesktopWallpaper
60 | {
61 | void SetWallpaper([MarshalAs(UnmanagedType.LPWStr)] string monitorID, [MarshalAs(UnmanagedType.LPWStr)] string wallpaper);
62 |
63 | [return: MarshalAs(UnmanagedType.LPWStr)]
64 | string GetWallpaper([MarshalAs(UnmanagedType.LPWStr)] string monitorID);
65 |
66 | [return: MarshalAs(UnmanagedType.LPWStr)]
67 | string GetMonitorDevicePathAt(uint monitorIndex);
68 |
69 | uint GetMonitorDevicePathCount();
70 |
71 | RECT GetMonitorRECT([MarshalAs(UnmanagedType.LPWStr)] string monitorID);
72 |
73 | void SetBackgroundColor([MarshalAs(UnmanagedType.U4)] COLORREF color);
74 |
75 | COLORREF GetBackgroundColor();
76 |
77 | void SetPosition([MarshalAs(UnmanagedType.I4)] DesktopWallpaperPosition position);
78 |
79 | [return: MarshalAs(UnmanagedType.I4)]
80 | DesktopWallpaperPosition GetPosition();
81 |
82 | void SetSlideshow(IntPtr /* IShellItemArray* */ items);
83 |
84 | IntPtr /* IShellItemArray* */ GetSlideshow();
85 |
86 | void SetSlideshowOptions(DesktopSlideshowOptions options, uint slideshowTick);
87 |
88 | void GetSlideshowOptions(out DesktopSlideshowOptions options, out uint slideshowTick);
89 |
90 | void AdvanceSlideshow([MarshalAs(UnmanagedType.LPWStr)] string monitorID, [MarshalAs(UnmanagedType.I4)] DesktopSlideshowDirection direction);
91 |
92 | DesktopSlideshowState GetStatus();
93 |
94 | void Enable([MarshalAs(UnmanagedType.Bool)] bool enable);
95 | }
96 |
97 | public static class DesktopWallpaperFactory
98 | {
99 | [ComImport]
100 | [Guid("C2CF3110-460E-4fc1-B9D0-8A1C0C9CC4BD")]
101 | class DesktopWallpaperCoclass { }
102 |
103 | public static IDesktopWallpaper Create()
104 | {
105 | return (IDesktopWallpaper)new DesktopWallpaperCoclass();
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/scripts/i18n_gettext.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import glob
3 | import os
4 | import re
5 | import sys
6 | import time
7 | from collections import OrderedDict
8 |
9 | os.chdir(os.path.dirname(os.path.realpath(__file__)))
10 |
11 | app_name = "WinDynamicDesktop"
12 | exclude_patterns = [
13 | r'^' + app_name + r'$', # Program name
14 | r'^\w+:\S+', # URL protocol
15 | r'^[a-z]+[A-Z0-9]+\w*$', # WinForms control names
16 | r'.+Dialog$', # WinForms dialog names
17 | ]
18 | pot_data = OrderedDict()
19 |
20 |
21 | def add_to_pot_data(msgid, filename, lineno):
22 | if any([re.match(pattern, msgid) for pattern in exclude_patterns]):
23 | return
24 | elif msgid not in pot_data:
25 | pot_data[msgid] = [(filename, lineno)]
26 | else:
27 | pot_data[msgid].append((filename, lineno))
28 |
29 |
30 | for filename in glob.glob("../src/**/*.cs", recursive=True):
31 | with open(filename, 'r', encoding="utf-8") as cs_file:
32 | if not filename.endswith(".Designer.cs"):
33 | msg_history = []
34 |
35 | for i, line in enumerate(cs_file):
36 | if msg_history:
37 | match = re.search(r'^\s*"(.+?)"(\)?)', line)
38 |
39 | if match:
40 | msg_history[0] += match.group(1)
41 |
42 | if match.group(2):
43 | add_to_pot_data(msg_history[0], filename[3:], msg_history[1])
44 | msg_history = []
45 |
46 | for match in re.finditer(r'(?:_|Localization\.GetTranslation)\("(.+?)"\)', line):
47 | add_to_pot_data(match.group(1), filename[3:], i + 1)
48 |
49 | if not msg_history:
50 | match = re.search(r'(?:_|Localization\.GetTranslation)\((?:"(.+)")?\s', line)
51 |
52 | if match:
53 | msg_history = [match.group(1) or "", i + 1]
54 | elif os.path.dirname(filename).endswith("src"):
55 | for i, line in enumerate(cs_file):
56 | match = re.search(r'\W"(.+)"\W', line)
57 |
58 | if match and not line.lstrip().startswith("//"):
59 | add_to_pot_data(match.group(1), filename[3:], i + 1)
60 |
61 | pot_lines = [
62 | "# SOME DESCRIPTIVE TITLE",
63 | "# Copyright (C) YEAR ORGANIZATION",
64 | "# FIRST AUTHOR , YEAR.",
65 | "#",
66 | "msgid \"\"",
67 | "msgstr \"\"",
68 | "\"Project-Id-Version: {}\\n\"".format(app_name),
69 | "\"POT-Creation-Date: {}\\n\"".format(time.strftime("%Y-%m-%d %H:%M%z")),
70 | "\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"",
71 | "\"Last-Translator: FULL NAME \\n\"",
72 | "\"Language-Team: LANGUAGE \\n\"",
73 | "\"MIME-Version: 1.0\\n\"",
74 | "\"Content-Type: text/plain; charset=UTF-8\\n\"",
75 | "\"Content-Transfer-Encoding: 8bit\\n\""
76 | ]
77 |
78 | for msgid, locs in pot_data.items():
79 | pot_lines.append("")
80 | pot_lines.append("#: {}".format(" ".join(["{}:{}".format(filename, lineno) for filename, lineno in locs])))
81 |
82 | if "\\n" in msgid:
83 | msgid_lines = msgid.split("\\n")
84 |
85 | for i in range(len(msgid_lines) - 1):
86 | msgid_lines[i] += "\\n"
87 |
88 | pot_lines.append("msgid \"\"")
89 | pot_lines.extend(["\"{}\"".format(line) for line in msgid_lines])
90 | else:
91 | pot_lines.append("msgid \"{}\"".format(msgid))
92 |
93 | pot_lines.append("msgstr \"\"")
94 |
95 | with open(sys.argv[1] if len(sys.argv) > 1 else "../src/locale/messages.pot", 'w', encoding="utf-8") as pot_file:
96 | for line in pot_lines:
97 | print(line, file=pot_file)
98 |
--------------------------------------------------------------------------------
/src/LocationManager.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using RestSharp;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Globalization;
9 | using System.Threading.Tasks;
10 | using System.Windows.Forms;
11 |
12 | namespace WinDynamicDesktop
13 | {
14 | class LocationManager
15 | {
16 | private static readonly Func _ = Localization.GetTranslation;
17 | private static ScheduleDialog locationDialog;
18 |
19 | public static void Initialize()
20 | {
21 | if (JsonConfig.settings.locationMode == 1 && !UwpLocation.HasAccess())
22 | {
23 | JsonConfig.settings.locationMode = 0;
24 | JsonConfig.settings.latitude = null;
25 | JsonConfig.settings.longitude = null;
26 | }
27 | }
28 |
29 | public static void ChangeLocation()
30 | {
31 | if (locationDialog == null)
32 | {
33 | locationDialog = new ScheduleDialog();
34 | locationDialog.FormClosed += OnLocationDialogClosed;
35 | locationDialog.Show();
36 | }
37 |
38 | locationDialog.BringToFront();
39 | }
40 |
41 | private static void OnLocationDialogClosed(object sender, FormClosedEventArgs e)
42 | {
43 | if (e.CloseReason == CloseReason.UserClosing)
44 | {
45 | locationDialog = null;
46 | LaunchSequence.NextStep();
47 | }
48 | }
49 |
50 | public static async Task FetchLocationData(string locationStr, ScheduleDialog dialog)
51 | {
52 | var client = new RestClient("https://us1.locationiq.com");
53 |
54 | var request = new RestRequest("v1/search.php");
55 | request.AddParameter("key", Environment.GetEnvironmentVariable("LOCATIONIQ_API_KEY"));
56 | request.AddParameter("q", locationStr);
57 | request.AddParameter("format", "json");
58 | request.AddParameter("limit", "1");
59 |
60 | var response = await client.ExecuteAsync>(request);
61 |
62 | if (response.IsSuccessful)
63 | {
64 | JsonConfig.settings.location = locationStr;
65 | HandleLocationSuccess(response.Data[0], dialog);
66 | }
67 | else
68 | {
69 | MessageDialog.ShowWarning(_("The location you entered was invalid, or you are not connected to " +
70 | "the Internet. Check your Internet connection and try a different location. You can use a " +
71 | "complete address or just the name of your city/region."), _("Error"));
72 | }
73 | }
74 |
75 | private static void HandleLocationSuccess(LocationIQData data, ScheduleDialog dialog)
76 | {
77 | JsonConfig.settings.latitude = double.Parse(data.lat, CultureInfo.InvariantCulture);
78 | JsonConfig.settings.longitude = double.Parse(data.lon, CultureInfo.InvariantCulture);
79 | SolarData solarData = SunriseSunsetService.GetSolarData(DateTime.Today);
80 |
81 | DialogResult result = MessageDialog.ShowQuestion(string.Format(_("Is this location correct?\n\n{0}\n{1}"),
82 | data.display_name, SunriseSunsetService.GetSunriseSunsetString(solarData)), _("Question"));
83 |
84 | if (result == DialogResult.Yes)
85 | {
86 | dialog.Invoke(new Action(() => dialog.HandleScheduleChange()));
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/ImportDialog.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace WinDynamicDesktop
2 | {
3 | partial class ImportDialog
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | label1 = new System.Windows.Forms.Label();
32 | progressBar1 = new System.Windows.Forms.ProgressBar();
33 | panel1 = new System.Windows.Forms.Panel();
34 | panel1.SuspendLayout();
35 | SuspendLayout();
36 | //
37 | // label1
38 | //
39 | label1.AutoSize = true;
40 | label1.Location = new System.Drawing.Point(13, 14);
41 | label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
42 | label1.Name = "label1";
43 | label1.Size = new System.Drawing.Size(175, 15);
44 | label1.TabIndex = 0;
45 | label1.Text = "Importing themes, please wait...";
46 | //
47 | // progressBar1
48 | //
49 | progressBar1.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
50 | progressBar1.Location = new System.Drawing.Point(13, 38);
51 | progressBar1.Margin = new System.Windows.Forms.Padding(4);
52 | progressBar1.Name = "progressBar1";
53 | progressBar1.Size = new System.Drawing.Size(349, 15);
54 | progressBar1.TabIndex = 1;
55 | //
56 | // panel1
57 | //
58 | panel1.AutoSize = true;
59 | panel1.Controls.Add(label1);
60 | panel1.Controls.Add(progressBar1);
61 | panel1.Dock = System.Windows.Forms.DockStyle.Fill;
62 | panel1.Location = new System.Drawing.Point(0, 0);
63 | panel1.Name = "panel1";
64 | panel1.Size = new System.Drawing.Size(378, 77);
65 | panel1.TabIndex = 0;
66 | //
67 | // ImportDialog
68 | //
69 | AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
70 | AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
71 | AutoSize = true;
72 | ClientSize = new System.Drawing.Size(378, 77);
73 | ControlBox = false;
74 | Controls.Add(panel1);
75 | FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
76 | Icon = Properties.Resources.AppIcon;
77 | Margin = new System.Windows.Forms.Padding(4);
78 | Name = "ImportDialog";
79 | StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
80 | Text = "WinDynamicDesktop";
81 | panel1.ResumeLayout(false);
82 | panel1.PerformLayout();
83 | ResumeLayout(false);
84 | PerformLayout();
85 | }
86 |
87 | #endregion
88 |
89 | private System.Windows.Forms.Label label1;
90 | private System.Windows.Forms.ProgressBar progressBar1;
91 | private System.Windows.Forms.Panel panel1;
92 | }
93 | }
--------------------------------------------------------------------------------
/src/AboutDialog.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Drawing;
7 | using System.Reflection;
8 | using System.Text;
9 | using System.Windows.Forms;
10 |
11 | namespace WinDynamicDesktop
12 | {
13 | public partial class AboutDialog : Form
14 | {
15 | private static readonly Func _ = Localization.GetTranslation;
16 | private readonly string websiteLink = "https://windd.info";
17 | private readonly string donateLink = "https://paypal.me/t1m0thyj";
18 | private readonly string rateLink = "ms-windows-store://review/?ProductId=9NM8N7DQ3Z5F";
19 |
20 | public AboutDialog()
21 | {
22 | InitializeComponent();
23 | Localization.TranslateForm(this);
24 | }
25 |
26 | public static string GetVersionString()
27 | {
28 | string versionStr = UpdateChecker.GetCurrentVersion();
29 | versionStr = versionStr.Remove(versionStr.Length - 2);
30 | versionStr += UwpDesktop.IsRunningAsUwp() ? " (UWP)" : string.Empty;
31 | return versionStr;
32 | }
33 |
34 | private void AboutDialog_Load(object sender, EventArgs e)
35 | {
36 | iconBox.Image = (new Icon(Properties.Resources.AppIcon, 64, 64)).ToBitmap();
37 | richTextBox1.Rtf = GetRtfUnicodeEscapedString(GetRtfText());
38 | }
39 |
40 | private string GetRtfLink(string href, string linkText = null)
41 | {
42 | return @"{\field{\*\fldinst HYPERLINK " + '"' + href + '"' + @"}{\fldrslt " + (linkText ?? href) + "}}";
43 | }
44 |
45 | private string GetRtfText()
46 | {
47 | string copyrightLine = ((AssemblyCopyrightAttribute)Attribute.GetCustomAttribute(
48 | Assembly.GetExecutingAssembly(), typeof(AssemblyCopyrightAttribute), false)).Copyright;
49 | string donateRateLine = string.Format(_("If you like the app, please consider {0} or {1} :)"),
50 | GetRtfLink(donateLink, _("donating")), GetRtfLink(rateLink, _("rating it")));
51 |
52 | return @"{\rtf1\pc\fs5\par" +
53 | @"\sb72\qc\fs30\b WinDynamicDesktop " + GetVersionString() + @"\b0\par" +
54 | @"\fs20 " + _("Port of macOS Mojave Dynamic Desktop feature to Windows") + @"\par " +
55 | copyrightLine + @"\par " +
56 | GetRtfLink(websiteLink) + @"\par " +
57 | donateRateLine + @"\par" +
58 | @"\par" +
59 | @"\b " + _("Thanks to:") + @"\b0\par " +
60 | _("Apple for the Mojave wallpapers") + @"\par " +
61 | _("Contributors and translators on GitHub") + @"\par " +
62 | _("LocationIQ for their free geocoding API") + @"\par " +
63 | _("Roundicons from flaticon.com for the icon (licensed by CC 3.0 BY)") + @"\par" +
64 | @"\par }";
65 | }
66 |
67 | // Code from https://stackoverflow.com/q/1368020/5504760
68 | private string GetRtfUnicodeEscapedString(string s)
69 | {
70 | var sb = new StringBuilder();
71 | foreach (var c in s)
72 | {
73 | if (c <= 0x7f)
74 | sb.Append(c);
75 | else
76 | sb.Append("\\u" + Convert.ToUInt32(c) + "?");
77 | }
78 | return sb.ToString();
79 | }
80 |
81 | private void richTextBox1_LinkClicked(object sender, LinkClickedEventArgs e)
82 | {
83 | System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(e.LinkText) { UseShellExecute = true });
84 | }
85 |
86 | private void closeButton_Click(object sender, EventArgs e)
87 | {
88 | this.Close();
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/FodyWeavers.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Used to control if the On_PropertyName_Changed feature is enabled.
12 |
13 |
14 |
15 |
16 | Used to control if the Dependent properties feature is enabled.
17 |
18 |
19 |
20 |
21 | Used to control if the IsChanged property feature is enabled.
22 |
23 |
24 |
25 |
26 | Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form.
27 |
28 |
29 |
30 |
31 | Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project.
32 |
33 |
34 |
35 |
36 | Used to control if equality checks should use the Equals method resolved from the base class.
37 |
38 |
39 |
40 |
41 | Used to control if equality checks should use the static Equals method resolved from the base class.
42 |
43 |
44 |
45 |
46 | Used to turn off build warnings from this weaver.
47 |
48 |
49 |
50 |
51 | Used to turn off build warnings about mismatched On_PropertyName_Changed methods.
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
60 |
61 |
62 |
63 |
64 | A comma-separated list of error codes that can be safely ignored in assembly verification.
65 |
66 |
67 |
68 |
69 | 'false' to turn off automatic generation of the XML Schema file.
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/test/SystemTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Win32;
2 | using OpenQA.Selenium;
3 | using OpenQA.Selenium.Appium;
4 | using OpenQA.Selenium.Appium.Windows;
5 | using System.Drawing;
6 |
7 | namespace WinDynamicDesktop.Tests
8 | {
9 | public class SystemTests : IDisposable
10 | {
11 | private const string AppiumServerUrl = "http://127.0.0.1:4723";
12 | private readonly string AppPath = Path.GetFullPath(@"..\..\..\bin\WinDynamicDesktop.exe");
13 | private readonly WindowsDriver driver;
14 |
15 | public SystemTests()
16 | {
17 | var appCapabilities = new AppiumOptions();
18 | appCapabilities.AddAdditionalCapability("app", AppPath);
19 | appCapabilities.AddAdditionalCapability("platformName", "Windows");
20 | appCapabilities.AddAdditionalCapability("deviceName", "WindowsPC");
21 | driver = new WindowsDriver(new Uri(AppiumServerUrl), appCapabilities);
22 | driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
23 | }
24 |
25 | [Fact, Trait("type", "system")]
26 | public void ShouldUpdateWallpaper()
27 | {
28 | try
29 | {
30 | driver.FindElementByXPath("//Window[@Name='Select Language']").Click();
31 | driver.FindElementByXPath("//Button[@Name='OK']").Click();
32 | Thread.Sleep(TimeSpan.FromSeconds(2));
33 |
34 | if (HandleLocationPrompt()) Thread.Sleep(TimeSpan.FromSeconds(2));
35 | driver.SwitchTo().Window(driver.WindowHandles[0]);
36 | driver.FindElementByXPath("//Window[@Name='Configure Schedule']").Click();
37 | driver.FindElementByAccessibilityId("radioButton3").Click();
38 | driver.FindElementByXPath("//Button[@Name='OK']").Click();
39 | Thread.Sleep(TimeSpan.FromSeconds(2));
40 |
41 | driver.SwitchTo().Window(driver.WindowHandles[0]);
42 | driver.FindElementByXPath("//Window[@Name='Select Theme']").Click();
43 | driver.FindElementByAccessibilityId("listView1").SendKeys(Keys.Control + Keys.End);
44 | driver.FindElementByXPath("//ListItem[@Name='Windows 11']").Click();
45 | driver.FindElementByXPath("//Button[@Name='Apply']").Click();
46 | Thread.Sleep(TimeSpan.FromSeconds(2));
47 |
48 | Assert.Contains(["scripts", "settings.json", "themes"],
49 | Directory.GetFileSystemEntries(Path.GetDirectoryName(AppPath)).Select(Path.GetFileName).ToArray());
50 | Assert.StartsWith(Path.Combine(Path.GetDirectoryName(AppPath), "themes", "Windows_11", "img"), GetWallpaperPath());
51 | }
52 | catch (WebDriverException)
53 | {
54 | TakeScreenshot(Path.Combine(Path.GetDirectoryName(AppPath), "screenshot.png"));
55 | throw;
56 | }
57 | }
58 |
59 | public void Dispose()
60 | {
61 | driver?.Quit();
62 | }
63 |
64 | private bool HandleLocationPrompt()
65 | {
66 | if (driver.WindowHandles.Count == 0)
67 | {
68 | // Default focus is on No button, so Shift+Tab to focus Yes, then Enter to confirm
69 | System.Windows.Forms.SendKeys.SendWait("+{TAB}");
70 | Thread.Sleep(500);
71 | System.Windows.Forms.SendKeys.SendWait("{ENTER}");
72 | return true;
73 | }
74 | return false;
75 | }
76 |
77 | private string? GetWallpaperPath()
78 | {
79 | using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop"))
80 | {
81 | return key?.GetValue("WallPaper") as string;
82 | }
83 | }
84 |
85 | private void TakeScreenshot(string filePath)
86 | {
87 | Rectangle bounds = System.Windows.Forms.Screen.PrimaryScreen.Bounds;
88 | using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
89 | {
90 | using (Graphics g = Graphics.FromImage(bitmap))
91 | {
92 | g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size);
93 | }
94 | bitmap.Save(filePath, System.Drawing.Imaging.ImageFormat.Png);
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/AboutDialog.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace WinDynamicDesktop
2 | {
3 | partial class AboutDialog
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | closeButton = new System.Windows.Forms.Button();
32 | richTextBox1 = new System.Windows.Forms.RichTextBox();
33 | iconBox = new System.Windows.Forms.PictureBox();
34 | ((System.ComponentModel.ISupportInitialize)iconBox).BeginInit();
35 | SuspendLayout();
36 | //
37 | // closeButton
38 | //
39 | closeButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
40 | closeButton.Location = new System.Drawing.Point(241, 261);
41 | closeButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
42 | closeButton.Name = "closeButton";
43 | closeButton.Size = new System.Drawing.Size(88, 27);
44 | closeButton.TabIndex = 0;
45 | closeButton.Text = "Close";
46 | closeButton.UseVisualStyleBackColor = true;
47 | closeButton.Click += closeButton_Click;
48 | //
49 | // richTextBox1
50 | //
51 | richTextBox1.Location = new System.Drawing.Point(15, 97);
52 | richTextBox1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
53 | richTextBox1.Name = "richTextBox1";
54 | richTextBox1.ReadOnly = true;
55 | richTextBox1.Size = new System.Drawing.Size(540, 146);
56 | richTextBox1.TabIndex = 1;
57 | richTextBox1.Text = "";
58 | richTextBox1.LinkClicked += richTextBox1_LinkClicked;
59 | //
60 | // iconBox
61 | //
62 | iconBox.Location = new System.Drawing.Point(243, 14);
63 | iconBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
64 | iconBox.Name = "iconBox";
65 | iconBox.Size = new System.Drawing.Size(84, 74);
66 | iconBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage;
67 | iconBox.TabIndex = 0;
68 | iconBox.TabStop = false;
69 | //
70 | // AboutDialog
71 | //
72 | AcceptButton = closeButton;
73 | AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
74 | AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
75 | ClientSize = new System.Drawing.Size(569, 301);
76 | Controls.Add(iconBox);
77 | Controls.Add(richTextBox1);
78 | Controls.Add(closeButton);
79 | FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
80 | Icon = Properties.Resources.AppIcon;
81 | Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
82 | MaximizeBox = false;
83 | MinimizeBox = false;
84 | Name = "AboutDialog";
85 | StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
86 | Text = "About WinDynamicDesktop";
87 | Load += AboutDialog_Load;
88 | ((System.ComponentModel.ISupportInitialize)iconBox).EndInit();
89 | ResumeLayout(false);
90 | }
91 |
92 | #endregion
93 | private System.Windows.Forms.Button closeButton;
94 | private System.Windows.Forms.RichTextBox richTextBox1;
95 | private System.Windows.Forms.PictureBox iconBox;
96 | }
97 | }
--------------------------------------------------------------------------------
/src/ThemeError.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Linq;
7 |
8 | namespace WinDynamicDesktop
9 | {
10 | public abstract class ThemeError
11 | {
12 | public string errorMsg;
13 | public string themeId;
14 | internal static readonly Func _ = Localization.GetTranslation;
15 |
16 | public ThemeError(string themeId_)
17 | {
18 | themeId = themeId_;
19 | }
20 | }
21 |
22 | class FailedToCopyImage : ThemeError
23 | {
24 | public FailedToCopyImage(string themeId, string imagePath) : base(themeId)
25 | {
26 | errorMsg = string.Format(_("Could not copy image file {0}"), imagePath);
27 | }
28 | }
29 |
30 | class FailedToCreateThumbnail : ThemeError
31 | {
32 | public FailedToCreateThumbnail(string themeId) : base(themeId)
33 | {
34 | errorMsg = _("Failed to generate thumbnail: The image could not be loaded");
35 | }
36 | }
37 |
38 | class FailedToDownloadImages : ThemeError
39 | {
40 | public FailedToDownloadImages(string themeId) : base(themeId)
41 | {
42 | errorMsg = string.Format(_("Could not download images for '{0}' theme"), themeId);
43 | }
44 | }
45 |
46 | class FailedToFindLocation : ThemeError
47 | {
48 | public FailedToFindLocation(string themeId, string path) : base(themeId)
49 | {
50 | errorMsg = string.Format(_("Could not find location {0}"), path);
51 | }
52 | }
53 |
54 | class InvalidImageInThemeJSON : ThemeError
55 | {
56 | public InvalidImageInThemeJSON(string themeId, int imageId, string filename) : base(themeId)
57 | {
58 | errorMsg = string.Format(_("Could not find image {0} at {1}"), imageId, filename);
59 | }
60 | }
61 |
62 | class InvalidThemeJSON : ThemeError
63 | {
64 | public InvalidThemeJSON(string themeId, string message) : base(themeId)
65 | {
66 | errorMsg = string.Format(_("Could not read theme JSON file: {0}"), message);
67 | }
68 | }
69 |
70 | class InvalidZIP : ThemeError
71 | {
72 | public InvalidZIP(string themeId, string zipPath) : base(themeId)
73 | {
74 | errorMsg = string.Format(_("Could not read ZIP file at {0} because its format is invalid"), zipPath);
75 | }
76 | }
77 |
78 | class MissingFieldsInThemeJSON : ThemeError
79 | {
80 | private string[] requiredFields = new string[] { "dayImageList", "imageFilename", "nightImageList" };
81 |
82 | public MissingFieldsInThemeJSON(string themeId) : base(themeId)
83 | {
84 | errorMsg = string.Format(_("Theme JSON file is missing one or more of these required fields: {0}"),
85 | string.Join(", ", requiredFields.Select((field) => "'" + field + "'")));
86 | }
87 | }
88 |
89 | class NoImagesInFolder : ThemeError
90 | {
91 | public NoImagesInFolder(string themeId, string path) : base(themeId)
92 | {
93 | errorMsg = string.Format(_("No images found in folder {0}"), path);
94 | }
95 | }
96 |
97 | class NoImagesInZIP : ThemeError
98 | {
99 | public NoImagesInZIP(string themeId, string zipPath) : base(themeId)
100 | {
101 | errorMsg = string.Format(_("No images found in ZIP file {0}"), zipPath);
102 | }
103 | }
104 |
105 | class NoImagesMatchingPattern : ThemeError
106 | {
107 | public NoImagesMatchingPattern(string themeId, string pattern) : base(themeId)
108 | {
109 | errorMsg = string.Format(_("No images found that match the pattern {0}"), pattern);
110 | }
111 | }
112 |
113 | class NoThemeJSON : ThemeError
114 | {
115 | public NoThemeJSON(string themeId) : base(themeId)
116 | {
117 | errorMsg = _("Theme JSON file not found");
118 | }
119 | }
120 |
121 | class NoThemeJSONInZIP : ThemeError
122 | {
123 | public NoThemeJSONInZIP(string themeId, string zipPath) : base(themeId)
124 | {
125 | errorMsg = string.Format(_("Theme JSON not found in ZIP file {0}"), zipPath);
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/UwpHelper.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.IO;
7 | using System.Threading.Tasks;
8 |
9 | namespace WinDynamicDesktop
10 | {
11 | class UwpHelper : PlatformHelper
12 | {
13 | private bool startOnBoot;
14 |
15 | public override string GetLocalFolder()
16 | {
17 | return Windows.Storage.ApplicationData.Current.LocalFolder.Path;
18 | }
19 |
20 | public override async void CheckStartOnBoot()
21 | {
22 | var startupTask = await Windows.ApplicationModel.StartupTask.GetAsync("WinDynamicDesktopUwp");
23 |
24 | switch (startupTask.State)
25 | {
26 | case Windows.ApplicationModel.StartupTaskState.Disabled:
27 | startOnBoot = false;
28 | break;
29 | case Windows.ApplicationModel.StartupTaskState.DisabledByUser:
30 | startOnBoot = false;
31 | TrayMenu.startOnBootItem.Enabled = false;
32 | break;
33 | case Windows.ApplicationModel.StartupTaskState.Enabled:
34 | startOnBoot = true;
35 | break;
36 | }
37 |
38 | TrayMenu.startOnBootItem.Checked = startOnBoot;
39 | }
40 |
41 | public override async void ToggleStartOnBoot()
42 | {
43 | var startupTask = await Windows.ApplicationModel.StartupTask.GetAsync("WinDynamicDesktopUwp");
44 |
45 | if (!startOnBoot)
46 | {
47 | var state = await startupTask.RequestEnableAsync();
48 |
49 | switch (state)
50 | {
51 | case Windows.ApplicationModel.StartupTaskState.DisabledByUser:
52 | startOnBoot = false;
53 | break;
54 | case Windows.ApplicationModel.StartupTaskState.Enabled:
55 | startOnBoot = true;
56 | break;
57 | }
58 | }
59 | else
60 | {
61 | startupTask.Disable();
62 | startOnBoot = false;
63 | }
64 |
65 | TrayMenu.startOnBootItem.Checked = startOnBoot;
66 | }
67 |
68 | public override async void OpenUpdateLink()
69 | {
70 | await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-windows-store://downloadsandupdates"));
71 | }
72 |
73 | public override async void SetWallpaper(string imagePath, int displayIndex)
74 | {
75 | if (displayIndex != -1 || !imagePath.StartsWith(Environment.CurrentDirectory))
76 | {
77 | WallpaperApi.SetWallpaper(imagePath, displayIndex);
78 | }
79 | else
80 | {
81 | WallpaperApi.EnableTransitions();
82 | var profileSettings = Windows.System.UserProfile.UserProfilePersonalizationSettings.Current;
83 | bool result = await profileSettings.TrySetWallpaperImageAsync(await LoadImageFile(imagePath));
84 | if (!result)
85 | {
86 | LoggingHandler.LogMessage("Failed to set wallpaper with UWP API: " + imagePath);
87 | }
88 | }
89 | }
90 |
91 | public override async void SetLockScreen(string imagePath)
92 | {
93 | var profileSettings = Windows.System.UserProfile.UserProfilePersonalizationSettings.Current;
94 | bool result = await profileSettings.TrySetLockScreenImageAsync(await LoadImageFile(imagePath));
95 | if (!result)
96 | {
97 | LoggingHandler.LogMessage("Failed to set lock screen image with UWP API: " + imagePath);
98 | }
99 | }
100 |
101 | private static Task LoadImageFile(string imagePath)
102 | {
103 | string[] pathSegments = imagePath.Split(Path.DirectorySeparatorChar);
104 | var uri = new Uri("ms-appdata:///local/themes/" +
105 | Uri.EscapeDataString(pathSegments[pathSegments.Length - 2]) + "/" +
106 | Uri.EscapeDataString(pathSegments[pathSegments.Length - 1]));
107 | return Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(uri).AsTask();
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/resources/default_themes.yaml:
--------------------------------------------------------------------------------
1 | Big_Sur:
2 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/big-sur/Big_Sur.ddw
3 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073274/download
4 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Big_Sur.ddw
5 |
6 | Big_Sur_Abstract:
7 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/big-sur/Big_Sur_Abstract_2.ddw
8 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073442/download
9 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Big_Sur_Abstract_2.ddw
10 |
11 | Catalina:
12 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/catalina/Catalina.ddw
13 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073211/download
14 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Catalina.ddw
15 |
16 | Dome:
17 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/big-sur/Dome.ddw
18 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073284/download
19 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Dome.ddw
20 |
21 | Iridescence:
22 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/big-sur/Iridescence.ddw
23 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073290/download
24 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Iridescence.ddw
25 |
26 | Mojave_Desert:
27 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/mojave/Mojave_Desert.ddw
28 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073142/download
29 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Mojave_Desert.ddw
30 |
31 | Monterey_Abstract:
32 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/monterey/Monterey_Abstract.ddw
33 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073394/download
34 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Monterey_Abstract.ddw
35 |
36 | Peak:
37 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/big-sur/Peak.ddw
38 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073297/download
39 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Peak.ddw
40 |
41 | Sequoia_Abstract:
42 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/sequoia/Sequoia_Abstract.ddw
43 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/144851881/download
44 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Sequoia_Abstract.ddw
45 |
46 | Solar_Gradients:
47 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/mojave/Solar_Gradients.ddw
48 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073163/download
49 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Solar_Gradients.ddw
50 |
51 | Sonoma_Abstract:
52 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/sonoma/Sonoma_Abstract.ddw
53 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/81679380/download
54 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Sonoma_Abstract.ddw
55 |
56 | Tahoe_Abstract:
57 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/tahoe/Tahoe_Abstract.ddw
58 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/218854387/download
59 |
60 | The_Beach:
61 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/big-sur/The_Beach.ddw
62 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073316/download
63 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/The_Beach.ddw
64 |
65 | The_Cliffs:
66 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/big-sur/The_Cliffs.ddw
67 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073337/download
68 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/The_Cliffs.ddw
69 |
70 | The_Desert:
71 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/big-sur/The_Desert.ddw
72 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073351/download
73 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/The_Desert.ddw
74 |
75 | The_Lake:
76 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/big-sur/The_Lake.ddw
77 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073378/download
78 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/The_Lake.ddw
79 |
80 | Tree:
81 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/big-sur/Tree.ddw
82 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073387/download
83 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Tree.ddw
84 |
85 | Ventura_Abstract:
86 | - https://github.com/t1m0thyj/WDD-mac-themes/releases/download/ventura/Ventura_Abstract.ddw
87 | - https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073401/download
88 | - https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Ventura_Abstract.ddw
89 |
--------------------------------------------------------------------------------
/src/AppContext.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Reflection;
7 | using System.Threading.Tasks;
8 | using System.Windows.Forms;
9 |
10 | namespace WinDynamicDesktop
11 | {
12 | class AppContext : ApplicationContext
13 | {
14 | private static readonly Func _ = Localization.GetTranslation;
15 | private IpcManager ipcManager;
16 |
17 | public static NotifyIcon notifyIcon;
18 | public static EventScheduler scheduler = new EventScheduler();
19 |
20 | public AppContext(string[] args) : base(new HiddenForm())
21 | {
22 | JsonConfig.LoadConfig();
23 | Localization.Initialize();
24 | LoggingHandler.RotateDebugLog();
25 |
26 | CheckSingleInstance(args);
27 | ipcManager.ProcessArgs(args);
28 |
29 | InitializeTrayIcon();
30 | ThemeManager.Initialize();
31 | ScriptManager.Initialize();
32 |
33 | Task.Run(() =>
34 | {
35 | scheduler.RunAndUpdateLocation(false, LocationManager.Initialize);
36 | MainForm.Invoke(() => LaunchSequence.NextStep());
37 | UpdateChecker.Initialize();
38 | });
39 | }
40 |
41 | private void CheckSingleInstance(string[] args)
42 | {
43 | ipcManager = new IpcManager();
44 |
45 | if (ipcManager.isFirstInstance)
46 | {
47 | ipcManager.ListenForArgs(this);
48 | }
49 | else
50 | {
51 | if (args.Length > 0 || JsonConfig.settings.hideTrayIcon)
52 | {
53 | ipcManager.SendArgsToFirstInstance(args);
54 | }
55 | else
56 | {
57 | MessageDialog.ShowWarning(_("Another instance of WinDynamicDesktop is already running. You can " +
58 | "access it by clicking on the icon in the system tray."), _("Error"));
59 | }
60 |
61 | Environment.Exit(0);
62 | }
63 | }
64 |
65 | private void InitializeTrayIcon()
66 | {
67 | Application.ApplicationExit += OnApplicationExit;
68 |
69 | notifyIcon = new NotifyIcon
70 | {
71 | Visible = !JsonConfig.settings.hideTrayIcon,
72 | Icon = Properties.Resources.AppIcon,
73 | Text = "WinDynamicDesktop",
74 | };
75 | notifyIcon.ContextMenuStrip = new TrayMenu();
76 | notifyIcon.MouseUp += OnNotifyIconMouseUp;
77 |
78 | Localization.NotifyIfTestMode();
79 | }
80 |
81 | public static void HandleThemeChange()
82 | {
83 | Application.SetColorMode(SystemColorMode.System);
84 | foreach (Form form in Application.OpenForms)
85 | {
86 | form.Invalidate();
87 | }
88 | }
89 |
90 | public static void ShowPopup(string message, string title = null)
91 | {
92 | notifyIcon.BalloonTipTitle = title ?? "WinDynamicDesktop";
93 | notifyIcon.BalloonTipText = message;
94 | notifyIcon.ShowBalloonTip(10000);
95 | }
96 |
97 | public static void ToggleTrayIcon()
98 | {
99 | bool isHidden = JsonConfig.settings.hideTrayIcon ^ true;
100 | JsonConfig.settings.hideTrayIcon = isHidden;
101 | TrayMenu.hideTrayItem.Checked = isHidden;
102 | notifyIcon.Visible = !isHidden;
103 | }
104 |
105 | private void OnNotifyIconMouseUp(object sender, MouseEventArgs e)
106 | {
107 | // Show context menu when taskbar icon is left clicked
108 | // Code from https://stackoverflow.com/a/2208910/5504760
109 | if (e.Button == MouseButtons.Left)
110 | {
111 | MethodInfo mi = typeof(NotifyIcon).GetMethod("ShowContextMenu",
112 | BindingFlags.Instance | BindingFlags.NonPublic);
113 | mi.Invoke(notifyIcon, null);
114 | }
115 | }
116 |
117 | private void OnApplicationExit(object sender, EventArgs e)
118 | {
119 | if (notifyIcon != null)
120 | {
121 | notifyIcon.Visible = false;
122 | }
123 |
124 | ipcManager?.Dispose();
125 | JsonConfig.SaveConfig();
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/ScriptManager.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using Newtonsoft.Json;
6 | using System;
7 | using System.Diagnostics;
8 | using System.IO;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace WinDynamicDesktop
13 | {
14 | class ScriptArgs
15 | {
16 | public int daySegment2;
17 | public int daySegment4;
18 | public int themeMode;
19 | public string[] imagePaths;
20 | }
21 |
22 | class ScriptManager
23 | {
24 | private static readonly Func _ = Localization.GetTranslation;
25 | private static string lastArgs;
26 |
27 | public static void Initialize()
28 | {
29 | Directory.CreateDirectory("scripts");
30 |
31 | string urlShortcutFile = Path.Combine("scripts", _("Browse for scripts online") + ".url");
32 | if (!File.Exists(urlShortcutFile))
33 | {
34 | File.WriteAllText(urlShortcutFile, "[InternetShortcut]\nURL=https://windd.info/scripts/");
35 | }
36 | }
37 |
38 | public static void ToggleEnableScripts()
39 | {
40 | bool enableScripts = JsonConfig.settings.enableScripts ^ true;
41 | TrayMenu.enableScriptsItem.Checked = enableScripts;
42 | JsonConfig.settings.enableScripts = enableScripts;
43 |
44 | if (enableScripts)
45 | {
46 | int scriptCount = Directory.GetFiles("scripts", "*.ps1").Length;
47 | AppContext.ShowPopup(string.Format(_("Found {0} PowerShell script(s) to enable"), scriptCount));
48 | if (scriptCount > 0)
49 | {
50 | lastArgs = null;
51 | AppContext.scheduler.Run();
52 | }
53 | }
54 | }
55 |
56 | public static void RunScripts(ScriptArgs args, bool forceUpdate = false)
57 | {
58 | string jsonArgs = JsonConvert.SerializeObject(args, Formatting.None);
59 | if (!JsonConfig.settings.enableScripts || (jsonArgs.Equals(lastArgs) && !forceUpdate))
60 | {
61 | return;
62 | }
63 |
64 | LoggingHandler.LogMessage("Running scripts with arguments: {0}", args);
65 | foreach (string scriptPath in Directory.EnumerateFiles("scripts", "*.ps1"))
66 | {
67 | Task.Run(() => RunScript(scriptPath, jsonArgs));
68 | }
69 | lastArgs = jsonArgs;
70 | }
71 |
72 | private static async void RunScript(string path, string jsonArgs)
73 | {
74 | Process proc = new Process();
75 | proc.StartInfo = new ProcessStartInfo(ExistsOnPath("pwsh.exe") ? "pwsh.exe" : "powershell.exe",
76 | "-NoProfile -ExecutionPolicy Bypass -File \"" + Path.GetFileName(path) + "\"")
77 | {
78 | CreateNoWindow = true,
79 | RedirectStandardError = true,
80 | RedirectStandardInput = true,
81 | UseShellExecute = false,
82 | WindowStyle = ProcessWindowStyle.Hidden,
83 | WorkingDirectory = Path.GetDirectoryName(path)
84 | };
85 | LoggingHandler.LogMessage("Running PowerShell script: {0}", proc.StartInfo.Arguments);
86 |
87 | var errors = new StringBuilder();
88 | proc.ErrorDataReceived += (sender, e) =>
89 | {
90 | if (!string.IsNullOrEmpty(e.Data))
91 | {
92 | errors.Append(e.Data + "\n");
93 | }
94 | };
95 | proc.Start();
96 | proc.BeginErrorReadLine();
97 | using (StreamWriter sw = proc.StandardInput)
98 | {
99 | sw.WriteLine(jsonArgs);
100 | }
101 | await proc.WaitForExitAsync();
102 |
103 | if (proc.ExitCode != 0 || errors.Length > 0)
104 | {
105 | LoggingHandler.LogMessage("Script failed with errors: {0}", errors);
106 | MessageDialog.ShowWarning(string.Format(_("Error(s) running PowerShell script '{0}':\n\n{1}"), path,
107 | errors), _("Script Error"));
108 | }
109 | }
110 |
111 | private static bool ExistsOnPath(string filename)
112 | {
113 | if (File.Exists(filename))
114 | {
115 | return true;
116 | }
117 | foreach (string path in Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator))
118 | {
119 | if (File.Exists(Path.Combine(path.Trim(), filename)))
120 | {
121 | return true;
122 | }
123 | }
124 | return false;
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/FullScreenApi.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Runtime.InteropServices;
7 |
8 | namespace WinDynamicDesktop
9 | {
10 | // Code based on https://stackoverflow.com/a/10280800/5504760
11 | // and https://www.richard-banks.org/2007/09/how-to-detect-if-another-application-is.html
12 | class FullScreenApi
13 | {
14 | public bool runningFullScreen = false;
15 | public bool timerEventPending = false;
16 |
17 | private Action timerEventHandler;
18 | private IntPtr winEventHook;
19 | private WinEventDelegate winEventProc;
20 |
21 | private struct RECT
22 | {
23 | public int Left;
24 | public int Top;
25 | public int Right;
26 | public int Bottom;
27 | }
28 |
29 | [DllImport("user32.dll")]
30 | private static extern IntPtr GetDesktopWindow();
31 |
32 | [DllImport("user32.dll")]
33 | private static extern IntPtr GetForegroundWindow();
34 |
35 | [DllImport("user32.dll")]
36 | private static extern IntPtr GetShellWindow();
37 |
38 | [DllImport("user32.dll", SetLastError = true)]
39 | private static extern int GetWindowRect(IntPtr hwnd, out RECT rc);
40 |
41 | [DllImport("user32.dll", SetLastError = true)]
42 | private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
43 |
44 | [DllImport("user32.dll")]
45 | private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc,
46 | WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
47 |
48 | private const uint WINEVENT_OUTOFCONTEXT = 0;
49 | private const uint EVENT_SYSTEM_FOREGROUND = 3;
50 |
51 | private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject,
52 | int idChild, uint dwEventThread, uint dwmsEventTime);
53 |
54 | public FullScreenApi(Action timerEventHandler)
55 | {
56 | this.timerEventHandler = new Action(() =>
57 | {
58 | LoggingHandler.LogMessage("Scheduler event triggered by fullscreen app closing");
59 | timerEventHandler();
60 | });
61 |
62 | if (JsonConfig.settings.fullScreenPause)
63 | {
64 | SetFullScreenPause(true);
65 | }
66 | }
67 |
68 | public void ToggleFullScreenPause()
69 | {
70 | bool fullScreenPause = JsonConfig.settings.fullScreenPause ^ true;
71 | TrayMenu.fullScreenItem.Checked = fullScreenPause;
72 | SetFullScreenPause(fullScreenPause);
73 | JsonConfig.settings.fullScreenPause = fullScreenPause;
74 | }
75 |
76 | private void SetFullScreenPause(bool fullScreenPause)
77 | {
78 | if (fullScreenPause)
79 | {
80 | winEventProc = new WinEventDelegate(WinEventProc);
81 | winEventHook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero,
82 | winEventProc, 0, 0, WINEVENT_OUTOFCONTEXT);
83 | }
84 | else
85 | {
86 | UnhookWinEvent(winEventHook);
87 | }
88 | }
89 |
90 | private bool IsRunningFullScreen()
91 | {
92 | IntPtr desktopHandle = GetDesktopWindow();
93 | IntPtr shellHandle = GetShellWindow();
94 | IntPtr hWnd = GetForegroundWindow();
95 |
96 | #pragma warning disable 0472
97 | if (hWnd != null && !hWnd.Equals(IntPtr.Zero))
98 | #pragma warning restore 0472
99 | {
100 | if (!(hWnd.Equals(desktopHandle) || hWnd.Equals(shellHandle)))
101 | {
102 | GetWindowRect(hWnd, out RECT appBounds);
103 | System.Drawing.Rectangle screenBounds = System.Windows.Forms.Screen.FromHandle(hWnd).Bounds;
104 |
105 | if ((appBounds.Bottom - appBounds.Top) == screenBounds.Height &&
106 | (appBounds.Right - appBounds.Left) == screenBounds.Width)
107 | {
108 | return true;
109 | }
110 | }
111 | }
112 |
113 | return false;
114 | }
115 |
116 | private void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild,
117 | uint dwEventThread, uint dwmsEventTime)
118 | {
119 | runningFullScreen = IsRunningFullScreen();
120 |
121 | if (!runningFullScreen && timerEventPending)
122 | {
123 | timerEventPending = false;
124 | timerEventHandler.Invoke();
125 | }
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/Skia/ImageCache.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Reflection;
10 | using System.Windows.Forms;
11 | using SkiaSharp;
12 |
13 | namespace WinDynamicDesktop.Skia
14 | {
15 | sealed class ImageCache
16 | {
17 | readonly int maxWidth;
18 | readonly int maxHeight;
19 | readonly object cacheLock = new object();
20 | readonly Dictionary images = new Dictionary();
21 |
22 | public SKImage this[Uri uri]
23 | {
24 | get
25 | {
26 | lock (cacheLock)
27 | {
28 | if (images.TryGetValue(uri, out var image))
29 | {
30 | return image;
31 | }
32 |
33 | var img = CreateImage(uri);
34 | if (img != null)
35 | {
36 | images.Add(uri, img);
37 | }
38 | return img;
39 | }
40 | }
41 | }
42 |
43 | public void Clear()
44 | {
45 | lock (cacheLock)
46 | {
47 | foreach (var image in images.Values)
48 | {
49 | image?.Dispose();
50 | }
51 | images.Clear();
52 | }
53 | GC.Collect();
54 | }
55 |
56 | public ImageCache(bool limitDecodeSize = true)
57 | {
58 | if (limitDecodeSize)
59 | {
60 | int maxArea = 0;
61 | foreach (Screen screen in Screen.AllScreens)
62 | {
63 | int area = screen.Bounds.Width * screen.Bounds.Height;
64 | if (area > maxArea)
65 | {
66 | maxArea = area;
67 | maxWidth = screen.Bounds.Width;
68 | maxHeight = screen.Bounds.Height;
69 | }
70 | }
71 | }
72 | else
73 | {
74 | maxWidth = int.MaxValue;
75 | maxHeight = int.MaxValue;
76 | }
77 | }
78 |
79 | private SKImage CreateImage(Uri uri)
80 | {
81 | try
82 | {
83 | Stream stream = null;
84 |
85 | if (uri.IsAbsoluteUri && uri.Scheme == "file")
86 | {
87 | string path = uri.LocalPath;
88 | if (File.Exists(path))
89 | {
90 | stream = File.OpenRead(path);
91 | }
92 | }
93 | else if (!uri.IsAbsoluteUri)
94 | {
95 | // Embedded resource
96 | stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(uri.OriginalString);
97 | }
98 |
99 | if (stream == null)
100 | {
101 | return null;
102 | }
103 |
104 | using (stream)
105 | {
106 | using (var codec = SKCodec.Create(stream))
107 | {
108 | if (codec == null)
109 | {
110 | return null;
111 | }
112 |
113 | var info = codec.Info;
114 |
115 | // Calculate target dimensions
116 | int targetWidth = info.Width;
117 | int targetHeight = info.Height;
118 |
119 | if (info.Width > maxWidth || info.Height > maxHeight)
120 | {
121 | float scale = Math.Min((float)maxWidth / info.Width, (float)maxHeight / info.Height);
122 | targetWidth = (int)(info.Width * scale);
123 | targetHeight = (int)(info.Height * scale);
124 | }
125 |
126 | // Decode at native size
127 | using (var sourceBitmap = new SKBitmap(info))
128 | {
129 | if (codec.GetPixels(sourceBitmap.Info, sourceBitmap.GetPixels()) != SKCodecResult.Success)
130 | {
131 | return null;
132 | }
133 |
134 | // If scaling is needed, create scaled version with high quality
135 | if (targetWidth != sourceBitmap.Width || targetHeight != sourceBitmap.Height)
136 | {
137 | using (var scaledBitmap = new SKBitmap(targetWidth, targetHeight, info.ColorType, info.AlphaType))
138 | {
139 | sourceBitmap.ScalePixels(scaledBitmap, new SKSamplingOptions(SKCubicResampler.Mitchell));
140 | var image = SKImage.FromBitmap(scaledBitmap);
141 | return image;
142 | }
143 | }
144 | else
145 | {
146 | // No scaling needed
147 | var image = SKImage.FromBitmap(sourceBitmap);
148 | return image;
149 | }
150 | }
151 | }
152 | }
153 | }
154 | catch
155 | {
156 | return null;
157 | }
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/JsonConfig.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using Newtonsoft.Json;
6 | using PropertyChanged.SourceGenerator;
7 | using System;
8 | using System.ComponentModel;
9 | using System.IO;
10 | using System.Windows.Forms;
11 |
12 | namespace WinDynamicDesktop
13 | {
14 | public partial class AppConfig : INotifyPropertyChanged
15 | {
16 | // Schedule settings
17 | [Notify] private int _locationMode { get; set; }
18 | [Notify] private string _location { get; set; }
19 | [Notify] private double? _latitude { get; set; }
20 | [Notify] private double? _longitude { get; set; }
21 | [Notify] private string _sunriseTime { get; set; }
22 | [Notify] private string _sunsetTime { get; set; }
23 | [Notify] private int _sunriseSunsetDuration { get; set; }
24 |
25 | // Theme settings
26 | [Notify] private string[] _activeThemes { get; set; }
27 | [Obsolete("Deprecated")]
28 | public bool darkMode { get; set; }
29 | [Notify] private int _appearanceMode { get; set; }
30 | [Obsolete("Deprecated")]
31 | public bool changeLockScreen { get; set; }
32 | [Notify] private int _lockScreenDisplayIndex { get; set; } = -1;
33 | [Notify] private string _lockScreenTheme { get; set; }
34 | [Obsolete("Deprecated")]
35 | public bool enableShuffle { get; set; }
36 | [Notify] private int _themeShuffleMode { get; set; } = (int)ShufflePeriod.EveryDay;
37 | [Obsolete("Deprecated")]
38 | public string lastShuffleDate { get; set; }
39 | [Notify] private string _lastShuffleTime { get; set; }
40 | [Notify] private string[] _shuffleHistory { get; set; }
41 | [Notify] private string[] _favoriteThemes { get; set; }
42 | [Notify] private bool _showInstalledOnly { get; set; }
43 |
44 | // General settings
45 | [Notify] private string _language { get; set; }
46 | [Notify] private bool _autoUpdateCheck { get; set; } = true;
47 | [Notify] private string _lastUpdateCheckTime { get; set; }
48 | [Notify] private bool _hideTrayIcon { get; set; }
49 | [Notify] private bool _fullScreenPause { get; set; }
50 | [Notify] private bool _enableScripts { get; set; }
51 | [Notify] private bool _debugLogging { get; set; }
52 | }
53 |
54 | class JsonConfig
55 | {
56 | private static System.Timers.Timer autoSaveTimer;
57 | private static bool restartPending = false;
58 | private static bool unsavedChanges;
59 | private static readonly object settingsFileLock = new object();
60 |
61 | public static AppConfig settings = new AppConfig();
62 | public static bool firstRun = !File.Exists("settings.json");
63 |
64 | public static void LoadConfig()
65 | {
66 | autoSaveTimer?.Stop();
67 | ConfigMigrator.RenameSettingsFile();
68 | string jsonText = null;
69 |
70 | if (!firstRun)
71 | {
72 | try
73 | {
74 | jsonText = File.ReadAllText("settings.json");
75 | ConfigMigrator.UpdateConfig(jsonText);
76 | settings = JsonConvert.DeserializeObject(jsonText);
77 | }
78 | catch (JsonReaderException)
79 | {
80 | DialogResult result = MessageDialog.ShowQuestion("The WinDynamicDesktop configuration file is " +
81 | "corrupt and could not be loaded. Do you want to reset to default settings?", "Error",
82 | MessageBoxIcon.Error);
83 | if (result == DialogResult.Yes)
84 | {
85 | firstRun = true;
86 | }
87 | else
88 | {
89 | Environment.Exit(0);
90 | }
91 | }
92 | }
93 |
94 | unsavedChanges = ConfigMigrator.UpdateObsoleteSettings(jsonText);
95 | autoSaveTimer = new System.Timers.Timer(1000);
96 | autoSaveTimer.AutoReset = false;
97 | autoSaveTimer.Enabled = unsavedChanges;
98 |
99 | settings.PropertyChanged += OnSettingsPropertyChanged;
100 | autoSaveTimer.Elapsed += OnAutoSaveTimerElapsed;
101 | }
102 |
103 | public static void ReloadConfig()
104 | {
105 | restartPending = true;
106 | autoSaveTimer.Start();
107 | }
108 |
109 | public static void SaveConfig()
110 | {
111 | if (!unsavedChanges)
112 | {
113 | return;
114 | }
115 |
116 | unsavedChanges = autoSaveTimer.Enabled = false;
117 | string jsonText = JsonConvert.SerializeObject(settings, Formatting.Indented);
118 | lock (settingsFileLock)
119 | {
120 | File.WriteAllText("settings.json.tmp", jsonText);
121 | File.Move("settings.json.tmp", "settings.json", true);
122 | }
123 | }
124 |
125 | public static bool IsNullOrEmpty(Array array)
126 | {
127 | return (array == null || array.Length == 0);
128 | }
129 |
130 | private static void OnSettingsPropertyChanged(object sender, EventArgs e)
131 | {
132 | unsavedChanges = autoSaveTimer.Enabled = true;
133 | }
134 |
135 | private static void OnAutoSaveTimerElapsed(object sender, EventArgs e)
136 | {
137 | if (!restartPending && !unsavedChanges)
138 | {
139 | return;
140 | }
141 |
142 | SaveConfig();
143 |
144 | if (restartPending)
145 | {
146 | restartPending = false;
147 | System.Diagnostics.Process.Start(Application.ExecutablePath);
148 | Application.Exit();
149 | }
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/LoggingHandler.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | using Newtonsoft.Json;
6 | using System;
7 | using System.Diagnostics;
8 | using System.Globalization;
9 | using System.IO;
10 | using System.Runtime.InteropServices;
11 |
12 | namespace WinDynamicDesktop
13 | {
14 | class LoggingHandler
15 | {
16 | private static readonly object debugLogLock = new object();
17 |
18 | public static void LogError(string cwd, Exception exc)
19 | {
20 | string errorMessage = exc.ToString();
21 | string processFilename = Process.GetCurrentProcess().MainModule.FileName;
22 | string logFilename = Path.Combine(cwd, Path.GetFileName(processFilename) + ".log");
23 |
24 | try
25 | {
26 | string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture);
27 | File.AppendAllText(logFilename, string.Format("[{0}] {1}\n\n", timestamp, errorMessage));
28 | WriteReportLog(exc);
29 |
30 | MessageDialog.ShowError(string.Format("See the logfile '{0}' for details", logFilename),
31 | "Errors occurred");
32 | }
33 | catch
34 | {
35 | MessageDialog.ShowError(string.Format("The logfile '{0}' could not be opened:\n {1}", logFilename,
36 | errorMessage), "Errors occurred");
37 | }
38 | }
39 |
40 | public static void LogMessage(string message, params object[] values)
41 | {
42 | #if !DEBUG
43 | if (!JsonConfig.settings.debugLogging)
44 | {
45 | return;
46 | }
47 | #endif
48 |
49 | string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture);
50 | if (values.Length > 0)
51 | {
52 | for (int i = 0; i < values.Length; i++)
53 | {
54 | #pragma warning disable SYSLIB0050
55 | if (!values[i].GetType().IsSerializable)
56 | #pragma warning restore SYSLIB0050
57 | {
58 | values[i] = JsonConvert.SerializeObject(values[i]);
59 | }
60 | }
61 | message = string.Format(message, values);
62 | }
63 |
64 | lock (debugLogLock)
65 | {
66 | File.AppendAllText("debug.log", string.Format("[{0}] {1}\n", timestamp, message));
67 | }
68 | }
69 |
70 | public static void RotateDebugLog()
71 | {
72 | #if !DEBUG
73 | if (!JsonConfig.settings.debugLogging)
74 | {
75 | return;
76 | }
77 | #endif
78 |
79 | if (File.Exists("debug.log") && new FileInfo("debug.log").Length > 1e6)
80 | {
81 | File.Move("debug.log", "debug.old.log", true);
82 | }
83 | }
84 |
85 | private static void WriteReportLog(Exception exc)
86 | {
87 | AppConfig settings = null;
88 |
89 | try
90 | {
91 | string jsonText = File.ReadAllText("settings.json");
92 | settings = JsonConvert.DeserializeObject(jsonText);
93 | }
94 | catch { /* Do nothing */ }
95 |
96 | using (StreamWriter reportLog = new StreamWriter("report.log"))
97 | {
98 | reportLog.WriteLine("//" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss UTCzzz",
99 | CultureInfo.InvariantCulture));
100 | reportLog.WriteLine("//" + Environment.OSVersion.VersionString + " " +
101 | RuntimeInformation.OSArchitecture.ToString());
102 | reportLog.WriteLine("//WinDynamicDesktop " + AboutDialog.GetVersionString());
103 | reportLog.WriteLine(JsonConvert.SerializeObject(exc, Formatting.Indented));
104 |
105 | if (settings != null)
106 | {
107 | if (settings.location != null)
108 | {
109 | settings.location = "XXX";
110 | }
111 | if (settings.latitude.HasValue)
112 | {
113 | settings.latitude = Math.Round(settings.latitude.Value, MidpointRounding.AwayFromZero);
114 | }
115 | if (settings.longitude.HasValue)
116 | {
117 | settings.longitude = Math.Round(settings.longitude.Value, MidpointRounding.AwayFromZero);
118 | }
119 |
120 | reportLog.WriteLine("./settings.json");
121 | reportLog.WriteLine(JsonConvert.SerializeObject(settings, Formatting.Indented));
122 | }
123 | else
124 | {
125 | reportLog.WriteLine("WARNING: Settings file not found or invalid");
126 | }
127 |
128 | if (Directory.Exists("scripts"))
129 | {
130 | foreach (string path in Directory.EnumerateFiles("scripts", "*.ps1"))
131 | {
132 | reportLog.WriteLine("./" + path.Replace('\\', '/'));
133 | }
134 | }
135 | else
136 | {
137 | reportLog.WriteLine("WARNING: Scripts directory not found");
138 | }
139 |
140 | if (Directory.Exists("themes"))
141 | {
142 | foreach (string path in Directory.EnumerateFiles("themes", "*", SearchOption.AllDirectories))
143 | {
144 | reportLog.WriteLine("./" + path.Replace('\\', '/'));
145 |
146 | if (Path.GetExtension(path) == ".json")
147 | {
148 | reportLog.WriteLine(File.ReadAllText(path));
149 | }
150 | }
151 | }
152 | else
153 | {
154 | reportLog.WriteLine("WARNING: Themes directory not found");
155 | }
156 | }
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace WinDynamicDesktop.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WinDynamicDesktop.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
65 | ///
66 | internal static System.Drawing.Icon AppIcon {
67 | get {
68 | object obj = ResourceManager.GetObject("AppIcon", resourceCulture);
69 | return ((System.Drawing.Icon)(obj));
70 | }
71 | }
72 |
73 | ///
74 | /// Looks up a localized string similar to Big_Sur:
75 | ///- https://github.com/t1m0thyj/WDD-mac-themes/releases/download/big-sur/Big_Sur.ddw
76 | ///- https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073274/download
77 | ///- https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Big_Sur.ddw
78 | ///
79 | ///Big_Sur_Abstract:
80 | ///- https://github.com/t1m0thyj/WDD-mac-themes/releases/download/big-sur/Big_Sur_Abstract_2.ddw
81 | ///- https://gitlab.com/t1m0thyj/wdd-themes/-/package_files/50073442/download
82 | ///- https://bitbucket.org/t1m0thyj/wdd-themes/downloads/Big_Sur_Abstract_2.ddw
83 | ///
84 | ///Cat [rest of string was truncated]";.
85 | ///
86 | internal static string DefaultThemesYaml {
87 | get {
88 | return ResourceManager.GetString("DefaultThemesYaml", resourceCulture);
89 | }
90 | }
91 |
92 | ///
93 | /// Looks up a localized resource of type System.Byte[].
94 | ///
95 | internal static byte[] DotEnv {
96 | get {
97 | object obj = ResourceManager.GetObject("DotEnv", resourceCulture);
98 | return ((byte[])(obj));
99 | }
100 | }
101 |
102 | ///
103 | /// Looks up a localized resource of type System.Drawing.Bitmap.
104 | ///
105 | internal static System.Drawing.Bitmap IconDismiss_Dark {
106 | get {
107 | object obj = ResourceManager.GetObject("IconDismiss_Dark", resourceCulture);
108 | return ((System.Drawing.Bitmap)(obj));
109 | }
110 | }
111 |
112 | ///
113 | /// Looks up a localized resource of type System.Drawing.Bitmap.
114 | ///
115 | internal static System.Drawing.Bitmap IconDismiss_Light {
116 | get {
117 | object obj = ResourceManager.GetObject("IconDismiss_Light", resourceCulture);
118 | return ((System.Drawing.Bitmap)(obj));
119 | }
120 | }
121 |
122 | ///
123 | /// Looks up a localized resource of type System.Drawing.Bitmap.
124 | ///
125 | internal static System.Drawing.Bitmap IconSearch_Dark {
126 | get {
127 | object obj = ResourceManager.GetObject("IconSearch_Dark", resourceCulture);
128 | return ((System.Drawing.Bitmap)(obj));
129 | }
130 | }
131 |
132 | ///
133 | /// Looks up a localized resource of type System.Drawing.Bitmap.
134 | ///
135 | internal static System.Drawing.Bitmap IconSearch_Light {
136 | get {
137 | object obj = ResourceManager.GetObject("IconSearch_Light", resourceCulture);
138 | return ((System.Drawing.Bitmap)(obj));
139 | }
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/scripts/windynamicdesktop.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | windynamicdesktop
26 |
27 |
28 |
29 | {{packageVersion}}
30 | https://github.com/t1m0thyj/WinDynamicDesktop/tree/{{releaseTagName}}
31 |
32 | Timothy Johnson
33 |
34 |
35 |
36 |
37 | WinDynamicDesktop (Install)
38 | Timothy Johnson
39 |
40 | https://windd.info
41 | https://cdn.statically.io/gh/t1m0thyj/WinDynamicDesktop/189f4bb0/imgs/choco_icon.png
42 | 2025 Timothy Johnson
43 |
44 | https://www.mozilla.org/en-US/MPL/2.0/
45 | true
46 |
47 | https://github.com/t1m0thyj/WinDynamicDesktop/wiki
48 | https://github.com/t1m0thyj/WinDynamicDesktop/discussions
49 | https://github.com/t1m0thyj/WinDynamicDesktop/issues
50 | windynamicdesktop windows10 dynamic-desktop wallpaper
51 | Port of macOS Mojave Dynamic Desktop feature to Windows
52 | Experience Dynamic Desktop on Windows!
53 |
54 | WinDynamicDesktop ports the Dynamic Desktop feature from macOS Mojave to Windows 10 and 11. It uses your location to determine the times of sunrise and sunset, and changes your desktop wallpaper based on the time of day. Choose a theme and enter your location the first time you run the app, then it will minimize to your system tray and change the wallpaper in the background. You can import custom themes or create your own, and customize the app to automatically change the Windows 10 theme color or update your location periodically.
55 |
56 | Note: This app uses the LocationIQ API to convert your location to latitude and longitude, or the Windows location API if permission is granted. Location data is anonymous and never saved without your consent.
57 | {{releaseNotes}}
58 |
59 |
60 |
61 |
69 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------