29 Commits

Author SHA1 Message Date
af63b5ad43 Bugfixes, Readme 2024-01-16 18:14:13 +01:00
3636e3ffac Remove HEIC option for beta release 2024-01-16 17:01:36 +01:00
c04ec20704 Adding OK button, bugfixes 2024-01-16 11:46:36 +01:00
506bb45dda Bugfix 2024-01-14 20:22:44 +01:00
4a4cc9f778 Rewrite preference system 2024-01-14 19:26:33 +01:00
20223b21de Calculating dynamic background gradient, option to stretch image 2024-01-14 15:32:45 +01:00
66e4023b7a HEIC import, reimplement image change system 2024-01-04 19:13:53 +01:00
22920ad712 Load images from source folder to preview 2024-01-04 15:42:11 +01:00
513cb38154 Load preview images on ComboBox changes 2024-01-03 22:47:53 +01:00
14f6548ce7 Documentation 2024-01-03 21:28:24 +01:00
12cd81bf65 Connect image set selection in preferences 2024-01-03 21:13:28 +01:00
1833399aaa Rewrite suntimes.py 2024-01-02 22:11:51 +01:00
4e1da62b90 Adding custom times configuration with live bar chart (#5) 2023-12-26 23:13:43 +01:00
a0fe24bbd1 Improve cinnamon preferences storage & load system 2023-12-26 16:39:31 +01:00
ccaf08e238 Get and display location to the preference UI 2023-12-26 15:29:27 +01:00
1a8a986674 Converting Python scripts to classes 2023-12-25 20:22:31 +01:00
7c5e86e8dc Bind settings window to Cinnamon Spiced pref button 2023-12-23 14:02:31 +01:00
132b33bdf8 New icon, README + CHANGELOG enhanced 2023-12-23 01:42:43 +01:00
0cfe14feed Documentation, change two colors of bar graph 2023-12-22 23:51:09 +01:00
f843e01847 New time period calculation system, bind it also to the UI 2023-12-22 23:20:39 +01:00
d21557b0f1 Read/Write system for Cinnamon preference file 2023-12-21 12:17:12 +01:00
6a997e4945 Preferences UI 2023-12-21 10:31:53 +01:00
d88a21c6f0 New source selection buttons 2023-12-16 05:00:11 +01:00
edc1e62ff3 Migrate time circle to time bar 2023-12-10 00:11:28 +01:00
f087c18ab1 Start preferences redesign 2023-10-12 00:23:56 +02:00
a5b9da3f34 Cleanup 2023-08-25 20:41:47 +02:00
d433ab579d Display period times in Image Configurator, Changelog 2023-08-25 19:55:39 +02:00
78a925ff8a Bugfixes 2023-08-25 19:26:10 +02:00
5ca5b76d7c Log file system implemented 2023-08-25 17:41:42 +02:00
40 changed files with 7614 additions and 1957 deletions

6
.gitignore vendored
View File

@@ -1,7 +1,9 @@
*.glade~
*.glade#
extracted/
custom_images/
*.txt
*.json
extracted_images/
selected/
__pycache__
time_bar.svg
time_bar_polylines.svg

View File

@@ -1,3 +1,20 @@
# Version 2.0 (upcoming)
- New App icon
- Preferences window redesign - All settings are now in one window accessable!
- Graphic visualizing of the time periods of a day with a bar chart
- Adding custom time periods by user choice
- Adding folder as image source
- Option to display image on lock screens
- Bugfixes
- Remove support for Cinnamon 5.2 and older
- Apply and OK button to test settings without closing the window
# Version 1.4
- Log System
- Bugfixes
- Display times in Image Configurator
- Display time of last location update in Preferences
# Version 1.3
- Adding option to stretch the image over multiple displays

View File

@@ -1,35 +1,44 @@
# Cinnamon Dynamic Wallpaper
<img src="cinnamon-dynamic-wallpaper@TobiZog/5.4/icons/icon.svg" alt="drawing" width="200" style="margin-left:auto; margin-right:auto; width:50%; display:block"/>
![](res/wallpaper_merged.jpg)
## About the project
Based on a location, this extension calculates the periods of a day and switches the background image of your Cinnamon desktop. The extension offers the choice between a set of included wallpapers or to select a HEIC-file.
This extension switches the background image of your Cinnamon desktop multiple times in a day, based on a location or custom time periods. You can choose between included image-sets, your own HEIC-file or a source folder with single images. Configuration through a user-friendly configuration window.
### Features
- 8 included image sets
- 9 day periods
- 10 day periods
- HEIF converter
- Image configuration assistent with simple one-click setup for image choose
- Image configuration assistent with simple one-click setup for image choices
- Online location estimation or offline with manual latitude and longitude input
- Time periods individual configured by user
- Offline sun angles estimation
- Image stretching over multiple displays or repeat image for every display
- Show image on lock screen
### Tested Cinnamon versions
- 4.8 (Mint 20.1)
- 5.0 (Mint 20.2)
- 5.2 (Mint 20.3)
- 5.4 (Mint 21)
- 5.6 (Mint 21.1)
- 5.8 (Mint 21.2)
- 6.0 (Mint 21.3)
#### Only supported with version 1.x
- 4.8 (Mint 20.1)
- 5.0 (Mint 20.2)
- 5.2 (Mint 20.3)
### Technology
- Using `JavaScript` for
- Sun angle estimation
- Location estimation
- Change of the desktop wallpapers
- `Python` displays the Image Configurator
- Image Configurator UI was written with `Glade`
- `JavaScript`
- Display desktop notifications
- Calling the Python loop script every 60 seconds to refresh the background image
- `Python`
- Handles the preference window
- Esimates the location
- Changes of the desktop wallpapers
- `Glade`
- Preference window UI design
---
## Installation
### From Built-in Extension Manager
![](res/download-manager.png)
@@ -42,19 +51,21 @@ Based on a location, this extension calculates the periods of a day and switches
1. Download the Repository
2. Extract the files
3. Copy the folder `cinnamon-dynamic-wallpaper@TobiZog` to `~/.local/share/cinnamon/extensions/`
## How to use it
---
## Usage
1. Active the Extension via Cinnamon Extension Manager
2. Open the settings
3. Keep `Estimate coordinates via network` active or disable it and insert latitude and longitude in the fields
4. Choose a set of images or disable it and select for every daytime an image manually
## Image Configurator
The Cinnamon Dynamic Wallpaper extension offers an integrated image configuration assistant. Here, you can choose an included image set or import a HEIC-file from your system. You have to choose the images for the time periods after the import.
3. Configure it to your
- You can apply the setted settings without closing the window if you click on "Apply"
4. If your config is complete, click on "OK"
---
## Preferences Window
Because of the lack of configuration options in the standard Cinnamon configuration system for extensions offers this extension a custom preference window.
All configuration will be handled there. You can choose between included image sets, a HEIC file or a folder source and set the image to ten different daytime periods. Time periods will be estimated via network, custom coordinations or custom time periods. Some behaviour preferences (strech image, fill empty background with gradient color) are also here.
![](res/image_configurator.png)
---
## Included image sets
The image sets are from https://github.com/adi1090x/dynamic-wallpaper

View File

@@ -1 +0,0 @@
../5.4/extension.js

View File

@@ -1 +0,0 @@
../5.4/icon/

View File

@@ -1 +0,0 @@
../5.4/icons/icon.png

View File

@@ -1 +0,0 @@
../5.4/icons/

View File

@@ -1 +0,0 @@
../../5.4/image-configurator/data/

View File

@@ -1 +0,0 @@
../../5.4/image-configurator/image-configurator.glade

View File

@@ -1,6 +0,0 @@
import os
import windowHandler
if __name__ == "__main__":
wh = windowHandler.WindowHandler(os.path.expanduser("~") + "/.cinnamon/configs/cinnamon-dynamic-wallpaper@TobiZog/cinnamon-dynamic-wallpaper@TobiZog.json")
wh.showMainWindow()

View File

@@ -1 +0,0 @@
../../5.4/image-configurator/windowHandler.py

View File

@@ -1 +0,0 @@
../5.4/images/

View File

@@ -1 +0,0 @@
../5.4/scripts/

View File

@@ -1 +0,0 @@
../5.4/settings-schema.json

View File

@@ -0,0 +1,4 @@
class ImageSourceEnum(enumerate):
IMAGESET = "image_set"
HEICFILE = "heic_file"
SOURCEFOLDER = "source_folder"

View File

@@ -0,0 +1,4 @@
class PeriodSourceEnum(enumerate):
NETWORKLOCATION = "network_location"
CUSTOMLOCATION = "custom_location"
CUSTOMTIMEPERIODS = "custom_time_periods"

View File

@@ -1,23 +1,23 @@
/**
* @name Cinnamon-Dynamic-Wallpaper
* @alias TobiZog
* @since 2023
* @since 2023-05-17
*
* @description Main application file
*/
/******************** Imports ********************/
const MessageTray = imports.ui.messageTray;
const St = imports.gi.St;
const Main = imports.ui.main;
const Util = imports.misc.util;
const Settings = imports.ui.settings;
const Mainloop = imports.mainloop;
const Lang = imports.lang;
const { find_program_in_path } = imports.gi.GLib;
const Gio = imports.gi.Gio;
const MessageTray = imports.ui.messageTray;
const St = imports.gi.St;
const Main = imports.ui.main;
let suntimes = require('./scripts/suntimes')
let location = require('./scripts/location')
/******************** Constants ********************/
@@ -34,11 +34,9 @@ const PATH = DIRECTORY.path;
let extension;
// Time and date of the last location update
let lastLocationUpdate = new Date()
// The last calculated suntime of the day
let lastDayTime = suntimes.DAYPERIOD.NONE
let lastLocationUpdate = -1
// Loop state
let looping = true
@@ -50,6 +48,9 @@ function CinnamonDynamicWallpaperExtension(uuid) {
CinnamonDynamicWallpaperExtension.prototype = {
/******************** Lifecycle ********************/
/**
* Initialization
*
@@ -58,54 +59,18 @@ CinnamonDynamicWallpaperExtension.prototype = {
_init: function(uuid) {
this.settings = new Settings.ExtensionSettings(this, uuid);
// Image set
this.bindSettings("sw_image_stretch", "imageStretch", this.settingsUpdated)
// Location estimation
this.bindSettings("sw_auto_location", "autolocation", this.settingsUpdated)
this.bindSettings("sc_location_refresh_time", "locationRefreshTime", this.settingsUpdated)
this.bindSettings("etr_latitude", "latitude", this.settingsUpdated)
this.bindSettings("etr_longitude", "longitude", this.settingsUpdated)
// Time periods
this.bindSettings("tv_times", "tvTimes")
// Image Configurator
this.bindSettings("etr_img_morning_twilight", "img_morning_twilight", this.settingsUpdated)
this.bindSettings("etr_img_sunrise", "img_sunrise", this.settingsUpdated)
this.bindSettings("etr_img_morning", "img_morning", this.settingsUpdated)
this.bindSettings("etr_img_noon", "img_noon", this.settingsUpdated)
this.bindSettings("etr_img_afternoon", "img_afternoon", this.settingsUpdated)
this.bindSettings("etr_img_evening", "img_evening", this.settingsUpdated)
this.bindSettings("etr_img_sunset", "img_sunset", this.settingsUpdated)
this.bindSettings("etr_img_night_twilight", "img_night_twilight", this.settingsUpdated)
this.bindSettings("etr_img_night", "img_night", this.settingsUpdated)
// Check for the first startup
if (this.settings.getValue("first_start")) {
// Welcome notification
this.showNotification("Welcome to Cinnamon Dynamic Wallpaper",
"Check the preferences to choose a dynamic wallpaper", true)
"Check the preferences to choose a dynamic wallpaper", true)
// Hide the notification on system restart
this.settings.setValue("first_start", false)
// Create the folder for the selected images
Util.spawnCommandLine("mkdir " + DIRECTORY.path + "/images/selected/")
// Link the default wallpaper to the folder
for (let i = 1; i <= 9; i++) {
Util.spawnCommandLine("ln -s " +
DIRECTORY.path + "/images/included_image_sets/lakeside/" + i + ".jpg " +
DIRECTORY.path + "/images/selected/" + i + ".jpg");
}
this.settings.setValue("source_folder", DIRECTORY["path"] + "/images/included_image_sets/lakeside/")
}
// Set image initial at desktop wallpaper
this.setImageToTime()
// Start the main loop, checks in fixed time periods the
this._loop()
},
@@ -114,9 +79,9 @@ CinnamonDynamicWallpaperExtension.prototype = {
/**
* Binding the settings objects
*
* @param {*} ui_name Name of preference in settings-schema.json
* @param {*} js_name Name of preference in JavaScript
* @param {*} func Function to call on change
* @param {string} ui_name Name of preference in settings-schema.json
* @param {string} js_name Name of preference in JavaScript
* @param {Function} func Function to call on change
*/
bindSettings: function (ui_name, js_name, func = this.on_settings_changed) {
this.settings.bindProperty(
@@ -129,25 +94,24 @@ CinnamonDynamicWallpaperExtension.prototype = {
/**
* Handles changes in settings
* Main loop
*/
settingsUpdated: function() {
lastDayTime = suntimes.DAYPERIOD.NONE
_loop: function () {
if (looping) {
try {
Util.spawnCommandLine("/usr/bin/env python3 " + DIRECTORY.path + "/loop.py")
} catch(e) {
this.showNotification("Error!",
"Cinnamon Dynamic Wallpaper got an error while running the loop script. Please create an issue on GitHub.")
}
this.updateLocation()
this.setImageToTime()
// Refresh every 60 seconds
Mainloop.timeout_add_seconds(60, Lang.bind(this, this._loop));
}
},
/**
* Displaying a desktop notification
*
* @param {string} title The Title in the notification
* @param {string} text The text in the notification
* @param {boolean} showOpenSettings Display the "Open settings" button in the notification,
* defaults to false
*/
showNotification: function (title, text, showOpenSettings = false) {
showNotification(title, text, showOpenSettings = false) {
let source = new MessageTray.Source(this.uuid);
// Parameter
@@ -158,7 +122,6 @@ CinnamonDynamicWallpaperExtension.prototype = {
icon_size: source.ICON_SIZE
})
};
// The notification itself
let notification = new MessageTray.Notification(source, title, text, params);
@@ -167,7 +130,8 @@ CinnamonDynamicWallpaperExtension.prototype = {
notification.addButton("open-settings", _("Open settings"));
notification.connect("action-invoked", () =>
Util.spawnCommandLine("xlet-settings extension " + UUID));
Util.spawnCommandLine("/usr/bin/env python3 " +
DIRECTORY.path + "/preferences.py"));
}
// Put all together
@@ -175,149 +139,6 @@ CinnamonDynamicWallpaperExtension.prototype = {
// Display it
source.notify(notification);
},
/**
* Changes the desktop background image
*
* @param {jpg} imageURI The new desktop image
*/
changeWallpaper: function(imageURI) {
let gSetting = new Gio.Settings({schema: 'org.cinnamon.desktop.background'});
gSetting.set_string('picture-uri', imageURI);
if (this.imageStretch) {
gSetting.set_string('picture-options', 'spanned')
}
else
{
gSetting.set_string('picture-options', 'zoom')
}
Gio.Settings.sync();
gSetting.apply();
},
/**
* Estimate the right image based on time period of the day
*/
setImageToTime: function() {
let times = suntimes.calcTimePeriod(this.latitude, this.longitude)
let now = new Date()
let timesArray = [
times["morning_twilight"], times["sunrise"], times["morning"],
times["noon"], times["afternoon"], times["evening"],
times["sunset"], times["night_twilight"], times["night"]
]
let imageSet = [
this.img_morning_twilight, this.img_sunrise, this.img_morning,
this.img_noon, this.img_afternoon, this.img_evening,
this.img_sunset, this.img_night_twilight, this.img_night
]
for(let i = 0; i < timesArray.length; i++) {
if(timesArray[i][0] <= now && now <= timesArray[i][1] && i != lastDayTime) {
this.changeWallpaper("file://" + PATH + "/images/selected/" + imageSet[i])
lastDayTime = i
break
}
}
function convertToTimeString(time) {
return time.getHours().toString().padStart(2, "0") + ":" + time.getMinutes().toString().padStart(2, "0")
}
this.tvTimes =
"Morning Twilight:\t\t" + convertToTimeString(timesArray[0][0]) + " - " + convertToTimeString(timesArray[0][1]) +
"\nSunrise:\t\t\t\t" + convertToTimeString(timesArray[1][0]) + " - " + convertToTimeString(timesArray[1][1]) +
"\nMorning:\t\t\t" + convertToTimeString(timesArray[2][0]) + " - " + convertToTimeString(timesArray[2][1]) +
"\nNoon:\t\t\t\t" + convertToTimeString(timesArray[3][0]) + " - " + convertToTimeString(timesArray[3][1]) +
"\nAfternoon:\t\t\t" + convertToTimeString(timesArray[4][0]) + " - " + convertToTimeString(timesArray[4][1]) +
"\nEvening:\t\t\t" + convertToTimeString(timesArray[5][0]) + " - " + convertToTimeString(timesArray[5][1]) +
"\nSunset:\t\t\t\t" + convertToTimeString(timesArray[6][0]) + " - " + convertToTimeString(timesArray[6][1]) +
"\nNight Twilight:\t\t" + convertToTimeString(timesArray[7][0]) + " - " + convertToTimeString(timesArray[7][1]) +
"\nNight:\t\t\t\t" + convertToTimeString(timesArray[8][0]) + " - " + convertToTimeString(timesArray[8][1])
},
/**
* Get the location of the user
* Callback for changes in preferences
*/
updateLocation: function () {
if (this.autolocation) {
let loc = location.estimateLocation()
this.latitude = loc["latitude"]
this.longitude = loc["longitude"]
} else {
this.latitude = this.latitude
this.longitude = this.longitude
}
// Update the update information
lastLocationUpdate = new Date()
},
/**
* Main loop
*/
_loop: function () {
if (looping) {
this.setImageToTime()
if (lastLocationUpdate < new Date().getTime() - this.locationRefreshTime * 1000) {
this.updateLocation()
lastLocationUpdate = new Date()
}
// Refresh every 60 seconds
Mainloop.timeout_add_seconds(60, Lang.bind(this, this._loop));
}
},
/******************** UI Callbacks ********************/
/**
* Callback for settings-schema
* Opens the external image configurator window
*/
openImageConfigurator: function() {
Util.spawnCommandLine("/usr/bin/env python3 " +
DIRECTORY.path + "/image-configurator/image-configurator.py");
},
/**
* Callback for settings-schema
* Opens the browser and navigates to the URL of the respository
*/
openRepoWebsite: function() {
Util.spawnCommandLine("xdg-open https://github.com/TobiZog/cinnamon-dynamic-wallpaper");
},
/**
* Callback for settings-schema
* Opens the browser and navigates to the URL of the Cinnamon Spices extension
*/
openSpicesWebsite: function() {
Util.spawnCommandLine("xdg-open https://cinnamon-spices.linuxmint.com/extensions/view/97")
},
/**
* Callback for settings-schema
* Opens the browser and navigates to the GitHub issue page
*/
openIssueWebsite: function() {
Util.spawnCommandLine("xdg-open https://github.com/TobiZog/cinnamon-dynamic-wallpaper/issues/new")
}
}

View File

@@ -1 +0,0 @@
icons/icon.png

View File

@@ -0,0 +1 @@
icons/icon.svg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="128px" viewBox="0 0 128 128" width="128px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<linearGradient id="a" gradientTransform="matrix(0.232143 0 0 0.328947 -9.109985 263.79837)" gradientUnits="userSpaceOnUse" x1="88.595886" x2="536.595886" y1="-449.394012" y2="-449.394012">
<stop offset="0" stop-color="#77767b"/>
<stop offset="0.0384615" stop-color="#c0bfbc"/>
<stop offset="0.0768555" stop-color="#9a9996"/>
<stop offset="0.923077" stop-color="#9a9996"/>
<stop offset="0.961538" stop-color="#c0bfbc"/>
<stop offset="1" stop-color="#77767b"/>
</linearGradient>
<linearGradient id="b" gradientTransform="matrix(0.978582 0 0 0.937323 -9.031522 -9.577706)" gradientUnits="userSpaceOnUse" x1="20.937046" x2="127.213333" y1="76.334984" y2="76.334984">
<stop offset="0" stop-color="#066996"/>
<stop offset="1" stop-color="#001533"/>
</linearGradient>
<path d="m 19.457031 15.972656 h 88 c 4.417969 0 8 3.582032 8 8 v 84 c 0 4.417969 -3.582031 8 -8 8 h -88 c -4.417969 0 -8 -3.582031 -8 -8 v -84 c 0 -4.417968 3.582031 -8 8 -8 z m 0 0" fill="url(#a)"/>
<path d="m 17.328125 11.972656 h 92.257813 c 3.242187 0 5.871093 2.519532 5.871093 5.625 v 88.75 c 0 3.105469 -2.628906 5.625 -5.871093 5.625 h -92.257813 c -3.242187 0 -5.871094 -2.519531 -5.871094 -5.625 v -88.75 c 0 -3.105468 2.628907 -5.625 5.871094 -5.625 z m 0 0" fill="url(#b)"/>
<path d="m 63.945312 74.6875 c 0 10.257812 -8.671874 18.574219 -19.367187 18.574219 s -19.367187 -8.316407 -19.367187 -18.574219 s 8.671874 -18.574219 19.367187 -18.574219 s 19.367187 8.316407 19.367187 18.574219 z m 0 0" fill="#f7c22c"/>
<path d="m 61.160156 74.136719 c 0 10.03125 -8.128906 18.164062 -18.160156 18.164062 c -10.027344 0 -18.160156 -8.132812 -18.160156 -18.164062 c 0 -10.027344 8.132812 -18.160157 18.160156 -18.160157 c 10.03125 0 18.160156 8.132813 18.160156 18.160157 z m 0 0" fill="#f7d97e"/>
<path d="m 85.46875 19.277344 c 7.65625 3.941406 12.472656 11.832031 12.472656 20.445312 c 0 12.695313 -10.292968 22.992188 -22.992187 22.992188 c -0.074219 -0.007813 -0.148438 -0.011719 -0.222657 -0.019532 c 3.253907 1.679688 6.863282 2.558594 10.527344 2.5625 c 12.695313 0 22.992188 -10.296874 22.992188 -22.992187 c -0.003906 -12.613281 -10.167969 -22.871094 -22.777344 -22.988281 z m 0 0" fill="#eaa22f"/>
<path d="m 82.804688 18.175781 c 7.660156 3.9375 12.472656 11.828125 12.476562 20.441407 c 0 12.699218 -10.296875 22.992187 -22.992188 22.992187 c -0.078124 -0.003906 -0.152343 -0.011719 -0.226562 -0.015625 c 3.253906 1.679688 6.863281 2.554688 10.527344 2.558594 c 12.699218 0 22.992187 -10.292969 22.992187 -22.992188 c -0.003906 -12.613281 -10.164062 -22.867187 -22.777343 -22.984375 z m 0 0" fill="#ffc61b"/>
<g fill="#f7d97e">
<path d="m 44.394531 45.09375 c 0.527344 0 0.953125 0.445312 0.953125 1 v 7.558594 c 0 0.554687 -0.425781 1 -0.953125 1 c -0.527343 0 -0.957031 -0.445313 -0.957031 -1 v -7.558594 c 0 -0.554688 0.429688 -1 0.957031 -1 z m 0 0"/>
<path d="m 44.222656 94.574219 c 0.527344 0 0.953125 0.449219 0.953125 1 v 7.5625 c 0 0.554687 -0.425781 1 -0.953125 1 s -0.957031 -0.445313 -0.957031 -1 v -7.5625 c 0 -0.550781 0.429687 -1 0.957031 -1 z m 0 0"/>
<path d="m 65.402344 74.839844 c 0 -0.527344 0.449218 -0.953125 1 -0.953125 h 7.5625 c 0.550781 0 1 0.425781 1 0.953125 s -0.449219 0.957031 -1 0.957031 h -7.5625 c -0.550782 0 -1 -0.429687 -1 -0.957031 z m 0 0"/>
<path d="m 13.84375 74.300781 c 0 -0.527343 0.445312 -0.957031 1 -0.957031 h 7.558594 c 0.554687 0 1 0.429688 1 0.957031 c 0 0.527344 -0.445313 0.953125 -1 0.953125 h -7.558594 c -0.554688 0 -1 -0.425781 -1 -0.953125 z m 0 0"/>
<path d="m 22.230469 53.699219 c 0.371093 -0.375 0.988281 -0.359375 1.378906 0.03125 l 5.347656 5.347656 c 0.390625 0.390625 0.40625 1.007813 0.03125 1.382813 c -0.371093 0.371093 -0.992187 0.355468 -1.382812 -0.035157 l -5.34375 -5.34375 c -0.390625 -0.390625 -0.40625 -1.011719 -0.03125 -1.382812 z m 0 0"/>
<path d="m 59.175781 88.910156 c 0.371094 -0.375 0.992188 -0.359375 1.382813 0.03125 l 5.347656 5.347656 c 0.390625 0.390626 0.402344 1.007813 0.03125 1.382813 c -0.375 0.371094 -0.992188 0.355469 -1.382812 -0.035156 l -5.347657 -5.34375 c -0.390625 -0.390625 -0.40625 -1.011719 -0.03125 -1.382813 z m 0 0"/>
<path d="m 59.378906 60.664062 c -0.371094 -0.371093 -0.359375 -0.992187 0.03125 -1.382812 l 5.347656 -5.34375 c 0.390626 -0.390625 1.007813 -0.40625 1.382813 -0.035156 c 0.371094 0.375 0.359375 0.992187 -0.03125 1.382812 l -5.347656 5.347656 c -0.390625 0.390626 -1.007813 0.40625 -1.382813 0.03125 z m 0 0"/>
<path d="m 22.867188 95.21875 c -0.375 -0.375 -0.359376 -0.992188 0.03125 -1.382812 l 5.34375 -5.347657 c 0.390624 -0.390625 1.011718 -0.40625 1.382812 -0.03125 c 0.375 0.371094 0.359375 0.992188 -0.03125 1.382813 l -5.347656 5.347656 c -0.390625 0.390625 -1.007813 0.402344 -1.378906 0.03125 z m 0 0"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -1,6 +0,0 @@
from enum import Enum
class Source(Enum):
SELECTED = 0 # Load previous selected images
EXTRACT = 1 # Use a custom image set from a heic file
SET = 2 # Use an included image set

View File

@@ -1,925 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<object class="GtkFileFilter" id="filefilter1">
<patterns>
<pattern>*.heic</pattern>
</patterns>
</object>
<object class="GtkScrolledWindow" id="page_config">
<property name="width-request">100</property>
<property name="height-request">80</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vscroll-policy">natural</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">8</property>
<property name="margin-end">8</property>
<property name="margin-top">8</property>
<property name="margin-bottom">8</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.009999999776482582</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="left-padding">8</property>
<property name="right-padding">8</property>
<child>
<!-- n-columns=2 n-rows=2 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">8</property>
<property name="margin-end">8</property>
<property name="margin-top">8</property>
<property name="margin-bottom">8</property>
<property name="row-spacing">8</property>
<property name="column-spacing">8</property>
<property name="column-homogeneous">True</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Use an included or a custom image set?</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
<property name="height">2</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="rb_included_image_set">
<property name="label" translatable="yes">Use an included image set</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<signal name="toggled" handler="onRadioImageSet" swapped="no"/>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="rb_external_image_set">
<property name="label" translatable="yes">Import a heic-file</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<property name="group">rb_included_image_set</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="label" translatable="yes">Image Source</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.009999999776482582</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="left-padding">8</property>
<property name="right-padding">8</property>
<child>
<!-- n-columns=2 n-rows=2 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">8</property>
<property name="margin-end">8</property>
<property name="margin-top">8</property>
<property name="margin-bottom">8</property>
<property name="row-spacing">8</property>
<property name="column-spacing">8</property>
<property name="column-homogeneous">True</property>
<child>
<object class="GtkLabel" id="lb_image_set">
<property name="height-request">36</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Select an image-set</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkFileChooserButton" id="fc_heic_file">
<property name="can-focus">False</property>
<property name="no-show-all">True</property>
<property name="filter">filefilter1</property>
<property name="title" translatable="yes"/>
<signal name="file-set" handler="onHeifSelected" swapped="no"/>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="lb_heic_file">
<property name="height-request">36</property>
<property name="can-focus">False</property>
<property name="no-show-all">True</property>
<property name="label" translatable="yes">Choose the file, which you want to use</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="cb_image_set">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="onImageSetSelected" swapped="no"/>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="label" translatable="yes">Image Set</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="label-xalign">0.009999999776482582</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="bottom-padding">8</property>
<property name="left-padding">8</property>
<property name="right-padding">8</property>
<child>
<!-- n-columns=3 n-rows=3 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="row-spacing">8</property>
<property name="column-spacing">8</property>
<property name="row-homogeneous">True</property>
<property name="column-homogeneous">True</property>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.5</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="top-padding">8</property>
<property name="bottom-padding">8</property>
<property name="left-padding">8</property>
<property name="right-padding">8</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkImage" id="img_preview_1">
<property name="width-request">300</property>
<property name="height-request">200</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="stock">gtk-missing-image</property>
<property name="pixel-size">30</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="cb_preview_1">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="onPreviewComboboxSelected" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="label" translatable="yes">Morning Twilight</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.5</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="top-padding">8</property>
<property name="bottom-padding">8</property>
<property name="left-padding">8</property>
<property name="right-padding">8</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkImage" id="img_preview_2">
<property name="width-request">300</property>
<property name="height-request">200</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="cb_preview_2">
<property name="can-focus">False</property>
<signal name="changed" handler="onPreviewComboboxSelected" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="label" translatable="yes">Sunrise</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.5</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="top-padding">8</property>
<property name="bottom-padding">8</property>
<property name="left-padding">8</property>
<property name="right-padding">8</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkImage" id="img_preview_3">
<property name="width-request">300</property>
<property name="height-request">200</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="cb_preview_3">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="onPreviewComboboxSelected" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="label" translatable="yes">Morning</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">2</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.5</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="top-padding">8</property>
<property name="bottom-padding">8</property>
<property name="left-padding">8</property>
<property name="right-padding">8</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkImage" id="img_preview_4">
<property name="width-request">300</property>
<property name="height-request">200</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="cb_preview_4">
<property name="can-focus">False</property>
<signal name="changed" handler="onPreviewComboboxSelected" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="label" translatable="yes">Noon</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.5</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="top-padding">8</property>
<property name="bottom-padding">8</property>
<property name="left-padding">8</property>
<property name="right-padding">8</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkImage" id="img_preview_5">
<property name="width-request">300</property>
<property name="height-request">200</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="cb_preview_5">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="onPreviewComboboxSelected" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="label" translatable="yes">Afternoon</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.5</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="top-padding">8</property>
<property name="bottom-padding">8</property>
<property name="left-padding">8</property>
<property name="right-padding">8</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkImage" id="img_preview_6">
<property name="width-request">300</property>
<property name="height-request">200</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="cb_preview_6">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="onPreviewComboboxSelected" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="label" translatable="yes">Evening</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">2</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.5</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="top-padding">8</property>
<property name="bottom-padding">8</property>
<property name="left-padding">8</property>
<property name="right-padding">8</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkImage" id="img_preview_7">
<property name="width-request">300</property>
<property name="height-request">200</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="cb_preview_7">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="onPreviewComboboxSelected" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="label" translatable="yes">Sunset</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.5</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="top-padding">8</property>
<property name="bottom-padding">8</property>
<property name="left-padding">8</property>
<property name="right-padding">8</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkImage" id="img_preview_8">
<property name="width-request">300</property>
<property name="height-request">200</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="cb_preview_8">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="onPreviewComboboxSelected" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="label" translatable="yes">Night Twilight</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.5</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="top-padding">8</property>
<property name="bottom-padding">8</property>
<property name="left-padding">8</property>
<property name="right-padding">8</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkImage" id="img_preview_9">
<property name="width-request">300</property>
<property name="height-request">200</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="cb_preview_9">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="onPreviewComboboxSelected" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="label" translatable="yes">Night</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">2</property>
<property name="top-attach">2</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="label" translatable="yes">Preview</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<object class="GtkWindow" id="main_window">
<property name="can-focus">False</property>
<property name="window-position">center</property>
<property name="default-width">1024</property>
<property name="default-height">768</property>
<property name="icon">../../icon.png</property>
<signal name="destroy" handler="onDestroy" swapped="no"/>
<child>
<object class="GtkStack" id="stack_main">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="transition-type">crossfade</property>
<child>
<placeholder/>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Cinnamon Dynamic Wallpaper</property>
<property name="subtitle" translatable="yes">Image Configuration</property>
<property name="show-close-button">True</property>
<child>
<object class="GtkButton">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-stock">True</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="onApply" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
<object class="GtkBox" id="page_load">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">8</property>
<property name="margin-end">8</property>
<property name="margin-top">8</property>
<property name="margin-bottom">8</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkSpinner">
<property name="height-request">64</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Proceeding...</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</interface>

View File

@@ -1,6 +0,0 @@
import os
import windowHandler
if __name__ == "__main__":
wh = windowHandler.WindowHandler(os.path.expanduser("~") + "/.config/cinnamon/spices/cinnamon-dynamic-wallpaper@TobiZog/cinnamon-dynamic-wallpaper@TobiZog.json")
wh.showMainWindow()

View File

@@ -1,367 +0,0 @@
import gi, os, glob, json, shutil, threading, subprocess
from data.enum import Source
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GdkPixbuf
CONFIGURATOR_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_DIR = os.path.dirname(CONFIGURATOR_DIR) + "/"
UI_PATH = CONFIGURATOR_DIR + "/" + "image-configurator.glade"
IMAGE_DIR = PROJECT_DIR + "images/"
IMAGE_EXTRACT_DIR = IMAGE_DIR + "extracted/"
IMAGE_SETS_DIR = IMAGE_DIR + "included_image_sets/"
IMAGE_SELECTED_DIR = IMAGE_DIR + "selected/"
class WindowHandler:
def __init__(self, pref_path: str) -> None:
########### Class variables ###########
self.pref_path = pref_path
self.pref_vars = [
"etr_img_morning_twilight",
"etr_img_sunrise",
"etr_img_morning",
"etr_img_noon",
"etr_img_afternoon",
"etr_img_evening",
"etr_img_sunset",
"etr_img_night_twilight",
"etr_img_night"
]
self.img_sets = [
"aurora",
"beach",
"bitday",
"cliffs",
"gradient",
"lakeside",
"mountains",
"sahara"
]
########### Create the folder ###########
try:
os.mkdir(IMAGE_EXTRACT_DIR)
except:
pass
try:
os.mkdir(IMAGE_SELECTED_DIR)
except:
pass
########### GTK stuff ###########
self.builder = Gtk.Builder()
self.builder.add_from_file(UI_PATH)
self.builder.connect_signals(self)
########### Glade Ressources ###########
self.rb_included_image_set = self.builder.get_object("rb_included_image_set")
self.rb_external_image_set = self.builder.get_object("rb_external_image_set")
self.lb_image_set = self.builder.get_object("lb_image_set")
self.cb_image_set = self.builder.get_object("cb_image_set")
self.lb_heic_file = self.builder.get_object("lb_heic_file")
self.fc_heic_file = self.builder.get_object("fc_heic_file")
self.img_previews = [
self.builder.get_object("img_preview_1"),
self.builder.get_object("img_preview_2"),
self.builder.get_object("img_preview_3"),
self.builder.get_object("img_preview_4"),
self.builder.get_object("img_preview_5"),
self.builder.get_object("img_preview_6"),
self.builder.get_object("img_preview_7"),
self.builder.get_object("img_preview_8"),
self.builder.get_object("img_preview_9")
]
self.cb_previews = [
self.builder.get_object("cb_preview_1"),
self.builder.get_object("cb_preview_2"),
self.builder.get_object("cb_preview_3"),
self.builder.get_object("cb_preview_4"),
self.builder.get_object("cb_preview_5"),
self.builder.get_object("cb_preview_6"),
self.builder.get_object("cb_preview_7"),
self.builder.get_object("cb_preview_8"),
self.builder.get_object("cb_preview_9")
]
# The GtkStack
self.stack_main = self.builder.get_object("stack_main")
self.stack_main.add_named(self.builder.get_object("page_config"), "config")
self.stack_main.add_named(self.builder.get_object("page_load"), "load")
self.stack_main.set_visible_child_name("config")
########### Load predefinitions and settings ###########
for set in self.img_sets:
self.cb_image_set.append_text(set)
self.image_source = Source.SELECTED
# Load preferences
self.loadFromSettings()
def showMainWindow(self):
""" Opens the main window, starts the Gtk main routine
"""
window = self.builder.get_object("main_window")
window.show_all()
self.imageSetVisibility(self.image_source)
self.rb_external_image_set.set_active(self.image_source == Source.EXTRACT)
Gtk.main()
def loadFromSettings(self):
""" Load preferences from the Cinnamon preference file
"""
#try:
# Load the settings
with open(self.pref_path, "r") as pref_file:
pref_data = json.load(pref_file)
# Get all images in the "selected" folder
choosable_images = os.listdir(IMAGE_SELECTED_DIR)
choosable_images.sort()
# Add the founded image names to the ComboBoxes
if pref_data["etr_choosen_image_set"]["value"] == "custom":
for combobox in self.cb_previews:
for option in choosable_images:
combobox.append_text(option)
else:
for i, set in enumerate(self.img_sets):
if set == pref_data["etr_choosen_image_set"]["value"]:
self.cb_image_set.set_active(i)
for i, val in enumerate(self.pref_vars):
# Set the preview image
self.changePreviewImage(i, IMAGE_SELECTED_DIR + pref_data[val]['value'])
# Set the ComboBox selection
if pref_data["etr_choosen_image_set"]["value"] == "custom":
self.image_source = Source.EXTRACT
for j, set in enumerate(choosable_images):
if set == pref_data[val]["value"]:
self.cb_previews[i].set_active(j)
else:
self.image_source = Source.SET
#except:
# pass
def writeToSettings(self):
""" Save preferences to the Cinnamon preference file
"""
# Load the settings
with open(self.pref_path, "r") as pref_file:
pref_data = json.load(pref_file)
# Update the settings
if self.image_source == Source.SET:
pref_data["etr_choosen_image_set"]["value"] = self.cb_image_set.get_active_text()
for i, val in enumerate(self.pref_vars):
pref_data[val]['value'] = str(i + 1) + ".jpg"
else:
pref_data["etr_choosen_image_set"]["value"] = "custom"
for i, val in enumerate(self.pref_vars):
pref_data[val]['value'] = self.cb_previews[i].get_active_text()
# Write the settings
with open(self.pref_path, "w") as pref_file:
json.dump(pref_data, pref_file, separators=(',', ':'), indent=4)
def changePreviewImage(self, imageId: int, imageURI: str):
""" Exchanges the image in the preview
Args:
imageId (int): The number of the preview (0-8)
imageURI (str): URI to the new image
"""
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(imageURI)
pixbuf = pixbuf.scale_simple(300, 200, GdkPixbuf.InterpType.BILINEAR)
self.img_previews[imageId].set_from_pixbuf(pixbuf)
except:
pass
def extractHeifImages(self, imageURI: str):
""" Extract all images in a heif file
Args:
imageURI (str): URI to the heif file
"""
imageURI = imageURI.replace("%20", "\ ")
filename = imageURI[imageURI.rfind("/") + 1:imageURI.rfind(".")]
self.image_source = Source.EXTRACT
self.wipeImages(Source.EXTRACT)
os.system("heif-convert " + imageURI + " " + IMAGE_EXTRACT_DIR + "/" + filename + ".jpg")
self.createExtracted()
def wipeImages(self, source: Source):
""" Removes all image of a folder
Args:
source (Source): Choose the folder by selecting the Source
"""
if source == Source.EXTRACT:
dir = IMAGE_EXTRACT_DIR + "*"
elif source == Source.SELECTED:
dir = IMAGE_SELECTED_DIR + "*"
for file in glob.glob(dir):
os.remove(file)
def createExtracted(self):
""" Create the extracted images array
"""
try:
if self.image_source == Source.SELECTED:
self.extracted = os.listdir(IMAGE_SELECTED_DIR)
elif self.image_source == Source.EXTRACT:
self.extracted = os.listdir(IMAGE_EXTRACT_DIR)
self.extracted.sort()
for combobox in self.cb_previews:
for option in self.extracted:
combobox.append_text(option)
except:
pass
self.stack_main.set_visible_child_name("config")
def copyToSelected(self, source: Source):
""" Copies the extracted images to "res/"
"""
# Clean the "selected folder up"
self.wipeImages(Source.SELECTED)
# Estimate the source folder
if source == Source.EXTRACT:
source_folder = IMAGE_EXTRACT_DIR
else:
source_folder = IMAGE_SETS_DIR + self.cb_image_set.get_active_text() + "/"
# Copy it to "selected/"
for image in os.listdir(source_folder):
shutil.copy(source_folder + image, IMAGE_SELECTED_DIR + image)
def imageSetVisibility(self, source: Source):
""" Toggle the visibility of the option in the "Image set" box
Args:
source (Source): Toggle by type of Source
"""
self.image_source = source
self.lb_image_set.set_visible(source == Source.SET)
self.cb_image_set.set_visible(source == Source.SET)
self.lb_heic_file.set_visible(source != Source.SET)
self.fc_heic_file.set_visible(source != Source.SET)
for i in range(0, 9):
self.cb_previews[i].set_visible(source != Source.SET)
########## UI Signals ##########
def onImageSetSelected(self, cb):
""" UI signal if the image set combo box value changed
Args:
cb (GtkComboBox): The active ComboBox
"""
if self.image_source != Source.SELECTED:
set_name = cb.get_active_text()
for i, _ in enumerate(self.img_previews):
self.changePreviewImage(i, IMAGE_SETS_DIR + set_name + "/" + str(i + 1) + ".jpg")
def onRadioImageSet(self, rb):
""" UI signal if the radio buttons are toggled
Args:
rb (GtkRadioButton): The toggled RadioButton
"""
if rb.get_active():
self.imageSetVisibility(Source.SET)
else:
self.imageSetVisibility(Source.EXTRACT)
def onHeifSelected(self, fc):
""" UI signal if the filechooser has a file selected
Args:
fc (filechooser): The selected filechooser
"""
# Get the URI to the file
uri = fc.get_file().get_uri()
uri = uri[7:]
self.stack_main.set_visible_child_name("load")
thread = threading.Thread(target=self.extractHeifImages, args=(uri, ))
thread.daemon = True
thread.start()
def onPreviewComboboxSelected(self, cb):
""" UI signal if one of the preview combobox is selected
Args:
cb (ComboBox): The selected combobox
"""
number = Gtk.Buildable.get_name(cb)
number = number[number.rfind("_") + 1:]
if self.image_source == Source.EXTRACT:
self.changePreviewImage(int(number) - 1, IMAGE_EXTRACT_DIR + cb.get_active_text())
def onApply(self, *args):
""" UI signal if the user presses the "Apply" button
"""
self.writeToSettings()
self.copyToSelected(self.image_source)
Gtk.main_quit()
def onDestroy(self, *args):
""" UI signal if the window is closed by the user
"""
Gtk.main_quit()

View File

@@ -0,0 +1,106 @@
#!/usr/bin/python3
from scripts.cinnamon_pref_handler import *
from scripts.suntimes import *
from datetime import datetime, time
from enums.PeriodSourceEnum import *
from scripts.location import *
from gi.repository import Gio
from PIL import Image
class Loop():
def __init__(self) -> None:
self.prefs = Cinnamon_Pref_Handler()
self.suntimes = Suntimes()
self.location = Location()
self.background_settings = Gio.Settings.new("org.cinnamon.desktop.background")
# Position should estimate by network
if self.prefs.period_source == PeriodSourceEnum.NETWORKLOCATION:
current_location = self.location.run()
self.suntimes.calc_suntimes(float(current_location["latitude"]), float(current_location["longitude"]))
self.start_times = self.suntimes.day_periods
# Position is given by user
elif self.prefs.period_source == PeriodSourceEnum.CUSTOMLOCATION:
self.suntimes.calc_suntimes(float(self.prefs.latitude_custom), float(self.prefs.longitude_custom))
self.start_times = self.suntimes.day_periods
# No position, concrete times
else:
def string_to_time_converter(raw_str: str) -> time:
hour = raw_str[0:raw_str.find(":")]
minute = raw_str[raw_str.find(":") + 1:]
return time(hour=int(hour), minute=int(minute))
self.start_times = [
string_to_time_converter(self.prefs.period_custom_start_time[0]),
string_to_time_converter(self.prefs.period_custom_start_time[1]),
string_to_time_converter(self.prefs.period_custom_start_time[2]),
string_to_time_converter(self.prefs.period_custom_start_time[3]),
string_to_time_converter(self.prefs.period_custom_start_time[4]),
string_to_time_converter(self.prefs.period_custom_start_time[5]),
string_to_time_converter(self.prefs.period_custom_start_time[6]),
string_to_time_converter(self.prefs.period_custom_start_time[7]),
string_to_time_converter(self.prefs.period_custom_start_time[8]),
string_to_time_converter(self.prefs.period_custom_start_time[9])
]
def exchange_image(self):
""" Replace the desktop image
"""
# Get the time of day
time_now = time(datetime.now().hour, datetime.now().minute)
# Assign the last image as fallback
self.current_image_uri = self.prefs.source_folder + self.prefs.period_images[9]
for i in range(0, 9):
# Replace the image URI, if it's not the last time period of the day
if self.start_times[i] <= time_now and time_now < self.start_times[i + 1]:
self.current_image_uri = self.prefs.source_folder + self.prefs.period_images[i]
break
# Set the background
self.background_settings['picture-uri'] = "file://" + self.current_image_uri
# Set background stretching
self.background_settings['picture-options'] = self.prefs.picture_aspect
self.set_background_gradient()
def set_background_gradient(self):
""" Setting a gradient background to hide images, which are not high enough
"""
# Load the image
im = Image.open(self.current_image_uri)
pix = im.load()
# Width and height of the current setted image
width, height = im.size
# Color of the top and bottom pixel in the middle of the image
top_color = pix[width / 2,0]
bottom_color = pix[width / 2, height - 1]
# Create the gradient
self.background_settings['color-shading-type'] = "vertical"
if self.prefs.dynamic_background_color:
self.background_settings['primary-color'] = f"#{top_color[0]:x}{top_color[1]:x}{top_color[2]:x}"
self.background_settings['secondary-color'] = f"#{bottom_color[0]:x}{bottom_color[1]:x}{bottom_color[2]:x}"
else:
self.background_settings['primary-color'] = "#000000"
self.background_settings['secondary-color'] = "#000000"
# Needed for JavaScript
if __name__ == "__main__":
l = Loop()
l.exchange_image()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,644 @@
#!/usr/bin/python3
# Imports
import gi, os, subprocess, time
from datetime import timedelta
from scripts.time_bar_chart import Time_Bar_Chart
from scripts.cinnamon_pref_handler import *
from scripts.suntimes import *
from scripts.location import *
from scripts.images import *
from enums.ImageSourceEnum import ImageSourceEnum
from enums.PeriodSourceEnum import PeriodSourceEnum
from loop import *
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GdkPixbuf
# Global definitions
PREFERENCES_URI = os.path.dirname(os.path.abspath(__file__))
GLADE_URI = PREFERENCES_URI + "/preferences.glade"
class Preferences:
""" Preference window class
"""
############################################################
# Lifecycle #
############################################################
def __init__(self) -> None:
# Objects from external scripts
self.time_bar_chart = Time_Bar_Chart()
self.c_prefs = Cinnamon_Pref_Handler()
self.suntimes = Suntimes()
self.images = Images()
self.location = Location()
# Glade
self.builder = Gtk.Builder()
self.builder.add_from_file(GLADE_URI)
self.builder.connect_signals(self)
########## UI objects ##########
#### Page 1: Image Configuration
self.tb_image_set: Gtk.ToggleButton = self.builder.get_object("tb_image_set")
self.tb_heic_file: Gtk.ToggleButton = self.builder.get_object("tb_heic_file")
self.tb_source_folder: Gtk.ToggleButton = self.builder.get_object("tb_source_folder")
# Image set
self.lbr_image_set: Gtk.ListBoxRow = self.builder.get_object("lbr_image_set")
self.cb_image_set: Gtk.ComboBox = self.builder.get_object("cb_image_set")
# HEIC file
self.lbr_heic_file: Gtk.ListBoxRow = self.builder.get_object("lbr_heic_file")
# Source folder
self.lbr_source_folder: Gtk.ListBoxRow = self.builder.get_object("lbr_source_folder")
self.fc_source_folder: Gtk.FileChooser = self.builder.get_object("fc_source_folder")
# Time bar chart
self.img_bar_images: Gtk.Image = self.builder.get_object("img_bar_images")
self.etr_periods: list[Gtk.Entry] = [
self.builder.get_object("etr_period_1"), self.builder.get_object("etr_period_2"),
self.builder.get_object("etr_period_3"), self.builder.get_object("etr_period_4"),
self.builder.get_object("etr_period_5"), self.builder.get_object("etr_period_6"),
self.builder.get_object("etr_period_7"), self.builder.get_object("etr_period_8"),
self.builder.get_object("etr_period_9"), self.builder.get_object("etr_period_10"),
]
self.img_periods: list[Gtk.Image] = [
self.builder.get_object("img_period_0"), self.builder.get_object("img_period_1"),
self.builder.get_object("img_period_2"), self.builder.get_object("img_period_3"),
self.builder.get_object("img_period_4"), self.builder.get_object("img_period_5"),
self.builder.get_object("img_period_6"), self.builder.get_object("img_period_7"),
self.builder.get_object("img_period_8"), self.builder.get_object("img_period_9"),
]
self.cb_periods: list[Gtk.ComboBox] = [
self.builder.get_object("cb_period_0"), self.builder.get_object("cb_period_1"),
self.builder.get_object("cb_period_2"), self.builder.get_object("cb_period_3"),
self.builder.get_object("cb_period_4"), self.builder.get_object("cb_period_5"),
self.builder.get_object("cb_period_6"), self.builder.get_object("cb_period_7"),
self.builder.get_object("cb_period_8"), self.builder.get_object("cb_period_9"),
]
#### Page 2: Location & Times
self.tb_network_location: Gtk.ToggleButton = self.builder.get_object("tb_network_location")
self.lb_current_location: Gtk.Label = self.builder.get_object("lb_current_location")
self.lbr_current_location: Gtk.ListBoxRow = self.builder.get_object("lbr_current_location")
self.tb_custom_location: Gtk.ToggleButton = self.builder.get_object("tb_custom_location")
self.tb_time_periods: Gtk.ToggleButton = self.builder.get_object("tb_time_periods")
self.lbr_network_location: Gtk.ListBoxRow = self.builder.get_object("lbr_network_location")
self.spb_network_location_refresh_time: Gtk.SpinButton = self.builder.get_object("spb_network_location_refresh_time")
self.lbr_custom_location_longitude: Gtk.ListBoxRow = self.builder.get_object("lbr_custom_location_longitude")
self.lbr_custom_location_latitude: Gtk.ListBoxRow = self.builder.get_object("lbr_custom_location_latitude")
self.lbr_time_periods: Gtk.ListBoxRow = self.builder.get_object("lbr_time_periods")
self.etr_longitude: Gtk.Entry = self.builder.get_object("etr_longitude")
self.etr_latitude: Gtk.Entry = self.builder.get_object("etr_latitude")
self.img_bar_times: Gtk.Image = self.builder.get_object("img_bar_times")
self.spb_periods_hour: list[Gtk.SpinButton] = [
self.builder.get_object("spb_period_1_hour"),
self.builder.get_object("spb_period_2_hour"),
self.builder.get_object("spb_period_3_hour"),
self.builder.get_object("spb_period_4_hour"),
self.builder.get_object("spb_period_5_hour"),
self.builder.get_object("spb_period_6_hour"),
self.builder.get_object("spb_period_7_hour"),
self.builder.get_object("spb_period_8_hour"),
self.builder.get_object("spb_period_9_hour"),
]
self.spb_periods_minute: list[Gtk.SpinButton] = [
self.builder.get_object("spb_period_1_minute"),
self.builder.get_object("spb_period_2_minute"),
self.builder.get_object("spb_period_3_minute"),
self.builder.get_object("spb_period_4_minute"),
self.builder.get_object("spb_period_5_minute"),
self.builder.get_object("spb_period_6_minute"),
self.builder.get_object("spb_period_7_minute"),
self.builder.get_object("spb_period_8_minute"),
self.builder.get_object("spb_period_9_minute")
]
self.lb_period_end: list[Gtk.Label] = [
self.builder.get_object("lb_period_0_end"), self.builder.get_object("lb_period_1_end"),
self.builder.get_object("lb_period_2_end"), self.builder.get_object("lb_period_3_end"),
self.builder.get_object("lb_period_4_end"), self.builder.get_object("lb_period_5_end"),
self.builder.get_object("lb_period_6_end"), self.builder.get_object("lb_period_7_end"),
self.builder.get_object("lb_period_8_end"), self.builder.get_object("lb_period_9_end"),
]
# Page 3: Behaviour
self.cb_picture_aspect: Gtk.ComboBox = self.builder.get_object("cb_picture_aspect")
self.sw_dynamic_background_color: Gtk.Switch = self.builder.get_object("sw_dynamic_background_color")
def show(self):
""" Display the window to the screen
"""
window = self.builder.get_object("window_main")
window.show_all()
# todo: Remove after HEIC implementation
self.tb_heic_file.set_visible(False)
# Load from preferences
if self.c_prefs.image_source == ImageSourceEnum.IMAGESET:
self.tb_image_set.set_active(True)
elif self.c_prefs.image_source == ImageSourceEnum.HEICFILE:
self.tb_heic_file.set_active(True)
elif self.c_prefs.image_source == ImageSourceEnum.SOURCEFOLDER:
self.tb_source_folder.set_active(True)
picture_aspects = ["mosaic", "centered", "scaled", "stretched", "zoom", "spanned"]
self.add_items_to_combo_box(self.cb_picture_aspect, picture_aspects)
self.set_active_combobox_item(self.cb_picture_aspect, self.c_prefs.picture_aspect)
self.sw_dynamic_background_color.set_active(self.c_prefs.dynamic_background_color)
if self.c_prefs.period_source == PeriodSourceEnum.NETWORKLOCATION:
self.tb_network_location.set_active(True)
elif self.c_prefs.period_source == PeriodSourceEnum.CUSTOMLOCATION:
self.tb_custom_location.set_active(True)
elif self.c_prefs.period_source == PeriodSourceEnum.CUSTOMTIMEPERIODS:
self.tb_time_periods.set_active(True)
# Time diagram
try:
self.refresh_chart()
except:
pass
# Show the main window
Gtk.main()
def on_destroy(self, *args):
""" Lifecycle handler when window will be destroyed
"""
Gtk.main_quit()
############################################################
# Local methods #
############################################################
def refresh_chart(self):
""" Recomputes both time bar charts and load them to the UI
"""
# Stores the start times of the periods in minutes since midnight
time_periods_min = []
if self.c_prefs.period_source == PeriodSourceEnum.CUSTOMTIMEPERIODS:
for i in range(0, 10):
time_str = self.c_prefs.period_custom_start_time[i]
time_periods_min.append(int(time_str[0:2]) * 60 + int(time_str[3:5]))
else:
if self.c_prefs.period_source == PeriodSourceEnum.NETWORKLOCATION:
self.suntimes.calc_suntimes(float(self.c_prefs.latitude_auto),
float(self.c_prefs.longitude_auto))
else:
self.suntimes.calc_suntimes(float(self.etr_latitude.get_text()), float(self.etr_longitude.get_text()))
# Get all time periods. Store the minutes to the list and print the values to the text views
for i in range(0, 10):
time_range_now = self.suntimes.day_periods[i]
if i != 9:
time_range_next = self.suntimes.day_periods[i + 1]
else:
time_range_next = time(hour=23, minute=59)
self.etr_periods[i].set_text(
str(time_range_now.hour).rjust(2, '0') + ":" + str(time_range_now.minute).rjust(2, '0') +\
" - " + str(time_range_next.hour).rjust(2, '0') + ":" + str(time_range_next.minute).rjust(2, '0'))
time_periods_min.append(time_range_now.hour * 60 + time_range_now.minute)
# Create time bar
self.time_bar_chart.create_bar_chart_with_polylines(PREFERENCES_URI, 1300, 150, time_periods_min)
self.time_bar_chart.create_bar_chart(PREFERENCES_URI, 1300, 150, time_periods_min)
# Load to the views
pixbuf = GdkPixbuf.Pixbuf.new_from_file(PREFERENCES_URI + "/time_bar_polylines.svg")
self.img_bar_images.set_from_pixbuf(pixbuf)
pixbuf2 = GdkPixbuf.Pixbuf.new_from_file(PREFERENCES_URI + "/time_bar.svg")
self.img_bar_times.set_from_pixbuf(pixbuf2)
def load_image_options_to_combo_boxes(self, options: list):
""" Add a list of Strings to all image option comboboxes
Args:
options (list): All possible options
"""
for combobox in self.cb_periods:
self.add_items_to_combo_box(combobox, options)
def load_image_to_preview(self, image_preview: Gtk.Image, image_src: list):
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_src)
pixbuf = pixbuf.scale_simple(250, 175, GdkPixbuf.InterpType.BILINEAR)
image_preview.set_from_pixbuf(pixbuf)
except:
pass
############################################################
# UI helper methods #
############################################################
def set_active_combobox_item(self, combobox: Gtk.ComboBoxText, active_item: str):
""" Change active item in combobox by String value
Args:
combobox (Gtk.ComboBoxText): ComboBox to set active
active_item (str): String item to set active
"""
list_store = combobox.get_model()
for i in range(0, len(list_store)):
row = list_store[i]
if row[0] == active_item:
combobox.set_active(i)
def add_items_to_combo_box(self, combobox: Gtk.ComboBox, items: list):
""" Add items to a combo box
Args:
combobox (Gtk.ComboBox): ComboBox where to add the options
items (list): Possible options
"""
model = combobox.get_model()
store = Gtk.ListStore(str)
for image_set in items:
store.append([image_set])
combobox.set_model(store)
if model == None:
renderer_text = Gtk.CellRendererText()
combobox.pack_start(renderer_text, True)
combobox.add_attribute(renderer_text, "text", 0)
############################################################
# Callbacks #
############################################################
## Image Configuration
# +-----------+-----------+---------------+
# | Image Set | HEIC file | Source Folder |
# +-----------+-----------+---------------+
def on_toggle_button_image_set_clicked(self, button: Gtk.Button):
if button.get_active():
self.c_prefs.image_source = ImageSourceEnum.IMAGESET
self.tb_heic_file.set_active(False)
self.tb_source_folder.set_active(False)
self.lbr_image_set.set_visible(True)
self.lbr_heic_file.set_visible(False)
self.lbr_source_folder.set_visible(False)
image_set_choices = ["aurora", "beach", "bitday", "cliffs", "gradient", "lakeside", "mountains", "sahara"]
self.add_items_to_combo_box(self.cb_image_set, image_set_choices)
self.set_active_combobox_item(self.cb_image_set, self.c_prefs.selected_image_set)
for i, combobox in enumerate(self.cb_periods):
selected_image_name = self.c_prefs.period_images[i]
self.set_active_combobox_item(combobox, selected_image_name)
# Make the comboboxes invisible
for combobox in self.cb_periods:
combobox.set_visible(False)
def on_toggle_button_heic_file_clicked(self, button: Gtk.Button):
if button.get_active():
self.c_prefs.image_source = ImageSourceEnum.HEICFILE
self.tb_image_set.set_active(False)
self.tb_source_folder.set_active(False)
self.lbr_image_set.set_visible(False)
self.lbr_heic_file.set_visible(True)
self.lbr_source_folder.set_visible(False)
# Make the comboboxes visible
for combobox in self.cb_periods:
combobox.set_visible(True)
def on_toggle_button_source_folder_clicked(self, button: Gtk.Button):
if button.get_active():
self.c_prefs.image_source = ImageSourceEnum.SOURCEFOLDER
self.tb_image_set.set_active(False)
self.tb_heic_file.set_active(False)
self.lbr_image_set.set_visible(False)
self.lbr_heic_file.set_visible(False)
self.lbr_source_folder.set_visible(True)
# Make the comboboxes visible
for combobox in self.cb_periods:
combobox.set_visible(True)
# Load the source folder to the view
# This will update the comboboxes in the preview to contain the right items
self.fc_source_folder.set_filename(self.c_prefs.source_folder)
def on_cb_image_set_changed(self, combobox: Gtk.ComboBox):
tree_iter = combobox.get_active_iter()
if tree_iter is not None and self.c_prefs.image_source == ImageSourceEnum.IMAGESET:
# Get the selected value
model = combobox.get_model()
selected_image_set = model[tree_iter][0]
# Store to the preferences
self.c_prefs.selected_image_set = selected_image_set
self.c_prefs.source_folder = os.path.abspath(os.path.join(PREFERENCES_URI, os.pardir)) + \
"/5.4/images/included_image_sets/" + selected_image_set + "/"
# Load all possible options to the comboboxes
image_names = self.images.get_images_from_folder(self.c_prefs.source_folder)
self.load_image_options_to_combo_boxes(image_names)
# Image sets have the same names for the images:
# 9.jpg = Period 0
# 1.jpg = Period 1
# 2.jpg = Period 2
# and so on....
self.cb_periods[0].set_active(8)
for i in range(1, 10):
self.cb_periods[i].set_active(i - 1)
def on_fc_heic_file_file_set(self, fc_button: Gtk.FileChooser):
file_path = fc_button.get_filename()
extract_folder = os.path.abspath(os.path.join(PREFERENCES_URI, os.pardir)) + \
"/images/extracted_images/"
file_name = file_path[file_path.rfind("/") + 1:]
file_name = file_name[:file_name.rfind(".")]
# Update the preferences
self.c_prefs.selected_image_set = ""
self.c_prefs.source_folder = extract_folder
# Create the buffer folder
try:
os.mkdir(extract_folder)
except:
pass
# Extract the HEIC file
for file in self.images.get_images_from_folder(extract_folder):
os.remove(extract_folder + file)
os.system("heif-convert " + file_path + " " + extract_folder + file_name + ".jpg")
# Collect all extracted images and push them to the comboboxes
image_names = self.images.get_images_from_folder(self.c_prefs.source_folder)
self.load_image_options_to_combo_boxes(image_names)
def on_fc_source_folder_file_set(self, fc_button: Gtk.FileChooser):
files = self.images.get_images_from_folder(fc_button.get_filename())
# Update the preferences
self.c_prefs.selected_image_set = ""
self.c_prefs.source_folder = fc_button.get_filename() + "/"
if len(files) != 0:
self.load_image_options_to_combo_boxes(files)
# Load the values for the images from the preferences
for i in range(0, 10):
self.set_active_combobox_item(self.cb_periods[i], self.c_prefs.period_images[i])
else:
pass
def on_cb_period_preview_changed(self, combobox: Gtk.ComboBox):
tree_iter = combobox.get_active_iter()
combobox_name = Gtk.Buildable.get_name(combobox)
period_index = int(combobox_name[10:11])
if tree_iter is not None:
# Get the selected value
model = combobox.get_model()
image_file_name = model[tree_iter][0]
# Store selection to preferences
self.c_prefs.period_images[period_index] = image_file_name
# Build up image path
image_path = self.c_prefs.source_folder + image_file_name
self.load_image_to_preview(self.img_periods[period_index], image_path)
## Location & Times
def on_toggle_button_network_location_clicked(self, button: Gtk.Button):
if button.get_active():
self.c_prefs.period_source = PeriodSourceEnum.NETWORKLOCATION
self.tb_custom_location.set_active(False)
self.tb_time_periods.set_active(False)
self.lbr_network_location.set_visible(True)
self.lbr_current_location.set_visible(True)
self.lbr_custom_location_longitude.set_visible(False)
self.lbr_custom_location_latitude.set_visible(False)
self.lbr_time_periods.set_visible(False)
self.spb_network_location_refresh_time.set_value(self.c_prefs.location_refresh_intervals)
# Display the location in the UI
current_location = self.location.run()
self.lb_current_location.set_text("Latitude: " + current_location["latitude"] + \
", Longitude: " + current_location["longitude"])
# Store the location to the preferences
self.c_prefs.latitude_auto = float(current_location["latitude"])
self.c_prefs.longitude_auto = float(current_location["longitude"])
self.refresh_chart()
def on_toggle_button_custom_location_clicked(self, button: Gtk.Button):
if button.get_active():
self.c_prefs.period_source = PeriodSourceEnum.CUSTOMLOCATION
self.tb_network_location.set_active(False)
self.tb_time_periods.set_active(False)
self.lbr_network_location.set_visible(False)
self.lbr_current_location.set_visible(False)
self.lbr_custom_location_longitude.set_visible(True)
self.lbr_custom_location_latitude.set_visible(True)
self.lbr_time_periods.set_visible(False)
self.etr_latitude.set_text(str(self.c_prefs.latitude_custom))
self.etr_longitude.set_text(str(self.c_prefs.longitude_custom))
def on_toggle_button_time_periods_clicked(self, button: Gtk.Button):
if button.get_active():
self.c_prefs.period_source = PeriodSourceEnum.CUSTOMTIMEPERIODS
self.tb_network_location.set_active(False)
self.tb_custom_location.set_active(False)
self.lbr_network_location.set_visible(False)
self.lbr_current_location.set_visible(False)
self.lbr_custom_location_longitude.set_visible(False)
self.lbr_custom_location_latitude.set_visible(False)
self.lbr_time_periods.set_visible(True)
for i in range(0, 9):
pref_value = self.c_prefs.period_custom_start_time[i + 1]
time_parts = [int(pref_value[0:pref_value.find(":")]), int(pref_value[pref_value.find(":") + 1:])]
self.spb_periods_hour[i].set_value(time_parts[0])
self.spb_periods_minute[i].set_value(time_parts[1])
def on_spb_period_value_changed(self, spin_button: Gtk.SpinButton):
""" Callback if one of the time spinners (minute or hour) will be clicked
(1) (2) (3)
Previous period Current period Next period
12:34 - 14:40 14:41 - 16:20 16:21 - 17:30
^
Variable to change
Args:
spin_button (Gtk.SpinButton): SpinButton which was changed
"""
spin_button_name = Gtk.Buildable.get_name(spin_button)
index = int(spin_button_name[11:12]) - 1
# Determe time string and store to prefs
time_current_start = datetime(2024,1,1, int(self.spb_periods_hour[index].get_value()), int(self.spb_periods_minute[index].get_value()))
time_current_start_str = str(time_current_start.hour).rjust(2, '0') + ":" + str(time_current_start.minute).rjust(2, '0')
self.c_prefs.period_custom_start_time[index + 1] = time_current_start_str
time_previous_end = time_current_start - timedelta(minutes=1)
self.lb_period_end[index].set_text(str(time_previous_end.hour).rjust(2, '0') + ":" + str(time_previous_end.minute).rjust(2, '0'))
self.refresh_chart()
def on_spb_network_location_refresh_time_changed(self, spin_button):
self.c_prefs.location_refresh_intervals = spin_button.get_value()
def on_etr_longitude_changed(self, entry):
try:
self.c_prefs.longitude_custom = float(entry.get_text())
self.refresh_chart()
except:
pass
def on_etr_latitude_changed(self, entry):
try:
self.c_prefs.latitude_custom = float(entry.get_text())
self.refresh_chart()
except:
pass
# Behaviour
def on_cb_picture_aspect_changed(self, combobox: Gtk.ComboBox):
tree_iter = combobox.get_active_iter()
if tree_iter is not None:
model = combobox.get_model()
self.c_prefs.picture_aspect = model[tree_iter][0]
def on_sw_dynamic_background_color_state_set(self, switch: Gtk.Switch, state):
self.c_prefs.dynamic_background_color = state
# About
def on_cinnamon_spices_website_button_clicked(self, button: Gtk.Button):
""" Callback for the button to navigate to the Cinnamon Spices web page of this project
Args:
button (Gtk.Button): Button which was clicked
"""
subprocess.Popen(["xdg-open", "https://cinnamon-spices.linuxmint.com/extensions/view/97"])
def on_github_website_button_clicked(self, button: Gtk.Button):
""" Callback for the button to navigate to the GitHub web page of this project
Args:
button (Gtk.Button): Button which was clicked
"""
subprocess.Popen(["xdg-open", "https://github.com/TobiZog/cinnamon-dynamic-wallpaper"])
def on_create_issue_button_clicked(self, button):
""" Callback for the button to navigate to the Issues page on GitHub of this project
Args:
button (Gtk.Button): Button which was clicked
"""
subprocess.Popen(["xdg-open", "https://github.com/TobiZog/cinnamon-dynamic-wallpaper/issues/new"])
def on_ok(self, *args):
""" Callback for the OK button in the top bar
"""
self.on_apply()
# Close the window
self.on_destroy()
def on_apply(self, *args):
""" Callback for the Apply button in the top bar
"""
# Store all values to the JSON file
self.c_prefs.store_preferences()
# Use the new settings
loop = Loop()
loop.exchange_image()
if __name__ == "__main__":
Preferences().show()

View File

@@ -0,0 +1,97 @@
import os, json
class Cinnamon_Pref_Handler:
def __init__(self) -> None:
# Location of the Cinnamon preference file since Cinnamon 5.4
self.pref_location = os.path.expanduser("~") + \
"/.config/cinnamon/spices/cinnamon-dynamic-wallpaper@TobiZog/cinnamon-dynamic-wallpaper@TobiZog.json"
self.load_preferences()
def load_preferences(self):
""" Load the JSON preferences to the Preference object
"""
with open(self.pref_location, "r") as pref_file:
pref_data = json.load(pref_file)
self.picture_aspect = pref_data['picture_aspect']['value']
self.dynamic_background_color = pref_data['dynamic_background_color']['value']
self.image_source = pref_data['image_source']['value']
self.selected_image_set = pref_data['selected_image_set']['value']
self.source_folder = pref_data['source_folder']['value']
self.period_images = [
pref_data['period_0_image']['value'],
pref_data['period_1_image']['value'],
pref_data['period_2_image']['value'],
pref_data['period_3_image']['value'],
pref_data['period_4_image']['value'],
pref_data['period_5_image']['value'],
pref_data['period_6_image']['value'],
pref_data['period_7_image']['value'],
pref_data['period_8_image']['value'],
pref_data['period_9_image']['value']
]
self.period_source = pref_data['period_source']['value']
self.location_refresh_intervals = pref_data['location_refresh_intervals']['value']
self.latitude_auto = pref_data['latitude_auto']['value']
self.longitude_auto = pref_data['longitude_auto']['value']
self.latitude_custom = pref_data['latitude_custom']['value']
self.longitude_custom = pref_data['longitude_custom']['value']
self.period_custom_start_time = [
pref_data['period_0_custom_start_time']['value'],
pref_data['period_1_custom_start_time']['value'],
pref_data['period_2_custom_start_time']['value'],
pref_data['period_3_custom_start_time']['value'],
pref_data['period_4_custom_start_time']['value'],
pref_data['period_5_custom_start_time']['value'],
pref_data['period_6_custom_start_time']['value'],
pref_data['period_7_custom_start_time']['value'],
pref_data['period_8_custom_start_time']['value'],
pref_data['period_9_custom_start_time']['value']
]
def store_preferences(self):
""" Store the values of the Preference object to the JSON file
"""
with open(self.pref_location, "r") as pref_file:
pref_data = json.load(pref_file)
pref_data['picture_aspect']['value'] = self.picture_aspect
pref_data['dynamic_background_color']['value'] = self.dynamic_background_color
pref_data['image_source']['value'] = self.image_source
pref_data['selected_image_set']['value'] = self.selected_image_set
pref_data['source_folder']['value'] = self.source_folder
pref_data['period_0_image']['value'] = self.period_images[0]
pref_data['period_1_image']['value'] = self.period_images[1]
pref_data['period_2_image']['value'] = self.period_images[2]
pref_data['period_3_image']['value'] = self.period_images[3]
pref_data['period_4_image']['value'] = self.period_images[4]
pref_data['period_5_image']['value'] = self.period_images[5]
pref_data['period_6_image']['value'] = self.period_images[6]
pref_data['period_7_image']['value'] = self.period_images[7]
pref_data['period_8_image']['value'] = self.period_images[8]
pref_data['period_9_image']['value'] = self.period_images[9]
pref_data['period_source']['value'] = self.period_source
pref_data['location_refresh_intervals']['value'] = self.location_refresh_intervals
pref_data['latitude_auto']['value'] = self.latitude_auto
pref_data['longitude_auto']['value'] = self.longitude_auto
pref_data['latitude_custom']['value'] = self.latitude_custom
pref_data['longitude_custom']['value'] = self.longitude_custom
pref_data['period_0_custom_start_time']['value'] = self.period_custom_start_time[0]
pref_data['period_1_custom_start_time']['value'] = self.period_custom_start_time[1]
pref_data['period_2_custom_start_time']['value'] = self.period_custom_start_time[2]
pref_data['period_3_custom_start_time']['value'] = self.period_custom_start_time[3]
pref_data['period_4_custom_start_time']['value'] = self.period_custom_start_time[4]
pref_data['period_5_custom_start_time']['value'] = self.period_custom_start_time[5]
pref_data['period_6_custom_start_time']['value'] = self.period_custom_start_time[6]
pref_data['period_7_custom_start_time']['value'] = self.period_custom_start_time[7]
pref_data['period_8_custom_start_time']['value'] = self.period_custom_start_time[8]
pref_data['period_9_custom_start_time']['value'] = self.period_custom_start_time[9]
# Write to file
with open(self.pref_location, "w") as pref_file:
json.dump(pref_data, pref_file, separators=(',', ':'), indent=4)

View File

@@ -0,0 +1,10 @@
import os
class Images:
def __init__(self) -> None:
pass
def get_images_from_folder(self, URI: str) -> list:
items = os.listdir(URI)
items.sort()
return items

View File

@@ -1,14 +0,0 @@
const Soup = imports.gi.Soup;
function estimateLocation() {
let sessionSync = new Soup.SessionSync();
let msg = Soup.Message.new('GET', "https://get.geojs.io/v1/ip/geo.json");
sessionSync.send_message(msg);
if (msg.status_code == 200) {
return JSON.parse(msg.response_body.data);
} else {
return -1;
}
}

View File

@@ -0,0 +1,15 @@
import urllib.request, json
class Location():
def __init__(self):
self.GEO_URL = "https://get.geojs.io/v1/ip/geo.json"
def run(self) -> dict:
request = urllib.request.urlopen(self.GEO_URL)
data = json.load(request)
return {
"latitude": data["latitude"],
"longitude": data["longitude"]
}

View File

@@ -1,158 +0,0 @@
/**
* @name Cinnamon-Dynamic-Wallpaper
* @alias TobiZog
* @since 2023
*/
const DAYPERIOD = {
MTWILIGHT: 0,
SUNRISE: 1,
MORNING: 2,
NOON: 3,
AFTERNOON: 4,
EVENING: 5,
SUNSET: 6,
NTWILIGHT: 7,
NIGHT: 8,
NONE: 10
}
const DAYMS = 1000 * 60 * 60 * 24
const J1970 = 2440588
const J2000 = 2451545
function fromJulian(j) {
let ms_date = (j + 0.5 - J1970) * DAYMS
return new Date(ms_date)
}
/**
* Calculating specific events of the sun during a day
*
* @param {float} latitude Location latitude
* @param {float} longitude Location longitude
*
* @returns List of sun events in a day: dawn, sunrise, noon, sunset, dusk
*/
function sunEventsOfDay(latitude, longitude, date) {
let rad = Math.PI / 180
let lw = rad * (-longitude)
let d = (date / DAYMS) - 0.5 + J1970 - J2000
let n = Math.round(d - 0.0009 - lw / (2 * Math.PI))
let ds = 0.0009 + lw / (2 * Math.PI) + n
let M = rad * (357.5291 + 0.98560028 * ds)
let C = rad * (1.9148 * Math.sin(M) + 0.02 * Math.sin(2 * M) + 0.0003 * Math.sin(3 * M))
let P = rad * 102.9372
let L = M + C + P + Math.PI
let dec = Math.asin(Math.sin(rad * 23.4397) * Math.sin(L))
// Angles for the sun
let angles = [-0.833, -6]
for(var i = 0; i < angles.length; i++) {
angles[i] = angles[i] * rad
angles[i] = Math.acos((Math.sin(angles[i]) - Math.sin(rad * latitude) * Math.sin(dec)) / (Math.cos(rad * latitude) * Math.cos(dec)))
angles[i] = 0.0009 + (angles[i] + lw) / (2 * Math.PI) + n
}
let jnoon = J2000 + ds + 0.0053 * Math.sin(M) - 0.0069 * Math.sin(2 * L)
return {
dawn: fromJulian(jnoon - (J2000 + angles[1] + 0.0053 * Math.sin(M) - 0.0069 * Math.sin(2 * L) - jnoon)),
sunrise: fromJulian(jnoon - (J2000 + angles[0] + 0.0053 * Math.sin(M) - 0.0069 * Math.sin(2 * L) - jnoon)),
noon: fromJulian(jnoon),
sunset: fromJulian(J2000 + angles[0] + 0.0053 * Math.sin(M) - 0.0069 * Math.sin(2 * L)),
dusk: fromJulian(J2000 + angles[1] + 0.0053 * Math.sin(M) - 0.0069 * Math.sin(2 * L))
}
}
function addMinutesToTime(date, minutes = 0) {
let newDate = new Date(date)
newDate.setMinutes(date.getMinutes() + minutes)
return newDate
}
function subTimesToMinutes(date1, date2) {
let diff = new Date(date1 - date2)
return diff.getUTCHours() * 60 + diff.getMinutes()
}
function calcTimePeriod(latitude, longitude) {
let todaySunEventsDay = sunEventsOfDay(latitude, longitude, Date.now())
let tomorrowSunEventsDay = sunEventsOfDay(latitude, longitude, addMinutesToTime(new Date(), 1440))
return {
morning_twilight: [
todaySunEventsDay.dawn,
addMinutesToTime(todaySunEventsDay.sunrise, -1)
],
sunrise: [
todaySunEventsDay.sunrise,
addMinutesToTime(
todaySunEventsDay.sunrise,
subTimesToMinutes(todaySunEventsDay.noon, todaySunEventsDay.sunrise) / 8 - 1
)
],
morning: [
addMinutesToTime(
todaySunEventsDay.sunrise,
(subTimesToMinutes(todaySunEventsDay.noon, todaySunEventsDay.sunrise) / 8) * 1
),
addMinutesToTime(
todaySunEventsDay.sunrise,
(subTimesToMinutes(todaySunEventsDay.noon, todaySunEventsDay.sunrise) / 8)*6 - 1
)
],
noon: [
addMinutesToTime(
todaySunEventsDay.sunrise,
(subTimesToMinutes(todaySunEventsDay.noon, todaySunEventsDay.sunrise) / 8) * 6
),
addMinutesToTime(
todaySunEventsDay.noon,
(subTimesToMinutes(todaySunEventsDay.sunset, todaySunEventsDay.noon) / 8) * 2 - 1
)
],
afternoon: [
addMinutesToTime(
todaySunEventsDay.noon,
(subTimesToMinutes(todaySunEventsDay.sunset, todaySunEventsDay.noon) / 8) * 2
),
addMinutesToTime(
todaySunEventsDay.noon,
(subTimesToMinutes(todaySunEventsDay.sunset, todaySunEventsDay.noon) / 8) * 5 - 1
)
],
evening: [
addMinutesToTime(
todaySunEventsDay.noon,
(subTimesToMinutes(todaySunEventsDay.sunset, todaySunEventsDay.noon) / 8) * 5
),
addMinutesToTime(
todaySunEventsDay.noon,
(subTimesToMinutes(todaySunEventsDay.sunset, todaySunEventsDay.noon) / 8) * 7 - 1
)
],
sunset: [
addMinutesToTime(
todaySunEventsDay.noon,
(subTimesToMinutes(todaySunEventsDay.sunset, todaySunEventsDay.noon) / 8) * 7 - 1
),
todaySunEventsDay.sunset
],
night_twilight: [
addMinutesToTime(todaySunEventsDay.sunset, 1),
todaySunEventsDay.dusk
],
night: [
addMinutesToTime(todaySunEventsDay.dusk, 1),
addMinutesToTime(tomorrowSunEventsDay.dawn, -1)
],
}
}

View File

@@ -0,0 +1,140 @@
from math import pi, sin, asin, acos, cos, floor, atan, tan
from datetime import datetime, timezone, time
class Suntimes:
""" Calculates all time periods based on latitude and longitude
Inspired by https://github.com/SatAgro/suntime
author: TobiZog
"""
def __init__(self) -> None:
""" Initialization
Args:
latitude (float): Latitude of the position
longitude (float): Longitude of the position
"""
self.today = datetime.now()
def calc_suntimes(self, latitude: float, longitude: float) -> None:
self.latitude = latitude
self.longitude = longitude
self.calc_sun_events()
def to_range(self, value: float, range_max: float) -> float:
""" Converting a variable to a given range
Args:
value (float): The given value
range_max (float): Upper boundary
Returns:
float: Corrected value inside range 0 to range_max
"""
if value < 0:
return value + range_max
elif value >= range_max:
return value - range_max
else:
return value
def calc_sun_events(self):
civial_dawn_start = self.calc_sunrise_sunset_time(True, 96)
sunrise_start = self.calc_sunrise_sunset_time(True)
morning_start = self.calc_sunrise_sunset_time(True, 89.167)
sunset_start = self.calc_sunrise_sunset_time(False, 89.167)
civial_dusk_start = self.calc_sunrise_sunset_time(False)
night_start = self.calc_sunrise_sunset_time(False, 96)
light_period_duration = (sunset_start - morning_start) / 8
noon_start = morning_start + 3 * light_period_duration
afternoon_start = morning_start + 5 * light_period_duration
evening_start = morning_start + 7 * light_period_duration
self.day_periods = [
time(hour=0, minute=0),
time(civial_dawn_start.hour, civial_dawn_start.minute),
time(sunrise_start.hour, sunrise_start.minute),
time(morning_start.hour, morning_start.minute),
time(noon_start.hour, noon_start.minute),
time(afternoon_start.hour, afternoon_start.minute),
time(evening_start.hour, evening_start.minute),
time(sunset_start.hour, sunset_start.minute),
time(civial_dusk_start.hour, civial_dusk_start.minute),
time(night_start.hour, night_start.minute),
]
def calc_sunrise_sunset_time(self, is_sunrise: bool, zenith=90.833) -> datetime:
""" Calculate all values to estimate the day periods
"""
RAD = pi / 180
# Day of the year
day_of_year = self.today.timetuple().tm_yday
# 2
lng_hour = self.longitude / 15
if is_sunrise:
t = day_of_year + ((6 - lng_hour) / 24)
else:
t = day_of_year + ((18 - lng_hour) / 24)
# 3
M = (0.9856 * t) - 3.289
# 4
L = self.to_range(M + (1.916 * sin(RAD * M)) + (0.020 * sin(RAD * 2 * M)) + 282.634, 360)
# 5
RA = self.to_range((1 / RAD) * atan(0.91764 * tan(RAD * L)), 360)
RA += ((floor(L / 90)) * 90 - (floor(RA / 90)) * 90)
RA /= 15
# 6
sin_dec = 0.39782 * sin(RAD * L)
cos_dec = cos(asin(sin_dec))
# 7a
cos_h = (cos(RAD * zenith) - (sin_dec * sin(RAD * self.latitude))) / (cos_dec * cos(RAD * self.latitude))
# The sun rises or sets never
if cos_h > 1 or cos_h < -1:
return None
# 7b
if is_sunrise:
H = 360 - (1 / RAD) * acos(cos_h)
else: #setting
H = (1 / RAD) * acos(cos_h)
H = H / 15
# 8
T = H + RA - (0.06571 * t) - 6.622
# 9
UT = T - lng_hour
UT = self.to_range(UT, 24) # UTC time in decimal format (e.g. 23.23)
# 10
hr = self.to_range(int(UT), 24)
min = round((UT - int(UT))*60, 0)
if min == 60:
hr += 1
min = 0
res = datetime(self.today.year, self.today.month, self.today.day, hr, int(min))
return res.replace(tzinfo=timezone.utc).astimezone(tz=None)

View File

@@ -0,0 +1,153 @@
import math
class Time_Bar_Chart:
def __init__(self) -> None:
self.image_code = []
self.colors = [
"00193d",
"05597f",
"54babf",
"bfe3c2",
"ffbf6b",
"fdb55c",
"f37f73",
"b45bbc",
"7e38ce",
"00285f"
]
self.bar_pos_x = []
def create_bar_chart_with_polylines(self, save_location: str, image_width: int, image_height: int, times: list):
""" Create a time bar chart
Args:
image_width (int): Width of the image in pixel
image_height (int): Height of the image in pixel
times (list): List of start times of the periods in minutes since midnight
"""
self.create_bar(image_width, image_height, times)
self.create_polylines(image_width, image_height)
self.create_time_markers(image_width, image_height)
# Write to file
self.image_code.insert(0, '<svg xmlns="http://www.w3.org/2000/svg" width="%s" height="%s">' % (image_width, image_height))
self.image_code.append('</svg>')
file = open(save_location + "/time_bar_polylines.svg", "w")
for i in self.image_code:
file.write(i + '\n')
self.image_code.clear()
self.bar_pos_x.clear()
def create_bar_chart(self, save_location: str, image_width: int, image_height: int, times: list):
self.create_bar(image_width, image_height, times)
self.create_time_markers(image_width, image_height)
# Write to file
self.image_code.insert(0, '<svg xmlns="http://www.w3.org/2000/svg" width="%s" height="%s">' % (image_width, image_height))
self.image_code.append('</svg>')
file = open(save_location + "/time_bar.svg", "w")
for i in self.image_code:
file.write(i + '\n')
self.image_code.clear()
self.bar_pos_x.clear()
def create_bar(self, image_width: int, image_height: int, times: list):
""" Generates the code for the horizontal multi-color bar chart
Args:
image_width (int): Total width of the image
image_height (int): Total height of the image
times (list): List of start times of the periods, in minutes
"""
x = 0
y = 40
width = 0
height = image_height - 80
if times[len(times) - 1] != 1440:
times.append(1440)
# Adding the bar parts
for i in range(1, len(times)):
width = math.ceil((((100 / 1440) * (times[i] - times[i - 1]) / 100) * image_width))
self.image_code.append(
'<rect fill="#%s" x="%s" y="%s" width="%s" height="%s"/>' % (self.colors[i - 1], x, y, width, height)
)
self.bar_pos_x.append(x)
x += width
def create_time_markers(self, image_width: int, image_height: int):
""" Generates the code for the vertical hour markers
Args:
image_width (int): Total width of the image
image_height (int): Total height of the image
"""
for i in range(0, 8):
self.image_code.append(
'<line x1="%s" y1="40" x2="%s" y2="%s" stroke="gray" stroke-width="2" />' %
(i * (image_width // 8), i * (image_width // 8), image_height - 40)
)
self.image_code.append(
'<text x="%s" y="%s" fill="gray" font-size="20" font-family="Liberation Sans">%s</text>' %
(i * (image_width // 8) + 5, image_height - 45, i * 3)
)
def create_polylines(self, image_width: int, image_height: int):
""" Generates the code for the polylines which connect the images with the bar sections
Args:
image_width (int): Total width of the image
image_height (int): Total height of the image
"""
bar_x_start = 0
self.bar_pos_x.append(image_width)
for i in range(0, len(self.bar_pos_x) - 1):
# X-Middle of a bar
bar_mid = bar_x_start + (self.bar_pos_x[i + 1] - bar_x_start) / 2
# Position of the image in the window
image_x = (image_width - 32) / 10 + ((i // 2) % 5) * image_width / 5
# i == 0, 2, 4, ... => Upper Polylines
if (i % 2 == 0):
polyline_y = 0
else:
polyline_y = image_height
if i == 0 or i == 8:
polyline_x = 30
elif i == 2 or i == 6:
polyline_x = 20
elif i == 1 or i == 9:
polyline_x = image_height - 30
elif i == 3 or i == 7:
polyline_x = image_height - 20
elif i == 5:
polyline_x = image_height - 10
else:
polyline_x = 10
self.image_code.append(
'<polyline points="%s,%s %s,%s %s,%s %s,%s" stroke="#%s" fill="none" stroke-width="5" />' %
(image_x, polyline_y, image_x, polyline_x, bar_mid, polyline_x, bar_mid, image_height / 2, self.colors[i])
)
# Store the end point of the bar as start point of the next
bar_x_start = self.bar_pos_x[i + 1]

View File

@@ -1,224 +1,130 @@
{
"layout": {
"type": "layout",
"pages": [
"pg_config",
"pg_about"
],
"pg_config": {
"type": "page",
"title": "Configuration",
"sections": [
"sec_image_configuration",
"sec_location",
"sec_times"
]
},
"pg_about": {
"type": "page",
"title": "About",
"sections": [
"sec_project",
"sec_github",
"sec_report_issue"
]
},
"sec_image_configuration": {
"type": "section",
"title": "Image set",
"keys": [
"lb_image_configuration",
"btn_config_images",
"sw_image_stretch"
]
},
"sec_location": {
"type": "section",
"title": "Location estimation",
"keys": [
"sw_auto_location",
"sc_location_refresh_time",
"etr_latitude",
"etr_longitude"
]
},
"sec_times": {
"type": "section",
"title": "Time periods",
"keys": [
"tv_times"
]
},
"sec_project": {
"type": "section",
"title": "About the project",
"keys": [
"lb_about",
"lb_author",
"lb_spices",
"btn_spices"
]
},
"sec_github": {
"type": "section",
"title": "Source Code on GitHub",
"keys": [
"lb_repository",
"btn_open_repository"
]
},
"sec_report_issue": {
"type": "section",
"title": "Report an issue",
"keys": [
"lb_report_issue",
"btn_report_issue"
]
}
},
"lb_image_configuration": {
"type": "label",
"description": "Choose an included image set or import a heic-file with the Image Configurator"
},
"btn_config_images": {
"type": "button",
"description": "Image Configurator",
"callback": "openImageConfigurator"
},
"sw_image_stretch": {
"type": "switch",
"description": "Expand image over all displays",
"default": true
},
"sw_auto_location": {
"type": "switch",
"default": true,
"description": "Estimate coordinates via network"
},
"sc_location_refresh_time": {
"type": "scale",
"default": 15,
"min": 5,
"max": 60,
"step": 5,
"description": "Interval time to refresh the location via network (min)",
"dependency": "sw_auto_location"
},
"etr_latitude": {
"type": "entry",
"default": "",
"description": "Latitude",
"dependency": "!sw_auto_location"
},
"etr_longitude": {
"type": "entry",
"default": "",
"description": "Longitude",
"dependency": "!sw_auto_location"
},
"tv_times": {
"type": "textview",
"description": "Time sections today",
"default": ""
},
"lb_about": {
"type": "label",
"description": "Based on a location, this extension calculates the periods of a day and switch the background image of your Cinnamon desktop. The extension offers the choice between a set of predownloaded wallpapers or to select a custom set of images."
},
"lb_author": {
"type": "label",
"description": "Developed by TobiZog"
},
"lb_spices": {
"type": "label",
"description": "If you want more information or rate the extension, you can visit the site Cinnamon Spices Website."
},
"btn_spices": {
"type": "button",
"description": "Cinnamon Dynamic Wallpaper at Cinnamon Spices Website",
"callback": "openSpicesWebsite"
},
"lb_repository": {
"type": "label",
"description": "This project is Open Source. You can visit the whole source code of this extension on GitHub"
},
"btn_open_repository": {
"type": "button",
"description": "Open the Repository",
"callback": "openRepoWebsite"
},
"lb_report_issue": {
"type": "label",
"description": "Do you find an issue? Or want a new feature? Go to the GitHub repository and create a new issue."
},
"btn_report_issue": {
"type": "button",
"description": "Submit an Issue",
"callback": "openIssueWebsite"
},
"etr_choosen_image_set": {
"type": "entry",
"default": "lakeside",
"description": ""
},
"etr_img_morning_twilight": {
"type": "entry",
"default": "1.jpg",
"description": ""
},
"etr_img_sunrise": {
"type": "entry",
"default": "2.jpg",
"description": ""
},
"etr_img_morning": {
"type": "entry",
"default": "3.jpg",
"description": ""
},
"etr_img_noon": {
"type": "entry",
"default": "4.jpg",
"description": ""
},
"etr_img_afternoon": {
"type": "entry",
"default": "5.jpg",
"description": ""
},
"etr_img_evening": {
"type": "entry",
"default": "6.jpg",
"description": ""
},
"etr_img_sunset": {
"type": "entry",
"default": "7.jpg",
"description": ""
},
"etr_img_night_twilight": {
"type": "entry",
"default": "8.jpg",
"description": ""
},
"etr_img_night": {
"type": "entry",
"default": "9.jpg",
"description": ""
},
"first_start": {
"type": "generic",
"default": true
}
"first_start": {
"type": "generic",
"default": true
},
"picture_aspect": {
"type": "generic",
"default": "Scaled"
},
"dynamic_background_color": {
"type": "generic",
"default": true
},
"image_source": {
"type": "generic",
"default": "image_set"
},
"selected_image_set": {
"type": "generic",
"default": "lakeside"
},
"source_folder": {
"type": "generic",
"default": ""
},
"period_0_image": {
"type": "generic",
"default": "9.jpg"
},
"period_1_image": {
"type": "generic",
"default": "1.jpg"
},
"period_2_image": {
"type": "generic",
"default": "2.jpg"
},
"period_3_image": {
"type": "generic",
"default": "3.jpg"
},
"period_4_image": {
"type": "generic",
"default": "4.jpg"
},
"period_5_image": {
"type": "generic",
"default": "5.jpg"
},
"period_6_image": {
"type": "generic",
"default": "6.jpg"
},
"period_7_image": {
"type": "generic",
"default": "7.jpg"
},
"period_8_image": {
"type": "generic",
"default": "8.jpg"
},
"period_9_image": {
"type": "generic",
"default": "9.jpg"
},
"period_source": {
"type": "generic",
"default": "network_location"
},
"location_refresh_intervals": {
"type": "generic",
"default": 15
},
"latitude_auto": {
"type": "generic",
"default": 0
},
"longitude_auto": {
"type": "generic",
"default": 0
},
"latitude_custom": {
"type": "generic",
"default": 0
},
"longitude_custom": {
"type": "generic",
"default": 0
},
"period_0_custom_start_time": {
"type": "generic",
"default": "00:00"
},
"period_1_custom_start_time": {
"type": "generic",
"default": "05:00"
},
"period_2_custom_start_time": {
"type": "generic",
"default": "07:00"
},
"period_3_custom_start_time": {
"type": "generic",
"default": "09:00"
},
"period_4_custom_start_time": {
"type": "generic",
"default": "11:00"
},
"period_5_custom_start_time": {
"type": "generic",
"default": "13:00"
},
"period_6_custom_start_time": {
"type": "generic",
"default": "15:00"
},
"period_7_custom_start_time": {
"type": "generic",
"default": "17:00"
},
"period_8_custom_start_time": {
"type": "generic",
"default": "19:00"
},
"period_9_custom_start_time": {
"type": "generic",
"default": "21:00"
}
}

View File

@@ -0,0 +1 @@
5.4/icons/icon.svg

View File

@@ -1,16 +1,15 @@
{
"external-configuration-app": "preferences.py",
"uuid": "cinnamon-dynamic-wallpaper@TobiZog",
"name": "Cinnamon Dynamic Wallpaper",
"description": "Cinnamon extension for dynamic desktop backgrounds based on time and location",
"version": "1.3",
"version": "2.0",
"multiversion": true,
"cinnamon-version": [
"4.8",
"5.0",
"5.2",
"5.4",
"5.6",
"5.8"
"5.8",
"6.0"
],
"max-instances": 1,
"url": "https://github.com/TobiZog/cinnamon-dynamic-wallpaper"

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 KiB

After

Width:  |  Height:  |  Size: 412 KiB