KDE: Bing image as a wallpaper

A post on describing how to update the desktop wallpaper in KDE taking images from https://bing.com using Rust.

Bing image of the day

The image changes daily and can be reached via: https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1

Sending n=1 at the end of the URL restricts only 1 image (latest) to be returned.

This returns a JSON response.

{
   "images":[
      {
         "startdate":"20220914",
         "fullstartdate":"202209142300",
         "enddate":"20220915",
         "url":"/th?id=OHR.PyreneesPark_EN-GB9616848199_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp",
         "urlbase":"/th?id=OHR.PyreneesPark_EN-GB9616848199",
         "copyright":"Roland's Breach in the Pyrenees, France (© SPANI Arnaud/Alamy)",
         "copyrightlink":"https://www.bing.com/search?q=Roland%27s+Breach+Pyrenees&form=hpcapt&filters=HpDate%3a%2220220914_2300%22",
         "title":"Into the breach",
         "quiz":"/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20220914_PyreneesPark%22&FORM=HPQUIZ",
         "wp":true,
         "hsh":"703d283825e97a4c86c031ca1c4155cd",
         "drk":1,
         "top":1,
         "bot":1,
         "hs":[
            
         ]
      }
   ],
   "tooltips":{
      "loading":"Loading...",
      "previous":"Previous image",
      "next":"Next image",
      "walle":"This image is not available to download as wallpaper.",
      "walls":"Download this image. Use of this image is restricted to wallpaper only."
   }
}

Using Rust to deserialize JSON content

First, we need to import serde_json.

This library provides the functionality to serialize/deserialize data.

In this case we have a JSON message which needs deserialization into Rust data structures.

The JSON message returned by Bing is comprised of strings, array, booleans, integer types and we need an efficient way to extract these.

Here is an example of using the library to deserialize the JSON message.

pub fn parse_json(content: &String) -> serde_json::Result<serde_json::Value> {
    let json: serde_json::Value = serde_json::from_str(&content)?;

    Ok(json)
}

serde_json includes a from_str(&str) function which takes in the JSON message as a reference to a string then tries to return a deserialized object.

From there we can parse the JSON message more efficiently.

let json_response = Response::parse_json(&response.text).unwrap();
if json_response["images"].as_array().unwrap().len() > 0 {
    let image_url = json_response["images"][0]["url"].as_str().unwrap();
    let start_date = json_response["images"][0]["startdate"].as_str().unwrap();
    let copyright = json_response["images"][0]["copyright"].as_str().unwrap();

json_response = the deserialzied object returned from serde_json

images = can now be interacted with as an array

image_url = contains the URL pointing to the location of the image

** Note the image url does not point to the actual image, a slight modification is required to append https://bing.com in front of the url. **

th?id=OHR.PyreneesPark_EN-GB9616848199_1920x1080.jpg

Becomes.

https://bing.com/th?id=OHR.PyreneesPark_EN-GB9616848199_1920x1080.jpg

The dimensions (1920x1080) of the image can also be altered to allow users to retrieve smaller/higher resolutions.

Using DBus to update the desktop wallpaper

** Note. These steps have only been tested on a Linux OS running the KDE. **

Once the image has been downloaded to disk.

The next step is to use DBus to:

  1. query the current wallpaper image

  2. set the wallpaper image

Querying and setting the wallpaper image involves a bit of JavaScript code.

The DBus method takes 1 argument, a string: evaluateScript(string) and is available on interface org.kde.plasmashell

Using qdbus to get the current wallpaper image

Checking for which image the wallpaper is currently set to ensures it is not unecessarily overwritten again and again with the same image.

Using qdbus we can simulate this call via the command line.

qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.evaluateScript \
"var allDesktops = desktops(); d = allDesktops[1]; d.wallpaperPlugin = \"org.kde.image\";d.currentConfigGroup = Array(\"Wallpaper\",\"org.kde.image\",\"General\");print(d.readConfig(\"Image\"));"

Returns the local path to where the image is stored on disk.

file:///home/demo/Pictures/bing/20220914_into_the_breach.jpg

allDeskstops[1] = Returns the last image set on the desktop

Using qdbusviewer to get the current wallpaper image

As an alternative to the command line option of using qdbus there is the GUI client called qdbusviewer.

Using Rust to get the current wallpaper image

The library https://docs.rs/dbus/0.9.6/dbus/ was used exclusively to interact with DBus using Rust.

Here is the code used to invoke the DBus method.

use dbus::{blocking::Connection, Error};
const DBUS_PROXY_TIMEOUT: u64 = 5000;

// create the DBus session connection
let connection = Connection::new_session().expect("D-Bus connection failed")

// create a DBus proxy so any subsequent calls to the interface can be re-used
let proxy = connection.with_proxy(
            "org.kde.plasmashell",
            "/PlasmaShell",
            Duration::from_millis(DBUS_PROXY_TIMEOUT),
        );

// create a string variable to store the JavaScript code
let jscript_get_wallpaper = concat!(
    "var allDesktops = desktops();",
    "d = allDesktops[1];",
    "d.wallpaperPlugin = \"org.kde.image\";",
    "d.currentConfigGroup = Array(\"Wallpaper\",\"org.kde.image\",\"General\");",
    "print(d.readConfig(\"Image\"));",
);

// call the method `evaluateScript` with the JavaScript code as an argument
let (proxy_result,): (String,) = proxy
    .method_call(
        "org.kde.PlasmaShell",
        "evaluateScript",
        (jscript_get_wallpaper,),
    )
    .unwrap();

proxy_result holds the image currently set as the wallpaper.

Setting the new wallpaper image

To set the wallpaper the following JavaScript code was used, and sent as an argument into the evaluateScript(string) method.

 var allDesktops = desktops();
 print (allDesktops);
 for (i=0;i<allDesktops.length;i++) {
 	d = allDesktops[i];
    d.wallpaperPlugin = "org.kde.image";
    d.currentConfigGroup = Array("Wallpaper", "%s", "General");
    d.writeConfig(\"Image\", \"file://{FILENAME}\")"
 }

Where FILENAME = is the absolute path to where the image is located on disk.

Last updated on 15 Sep 2022
Published on 15 Sep 2022