写在前面
每到年末各大App都会给你来一次年度总结,最近突发奇想,键盘是每天必备的工具,为啥不给键盘也来个工作总结,记录一下所以每天的工作中我到底敲了多少下键盘。
分析
语言用python,实现方式那必然是要监听键盘的输入信号,然后对每个按键做统计,最后显示结果。
核心代码
查阅一下,python中有一个pynput库是专门用来做键盘、鼠标操作相关的,可以对事件进行监听,同时还可以模拟键盘、鼠标进行操作(感觉这个功能很强大,顺带成为了我后续的一个项目的解决方案)。
bash
pip install pynput
直接装就行,示例代码如下:
python
from pynput.keyboard import Listener
def on_press(key):
pass
def on_release(key):
pass
with Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()
很直观,自己实现一个on_press和on_release的方法赋给Listener对象,启动现成监听即可。其中,on_press和on_release分别代表按下和释放时的相应操作,传入的key类型为pynput内定的Key类,具体细节大家可以看源代码。
按键统计
有了核心代码之后,剩下的就是在这个基础上进行功能扩展了,先实现统计功能
python
from pynput.keyboard import Listener
# 单个按键次数
key_counts = {}
# key格式化函数
def format_key(key):
if hasattr(key, 'name'):
return key.name
elif hasattr(key, 'vk'):
if 65 <= key.vk <= 127:
return chr(key.vk)
else:
return str(key.char)
def on_press(key):
current_key.add(key_id)
print(key_id)
if key_counts.get(key_id):
key_counts[key_id] += 1
else:
key_counts[key_id] = 1
def on_release(key):
pass
with Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()
这里我除了引入一个key_counts来计数之外,还专门写了一个key格式化的函数,因为如果debug的话就会发现,除了标准的可见字符之外,当输入类似Ctrl+A这种实际传入的是一个不可见的控制字符的十六进制编码,这里将类似的字符做了一层转换。
现在整个雏形就搞定了。
设定组合键
这个程序是要后台运行的,那么就要考虑两点,程序怎么退出?我怎么看到按键的统计?
于是乎就需要设定组合键来做一些操作。
python
from pynput.keyboard import Listener
import time
from ui.TextDialog import create_window_with_text
# 单个按键次数
key_counts = {}
exit_cmd = {'alt_l', 'ctrl_l', 'shift', 'A'}
show_cmd = {'alt_l', 'ctrl_l', 'shift', 'space'}
current_key = set()
def format_key(key):
if hasattr(key, 'name'):
return key.name
elif hasattr(key, 'vk'):
if 65 <= key.vk <= 127:
return chr(key.vk)
else:
return str(key.char)
def get_result():
pass
def cmd_parse():
if exit_cmd.issubset(current_key):
get_result()
return False
if show_cmd.issubset(current_key):
get_result()
return True
def on_press(key):
key_id = format_key(key)
current_key.add(key_id)
print(key_id)
if key_counts.get(key_id):
key_counts[key_id] += 1
else:
key_counts[key_id] = 1
if not cmd_parse():
return False
def on_release(key):
try:
key_id = format_key(key)
current_key.remove(key_id)
except KeyError:
pass
with Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()
根据代码,我新增了组合键的功能,同时引入了current_key作为集合变量,来存储当前按住的按键。
- Ctrl+Shift+Alt+空格:显示统计结果
- Ctrl+Shift+Alt+A:显示统计结果并退出
值得一提的是,键盘on_press或者on_release方法默认是没有返回值的,如果返回False,则就会退出线程,进而结束程序。
那么如此一来,基础功能就有了,咱们再补充上get_result()这个用来显示统计结果的函数。
python
...
start_time = time.time()
def time_counter(begin_time, end_time):
# 根据传入的时间计算,通过run_time.round()函数取整
runtime = round(end_time - begin_time)
# 计算时分秒
hour = runtime // 3600
minute = (runtime - 3600 * hour) // 60
second = runtime - 3600 * hour - 60 * minute
# 输出
return f'程序运行\n{hour}小时 {minute}分钟 {second}秒\n'
def get_result():
end_time = time.time()
counts = dict(sorted(key_counts.items(), key=lambda item: item[1], reverse=True))
content = ""
count = 0
col = 0
for k, v in counts.items():
col += 1
content += f"{k}: {v}\t"
count += v
if col == 4:
col = 0
content += "\n"
current_key.clear()
try:
max_key = list(counts.keys())[0]
except:
max_key = None
fixed = "\n" + time_counter(start_time, end_time) + f"\n你总共敲了 {count} 下键盘\n\n最多使用\n{max_key}"
print(fixed)
print(counts)
...
很简单就是打印一下
UI界面
控制台打印肯定不行,太low了而且还看的不直观,于是让ChatGPT简单用tkinter搞了个界面,实际效果如下:
代码如下:
python
import tkinter as tk
from tkinter import ttk
def create_window_with_text(title, fixed_text, data):
window = tk.Tk()
window.title(title)
# 设置窗口初始大小和在屏幕上居中显示
window_width = 400
window_height = 200
screen_width = window.winfo_screenwidth()
screen_height = window.winfo_screenheight()
center_x = int(screen_width / 2 - window_width / 2)
center_y = int(screen_height / 2 - window_height / 2)
window.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')
# 创建PanedWindow,分为左右两块
paned_window = tk.PanedWindow(window, orient=tk.HORIZONTAL)
paned_window.pack(fill=tk.BOTH, expand=1)
# 左边的面板,设置较大的字体
left_panel = tk.Frame(paned_window, width=100, height=400, bg='white')
left_panel.pack_propagate(False)
label = tk.Label(left_panel, text=fixed_text, bg='white', font=("Arial", 12))
label.pack(side=tk.TOP, fill=tk.BOTH, padx=20) # padx为文本增加左侧内边距
paned_window.add(left_panel, stretch="always")
# 右边的面板,使用Treeview作为可拖拽的列表
right_panel = tk.Frame(paned_window, width=100, height=400)
right_panel.pack_propagate(False)
tree = ttk.Treeview(right_panel, columns=('Key', 'Count'), show='headings')
tree.heading('Key', text='按键')
tree.heading('Count', text='次数')
tree.column('Key', width=60) # 调整列的宽度
tree.column('Count', width=30)
tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
scrollbar = ttk.Scrollbar(right_panel, orient='vertical', command=tree.yview)
tree.configure(yscroll=scrollbar.set)
scrollbar.pack(side=tk.RIGHT, fill='y')
# 将数据添加到Treeview
for key, count in data.items():
tree.insert('', 'end', values=(key, count))
paned_window.add(right_panel, stretch="always")
# 窗口保持在最前面
window.attributes('-topmost', True)
# 启动事件循环
window.mainloop()
直接将get_result()函数中print()的部分调用该函数传入参数即可。
收工!