バックアップだったり、巨大なファイルの変換処理だったり、処理に長時間かかるタスクがあります。時間がかかるので、ちょっと退席している間に、PCがスリープになってしまって、処理が終わってなくてガッカリしたなんてことはないでしょうか。今回は、明示的にPCのスリープを禁止するコマンドをGo言語で作ってみましょう。WindowsとmacOSで異なる処理を行う方法も解説します。
macOSには最初からあるスリープ禁止コマンド
なお、macOSには、最初から「caffeinate」というコマンドがあり、このコマンドを実行している間は、OSがスリープにならないようになっています。なお、「caffeinate」とは、日本語にすると「カフェインを与える」という意味であり、Macにコーヒーを飲ませて眠らせないようにするという洒落のきいたコマンド名となっています。
Windowsには、このコマンドがデフォルトで用意されていないので、Windows/macOSの両方で使えるスリープ禁止コマンドを作ってみましょう。
Go言語ならWindows/macOS両対応のツールが簡単に作れる
なお、WindowsとmacOSの両方の実行ファイルを作成できるツールを作るのに便利なのが、Go言語です。両方のプラットフォームで使える実行ファイルを一気に生成できます。こうしたちょっとしたツールを作るのに、Go言語を使うと便利です。
しかも、Windowsには、もともとSetThreadExecutionStateというWindows APIが用意されており、Go言語からこのAPIを呼び出せば、Windowsでスリープ禁止コマンドを作成できます。Go言語ではシステムに用意されている機能を手軽に呼び出すことができるので便利です。
なお、macOSには最初から「caffeinate」コマンドがあるので、macOSで使う場合には、このコマンドを実行するだけで事足ります。
Windows/macOSで異なる処理を実現するには?
今回のプログラムでは、WindowsとmacOSで、利用する機能を切り替える必要があります。WindowsではOSのAPIを呼び出し、macOSではコマンドを実行します。そこで、次のようなファイル構成にします。
- main.go --- メインプログラム
- sleep_windows.go --- Windows用の実装ファイル
- sleep_darwin.go --- macOS用の実装ファイル
メインプログラム「main.go」にはWindowsとmacOSで共通の処理を記述し、preventSleep()関数の実装だけをWindows用とmacOS用のファイルに分けます。
Go言語では、ファイル名の末尾を「_windows.go」や「_darwin.go」とすると、Goのビルドツールが対象OSに応じて必要なファイルだけを自動的にコンパイルします。
まず、両方のファイルで、同じ関数名 preventSleep() を定義しておきます。そうすると、main.go からは、OSを気にせず preventSleep() を呼び出すことができます。呼び出す側のコードは1つで済み、中身の実装だけがOSごとに切り替わるという仕組みになっています。
実際のプログラムを完成させよう
今回のプログラム全体は、こちらのGitHubにアップロードしました。「Source Code(zip)」から、ソースコードをダウンロードできます。
これから、ポイントとなる部分を、見てみましょう。メインプログラムは、次のようになります。
// 省略
func main() {
preventSleep() // OSごとの実装 --- (*1)
// 引数なし: Ctrl+C で終了するまでスリープ禁止 --- (*2)
if len(os.Args) < 2 {
fmt.Println("スリープを禁止しています (Ctrl+Cで終了)")
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c // --- (*3)
return
}
// 省略
}
プログラムの(*1)では、メイン関数内で、最初にOSごとに処理の異なるpreventSleep()関数を呼び出します。これによって、異なる処理が実行されます。そして、(*2)で、[Ctrl] + [C]キーが押されるのを待ちます。なお、(*3)にある「<-c」は、チャネルcから値が届くまで待つという意味になります。つまり、[Ctrl]+[C]による割り込みシグナルが送られてくるのを待つことができます。
次に、Windows版のpreventSleep()関数の定義を確認してみましょう。macOS版は、caffeinateコマンドを呼び出すだけなので省略します。
ファイル「sleep_windows.go」の内容は次のようになります。
package main
import (
"runtime"
"syscall"
)
const (
esContinuous = 0x80000000
esSystemRequired = 0x00000001
)
// Windows APIのSetThreadExecutionStateを呼ぶ
// プロセスが終了すると自動で解除される
func preventSleep() {
runtime.LockOSThread() // 設定はスレッド単位なのでスレッドを固定する
kernel32 := syscall.NewLazyDLL("kernel32.dll") // --- (*1)
kernel32.NewProc("SetThreadExecutionState").Call(esContinuous | esSystemRequired) // --- (*2)
}
プログラムの(*1)では、WindowsのシステムDLLファイル「kernel32.dll」を利用するための準備をします。そして、(*2)では、kernel32が提供するAPIである「SetThreadExecutionState」を呼び出します。引数に、esContinuousとesSystemRequiredを指定することで、スリープを禁止します。
なお、SetThreadExecutionStateの設定はOSスレッドにひも付きます。一方、Goのgoroutineは実行途中で別のOSスレッドに移ることがあります。そこで、runtime.LockOSThread()を使って、このgoroutineを現在のOSスレッドに固定しています。そこで、(*1)と(*2)の前で、「runtime.LockOSThread()」を実行しておくことで、確実にスリープを禁止することができます。
コマンドを実行しよう
それでは、実行ファイルを作成してみましょう。以下のコマンドを実行すると、Windowsなら「nosleep.exe」、macOSなら「nosleep」という実行ファイルが生成されます。
# Windowsの場合
go build -o nosleep.exe
# macOSの場合
go build -o nosleep
ターミナル(WindowsならPowerShell、macOSならターミナル.app)上で、「nosleep」の実行ファイルを実行している間、スリープ禁止状態になります。スリープ禁止を解除したい場合には、[Ctrl]+[C]キーを押します。
なお、スリープとは、PC本体を省電力状態にすることで、CPUなどの動作が大きく抑え、処理は基本的に停止・休止する状態のことです。これは、画面ロックやディスプレイオフとは関係がなく、スリープ禁止状態でも、ディスプレイがオフになることがあります。スリープ禁止状態であれば、ディスプレイがオフになっても、動作は続いています。
ただし、スリープ禁止と言っても、基本的には、アイドル状態からのスリープを禁止するだけで、ユーザーが明示的にスリープボタンを押した場合や、ノートPCの蓋を閉じたときなどは、スリープになってしまいますので、注意してください。
まとめ
以上、今回は、PCのスリープを禁止するコマンドをGo言語で作ってみました。Go言語で、Windows/macOSの両方で使えるツールの作り方を紹介しました。スリープ管理は、OSと結びついた処理なので、なかなか同じプログラムで、同じ動作を作成するのは難しいものです。しかし、Windowsならこの処理、macOSならこの処理と、ファイルを分けて実装することで、どちらのOSでも動くプログラムを作ることができます。参考にしてみてください。
自由型プログラマー。くじらはんどにて、プログラミングの楽しさを伝える活動をしている。代表作に、日本語プログラミング言語「なでしこ」 、テキスト音楽「サクラ」など。2001年オンラインソフト大賞入賞、2004年度未踏ユース スーパークリエータ認定、2010年 OSS貢献者章受賞。これまで50冊以上の技術書を執筆した。直近では、「大規模言語モデルを使いこなすためのプロンプトエンジニアリングの教科書(マイナビ出版)」「Pythonでつくるデスクトップアプリ(ソシム)」「実践力を身につける Pythonの教科書 第2版」「シゴトがはかどる Python自動処理の教科書(マイナビ出版)」など。
