職場で、友人が私に手紙を書いた。 対話はおよそ次のとおりでした。
-こんにちは、タッチタイピングのテクニックを学んでいます。 実際のところ、Linuxには私に役立つプログラムはありません。 一般的に、これをすぐに書くことができますか?
友人を助けることは神聖な出来事であり、タスクは面白そうに見えたので、私は助けることに同意しました。
結果は次のとおりです。
誰が気にしますか、以下の詳細
警告
私はpythonの第一人者のふりをしていないので、コードには(そして、ほぼ確実に)顕微鏡やその他の不条理な点があります。
友人と一緒に問題の声明を発表しました。
プログラムの動作原理は次のとおりです。デスクトップで起動すると、キーボードの概略図が表示されたウィンドウが表示されます。 実際のキーボードでボタンを押すと、回路図画像で、押されたボタンが押されます。
これは、キーボードで入力しながらモニターを見る反射神経を発達させるのに役立ちます。
プログラムの主な要件:
- 非常に迅速に実装します。
- 押されたキーをリアルタイムで表示します。
- キーボード言語を切り替える際のウィンドウの「レイアウトの切り替え」。
- テキスト構成ファイルを介してプログラムを構成します。
執筆の過程でも追加されました:
- 最後に押されたキーの貼り付けモード(指をさらに押す場所を見つけるのに役立ちます);
- シフトキーを操作します。
- キーボード上の指の位置の色をマークする機能。
- フォントをカスタマイズする機能。
- ボタンの幅を調整する機能。
- ホバー時にウィンドウのコンテンツを自動的に非表示にします。
プログラムが登場した時点で、Tkinterを使用した経験があり、複数のスレッドを使用していました。 さらに、アクティビティの性質上、システム管理者である必要もあるため、コマンドラインでの作業は異質ではありませんでした。
プログラムの内部の一般的な説明:
キーを読み取るには、googleにあるbash行を使用します。これにより、xinputユーティリティを介してキーボードで押されたキーを読み取ることができます。 この方法は、要件の条項1を満足するように選択されています。 文字を読み取るプロセスは、別のスレッドで開始されます。 レイアウト言語の読み取りも実装されています(もう一度、ポイント1)。 押されたボタンに関する情報はキューで発行されます。 メインプログラムウィンドウでキューを操作するには、periodicCall関数を定期的に呼び出します。 したがって、2つのスレッドがキューに書き込み、1つのスレッドが読み取ります。
プログラムは特異な方法で終了します-スレッド内のステータス変数を介して。
プログラム設定を操作する
プログラム設定は、ConfigManagerクラスのインスタンスにロードおよび保存されます。 メインテキスト設定ファイルからの読み取りは、 ConfigParserを使用して行われます。 このモジュールを使用すると、INIに似た構成ファイルを使用できます。 クラスコンストラクターは、パス "〜/ .key_trainer / program.conf"に沿って配置された構成ファイルの存在を確認します。 そうでない場合、プログラムは現在のフォルダーにあるprogram.confファイルをプログラムとともに読み取ります。
いくつかのコード
import os ... filename='program.conf' home = os.path.expanduser("~") if os.path.isfile(home+'/.key_trainer/'+filename): filename=home+'/.key_trainer/'+filename ...
ConfigParserは素晴らしいモジュールです。 すべてのセクションの名前を読み取ることができます。また、セクション内の値を持つキーをタプルとして読み取ることができます。 そのため、たとえば、KEYBOARD SETTINGSセクションのセクション名とキーの読み取りが実装されています。
もう少しコード
from ConfigParser import RawParser ... myParser=RawConfigParser(allow_no_value=True) myParser.read(path_to_file) # self.sections = myParser.sections() # , KEYBOARD SETTINGS keyboard_settings_keys=[x[0] for x in myParser.items("KEYBOARD SETTINGS")]
メインの設定ファイルに加えて、同様に重要なもう1つのファイル「keyboard.conf」があります。 表示されるボタン、つまりボタンコード、ボタン上のテキスト(暗号化およびレイアウト内)、ボタンの位置の構成に使用されます。 このファイルにエントリを削除/追加することで、メインプログラムウィンドウのボタン(およびボタンのある行)の数と品質を変更できます。
keyboard.confのエントリの形式
ファイルには、次の形式のエントリが含まれます。
[ボタンコード]: "[英語レイアウトの小文字]、[英語レイアウトの大文字]、[ロシア語レイアウトの小文字]、[ロシア語レイアウトの大文字]]:[ボタン行番号]、[ボタン列番号]
次に例を示します。
24: "q、Q、y、y":3.2
25: "w、w、q、ts":3.3
26:「e、E、y、Y」:3.4
27:「r、R、k、K」:3.5
[ボタンコード]: "[英語レイアウトの小文字]、[英語レイアウトの大文字]、[ロシア語レイアウトの小文字]、[ロシア語レイアウトの大文字]]:[ボタン行番号]、[ボタン列番号]
次に例を示します。
24: "q、Q、y、y":3.2
25: "w、w、q、ts":3.3
26:「e、E、y、Y」:3.4
27:「r、R、k、K」:3.5
キーボードから文字を読む
文字を読み取るために、KeyboardStatusクラスが書き込まれます。このクラスは、構成クラスを入力パラメーターとして受け入れます(上記を参照)。 スレッドセーフキューは、このクラス内にカプセル化されます。
キーボードからの文字の読み取りは、2つのストリームで実行されます。 なぜ2つ-実際には簡単になったからです。 1つのスレッドはキーボードレイアウトを読み取り、2番目のボタンはボタンを押します。 両方のスレッドはThreadを介して生成され、各スレッドでサブプロセスPopenを介して、キーまたはレイアウトを読み取る対応するプロセスが起動されます。 プロセスの出力を読み取るには、 subprocess.PIPEが使用されます。 テキストがプロセス出力ストリームに入るとすぐに、テキストが読み取られて処理され、必要に応じてキューに入れられます。
コード
from subprocess import Popen from subprocess import PIPE import threading ... def doReadingKeys(self): self.myProcess=Popen('xinput list '+'|'+' grep -Po \'id=\K\d+(?=.*slave\s*keyboard)\' '+'|'+' xargs -P0 -n1 xinput test',shell=True,stdout=PIPE) while self.proc_started: symbol=self.myProcess.stdout.read(1) if symbol in press_release_dict: symbol_pressed=press_release_dict[symbol] while symbol!='\n': symbol=self.myProcess.stdout.read(1) if symbol.isdigit(): symbol_index=symbol_index*10+int(symbol) self.myQueue.put((symbol_index,symbol_pressed)) symbol_index=0 ... keysThread=threading.Thread(target=self.doReadingKeys) keysThread.start() ...
ストリームを終了するには、proc_startedクラスの変数が使用されます。 プログラムのメインウィンドウが閉じられると、Falseに設定され、読み取りサイクルが終了し、終了までのキーの読み取りプロセスが完了し、プロセスが完了するまで待機するために待機します。
発言
このアプローチには1つの欠点があります-読み取りメソッドのロック解除(およびスレッドとプロセスのさらなる終了)。myProcessプロセスの出力ストリームから何かがカウントされるまでループ内では発生しません。 しかし実際には、ボタンを押すことが多いため、このため問題はありませんでした。
GUI
グラフィカルインターフェイスをすばやく作成するために、 Tkinterが使用されました。 このモジュールを使用すると、単純なグラフィカルインターフェイス(ウィンドウ、ボタン、チェックマークなど)を簡単に操作できます。 GuiManagerの入力ウィンドウクラスは、他のパラメーターの中でも、構成クラスを受け入れます。 それからボタン設定が取得され、これらのボタンが作成されてメインプログラムウィンドウに追加されます。
ボタンコードを追加
from Tkinter import * import tkFont ... self.buttonFont=tkFont.Font(family=config.font_name,size=config.font_size) self.boldUnderscoredButtonFont=tkFont.Font(family=config.font_name,size=config.font_size,weight='bold',underline=1) for row_index in xrange(1,config.getNumOfRows()+1): self.gui_rows[int(row_index)]=Frame(master) self.gui_row_buttons[int(row_index)]=[] for button_num in xrange(1,config.getNumOfKeysInRow(row_index)+1): newButton=Button(self.gui_rows[int(row_index)]) if self.config.padx!=-1: newButton.config(padx=self.config.padx) if self.config.pady!=-1: newButton.config(pady=self.config.pady) if (row_index,int(button_num)) in config.key_pos_to_index: self.gui_all_buttons[config.key_pos_to_index[(row_index,int(button_num))]] = newButton self.gui_row_buttons[int(row_index)].append(newButton) newButton.pack(side=LEFT) self.gui_rows[int(row_index)].pack() self.reconfigure_text_on_buttons(config,shift_pressed=0,lang=0) ...
途中でフォームにボタンを追加すると、行番号のキーと値(ボタンが配置される各フレームオブジェクト)を使用して辞書が作成されます。 コードからわかるように、ボタンは行ごとに形成され、行の形成が完了すると、pack()メソッドを使用してウィジェットがウィンドウに配置されます。
とりわけ、この機能はprocessQueueを追加しました。これは、グラフィカルインターフェイスのフローから、押されたボタンのイベントを含むタプルキューを取り出し、ボタンの外観を変更します。「クリック」、「レイアウトの切り替え」、「シフトボタン」
GUIキュー処理
def processQueue(self): while not self.queue.empty(): msg = self.queue.get(0) if msg[0] == -1: # -1 message is for changing language self.currentLang=int(msg[1]) if self.config.debug: print "Changed lang!" self.reconfigure_text_on_buttons(self.config,0,msg[1]) if msg[0] in self.gui_all_buttons: if msg[0] in self.shift_key_codes: self.reconfigure_text_on_buttons(self.config,msg[1],self.currentLang) if msg[1]==1: self.gui_all_buttons[msg[0]].config(relief=SUNKEN) if self.sticky_key_behaviour: if self.last_sticky_button!=msg[0]: self.gui_all_buttons[self.last_sticky_button].config(relief=RAISED) self.last_sticky_button=msg[0] else: if not self.sticky_key_behaviour: self.gui_all_buttons[msg[0]].config(relief=RAISED) if self.config.debug: print msg
GuiManagerクラスはThreadedClientクラス内にカプセル化され、Tkinterメインストリームを受信し、20ミリ秒ごとにキュー解析関数を設定します。
GuiManagerカプセル化クラス
class ThreadedClient: def __init__(self, master): self.master = master self.config=ConfigManager() self.keyTrainer=keyboardStatus(self.config) keyTrainer=self.keyTrainer master.protocol('WM_DELETE_WINDOW', self.kill_and_destroy) self.guiManager=GuiManager(master,self.config,keyTrainer.myQueue,keyTrainer) keyTrainer.begin_scan() self.running = 1 self.periodicCall() def kill_and_destroy(self): self.running = 0 self.keyTrainer.stop_scan() if self.config.debug: print "Stopping scan..." self.master.destroy() def periodicCall(self): self.guiManager.processQueue() if not self.running: # import sys # sys.exit(1) self.kill_and_destroy() self.master.after(20, self.periodicCall)
いくつかの写真
プログラムウィンドウの一般的なビュー:
左Altキーが押された:
再構成後のプログラムウィンドウ:
マウスをホバーすると、プログラムウィンドウは見出しの下に「残ります」(白い背景に残る色はビデオ圧縮のアーティファクトです):
Shiftキーを押しながら言語を切り替えます。
おわりに
最後に何が起こったのですか? しかし、それは人々がキーボードで盲目的にタイプすることを学ぶのを助けるための良いプログラムであることが判明しました。 はい、彼女には次のような欠点と非効率があります。
- 文字を読み取るためのbashコマンドでサイドから起動されるプロセス。
- ハードセット言語(ロシア語と英語のみ);
- 正方形のインターフェース。
- UbuntuおよびLinux Mint(MATE)で動作しますが、他のディストリビューションではテストされていません。
コードは次の場所からダウンロード/表示できます: bitbucketへのリンク
このプログラムにはpython 2.7とTkinterが必要です。 後者をインストールするには、次のコマンドを実行する必要があります。
sudo apt-get install python-tk
プログラムは、プログラムのあるディレクトリからスクリプトStart.shによって起動されます。
ご清聴ありがとうございました!
PS質問がありました:プログラムを書くのにどれくらい時間がかかりましたか? 最初の3つのテストが活発に行われ、あらゆる種類の詳細が完了した後、合計で6〜8時間かかりました。
UPD:GUIキュー処理からtry / exceptを削除