El día de hoy veremos cómo cambiar la imagen de fondo de escritorio de manera remota para molestar al usuario.
Dejaremos un programa ejecutándose en segundo plano, mismo que va a revisar cada X segundos si debe cambiar el fondo y en caso de que sí va a descargar la imagen del sitio que le indiquemos para colocarla como nuevo fondo.
Mientras el usuario esté conectado a internet y el programa se esté ejecutando, no se podrá hacer nada para cambiar el fondo de pantalla al original, ya que nuestro programa va a cambiar la imagen de escritorio cada tiempo que nosotros indiquemos.
Al final esto es para hacer bromas, no le veo otra utilidad. Eso sí, vas a aprender varias cosas, por ejemplo, cómo consultar un gist con HTTP, poner un cron en Go e invocar a un programa de C#.
Cambiando el fondo de escritorio
Ya tengo todo un post explicando cómo cambiar el wallpaper de manera programada con C#.
Es importante que compiles esa aplicación de tal forma que se pueda invocar en la línea de comandos pasándole la ruta de la imagen.
En mi caso la app necesitaba .net framework 3.1 para ejecutarse. Puedes instalar ese SDK o puedes compilar en un standalone la app de C# en Visual Studio. Al intentar ejecutarla sin el SDK instalado me decía:
A fatal error occurred. The required library hostfxr.dll could not be found.
If this is a self-contained application, that library should exist in [C:\Users\parzibyte\Documents\desarrollo\go\wallpaper-change\].
If this is a framework-dependent application, install the runtime in the global location [C:\Program Files\dotnet] or use the DOTNET_ROOT environment variable to specify the runtime location or register the runtime location in [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x64\InstallLocation].The .NET Core runtime can be found at:
– https://aka.ms/dotnet-core-applaunch?missing_runtime=true&arch=x64&rid=win10-x64
Como sea, antes de usar a Go prueba que el .exe de cambiar wallpaper funciona.
Una vez comprobado coloca el ejecutable y sus dependencias en la misma ubicación donde se ejecute el programa de Golang que vamos a compilar a continuación.
Explicación del funcionamiento
Vamos a ejecutar unas líneas de código cada cierto tiempo (cada segundo para ser específicos). Lo que se hará será:
Comprobar base de datos local y ver cuándo fue el último cambio de wallpaper hecho por el propio programa.
func revisarGistYCambiarImagenSiEsNecesario() error {
ultimoCambio, err := obtenerUltimoCambioDeImagen()
err, rutaImagen, fecha := obtenerDetallesGist()
if err != nil {
return err
}
if ultimoCambio.Fecha < fecha || ultimoCambio.UrlImagen != rutaImagen {
return descargarImagenYPonerlaComoFondo(rutaImagen)
}
return nil
}
Si la fecha es anterior a la que se indica en el servidor remoto entonces vamos a descargar la imagen que el servidor nos haya dicho, ponerla como fondo de escritorio y guardar eso en la base de datos local.
Nota: en este caso nuestro “servidor” será un gist de GitHub, es decir, algo como un “txt” que podemos alojar en GitHub. Se puede ver algo así:
https://www.nintenderos.com/wp-content/uploads/2018/08/super-smash-bros-ultimate-king-k-rool-min.jpg,2030-09-20T17:00:00
La estructura son 2 valores separados por coma. El primero es la URL y el segundo es la fecha en la cual se dejará de colocar ese wallpaper como fondo.
Según el gist que vemos arriba, el wallpaper será cambiado cada segundo hasta que sea 20 de septiembre de 2030.
Descargando imagen
Una cosa es consultar el gist y parsearlo:
func obtenerDetallesGist() (error, string, string) {
clienteHttp := &http.Client{}
peticion, err := http.NewRequest("GET", GistControlador, nil)
if err != nil {
return err, "", ""
}
respuesta, err := clienteHttp.Do(peticion)
if err != nil {
return err, "", ""
}
defer respuesta.Body.Close()
cuerpoRespuesta, err := ioutil.ReadAll(respuesta.Body)
if err != nil {
return err, "", ""
}
respuestaString := string(cuerpoRespuesta)
if respuesta.StatusCode != http.StatusOK {
return fmt.Errorf("status code no fue OK, fue %v", respuesta.StatusCode), "", ""
}
respuestaArreglo := strings.Split(respuestaString, ",")
if len(respuestaArreglo) != 2 {
return fmt.Errorf("se esperaban 2 valores separados por coma (,), pero se encontraron: %d", len(respuestaArreglo)), "", ""
}
rutaImagen, fecha := respuestaArreglo[0], respuestaArreglo[1]
return nil, rutaImagen, fecha
}
Y otra cosa es descargar la imagen para almacenarla en el disco duro con un nombre aleatorio:
func descargarArchivoDeInternet(url string) (string, error) {
respuesta, err := http.Get(url)
if err != nil {
return "", err
}
defer respuesta.Body.Close()
nombreArchivoSalida := fmt.Sprintf("%s.%s", xid.New().String(), extensionImagenSegunContentType(respuesta.Header.Get("Content-Type")))
archivoSalida, err := os.Create(nombreArchivoSalida)
if err != nil {
return "", err
}
defer archivoSalida.Close()
_, err = io.Copy(archivoSalida, respuesta.Body)
return nombreArchivoSalida, err
}
Esta función devuelve el nombre de la imagen que se acaba de descargar. Y con eso ya podemos invocar al ejecutable que compilamos en Visual Studio.
Invocando a ejecutable de C# desde Go
Ya sabemos cómo cambiar el wallpaper manualmente ejecutando el programa, ¿pero cómo lo hacemos desde Go? veamos:
/*
Recibe el nombre de una imagen para poner como fondo. La imagen debe estar
en el mismo directorio que este ejecutable
*/
func cambiarWallpaper(nombreImagen string) ([]byte, error) {
ubicacionActual, _ := os.Getwd()
ubicacionImagenCompleta := path.Join(ubicacionActual, nombreImagen)
return exec.Command(NombreEjecutableWallpaperChange, ubicacionImagenCompleta).Output()
}
Podemos invocar al ejecutable con exec.Command
pasándole la ubicación completa de la imagen.
Nota: recuerda que cada cambio de imagen se guarda en la base de datos local con SQLite3 y que también se consulta de esa base de datos cuando se revisa si se debería cambiar el fondo.
Configurando cron
Ya tenemos todas las funciones necesarias, ahora hay que configurar el programa para que se ejecute cada cierto tiempo. Queda así con el paquete cron:
func main() {
err := crearTablas()
if err != nil {
log.Printf("Error creando tablas: %v", err)
return
}
c := cron.New()
defer c.Stop()
// Agregarle funciones...
// Ejecutar cada segundo toda la vida
err = c.AddFunc("0 */1 * * *", func() {
log.Printf("Soy cron")
revisarGistYCambiarImagenSiEsNecesario()
})
if err != nil {
log.Printf("Error iniciando cron: %v", err)
return
}
// Comenzar
c.Start()
// Lo siguiente es únicamente para pausar el programa y no tiene nada
// que ver con cron o el ejemplo, recuerda que
// el programa se detiene con Ctrl + C
select {}
}
Poniendo todo junto
El código completo de Golang lo dejo en GitHub. Mira el makefile para ver cómo se compila o si cuentas con make simplemente ejecuta make
.
En ese mismo repositorio vas a encontrar el código de C#, mismo que debes compilar antes desde Visual Studio Code, colocar junto al ejecutable de Go y luego cambiar la constante del nombre del .exe
en caso de ser necesario.
Por cierto, debido a que el programa usa SQLite3 vas a necesitar el compilador de GCC de 64 bits.
Te repito que el programa solo es para molestar al usuario, no tiene otra utilidad, pero demuestra las cosas que se pueden hacer.
Por cierto, si quieres se puede ejecutar cada que Windows inicie.