49 Commits

Author SHA1 Message Date
d4cc8f2616 Backing up and restore the slick-greeter file 2024-02-15 20:27:23 +01:00
333bb3b995 Hungarian translation, move current login image to usr/share/pixmaps 2024-02-15 19:58:53 +01:00
77db85cc71 Bugfix for locations near the dateline 2024-02-15 18:31:32 +01:00
1c6060d8d5 Small fixes, changlelog 2024-02-15 17:34:07 +01:00
TobiZog
8da056350a Merge pull request #16 from blogdron/main
Add Russian translation
2024-02-15 17:24:25 +01:00
0ab3297054 Adding support for Login screen wallpaper 2024-02-15 17:04:20 +01:00
blogdron
6ac34770ff Add Russian translation 2024-02-15 16:46:46 +03:00
45e95d0e2b Readme 2024-02-15 13:20:59 +01:00
d2d954b25f Small fix 2024-02-12 12:17:33 +01:00
72144f76c6 Bugfixes, translation 2024-02-12 12:15:45 +01:00
755664230a Adding dutch, german and spanish (rcalixte) translation 2024-02-11 19:34:58 +01:00
aae46e4b1e Readme 2024-02-04 16:11:31 +01:00
14497e21f2 Changelog, Readme, dynamic background example image 2024-02-04 16:08:05 +01:00
34e44f4b3c Observer pattern, Bugfixes 2024-02-04 14:45:12 +01:00
53f5984eb5 MVVM pattern finished, adding multiple location provider 2024-02-02 19:56:14 +01:00
8acc18c7fe Start migration to MVVM 2024-02-02 15:30:33 +01:00
fe1cea9e2b Metadata/Readme for release of 2.1 2024-01-26 18:56:12 +01:00
0bf78c54d0 Bugfix 2024-01-26 18:52:51 +01:00
8a58e66c20 Bugfixes, Smaller UI for displays with reduced resolution 2024-01-23 18:11:13 +01:00
f9a84b18e8 Bugfix, Readme 2024-01-18 20:03:43 +01:00
c53c149d31 New image set (earth), refresh all image sets 2024-01-17 22:23:12 +01:00
17348f7acf Documentation, cleanup 2024-01-17 20:53:18 +01:00
e1b464e36c HEIC import 2024-01-17 20:04:12 +01:00
d97ec091c5 Bugfixes, exchange file chooser button 2024-01-17 19:09:37 +01:00
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
178 changed files with 10062 additions and 2332 deletions

7
.gitignore vendored
View File

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

View File

@@ -1,3 +1,35 @@
# Version 2.4
- Adding Login window support
- Adding Russian translation (Thanks to blogdron)
- Adding Hungarian translation (Thanks to vajdao)
- Bugfix: Time period calculation on locations near the dateline, like Japan
# Version 2.3
- Adding Dutch translation
- Adding German translation
- Adding Spanish translation (Thanks to rcalixte)
# Version 2.2
- Bugfixes
- Migrate code to MVVM pattern
- Adding option to change the location provider
- Adding example image for dynamic background color
# Version 2.1
- Bugfixes
- Smaller UI for displays with reduced resolution (< 1000px height)
# Version 2.0
- 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

View File

@@ -1,35 +1,39 @@
# Cinnamon Dynamic Wallpaper
<img src="cinnamon-dynamic-wallpaper@TobiZog/5.4/res/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
- 9 included image sets
- 10 day periods
- HEIF converter
- Image configuration assistent with simple one-click setup for image choose
- Online location estimation or offline with manual latitude and longitude input
- Image configuration assistent with simple one-click setup for image choices
- Online location estimation (three provider) 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
- Creating a color gradient based on the current wallpaper for images which not fill the whole 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)
### 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)
@@ -39,33 +43,46 @@ Based on a location, this extension calculates the periods of a day and switches
3. Search and download it
### From the repo
1. Download the Repository
1. Download the latest from the Releases page on GitHub: https://github.com/TobiZog/cinnamon-dynamic-wallpaper/releases
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)
---
## Contribute
### Translation
You want to contribute a language which isn't supported yet? Here is how to do:
1. Fork the cinnamon-spices-extensions project: https://github.com/linuxmint/cinnamon-spices-extensions
2. Pull the repository
3. Open `cinnamon-dynamic-wallpaper@TobiZog/files/cinnamon-dynamic-wallpaper@TobiZog/po/cinnamon-dynamic-wallpaper@TobiZog.pot` with a po-tool like poedit (https://poedit.net/).
4. Create a new translation in your language
5. Push the changes to your repository
6. Create a pull request
---
## Included image sets
The image sets are from https://github.com/adi1090x/dynamic-wallpaper
| Aurora | Beach | Bitday |
| ------ | ----- | ------ |
| ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/images/included_image_sets/aurora/5.jpg) | ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/images/included_image_sets/beach/4.jpg) | ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/images/included_image_sets/bitday/4.jpg) |
| ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/res/images/included_image_sets/aurora/5.jpg) | ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/res/images/included_image_sets/beach/4.jpg) | ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/res/images/included_image_sets/bitday/4.jpg) |
| Cliffs | Gradient | Lakeside |
| Cliffs | Earth | Gradient |
| -------- | --------- | ------ |
| ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/images/included_image_sets/cliffs/4.jpg) | ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/images/included_image_sets/gradient/4.jpg) | ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/images/included_image_sets/lakeside/4.jpg) |
| ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/res/images/included_image_sets/cliffs/4.jpg) | ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/res/images/included_image_sets/earth/4.jpg) | ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/res/images/included_image_sets/gradient/4.jpg) |
| Mountains | Sahara |
| --------- | ------ |
| ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/images/included_image_sets/mountains/4.jpg) | ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/images/included_image_sets/sahara/4.jpg) |
| Lakeside | Mountains | Sahara |
| --------- | ------ | ------ |
| ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/res/images/included_image_sets/lakeside/4.jpg) | ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/res/images/included_image_sets/mountains/4.jpg) | ![](cinnamon-dynamic-wallpaper@TobiZog/5.4/res/images/included_image_sets/sahara/4.jpg) |

View File

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

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

@@ -1,7 +1,7 @@
/**
* @name Cinnamon-Dynamic-Wallpaper
* @alias TobiZog
* @since 2023-05-17
* @name Cinnamon-Dynamic-Wallpaper
* @alias TobiZog
* @since 2023-05-17
*
* @description Main application file
*/
@@ -14,10 +14,11 @@ const Mainloop = imports.mainloop;
const Lang = imports.lang;
const { find_program_in_path } = imports.gi.GLib;
const Gio = imports.gi.Gio;
let suntimes = require('./scripts/suntimes')
let location = require('./scripts/location')
let communication = require('./scripts/communication')
const MessageTray = imports.ui.messageTray;
const St = imports.gi.St;
const Main = imports.ui.main;
const Gettext = imports.gettext;
const GLib = imports.gi.GLib;
/******************** Constants ********************/
@@ -33,12 +34,6 @@ const PATH = DIRECTORY.path;
// The extension object
let extension;
// Time and date of the last location update
let lastLocationUpdate = -1
// The last calculated suntime of the day
let lastDayTime = suntimes.DAYPERIOD.NONE
// Loop state
let looping = true
@@ -50,6 +45,15 @@ function CinnamonDynamicWallpaperExtension(uuid) {
}
function _(str) {
let customTranslation = Gettext.dgettext(UUID, str);
if (customTranslation !== str) {
return customTranslation;
}
return Gettext.gettext(str);
}
CinnamonDynamicWallpaperExtension.prototype = {
/******************** Lifecycle ********************/
@@ -62,75 +66,20 @@ CinnamonDynamicWallpaperExtension.prototype = {
_init: function(uuid) {
this.settings = new Settings.ExtensionSettings(this, uuid);
/** Configuration */
// 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_last_update", "etrLastUpdate")
this.bindSettings("etr_latitude", "latitude", this.settingsUpdated)
this.bindSettings("etr_longitude", "longitude", this.settingsUpdated)
// Time periods
this.bindSettings("tv_times", "tvTimes")
/** Debugging */
// Logs
this.bindSettings("tv_logs", "tvLogs")
// 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)
this.bindSettings("etr_morning_twilight_times", "img_morning_twilight_times")
this.bindSettings("etr_sunrise_times", "img_sunrise_times")
this.bindSettings("etr_morning_times", "img_morning_times")
this.bindSettings("etr_noon_times", "img_noon_times")
this.bindSettings("etr_afternoon_times", "img_afternoon_times")
this.bindSettings("etr_evening_times", "img_evening_times")
this.bindSettings("etr_sunset_times", "img_sunset_times")
this.bindSettings("etr_night_twilight_times", "img_night_twilight_times")
this.bindSettings("etr_night_times", "img_night_times")
Gettext.bindtextdomain(UUID, GLib.get_home_dir() + '/.local/share/locale');
// Check for the first startup
if (this.settings.getValue("first_start")) {
this.writeToLogs("First time start")
// Welcome notification
communication.showNotification("Welcome to Cinnamon Dynamic Wallpaper",
"Check the preferences to choose a dynamic wallpaper", true)
this.showNotification(_("Welcome to Cinnamon Dynamic Wallpaper"),
_("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"] + "/res/images/included_image_sets/lakeside/")
}
this.writeToLogs("Initialization completed")
// Set image initial at desktop wallpaper
this.setImageToTime()
// Start the main loop, checks in fixed time periods the
this._loop()
},
@@ -158,180 +107,46 @@ CinnamonDynamicWallpaperExtension.prototype = {
*/
_loop: function () {
if (looping) {
this.setImageToTime()
// Update the location, if the user choose "autoLocation" and the timer has expired
if ((lastLocationUpdate == -1 ||
lastLocationUpdate.getTime() < new Date().getTime() - this.locationRefreshTime * 60000) &&
this.autolocation)
{
this.updateLocation()
lastLocationUpdate = new Date()
try {
Util.spawnCommandLine("/usr/bin/env python3 " + DIRECTORY.path + "/src/main.py loop")
} catch(e) {
this.showNotification(_("Error!"),
_("Cinnamon Dynamic Wallpaper got an error while running the loop script. Please create an issue on GitHub."))
}
// Refresh every 60 seconds
Mainloop.timeout_add_seconds(60, Lang.bind(this, this._loop));
this.writeToLogs("Main loop runs...")
}
},
/******************** Settings handling ********************/
showNotification(title, text, showOpenSettings = false) {
let source = new MessageTray.Source(this.uuid);
/**
* Handles changes in settings
*/
settingsUpdated: function() {
lastDayTime = suntimes.DAYPERIOD.NONE
// Parameter
let params = {
icon: new St.Icon({
icon_name: "icon",
icon_type: St.IconType.FULLCOLOR,
icon_size: source.ICON_SIZE
})
};
// The notification itself
let notification = new MessageTray.Notification(source, title, text, params);
this.updateLocation()
this.setImageToTime()
},
// Display the "Open settings" button, if showOpenSettings
if (showOpenSettings) {
notification.addButton("open-settings", _("Open settings"));
/**
* 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")
},
/******************** Other functions ********************/
/**
* 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')
notification.connect("action-invoked", () =>
Util.spawnCommandLine("/usr/bin/env python3 " + DIRECTORY.path + "/src/main.py"));
}
Gio.Settings.sync();
gSetting.apply();
// Put all together
Main.messageTray.add(source);
this.writeToLogs("Set new image: " + imageURI)
},
/**
* 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.img_morning_twilight_times = convertToTimeString(timesArray[0][0]) + " - " + convertToTimeString(timesArray[0][1])
this.img_sunrise_times = convertToTimeString(timesArray[1][0]) + " - " + convertToTimeString(timesArray[1][1])
this.img_morning_times = convertToTimeString(timesArray[2][0]) + " - " + convertToTimeString(timesArray[2][1])
this.img_noon_times = convertToTimeString(timesArray[3][0]) + " - " + convertToTimeString(timesArray[3][1])
this.img_afternoon_times = convertToTimeString(timesArray[4][0]) + " - " + convertToTimeString(timesArray[4][1])
this.img_evening_times = convertToTimeString(timesArray[5][0]) + " - " + convertToTimeString(timesArray[5][1])
this.img_sunset_times = convertToTimeString(timesArray[6][0]) + " - " + convertToTimeString(timesArray[6][1])
this.img_night_twilight_times = convertToTimeString(timesArray[7][0]) + " - " + convertToTimeString(timesArray[7][1])
this.img_night_times = convertToTimeString(timesArray[8][0]) + " - " + convertToTimeString(timesArray[8][1])
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 () {
// Update the update information
lastLocationUpdate = new Date()
if (this.autolocation) {
let loc = location.estimateLocation()
this.latitude = loc["latitude"]
this.longitude = loc["longitude"]
this.etrLastUpdate = lastLocationUpdate.getHours() + ":" + lastLocationUpdate.getMinutes()
}
this.writeToLogs("Location updated")
},
/**
* Adding text to the logs
*
* @param {string} msg New message string
*/
writeToLogs: function(msg) {
this.tvLogs = communication.createLogs(this.tvLogs, msg)
// Display it
source.notify(notification);
}
}

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 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,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,398 +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/"
IMAGE_DEFAULT_DIR = IMAGE_DIR + "default/"
class WindowHandler:
def __init__(self, pref_path: str) -> None:
########### Class variables ###########
self.pref_path = pref_path
self.time_values = [
"etr_morning_twilight_times",
"etr_sunrise_times",
"etr_morning_times",
"etr_noon_times",
"etr_afternoon_times",
"etr_evening_times",
"etr_sunset_times",
"etr_night_twilight_times",
"etr_night_times"
]
self.img_values = [
"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.lb_times = [
self.builder.get_object("lb_times_1"),
self.builder.get_object("lb_times_2"),
self.builder.get_object("lb_times_3"),
self.builder.get_object("lb_times_4"),
self.builder.get_object("lb_times_5"),
self.builder.get_object("lb_times_6"),
self.builder.get_object("lb_times_7"),
self.builder.get_object("lb_times_8"),
self.builder.get_object("lb_times_9")
]
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.img_values):
# Bugfix: Load the images only, if there is choosen one
if pref_data[val]['value'] != None:
# 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
# Print the times of the day
for i, val in enumerate(self.time_values):
self.lb_times[i].set_text(pref_data[val]['value'])
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.img_values):
pref_data[val]['value'] = str(i + 1) + ".jpg"
else:
pref_data["etr_choosen_image_set"]["value"] = "custom"
for i, val in enumerate(self.img_values):
image_name = self.cb_previews[i].get_active_text()
pref_data[val]['value'] = image_name
# 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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 485 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 558 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 681 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 654 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 631 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 548 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 465 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Some files were not shown because too many files have changed in this diff Show More