├── 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 | GitHub releases 5 | Chocolatey package 6 | Build status 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 | GitHub download 13 | Microsoft Store 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 | ![Screenshot of Select Theme window](images/select_theme.png) 20 | 21 | ## Schedule 22 | 23 | Choose a schedule for cycling through wallpaper images over 24 hours 24 | 25 | ![Screenshot of Configure Timing window](images/configure_schedule.png) 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 | --------------------------------------------------------------------------------