Pythonで「時をかける少女」風のスクリーンセーバーを作る

 
# はじめに

### スクリーンセーバーとは

スクリーンセーバーは、パソコンを一定時間放置したときに、自動的にディスプレー画面を暗くしたり、アニメーションを表示したりするプログラムです。もともとは、同じ画面をずっと表示させ続けると、画面を変えても元の画面が残ってしまう「焼き付き」を防ぐためのものでした。しかし、ディスプレイの性能もあがりほとんど焼き付きはおこらなくなったので、最近ではのぞき見の防止のために使われることが多いです。<br>また、パソコンを放置している間にディスプレイに表示してくれるので、ある種のアートとしてさまざまなものを表示させて楽しむという使い方もあります。

Windows10のスクリーンセーバーだと、メタリックなWindows10のマークがぐるぐる回っているものが一番メジャーじゃないかと思われます。

自分は、もともとは「Fliqlo」という、パタパタ時計(正式名称はフリップクロック)を模したデザインのスクリーンセーバーを友達のすすめで使っていました、しかし、2021年1月12日のAdobe Flush Playerが使えなくなったことに伴って、Fliqloも実行できなくなってしまいました[^1]。

Fliqloを使う前は、スクリーンセーバーすら使ったことがなかったのですが、いざ使えなくなってみると、どうしても画面がさみしい感じがしてしまうので、今回はスクリーンセーバーをPythonで自作しようと思います。<br>また、これをFliqloを薦めてくれたくれた友人から、Fliqloと同様に時間が分かるものを作って欲しいと言われたので、時間を表示してくれるスクリーンセーバーを作ろうと思います。

[^1]:最新バージョンでは、Flush Playerがなくても動作するそうです

# スクリーンセーバーの作り方

まず、スクリーンセーバーを作る前に、スクリーンセーバーがどうやって動作しているかを確認しておきましょう。

試しに、Fliqloをダウンロードすると、「Fliqlo.scr」というファイルが入っていて、これをインストールするとスクリーンセーバーに設定できます。<br>この「.scr」という拡張子はスクリーンセーバーであることを表しているのですが、実は、このファイルの中身は普通の実行ファイルです。そのため、拡張子を「.exe」に変えてもほとんど同じように時計が出てきます。

Pythonには、PyinstallerというPythonで書いたスクリプトを実行ファイルに変換するライブラリがあるので、簡単に実行ファイルと作ることができます。よって、スクリーンセーバーと同じ動作するPythonのプログラムを作れば、それをスクリーンセーバーにすることができるということになります。

では、どのようなプログラムを書けば良いでしょうか。<br>今回作るスクリーンセーバーは時間を表示するものなので、大雑把に考えると以下の動作ができればよさそうです。
* 時間を画面に表示する機能
* マウス移動(キー入力)で画面を閉じる機能

この2つを実装していこうと思います。

## 時計を画面に表示する機能

まず、画面に文字を表示させることを考えるわけですが、print文ではただコンソールに文字が表示されるだけなので、ウィンドウを開いてそこに文字を表示させる必要があります。

今回は、この動作を実装するのに、「Tkinter」というライブラリを使おうと思います。Tkinterは、PythonでGUIを扱うためのツールキットになります。

今回使うTkinterのメソッドについては、分かりやすくまとめてある物があるので、そちらを見てください。

* [PythonのTkinterを使ってみる](https://qiita.com/nnahito/items/ad1428a30738b3d93762)
* [after()メソッド：関数を指定時間経過後（定期的）に実行する](https://office54.net/python/tkinter/after-method-schedule)

まず、文字だけ表示するプログラムを作ってみましょう。

```python:screen_time.py
import tkinter
import datetime

# ウィンドウを作成
root = tkinter.Tk()

# 文字を表示させるウィジェット
label = tkinter.Label(root)

# textは表示させる文字。実行した瞬間の時刻をHH:MM:SSで表示
# fontには、順番に文字のフォントと大きさを入れます
# 'Consolas'はコードの文字のフォントと同じものです
label["text"] = datetime.datetime.now().strftime("%H:%M:%S")
label["font"] = ("Consolas", 100)

# labelを配置するコマンド
label.pack()

# rootを実行
root.mainloop()
```

これを実行すると、以下のようなウィンドウが出てきて、実行したタイミングの時刻が表示されます。

<img width="400" alt="IMG_5542.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748627/9f9c042e-11f4-a62f-226e-f5c7addcb3c7.jpeg">

これで、スクリーンに時間を表示させることができました。次は、この時計を動くようにします。

この時計を動かすために、今回は、afterメソッドを使います。afterメソッドは指定した動作を遅らせて実行するメソッドです。例えば、ボタンをクリックした1秒後に文字を表示させるなどの動作ができます。

これを、時計を動かすために使うには、「現在時刻を表示する」関数を実行した後に、afterで1秒後に「現在時刻を表示する」関数を実行する、というものを繰り返すことで、1秒ごとにスクリーンの時間を書き換えることができます。

言葉だと難しいので実際を作ってみましょう。

```python:change_clock.py
label.pack()

#上のプログラムに以下を追加----------------------------------------------------

# 時計の時間を更新する関数
def change_clock():
    #　表示する時刻を更新する
    label["text"] = datetime.datetime.now().strftime("%H:%M:%S")

    # 遅らせる時間(ms), 実行する関数を入れます。
    # この場合は、0.1秒後にchange_clockを実行してくれるので、0.1秒おきに時刻を更新できます。
    root.after(100, change_clock)

# ループの一回目を実行します
root.after(0, change_clock)

#ここまで---------------------------------------------------------------------

root.mainloop()
```

実行結果はこんな感じ

![time_consolas.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748627/12e04838-5a32-4ea8-a800-1a9b393903fa.gif)

これで表示した時刻を動かすことができました。

## マウス操作とキー入力で画面を閉じる機能

実際のスクリーンセーバーでは、マウスやキーを操作することでスクリーンセーバーが終了し元の画面に戻りますが、この機能も実行ファイルに入れる必要があります。<br>元に画面に戻るとか言っていますが、時計のウィンドウを閉じて、プログラムを終了させるようにすればよいです。

これを実行するために、bindメソッドを使います。bindでは、イベントを検出して、指定した関数を実行してくれます。イベントとは、「画面を右クリックした」や、「文字を入力した」などがあります。<br>今回作るスクリーンセーバーでは、「マウスを動かした」イベントと「キーが押された」イベントを検出して、プログラムを閉じる関数を実行すればよいです。

この機能を入れてみましょう。

```python:close_window.py
# root.bind(sequence, func)で設定できます。
# この場合は、root(セーバーのウィンドウ)の中で、sequenceのイベントを検出して、funcを実行してくれます。
# "<Motion>"はマウスの動き、<Key>は、キーが押されたことを検出してくれます。
# funcの方は、無名関数を使って、画面を閉じる関数をしてしてあります。
root.bind("<Motion>", func=lambda x: root.destroy())
root.bind("<Key>", func=lambda x: root.destroy())
```

このコードを`root.mainloop()`の直前に入れると、マウスやキー入力で画面が閉じるようになります。

# 「時をかける少女」風にする

ここまでのプログラムで、スクリーンセーバーのもとが作れました。これを改造して、少しおしゃれな時計のスクリーンセーバーにしてみます。Tkinterには、文字を表示するもの以外にも様々なウィジェットがあるので、それらを組み合わせでかっこいいやつも作れると思います。

しかし、残念ながら自分には、デザイン関連の知識やセンスがないため、既存のデザインを利用させてもらおうと思います。デジタルの時計で印象的なものとして、細田守監督の「時をかける少女」にでてくる時計が思いついたので、今回はそれに改造します。映画を見た人にしか分からないと思いますが、腕に書かれていたり、タイムリープするときにでてくるアレです[^2]。独特な見た目で、個人的には結構好きな数字フォントです。

[^2]:分からない人は、YouTubeに上がっている映画の予告編を見てもらうと分かると思います。

今回は、「時かけ」の予告動画を参考にkilo氏が制作した[こちら](https://ola.kironono.com/entry/fonts-timeleap)の「timeleap」というフォントを使わせてもらおうと思います。

フォントを変えるほかに、映画の感じに合わせて、文字や背景色も変えましょう。

改造した結果、全体のプログラムは以下のようになりました。

```python:timeleap.py
import tkinter
import datetime

# ウィンドウを作成
root = tkinter.Tk()
# ウィンドウをフルスクリーンにします。タイトルバーの部分も表示されなくなります。
root.attributes('-fullscreen', True)

# 文字を表示させるウィジェット
label = tkinter.Label(root)

#ここからは「時かけ」風に改造するための部分-------------------------------------------

# ウィンドウの背景色(background)
root["bg"] = "black"

# font:文字のフォントと大きさ
# bg:文字領域の背景色
# fg:文字の色
label["font"] = ("timeleap", 90)
label["bg"] = "black"
label["fg"] = "red"

# labelを配置するコマンド
# anchorとexpandは、labelをウィンドウの中央に配置するためのオプションです。
label.pack(anchor = "ce", expand = True)

#ここまでが改造するための部分----------------------------------------------------------

# 時計の時間を更新する関数
def change_clock():
    #　表示する時刻を更新する
    label["text"] = datetime.datetime.now().strftime("%H:%M:%S")

    # 遅らせる時間(ms), 実行する関数を入れます。
    # この場合は、0.1秒後にchange_clockを実行してくれるので、0.1秒おきに時刻を更新できます。
    root.after(100, change_clock)

# ループの一回目を実行します
root.after(0, change_clock)

# root.bind(sequence, func)で設定できます。
# この場合は、root(セーバーのウィンドウ)の中で、sequenceのイベントを検出して、funcを実行してくれます。
# "<Motion>"はマウスの動き、<Key>は、キーが押されたことを検出してくれます。
# funcの方は、無名関数を使って、画面を閉じる関数をしてしてあります。
root.bind("<Motion>", func=lambda x: root.destroy())
root.bind("<Key>", func=lambda x: root.destroy())

# rootを実行
root.mainloop()
```

実行結果

![time_ani.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748627/99f49c0c-27e9-b9b4-ec9d-fcf23fa6a00d.gif)

# スクリーンセーバーファイルにする

最後に、このプログラムをスクリーンセーバー用のファイルにします。最初に説明したように、scrファイルの中身は実行ファイル(exeファイル)なので、このプログラムを実行ファイルに変えれば良いです。

実行ファイルに変換するために、PyInstallerを使います。(PyInstallerはpipで簡単にインストールできます)

といっても、やることはコマンドプロンプトでコマンドを打つだけです。Pythonのプログラムがあるディレクトリで、`pyinstaller timeleap.py --onefile --nonconsole`を実行します。

`--onefile`は、実行に関連するものを1つにまとめるオプションで、`--nonconsole`は実行時にコンソールを表示しないオプションです。

実行すると、distというファイルが作られて、その中にtimeleap.exeが入っています。このファイルの拡張子を「.scr」に変更して、windowsの設定からスクリーンセーバーに設定すると、PCを放置したときにpythonプログラムと同じ画面がでると思います。

これで、pythonで「時をかける少女」風のスクリーンセーバーはできあがりです。実行ファイルについては、他のwindowsのPCでも同じように動作するので、Pythonを使っていない友達に送ることも可能です(timeleapフォントをダウンロードしてもらう必要はありますが)。時計のスクリーンセーバーは、今回作ったコードで色々と応用ができるので、もっとおしゃれなやつを自作してみてはいかかでしょうか。

# おまけ

さらにいじった結果を載せておきます

* 小数第2位まで表示してみました。フレーム数のせいでカクカクですが、さらに映画っぽくなりました。

<details><summary>折りたたみ</summary>

![time_shosu.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748627/ad3afdea-c198-3546-4abe-79d410d72912.gif)

</details>

* いっぱいにしてみました。時刻がいっぱい並んでいますが、フォントと色のせいで、ちょっと怖い感じになりました。放置されてるPCが急にこの画面に変わったら、ハッキングされたんじゃないかと思うじゃないでしょうか。

<details><summary>折りたたみ</summary>

![time_7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2748627/d3127e49-6c2b-05da-096e-096dfa6514ce.gif)

</details>