この記事を作った動機
なんか ChatGPT に色々要望を投げたら、とりあえず動くものが自分で理解してなにか作れる前にできたので、まずはそっちに集中しようかなと思いまとめを作ろうとしてみた。
結局まともに動かせるようにするために、 ChatGPT とのやり取りで出てきた着想を元に、色々調べて自分で組み合わせたり、コードを書いたりすることになった。
前回の記事
機能の概要
Gnome 拡張機能として、トップバーに全角半角切り替えキーを表示し、押したら、入力モードがアルファベットを直接入力するモードと、日本語をローマ字で入力するモードに切り替えられるようにする。
主にタッチパネル環境を使って日本語入力するときに、Gnome OSK が US配列しか日本語レイアウトを指定しても表示しないために、補助として作った。

想定された利用する状況としては、以下のようなものがある。
- GNOME 47 以降
- タッチパネル運用
- GNOME OSK を利用する
- Wayland 環境
ちなみに X11 環境である場合は、onboardなどすでにある OSK とかを使ったほうがいいかもしれない。
リポジトリ
基本的な動作
- pyhton のキーイベントを送る常駐サーバがある。
(セキュリティの都合により汎用性はなく “`” の日本語キーボードで言う全角半角キー相当のキーイベントしか送らない) - ボタンが押されたら、localhost 経由で Gnome 拡張機能からリクエストを送る
python サーバ
必要なパッケージ
- python
- python-uinput
- python-websockets
yay -S python python-uinput python-websockets
最新版
最初は、ChatGPT に書いてもらったコードを動かしていたが、次第に HTTP 通信だと限界があることがわかり、 WebSocket へ移行した過程で、結局自分でネットで python のsockets
の公式ライブラリなり、色んなサイトを漁ってコードを書くことになった。ChatGPT が書いたコードについては、uinput 以外は、結局いろんなサイトからの試しながら切り貼りといった感じである。
#!/usr/bin/env python3
import uinput
import asyncio
from websockets.asyncio.server import serve
from websockets.exceptions import ConnectionClosedOK
device = uinput.Device([uinput.KEY_GRAVE])
async def hello(websocket):
while True:
try:
message = await websocket.recv()
if(message == "enter"):
print("working")
device.emit_click(uinput.KEY_GRAVE)
print(f"<<< {message}")
except ConnectionClosedOK:
break
async def main():
async with serve(hello, "localhost", 50096) as server:
await server.serve_forever()
if __name__ == "__main__":
asyncio.run(main())
systemd service テンプレート
- 有効化
sudo systemctl enable --now ZenkakuHankakuKeyd.service
- ZenkakuHankakuKeyd.service(まだ未完成)
[Unit]
Description=ZenkakuHankaku-key extension backend
[Service]
Type=simple
User=root
ExecStart=/path/to/backend.py
Restart=on-abort
[Install]
WantedBy=multi-user.target
- 配置
# arch linux
/etc/systemd/system/ZenkakuHankakuKeyd.service
細かいこと
Part 1 - Send & receive - websockets 15.0.1 documentationによれば、単純に While とかで無限ループしていればいいとのこと。最新版を書く過程で躓いたこと
socket が勝手に閉じないようにする
async def handler(websocket):
while True:
try:
message = await websocket.recv()
except ConnectionClosedOK:
break
print(message)
以下は ChatGPT-5 のモデルが吐き出したコードである。 このままでも、とりあえず 以下は自分で修正したコードである。と入ってもシバンを書き換えて、参考資料を元に waitress を使うようにしただけであり、全然私は慣れてなかったり知らないものも使われてたりする。flask とか waitress はこの記事を書いた時点では全然使ったことないし。これでとりあえず警告は出なくなった。 WebSocket を使う案に移行したため、以下のパッケージは使わなくなった。色々試した形跡
main.py
#!/usr/bin/env python3
from flask import Flask
import uinput
app = Flask(__name__)
device = uinput.Device([uinput.KEY_GRAVE])
@app.route("/trigger")
def trigger():
device.emit_click(uinput.KEY_GRAVE) # press+release
return "OK\n"
if __name__ == "__main__":
# listen only on localhost for safety
app.run(host="127.0.0.1", port=50096)
http://127.0.0.1:PORT/trigger
に wget とかで HTTP リクエストを送ると、キーイベントが OS 側に送られ、バックエンドとしては動作したが、以下のようにエラーが出てしまうので python - Flask at first run: Do not use the development server in a production environment - Stack Overflow を参考に waitress を使うようにすることにした。
sudo ./main.py
# * Serving Flask app 'main'
# * Debug mode: off
# WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
# * Running on http://127.0.0.1:50096
# Press CTRL+C to quit
# 127.0.0.1 - - [21/Aug/2025 18:46:34] "GET /trigger HTTP/1.1" 200 -
main.py (修正版)
#!/bin/python
from flask import Flask
import uinput
app = Flask(__name__)
device = uinput.Device([uinput.KEY_GRAVE])
@app.route("/trigger")
def trigger():
device.emit_click(uinput.KEY_GRAVE) # press+release
return "OK\n"
if __name__ == "__main__":
from waitress import serve
# listen only on localhost for safety
serve(app, host="127.0.0.1", port=50096)
試行錯誤の過程で不要になったパッケージ
Gnome 拡張機能側
ブラウザで標準の API は使えない
fetch API とかは使えないので、http とかネットワーク関連でなにかしたかったら、libsoup
を使う必要がある。fetch API とか使うおうとすると、以下のようなエラーとなる。
sudo journalctl /usr/bin/gnome-shell
# ...
# Stack trace:
# _sendBacktick@file:///home/username/.local/share/gnome-shell/extensions/ZenkakuHankaku-Key@www.nyanmo.info/extension.js:32:23
# _init/<@file:///home/username/.local/share/gnome-shell/extensions/ZenkakuHankaku-Key@www.nyanmo.info/extension.js:55:13
# activate@resource:///org/gnome/shell/ui/popupMenu.js:193:14
# _init/<@resource:///org/gnome/shell/ui/popupMenu.js:110:24
# @resource:///org/gnome/shell/ui/init.js:21:20
#
# 8月 21 19:48:05 hostname gnome-shell[93119]: Failed to claim accelerometer: タイムアウトしました
# 8月 21 19:48:49 hostname gnome-shell[93119]: ReferenceError: fetch is not defined
# ...
エラーで拡張機能がそもそも起動しないときのデバッグ方法
- alt + F2 を押す
- lg を実行する
- Extention タブを開く
- エラーを表示する
拡張機能を認識させたり変更を反映する
単純には、ログアウトしてログインしなおしたり、コマンドを実行のところで、、とにかく Gnome-Shell のユーザセッションを再起動しないといけない模様である。restart
を実行したりすることで
- wayland 環境だと restart できないらしい
画像を使う方法がマゾ
最終的に以下のように、パスを割り出す方法でとりあえず動くようになった。
- js/misc/dbusUtils.js · main · GNOME / gnome-shell · GitLab
https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/misc/dbusUtils.js (2025年8月22日) - GLib – 2.0
https://docs.gtk.org/glib/?q= (2025年8月22日) - GLib – 2.0
https://docs.gtk.org/glib/?q=get (2025年8月22日) - GLib.get_home_dir
https://docs.gtk.org/glib/func.get_home_dir.html (2025年8月22日)
// /home/username までわかる
let homedir = GLib.get_home_dir() + "/"
// "ZenkakuHankaku-Key@www.nyanmo.info" 作った拡張機能名に置き換える
let extdir = homedir + ".local/share/gnome-shell/extensions/ZenkakuHankaku-Key@www.nyanmo.info/"
// 拡張機能のフォルダを起点として、画像を指定する
let iconPath = extdir + "imgs/targetImage.png"
// やっと画像を読み込んで表示できる
let gicon = Gio.icon_new_for_string(`${iconPath}`);
this.add_child(new St.Icon({
gicon: gicon,
style_class: 'system-status-icon switchButton',
}));
続き
参考にしたサイトとか
フリースペース
- ChatGPT
https://chatgpt.com/ (2025年8月21日)
コーディング関連で調べたこと
- python - Flask at first run: Do not use the development server in a production environment - Stack Overflow
https://stackoverflow.com/questions/51025893/flask-at-first-run-do-not-use-the-development-server-in-a-production-environmen (2025年8月21日) - Getting Started | GNOME JavaScript
https://gjs.guide/extensions/development/creating.html (2025年8月21日) - soup server example of gjs at github - server stops listening after 2 seconds - Stack Overflow
https://stackoverflow.com/questions/77733763/soup-server-example-of-gjs-at-github-server-stops-listening-after-2-seconds (2025年8月21日) - Color Picker - GNOME Shell 拡張機能
https://extensions.gnome.org/extension/3396/color-picker/ (2025年8月21日) - tuberry/color-picker: GNOME Shell extension to pick colors on the desktop
https://github.com/tuberry/color-picker (2025年8月21日) - js/ui/panelMenu.js · main · GNOME / gnome-shell · GitLab
https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/panelMenu.js (2025年8月21日) - Basics — Gjs/GTK+ 3 Tutorial 0 documentation
https://gjs-tutorial.readthedocs.io/en/latest/basics.html (2025年8月21日) - gnome shell - How to get more information about “Error loading extension”? - Ask Ubuntu
https://askubuntu.com/questions/1006499/how-to-get-more-information-about-error-loading-extension (2025年8月21日)
St.Widget (PanelMenu.Button) のシグナルの参考になるところ
- Clutter.Actor - Classes - Clutter 1.0
https://lazka.github.io/pgi-docs/Clutter-1.0/classes/Actor.html#signal-details (2025年8月21日) - Clutter.Actor - Classes - Clutter 1.0
https://lazka.github.io/pgi-docs/Clutter-1.0/classes/Actor.html#Clutter.Actor.signals.button_press_event (2025年8月21日) - Clutter.Actor - Classes - Clutter 1.0
https://lazka.github.io/pgi-docs/Clutter-1.0/classes/Actor.html#Clutter.Actor.signals.touch_event (2025年8月22日) - Clutter.Event - Unions - Clutter 1.0
https://lazka.github.io/pgi-docs/Clutter-1.0/classes/Event.html#Clutter.Event (2025年8月22日) - Clutter.TouchEvent - Structures - Clutter 1.0
https://lazka.github.io/pgi-docs/Clutter-1.0/classes/TouchEvent.html#Clutter.TouchEvent (2025年8月22日) - EventType · 検索 · GitLab
https://gitlab.gnome.org/search?search=EventType&nav_source=navbar&project_id=546&group_id=8&search_code=true&repository_ref=main (2025年8月22日) - js/gdm/loginDialog.js · main · GNOME / gnome-shell · GitLab
https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/gdm/loginDialog.js (2025年8月22日) - Clutter 10 / EventType — DevDocs
https://gjs-docs.gnome.org/clutter10~10/clutter.eventtype (2025年8月22日) - Enums - Clutter 1.0
https://lazka.github.io/pgi-docs/Clutter-1.0/enums.html#Clutter.EventType.TOUCH_BEGIN (2025年8月22日)
GJS と GNOME 拡張機能における画像などのあり方
- gio - Custom Icon for Gnome Shell Extension in Top Panel not visible - Stack Overflow
https://stackoverflow.com/questions/61243243/custom-icon-for-gnome-shell-extension-in-top-panel-not-visible (2025年8月22日) - //Gio · 検索 · GitLab
https://gitlab.gnome.org/search?search=%2F%2FGio&nav_source=navbar&project_id=546&group_id=8&search_code=true&repository_ref=main (2025年8月22日) - Gio – 2.0
https://docs.gtk.org/gio/index.html (2025年8月22日) - Gio – 2.0
https://docs.gtk.org/gio/?q=icon_new_for_string (2025年8月22日) - Gio.Icon.new_for_string
https://docs.gtk.org/gio/type_func.Icon.new_for_string.html (2025年8月22日) - Gio.BytesIcon.new
https://docs.gtk.org/gio/ctor.BytesIcon.new.html (2025年8月22日)
拡張機能までのパスの解決方法 (今のところ不明)
- Extension (ESModule) | GNOME JavaScript
https://gjs.guide/extensions/topics/extension.html (2025年8月22日) - tests/unit/extensionUtils.js · main · GNOME / gnome-shell · GitLab
https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/tests/unit/extensionUtils.js (2025年8月22日) - GLib.get_home_dir
https://docs.gtk.org/glib/func.get_home_dir.html (2025年8月22日) - GLib – 2.0
https://docs.gtk.org/glib/?q=get (2025年8月22日) - js/misc/dbusUtils.js · main · GNOME / gnome-shell · GitLab
https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/misc/dbusUtils.js (2025年8月22日)
GJS における HTTP や WebSocket 事情 (libsoup)
- SoupSession: libsoup Reference Manual
https://libsoup.gnome.org/libsoup-2.4/SoupSession.html#soup-session-abort (2025年8月21日) - examples/websocket-client.js · master · GNOME / gjs · GitLab
https://gitlab.gnome.org/GNOME/gjs/-/blob/master/examples/websocket-client.js (2025年8月21日) - gjs/examples/http-server.js at master · GNOME/gjs
https://github.com/GNOME/gjs/blob/master/examples/http-server.js (2025年8月21日) - gjs/examples/http-client.js at master · GNOME/gjs
https://github.com/GNOME/gjs/blob/master/examples/http-client.js (2025年8月21日)
python 関連
- websockets 15.0.1 documentation
https://websockets.readthedocs.io/en/stable/ (2025年8月21日) - Part 1 - Send & receive - websockets 15.0.1 documentation
https://websockets.readthedocs.io/en/stable/intro/tutorial1.html (2025年8月21日) - python - How can I stop my websocket connection from closing? - Stack Overflow
https://stackoverflow.com/questions/67309060/how-can-i-stop-my-websocket-connection-from-closing (2025年8月21日)
JS websocket
- WebSocket
https://developer.mozilla.org/en-US/docs/Web/API/WebSocket (2025年8月21日)