概要
パスワード変更などを沢山行っている時にコピペ出来ない構造のサイトがいくつかありました。しかし現代のパスワードは文字数が多く、ランダムにしていると入力が面倒で間違いもしやすい。という事でユーザの代わりに直接入力してくれるアプリを作りました。

ダウンロード
TextTyper1.0.2.zip
Zipファイルを展開して使ってください。
exe実行時に青い画面(SmartScreen)でブロックされる場合は、詳細情報をクリックして実行してください。Microsoftへは解除依頼中です。

使い方
概要
Text Typer は、ブラウザやアプリケーションの入力欄に対して、
コピー&ペーストを使わず「キーボード入力として文字列を送信」する
Windows 用ユーティリティです。
貼り付け禁止が設定されているログイン画面や入力フォームでも、
タイプ入力として文字列を送信できる場合があります。
本ツールは、長くランダムなパスワードを安全かつ確実に入力することを
主な目的としています。
====================
主な機能
- ホットキーによる文字列のタイプ入力送信
- 送信先は「現在アクティブな入力欄」
- ホットキーの変更(修飾キー + 任意キー)
- 1文字ごとの入力遅延調整(5〜50ms)
- 送信後に入力内容を自動クリア
- ボタン送信(カウントダウン付き)による代替入力
- ツールチップによる操作説明表示(環境依存問題を回避する強制表示方式)
- Shift + F1 による詳細ヘルプ(WhatsThis)
====================
対応環境
- OS: Windows 10 / Windows 11
※ 本アプリは Windows API(RegisterHotKey)を使用しているため、
Windows 専用です。
使い方
- 本アプリを起動します。
- メイン画面の入力欄に、送信したいテキストを入力または貼り付けます。
- ブラウザやアプリ側の入力欄をクリックして、フォーカスを当てます。
- 設定されたホットキーを押すと、文字列がタイプ入力されます。
ホットキーが使用できない環境では、
「送信(カウントダウン後)」ボタンを利用してください。
設定項目
■ ホットキー
タイプ入力を開始するためのショートカットキーです。
Ctrl + Alt + V など、修飾キーを含む組み合わせのみ設定可能です。
■ タイピング遅延
1文字ごとの入力間隔です(5〜50ms)。
入力が不安定な場合は、5〜15ms程度が安定しやすくなります。
■ 送信後に自動クリア
送信完了後に、アプリ内の入力テキストを自動的に消去します。
パスワードの取り扱い事故防止に有効です。
■ ボタン送信カウントダウン
送信ボタンを押してから、入力を開始するまでの待ち時間です。
ボタン操作後に入力欄へフォーカスを移す時間を確保できます。
注意事項
- 一部のサービスやアプリケーションでは、
セキュリティ対策により自動入力が検知・拒否される場合があります。 - 業務用端末や社内PCでは、ポリシーにより使用が制限されることがあります。
- 本ツールはキーロガーや保存機能を持ちません。
入力テキストはメモリ上のみで扱われ、永続保存されません。
バージョン履歴
Version 1.0.2
- ツールチップが表示されない環境への対策として、
強制表示機構を実装 - タイピング遅延の最小値を 5ms に変更(安定性向上)
- UI 操作説明の充実(ツールチップ / WhatsThis)
- 安定性および操作性の改善
Version 1.0.1
- ホットキー送信時の修飾キー残留問題を修正
- 小文字入力の不安定動作を改善
Version 1.0.0
- 初回リリース
ライセンス
MIT License
Copyright (c) 2025 IT Libero
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
コード説明
必要なライブラリのインストール
pip install PySide6 keyboard pynput実行ファイルのコード
import sys
import time
import threading
import ctypes
from ctypes import wintypes
from dataclasses import dataclass
from typing import Optional, Tuple
from PySide6 import QtCore, QtGui, QtWidgets
from pynput import keyboard as pynput_keyboard # typing sender
# ============================================================
# Sender: type text to active control
# ============================================================
class TextSender:
def __init__(self):
self._controller = pynput_keyboard.Controller()
def type_text(self, text: str, delay_per_char_ms: int = 0) -> None:
if not text:
return
delay = max(0, delay_per_char_ms) / 1000.0
for ch in text:
self._controller.type(ch)
if delay:
time.sleep(delay)
# ============================================================
# Windows RegisterHotKey (no keyboard hook)
# ============================================================
if sys.platform != "win32":
raise SystemExit("このアプリは Windows 専用です。")
user32 = ctypes.WinDLL("user32", use_last_error=True)
WM_HOTKEY = 0x0312
MOD_ALT = 0x0001
MOD_CONTROL = 0x0002
MOD_SHIFT = 0x0004
MOD_WIN = 0x0008
VK_MAP = {
"enter": 0x0D,
"tab": 0x09,
"space": 0x20,
"esc": 0x1B,
"backspace": 0x08,
"delete": 0x2E,
"home": 0x24,
"end": 0x23,
"page_up": 0x21,
"page_down": 0x22,
"left": 0x25,
"up": 0x26,
"right": 0x27,
"down": 0x28,
}
for i in range(1, 13):
VK_MAP[f"f{i}"] = 0x70 + (i - 1)
RegisterHotKey = user32.RegisterHotKey
RegisterHotKey.argtypes = [wintypes.HWND, wintypes.INT, wintypes.UINT, wintypes.UINT]
RegisterHotKey.restype = wintypes.BOOL
UnregisterHotKey = user32.UnregisterHotKey
UnregisterHotKey.argtypes = [wintypes.HWND, wintypes.INT]
UnregisterHotKey.restype = wintypes.BOOL
GetAsyncKeyState = user32.GetAsyncKeyState
GetAsyncKeyState.argtypes = [wintypes.INT]
GetAsyncKeyState.restype = wintypes.SHORT
VK_SHIFT = 0x10
VK_CONTROL = 0x11
VK_MENU = 0x12 # Alt
VK_LWIN = 0x5B
VK_RWIN = 0x5C
def _is_down(vk: int) -> bool:
return (GetAsyncKeyState(vk) & 0x8000) != 0
def wait_modifiers_released(timeout_sec: float = 0.8) -> bool:
"""
Ctrl/Alt/Shift/Win が離されるまで待つ。
"""
deadline = time.time() + timeout_sec
while time.time() < deadline:
if not (
_is_down(VK_CONTROL)
or _is_down(VK_MENU)
or _is_down(VK_SHIFT)
or _is_down(VK_LWIN)
or _is_down(VK_RWIN)
):
return True
time.sleep(0.005)
return False
def parse_hotkey_string(hk: str) -> Tuple[int, int]:
parts = [p.strip().lower() for p in hk.split("+") if p.strip()]
mods = 0
key_part = None
for p in parts:
if p in ("ctrl", "control"):
mods |= MOD_CONTROL
elif p == "alt":
mods |= MOD_ALT
elif p == "shift":
mods |= MOD_SHIFT
elif p in ("win", "windows", "cmd", "meta"):
mods |= MOD_WIN
else:
key_part = p
if not key_part:
raise ValueError("ホットキーのキー部分が見つかりません(例: ctrl+alt+v)")
if len(key_part) == 1:
return mods, ord(key_part.upper())
if key_part in VK_MAP:
return mods, VK_MAP[key_part]
raise ValueError(f"未対応のキー: {key_part}")
class WinHotkeyFilter(QtCore.QAbstractNativeEventFilter):
def __init__(self, callback):
super().__init__()
self._callback = callback
def nativeEventFilter(self, eventType, message):
if eventType == "windows_generic_MSG":
msg = ctypes.wintypes.MSG.from_address(int(message))
if msg.message == WM_HOTKEY:
self._callback()
return True, 0
return False, 0
# ============================================================
# Hotkey Capture Dialog (Qt)
# ============================================================
def qt_event_to_hotkey_text(event: QtGui.QKeyEvent) -> Optional[str]:
mods = event.modifiers()
parts = []
if mods & QtCore.Qt.ControlModifier:
parts.append("ctrl")
if mods & QtCore.Qt.AltModifier:
parts.append("alt")
if mods & QtCore.Qt.ShiftModifier:
parts.append("shift")
if mods & QtCore.Qt.MetaModifier:
parts.append("win")
key = event.key()
if key in (QtCore.Qt.Key_Control, QtCore.Qt.Key_Shift, QtCore.Qt.Key_Alt, QtCore.Qt.Key_Meta):
return None
text = event.text().lower().strip()
if text and len(text) == 1:
parts.append(text)
else:
special = {
QtCore.Qt.Key_Return: "enter",
QtCore.Qt.Key_Enter: "enter",
QtCore.Qt.Key_Tab: "tab",
QtCore.Qt.Key_Space: "space",
QtCore.Qt.Key_Backspace: "backspace",
QtCore.Qt.Key_Delete: "delete",
QtCore.Qt.Key_Escape: "esc",
QtCore.Qt.Key_Home: "home",
QtCore.Qt.Key_End: "end",
QtCore.Qt.Key_PageUp: "page_up",
QtCore.Qt.Key_PageDown: "page_down",
QtCore.Qt.Key_Left: "left",
QtCore.Qt.Key_Right: "right",
QtCore.Qt.Key_Up: "up",
QtCore.Qt.Key_Down: "down",
}
if QtCore.Qt.Key_F1 <= key <= QtCore.Qt.Key_F12:
parts.append(f"f{key - QtCore.Qt.Key_F1 + 1}")
else:
k = special.get(key)
if not k:
return None
parts.append(k)
if len(parts) < 2:
return None
return "+".join(parts)
class HotkeyCaptureDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("ホットキー設定")
self.setModal(True)
self.resize(460, 200)
self._result: Optional[str] = None
layout = QtWidgets.QVBoxLayout(self)
label = QtWidgets.QLabel(
"設定したいキーを押してください(例: Ctrl + Alt + V)\n"
"※ Esc でキャンセル\n"
"※ 誤爆防止のため『修飾キー + キー』のみ許可",
self
)
label.setAlignment(QtCore.Qt.AlignCenter)
layout.addWidget(label)
self.preview = QtWidgets.QLabel("(未入力)", self)
self.preview.setAlignment(QtCore.Qt.AlignCenter)
font = self.preview.font()
font.setPointSize(font.pointSize() + 3)
font.setBold(True)
self.preview.setFont(font)
layout.addWidget(self.preview)
btns = QtWidgets.QHBoxLayout()
self.btn_ok = QtWidgets.QPushButton("OK", self)
self.btn_ok.setEnabled(False)
self.btn_cancel = QtWidgets.QPushButton("キャンセル", self)
btns.addStretch(1)
btns.addWidget(self.btn_ok)
btns.addWidget(self.btn_cancel)
layout.addLayout(btns)
self.btn_ok.clicked.connect(self.accept)
self.btn_cancel.clicked.connect(self.reject)
def captured_hotkey(self) -> Optional[str]:
return self._result
def keyPressEvent(self, event: QtGui.QKeyEvent) -> None:
if event.key() == QtCore.Qt.Key_Escape:
self.reject()
return
hk = qt_event_to_hotkey_text(event)
if hk:
self._result = hk
self.preview.setText(hk.replace("+", " + "))
self.btn_ok.setEnabled(True)
else:
self.preview.setText("(無効:modifier + キーが必要 / 未対応キー)")
self.btn_ok.setEnabled(False)
event.accept()
# ============================================================
# Tooltip forcing: eventFilter + showText
# ============================================================
class TooltipEnforcer(QtCore.QObject):
"""
環境差で通常のツールチップが出ない場合でも、
Hover/Focus を拾って QToolTip.showText() で強制表示する。
"""
def __init__(self, parent=None, show_delay_ms: int = 350):
super().__init__(parent)
self._timer = QtCore.QTimer(self)
self._timer.setSingleShot(True)
self._timer.timeout.connect(self._show_now)
self._pending_widget: Optional[QtWidgets.QWidget] = None
self._show_delay_ms = show_delay_ms
def attach_recursively(self, root: QtWidgets.QWidget) -> None:
# root + children
targets = [root] + root.findChildren(QtWidgets.QWidget)
for w in targets:
w.setMouseTracking(True)
w.setAttribute(QtCore.Qt.WA_AlwaysShowToolTips, True)
w.installEventFilter(self)
def eventFilter(self, obj, event):
if not isinstance(obj, QtWidgets.QWidget):
return False
et = event.type()
# ホバー開始/移動/フォーカスで「表示候補」を更新
if et in (QtCore.QEvent.Enter, QtCore.QEvent.HoverEnter, QtCore.QEvent.HoverMove, QtCore.QEvent.FocusIn):
tip = obj.toolTip()
if tip:
self._pending_widget = obj
self._timer.start(self._show_delay_ms)
return False
# 退出/フォーカスアウトで隠す
if et in (QtCore.QEvent.Leave, QtCore.QEvent.HoverLeave, QtCore.QEvent.FocusOut):
QtWidgets.QToolTip.hideText()
self._timer.stop()
self._pending_widget = None
return False
# クリックしたら邪魔なので消す
if et in (QtCore.QEvent.MouseButtonPress,):
QtWidgets.QToolTip.hideText()
self._timer.stop()
self._pending_widget = None
return False
return False
def _show_now(self):
w = self._pending_widget
if w is None:
return
tip = w.toolTip()
if not tip:
return
# ウィジェットの右下あたりに表示
global_pos = w.mapToGlobal(QtCore.QPoint(w.width(), w.height()))
QtWidgets.QToolTip.showText(global_pos, tip, w)
# ============================================================
# Main App
# ============================================================
@dataclass
class AppConfig:
hotkey: str = "ctrl+alt+v"
delay_per_char_ms: int = 5 # ★デフォルトは5
clear_after_send: bool = True
countdown_seconds: int = 3
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Text Typer(貼り付け禁止対策)")
self.resize(680, 420)
self.config = AppConfig()
self.sender = TextSender()
# Native hotkey
self._hotkey_id = 1
self._native_filter = WinHotkeyFilter(self._on_hotkey_native)
QtCore.QCoreApplication.instance().installNativeEventFilter(self._native_filter)
# UI
self._build_menu()
central = QtWidgets.QWidget(self)
self.setCentralWidget(central)
layout = QtWidgets.QVBoxLayout(central)
# Text input
self.edit = QtWidgets.QPlainTextEdit(self)
self.edit.setPlaceholderText(
"ここに送信したいテキストを入力/貼り付け → 送信先の入力欄をクリック → ホットキーでタイプ入力"
)
self.edit.setToolTip(
"送信したいテキストを入力/貼り付けします。\n"
"次に、送信先の入力欄をクリックしてフォーカスを当て、\n"
"ホットキーで『タイプ入力』として送信します。"
)
self.edit.setWhatsThis(
"【入力欄】\n"
"ここに送信したい文字列を入れます。\n"
"貼り付け禁止のUIでも、タイプ入力なら通る場合があります。\n"
"送信先の入力欄にフォーカスを当てた後、ホットキーで送信してください。\n"
"(Shift+F1 でこの説明を表示できます)"
)
layout.addWidget(self.edit)
# Options
grid = QtWidgets.QGridLayout()
layout.addLayout(grid)
self.lbl_hotkey = QtWidgets.QLabel(self.config.hotkey.replace("+", " + "), self)
self.lbl_hotkey.setToolTip(
"現在のホットキーです。\n"
"『ホットキー変更』から変更できます。"
)
self.btn_set_hotkey = QtWidgets.QPushButton("ホットキー変更", self)
self.btn_set_hotkey.clicked.connect(self.change_hotkey)
self.btn_set_hotkey.setToolTip(
"ホットキーを変更します。\n"
"例: Ctrl + Alt + V\n"
"他アプリと競合すると登録に失敗する場合があります。"
)
grid.addWidget(QtWidgets.QLabel("ホットキー:", self), 0, 0)
grid.addWidget(self.lbl_hotkey, 0, 1)
grid.addWidget(self.btn_set_hotkey, 0, 2)
# ★あなたの変更点:range(5,50) / default=5 / suffix=ms/char
self.spin_delay = QtWidgets.QSpinBox(self)
self.spin_delay.setRange(5, 50)
self.spin_delay.setValue(self.config.delay_per_char_ms)
self.spin_delay.setSuffix("ms/char")
self.spin_delay.valueChanged.connect(lambda v: setattr(self.config, "delay_per_char_ms", int(v)))
self.spin_delay.setToolTip(
"1文字ごとの入力間隔です。\n"
"入力が不安定な場合は 5〜15ms あたりが安定しやすいです。\n"
"(このアプリでは最小値を5msに固定しています)"
)
grid.addWidget(QtWidgets.QLabel("タイピング遅延:", self), 1, 0)
grid.addWidget(self.spin_delay, 1, 1)
self.chk_clear = QtWidgets.QCheckBox("送信後に自動クリア", self)
self.chk_clear.setChecked(self.config.clear_after_send)
self.chk_clear.toggled.connect(lambda b: setattr(self.config, "clear_after_send", bool(b)))
self.chk_clear.setToolTip(
"送信後に、このアプリ内のテキストを自動で消します。\n"
"パスワード等の取り扱い事故を減らせます。"
)
grid.addWidget(self.chk_clear, 2, 1)
self.spin_countdown = QtWidgets.QSpinBox(self)
self.spin_countdown.setRange(0, 10)
self.spin_countdown.setValue(self.config.countdown_seconds)
self.spin_countdown.setSuffix(" 秒")
self.spin_countdown.valueChanged.connect(lambda v: setattr(self.config, "countdown_seconds", int(v)))
self.spin_countdown.setToolTip(
"送信ボタンを押してから入力開始までの待ち時間です。\n"
"ボタン送信のときに、押した後に送信先入力欄へフォーカスする時間を確保します。\n"
"ホットキーが使えない環境の保険として使います。"
)
grid.addWidget(QtWidgets.QLabel("ボタン送信カウントダウン:", self), 3, 0)
grid.addWidget(self.spin_countdown, 3, 1)
# Buttons
row_btn = QtWidgets.QHBoxLayout()
self.btn_send = QtWidgets.QPushButton("送信(カウントダウン後)", self)
self.btn_send.clicked.connect(self.send_with_countdown)
self.btn_send.setToolTip(
"カウントダウン後、アクティブな入力欄へ文字をタイプ入力します。\n"
"手順: (1)ボタン押す → (2)送信先入力欄をクリック → (3)自動入力"
)
self.btn_clear = QtWidgets.QPushButton("クリア", self)
self.btn_clear.clicked.connect(self.clear_text)
self.btn_clear.setToolTip("入力テキストを消去します。")
row_btn.addStretch(1)
row_btn.addWidget(self.btn_send)
row_btn.addWidget(self.btn_clear)
layout.addLayout(row_btn)
# Status label + status bar
self.status_label = QtWidgets.QLabel("", self)
self.status_label.setWordWrap(True)
self.status_label.setToolTip("現在の状態(登録状況、送信中など)を表示します。")
layout.addWidget(self.status_label)
self.setStatusBar(QtWidgets.QStatusBar(self))
self.statusBar().showMessage("待機中")
# Countdown timer
self._countdown_timer = QtCore.QTimer(self)
self._countdown_timer.timeout.connect(self._countdown_tick)
self._countdown_remaining = 0
self._countdown_text = ""
# ToolTip: 強制表示の仕組みを有効化
self._tooltip_enforcer = TooltipEnforcer(self, show_delay_ms=350)
self._tooltip_enforcer.attach_recursively(self)
# Hotkey register
self._register_hotkey(self.config.hotkey)
# ----------------------------
# Menu
# ----------------------------
def _build_menu(self):
menu_bar = self.menuBar()
m_file = menu_bar.addMenu("ファイル(&F)")
act_clear = QtGui.QAction("クリア(&C)", self)
act_clear.setStatusTip("入力テキストを消去します。")
act_clear.triggered.connect(self.clear_text)
m_file.addAction(act_clear)
m_file.addSeparator()
act_exit = QtGui.QAction("終了(&X)", self)
act_exit.setStatusTip("アプリを終了します。")
act_exit.triggered.connect(self.close)
m_file.addAction(act_exit)
m_settings = menu_bar.addMenu("設定(&S)")
act_hotkey = QtGui.QAction("ホットキー変更(&H)...", self)
act_hotkey.setStatusTip("ホットキーを変更します(modifier + キー)。")
act_hotkey.triggered.connect(self.change_hotkey)
m_settings.addAction(act_hotkey)
m_help = menu_bar.addMenu("ヘルプ(&?)")
act_whats = QtGui.QAction("使い方(Shift+F1)(&W)", self)
act_whats.setStatusTip("Shift+F1 で『これは何?』モードに入り、部品の説明を表示します。")
act_whats.triggered.connect(self._show_whats_this_hint)
m_help.addAction(act_whats)
act_about = QtGui.QAction("このアプリについて(&A)", self)
act_about.setStatusTip("アプリ概要を表示します。")
act_about.triggered.connect(self._show_about)
m_help.addAction(act_about)
def _show_whats_this_hint(self):
QtWidgets.QMessageBox.information(
self,
"使い方(Shift+F1)",
"Shift+F1 を押すと『これは何?』モードになります。\n"
"その状態で画面の部品をクリックすると、詳細説明(WhatsThis)が表示されます。"
)
def _show_about(self):
QtWidgets.QMessageBox.information(
self,
"このアプリについて",
"Text Typer(貼り付け禁止対策)\n\n"
"・ホットキーでアクティブな入力欄へ文字列をタイプ入力します。\n"
"・貼り付け禁止UIでも入力できる場合があります。\n\n"
"注意: サービス/社内ポリシーにより自動入力が禁止されている場合があります。"
)
# ----------------------------
# Hotkey registration
# ----------------------------
def _register_hotkey(self, hk: str):
UnregisterHotKey(int(self.winId()), self._hotkey_id)
try:
mods, vk = parse_hotkey_string(hk)
except Exception as e:
self._set_status(f"ホットキー解析エラー: {e}")
return
ok = RegisterHotKey(int(self.winId()), self._hotkey_id, mods, vk)
if not ok:
err = ctypes.get_last_error()
self._set_status(
f"ホットキー登録失敗: {hk.replace('+', ' + ')} / WinError={err}\n"
"他アプリと競合しているか、権限/ポリシーで禁止されている可能性があります。"
)
else:
self._set_status(f"ホットキー登録OK: {hk.replace('+', ' + ')}(押すと送信)")
def change_hotkey(self):
dlg = HotkeyCaptureDialog(self)
if dlg.exec() == QtWidgets.QDialog.Accepted:
hk = dlg.captured_hotkey()
if hk:
self.config.hotkey = hk
self.lbl_hotkey.setText(hk.replace("+", " + "))
self._register_hotkey(hk)
# ----------------------------
# Native hotkey callback
# ----------------------------
def _on_hotkey_native(self):
text = self.edit.toPlainText()
if not text:
self._set_status("ホットキー検知OK。ただし送信テキストが空です。")
return
self._set_status("ホットキー検知OK → 送信中…")
threading.Thread(target=self._send_worker, args=(text,), daemon=True).start()
def _send_worker(self, text: str):
try:
wait_modifiers_released(0.9)
time.sleep(0.02)
self.sender.type_text(text, delay_per_char_ms=self.config.delay_per_char_ms)
finally:
QtCore.QMetaObject.invokeMethod(self, "_after_send", QtCore.Qt.QueuedConnection)
@QtCore.Slot()
def _after_send(self):
self._set_status("送信しました。")
if self.config.clear_after_send:
self.edit.clear()
# ----------------------------
# Button fallback (countdown)
# ----------------------------
def send_with_countdown(self):
text = self.edit.toPlainText()
if not text:
self._set_status("送信するテキストが空です。")
return
sec = int(self.config.countdown_seconds)
if sec <= 0:
self._set_status("送信中…(ボタン直送)")
threading.Thread(target=self._send_worker, args=(text,), daemon=True).start()
return
self._countdown_text = text
self._countdown_remaining = sec
self.btn_send.setEnabled(False)
self._set_status(f"{sec}秒後に送信します。今のうちに送信先入力欄をクリックしてフォーカスしてください。")
self._countdown_timer.start(1000)
def _countdown_tick(self):
self._countdown_remaining -= 1
if self._countdown_remaining <= 0:
self._countdown_timer.stop()
self._set_status("送信中…(カウントダウン送信)")
threading.Thread(target=self._send_worker, args=(self._countdown_text,), daemon=True).start()
self.btn_send.setEnabled(True)
return
self._set_status(
f"{self._countdown_remaining}秒後に送信します。送信先入力欄をクリックしてフォーカスしてください。"
)
# ----------------------------
# Misc
# ----------------------------
def clear_text(self):
self.edit.clear()
self._set_status("クリアしました。")
def _set_status(self, msg: str):
self.status_label.setText(msg)
if self.statusBar():
self.statusBar().showMessage(msg)
def closeEvent(self, event: QtGui.QCloseEvent) -> None:
UnregisterHotKey(int(self.winId()), self._hotkey_id)
QtCore.QCoreApplication.instance().removeNativeEventFilter(self._native_filter)
event.accept()
def main():
app = QtWidgets.QApplication(sys.argv)
# 目視しやすいツールチップ見た目(任意)
app.setStyleSheet("""
QToolTip {
border: 1px solid #888;
padding: 4px;
background: #ffffe1;
color: #000;
}
""")
w = MainWindow()
w.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()




