The making of a wallpaper-changing app with electron and vue.js

Nguyễn Việt Hưng
The happy lone guy
Published in
11 min readSep 25, 2017
A great picture from Team UI8

Purpose and technologies

Purpose

The main purpose of this app is to help users to change their desktop wallpaper and maybe more awesome features such as finding an image, etc. Because there is a vast number of devices and OSs out there so we need to find some frameworks that enable our app to run cross-platform.

Technologies

Enough with the story! Let’s start building the real thing!

So with this app, we are going to use:

  • Vue.js — as our front-end framework.
  • Vue-loader — as our Vue.js component loader.
  • Webpack — as our module bundler.
  • Buefy — It contains all the Vue components that we need.
  • Electron — This is the framework for building cross-platform apps using web technologies.
  • Yarn — as our dependencies manager.

OK, that is all the ingredients that we need.

Setting up the working environment

Code editor

I’m using sublime text 3 with these packages:

Vue.js, vue-cli and generating project folder

I assume that you have already had some basic knowledge about Vue.js and have vue-cli installed. If not, you can learn more about Vue.js at the link on the top section and install vue-cli using:

npm install -g vue-cliORyarn global add vue-cli

After installing vue-cli globally, we can use the vue command to create our new project folder:

vue init webpack-simple [Your project folder name]

In my case, I’m going to name my folder as BGChanger (worst name ever) so I going to type in my command prompt:

vue init webpack-simple BGChanger

After that it will ask some questions about the project in order to generate the package.json file:

creating my project folder

Now let’s navigate to our “BGChanger” folder using the command:

cd BGChanger

and then, as you guys have ready guessed:

npm installORyarn install 

In this case, I’m going to use yarn to install all my dependencies so I going to use the second command.

After that execute this command

npm run dev

That command helps us to run and check if the project is correctly installed, if it does then it should open up the browser and appear like this:

Buefy

According to Buefy title: “Lightweight UI components for Vue.js based on Bulma” so this is an adequate overview of what Buefy is. Now let’s install it using

yarn add buefyORnpm install buefy

Electron

Now, the next step is to install electron which is the framework that we can use to build cross-platform apps using HTML, CSS and JavaScript.

Note: we have to install both locally and globally in order to use the electron command in the later step.

To install electron locally

yarn add electronORnpm install electron

And globally

yarn global add electronORnpm install -g electron

Electron apps always begin with an entry file. That file will first create the electron browser window and then load a specific HTML file, in this case, our HTML file is auto-generated by vue.js in the previous step. So, to begin I create an entry file a named it as elect.js and here is the content of it

elect.js

To test it, we must add our file to the main property in the package.json file and also remember to remove the / before any link in our source folder and index.html

package.json

After that open up the terminal or command prompt and type in these 2 commands

webpackelectron .

As a result, a small window pops up with the same output that we have seen in the previous step after we install vue.

Image? Where?

Well, you can’t make a wallpaper-changing app without any image, so where can we find a service that provides free images with decent quality?

Unsplash is your saviour, Unsplash is an image-providing service that contains thousands of creative full HD photos taken by many talented photographers all around the globe. One great thing about Unsplash is that it helps you find images in different categories and in your desired sizes, with a price of $0.

Learn more about Unsplash

So Unsplash provided us with a link to get a random image with the size that we want and that is where we’re going to get the image from. Here is the link structure

https://source.unsplash.com/random/[width]x[height]

Designing app layout!

Visualizing

This is just a basic app so I’m not going to draw anything, but instead, imagine an app that has a 4 columns grid, each column contain a random image.

Turn it into code!

The src folder contains 2 files: main.jsand App.vue.

App.vue

This is the auto-generated file that contains the welcome page in the earlier step that we have seen. Now, we are going to clear the content of this file so that we can fill in our new code later

App.vue

Everything that is in the templatetag will be rendered so that is where we’re going to put our HTML. The script tag is where our scripts and definition for the component go. The style tag will help us to style the component and the lang attribute is indicating that we will use SCSS instead of the traditional CSS that’s why at the first step of generating the project, vue-cli ask if we want to use SASS.

Main.js

This file contained our main script which will import all other components and we will also use it to attach other libraries to our app. For example, we need to use Buefy for our front-end part, we will import the Buefy dependency that we installed earlier and tell Vue to use it. Here is the content of main.js after we do it and also don’t forget to add the css extension to the webpack’s config file.

main.js
webpack.config.js

A grid of image

Buefy is based on Bulma so we can actually read the bulma documentation and implement it using Buefy without any problem. In this case, you can check out the Bulma documentation about their grid system to have a better understand what we’re going to make in the next step.

As I mentioned before, our app will contain a 4 columns grid and each column will contain an image that user can click on and set that image as their desktop wallpaper, so our HTML will look like this:

<template>
<div class="columns is-multiline">
<div class="column is-one-quarter">
<code>is-one-quarter</code>
</div>
<div class="column is-one-quarter">
<code>is-one-quarter</code>
</div>
<div class="column is-one-quarter">
<code>is-one-quarter</code>
</div>
<div class="column is-one-quarter">
<code>is-one-quarter</code>
</div>
<div class="column is-one-quarter">
<code>is-one-quarter</code>
</div>
</div>
</template>
<script>
export default {

}
</script>
<style lang="scss"></style>

As you can see, we have 4 columns and each of them is one-quarter wide. I also added the is-multiline class so that every time a row is filled with 4 columns, the grid will add another row and repeat the process.

Now, let’s replace the code tag with the img tag and pass the random image into it.

<template>
<div class="columns is-multiline" id="images">
<div class="column is-one-quarter">
<img src="https://source.unsplash.com/random/1600x900" class="image">
</div>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss">
#images{
padding: 5px;
}
</style>

The code should work fine and this time, we are going to display n images

<template>
<div class="columns is-multiline" id="images">
<div class="column is-one-quarter" v-for="n in numberOfImages">
<img :src="'https://source.unsplash.com/random/1600x900?sig=' + n" class="image">
</div>
</div>
</template>
<script>
export default {
data(){
return {
numberOfImages: 10
}
}
}
</script>
<style lang="scss">
#images{
padding: 5px;
}
</style>

So, by looking at the code, we can realize that we are using the v-for to loop numberOfImages times. But wait, what is the ?sig= doing in the image source? Well, actually the sig part doesn’t do anything. When you create multiple image tag that shares the same source, the browser will automatically help you to improve the performance by caching the image. As a result, all your image tag will render the same image! So, in order to not let that happens we must make each of our URL unique, therefore, I added the sig part to the URL. This solution was taken from here.

After implementing the solution, our app displayed as we expected

10 great images rendered as we expected

Great! now we want to be able to control how many images to display. So we can easily do that by creating an input and add the v-model property to it.

<template>
<div>
<section class="section">
<b-field label="How many images to display">
<b-input v-model.number="numberOfImage" :value="numberOfImage"></b-input>
</b-field>
</section>
<div class="columns is-multiline" id="images">
<div class="column is-one-quarter" v-for="n in numberOfImage">
<img :src="'https://source.unsplash.com/random/1600x900?sig=' + n" class="image">
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return {
numberOfImage: 10
}
},
}
</script>
<style lang="scss">
#images{
padding: 5px;
}
.image:hover{
transition: all 0.3s;
box-shadow: 0 0.3125rem 1rem 0 rgba(0,0,0,0.4);
}
</style>

Same code but added a b-field and b-input components, these are the components that come from buefy. The b-input component is the input field that we will type in the number, so we need to use v-model onto the b-input but that is not enough. If you only use v-model it’s going to assign the string to our variable. Because our numberOfImage is a number, we must use the .number modifier so that everything we type in will be cast into number type.

The result is unbelievable ! It’s like

Anyway here is how it look:

We can load how many images that we like

Into the woods

We will now dig deeper into our app’s core. Firstly, we have used the fixed size for all of the images, now we want to be able to find images that will fit our screen. Secondly, how to set an image as our desktop background? Let’s break it down.

Different images with the appropriate size

This process involves 2 steps:

  • Detect screen size
  • Replace the value of the src property of each image

Detecting screen size is pretty simple and straightforward, especially, if you are a web developer this process is too familiar. Anyway, here is how we get the screen width and height

const width  = window.screen.width;
const height = window.screen.height;

Moving on assigning the screen size into the src property. This is similar to the previous process where we assign the number into the seq query of the src . First, we create 2 variable width and height just like we did in the previous step. Afterwards, we concatenate those 2 variables into one computed variable call size and then we add the size variable into the src value.

<template>
<div>
<section class="section">
<b-field label="How many images to display">
<b-input v-model.number="numberOfImage" :value="numberOfImage"></b-input>
</b-field>
</section>
<div class="columns is-multiline" id="images">
<div class="column is-one-quarter" v-for="n in numberOfImage">
<img :src="'https://source.unsplash.com/random/' + size + '?sig=' + n" class="image">
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return {
numberOfImage: 10,
width: window.screen.width,
height: window.screen.height
}
},
computed:{
size(){
return this.width + 'x' + this.height;
}
}
}
</script>
<style lang="scss">
#images{
padding: 5px;
}
.image:hover{
transition: all 0.3s;
box-shadow: 0 0.3125rem 1rem 0 rgba(0,0,0,0.4);
}
</style>

Set an image as wallpaper

This process sounds a little bit confusing as the traditional JavaScript that executes in the browser isn’t allowed to change anything beyond the sandbox. However, this is NodeJs, where you can use JavaScript to do almost anything that you want. After 5 minutes of googling, I came across this amazing module by Sindre Sorhus called wallpaper. So finally, this process takes 2 steps:

  • Save and store the image
  • Set background image using wallpaper dependency

Saving the image by downloading is not a great idea because when we go to the random image URL it will redirect us to the real image URL but when passing in the URL to the src attribute of each image, it still keeps the old random URL and not following the redirection. In order to archive this, there is a way that we can try is that to convert that image into base64 using canvas and then write it to the file.

convertToBase64(img){
let canvas = document.createElement("canvas");
canvas.width = this.width;
canvas.height = this.height;
let context = canvas.getContext("2d");
context.drawImage(img, 0, 0);
let dataURL = canvas.toDataURL("image/jpg");
return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
}

The two variables this.width and this.height were declared in the data function of the Vue instance in the previous step. So, here, we create a new canvas and set it width and height to our screen width and height, after that we draw the image and convert canvas content into dataURL, replacing some unnecessary parts and then return it.

So here is how the set background image look like

setAsBackground(event){
let base64Image = this.convertToBase64(event.target);
let picturePath = path.join(os.homedir(), "/Pictures", "background.jpg");
picturePath = path.normalize(picturePath);
fs.writeFile(picturePath, base64Image, 'base64', (err) => {
wallpaper.set(picturePath, {scale: "stretch"})
.then(() => {
console.log(path.resolve(picturePath));
this.$snackbar.open("Done !");
});
});
}

First, we convert the target image to base64 this function is going to be attached to every image that we display so the event.target will point to the image that we click on. Then, we write the base64 data to the background.jpg file. Afterwards, we use the wallpaper library to set the background image.

But there’s a new problem, our app exports everything that it needs to use into the build.js in the dist folder, so in order for the wallpaper library to work on windows, we need to change the path to the dist folder inside the win.js file in the library which is located in node_modules/wallpaper/lib . After that, we will have to import the .exe file in node_modules/wallpaper/lib to our dist folder.

win.js content after modification
Result!

End of story!

That is it! That is the whole process of me making this amazing app, it still pretty basic but I’m going to let you improve it with your imagination. If you have any question then comment below. If you want to see the whole source, you can find it here (Yes, I renamed it as “Alluring”, I’m really bad at naming stuff)

Don’t forget to clap as many as you want, it’s free!

--

--

Nguyễn Việt Hưng
The happy lone guy

A web developer who love to create everything using web technology