Skip to content

Commit 3604190

Browse files
committed
keylock
0 parents  commit 3604190

24 files changed

+1256
-0
lines changed

.env

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PYTHON_VERSION=3.9.13

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
env/
2+
dist/
3+
build/
4+
Output/
5+
__pycache__/
6+
*-log-*.txt
7+
*.config
8+
*.spec

LICENSE

+661
Large diffs are not rendered by default.

README.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<p align="center">
2+
<img src="./assets/icon.png" width="100" height="100"/>
3+
</p>
4+
5+
<h3 align="center">keylock</h3>
6+
7+
<p align="center">Lock your keys with ease</p>
8+
9+
## 💠 Preview
10+
11+
<p align="center">
12+
<img src="./thumbnail.png" />
13+
</p>
14+
15+
## 😎 Config file
16+
17+
You can make a file called `keylock.config` in the directory where the executable is. It will be used by the app to load the settings. All config options are:
18+
19+
```
20+
&unlock@!@ctrl+q # Shortcut to unlock (Examples: ctrl+q, alt+s, shift+ctrl+q)
21+
&onstart_lock_keyboard@!@false # Lock keyboard on start (true or false)
22+
&onstart_lock_mouse@!@false # Lock mouse on start (true or false)
23+
&refresh_rate@!@1500 # Check for lock every x milliseconds (integer only)
24+
&quit_after@!@never # Exit app after some time (never or number in milliseconds, examples: 1000, 5000, never)
25+
```
26+
27+
> [!Important]
28+
> The "Mouse lock" button is a bit buggy. When you lock only mouse, if exit shortcut has "ctrl" then you can only use a-z characters and nothing else. This is not an issue when you lock only keyboard or both.
29+
30+
---
31+
32+
<p align="center"><a href="https://www.patreon.com/axorax">Support me on Patreon</a> — <a href="https://github.com/axorax/socials">Check out my socials</a></p>

assets/1.png

1.64 KB
Loading

assets/2.png

1007 Bytes
Loading

assets/3.png

785 Bytes
Loading

assets/icon.ico

456 KB
Binary file not shown.

assets/icon.png

19.9 KB
Loading

assets/keyboard_locked.png

1.65 KB
Loading

assets/keyboard_unlocked.png

1.88 KB
Loading

assets/layout.png

7.03 KB
Loading

assets/mouse_locked.png

1.5 KB
Loading

assets/mouse_unlocked.png

1.69 KB
Loading

build.bat

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@echo off
2+
3+
pyinstaller ^
4+
--name="keylock" ^
5+
--onefile ^
6+
--strip ^
7+
--paths=env\Lib\site-packages ^
8+
--add-data="assets;assets" ^
9+
--noconsole ^
10+
--icon=assets/icon.ico ^
11+
--exclude-module numpy ^
12+
main.py

build.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import subprocess
2+
3+
subprocess.run([
4+
"pyinstaller",
5+
"--name=keylock",
6+
"--onefile",
7+
"--strip",
8+
"--paths=env/Lib/site-packages",
9+
"--add-data=assets:assets",
10+
"--noconsole",
11+
"--icon=assets/icon.ico",
12+
"--exclude-module=numpy",
13+
"main.py"
14+
])

build.sh

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
3+
pyinstaller \
4+
--name="keylock" \
5+
--onefile \
6+
--strip \
7+
--paths=env/Lib/site-packages \
8+
--add-data="assets:assets" \
9+
--noconsole \
10+
--icon=assets/icon.ico \
11+
--exclude-module=numpy \
12+
main.py

core.py

+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import pynput
2+
from pynput.keyboard import Key, KeyCode
3+
4+
keyboard_listener = None
5+
mouse_listener = None
6+
shortcut_listener = None
7+
pressed_keys = set()
8+
shortcut_keys = []
9+
keyboard_locked = False
10+
mouse_locked = False
11+
changed = False
12+
13+
14+
def parse_shortcut(shortcut_str):
15+
if shortcut_str is None:
16+
shortcut_str = "ctrl+q"
17+
return [
18+
(
19+
pynput.keyboard.Key.ctrl_l
20+
if k == "ctrl"
21+
else (
22+
pynput.keyboard.Key.shift
23+
if k == "shift"
24+
else (
25+
pynput.keyboard.Key.alt_l
26+
if k == "alt"
27+
else pynput.keyboard.KeyCode.from_char(k)
28+
)
29+
)
30+
)
31+
for k in shortcut_str.lower().split("+")
32+
]
33+
34+
35+
def stop_keyboard():
36+
global keyboard_listener, keyboard_locked
37+
if keyboard_listener:
38+
keyboard_listener.stop()
39+
keyboard_listener = None
40+
keyboard_locked = False
41+
print("Keyboard unlocked.")
42+
43+
44+
def start_keyboard():
45+
global keyboard_listener, keyboard_locked
46+
keyboard_listener = pynput.keyboard.Listener(suppress=True)
47+
keyboard_listener.start()
48+
keyboard_locked = True
49+
print("Keyboard locked.")
50+
51+
52+
def stop_mouse():
53+
global mouse_listener, mouse_locked
54+
if mouse_listener:
55+
mouse_listener.stop()
56+
mouse_listener = None
57+
mouse_locked = False
58+
print("Mouse unlocked.")
59+
60+
61+
def start_mouse():
62+
global mouse_listener, mouse_locked
63+
mouse_listener = pynput.mouse.Listener(suppress=True)
64+
mouse_listener.start()
65+
mouse_locked = True
66+
print("Mouse locked.")
67+
68+
69+
def start_shortcut_listener(shortcut):
70+
global shortcut_listener, shortcut_keys
71+
if not shortcut_listener:
72+
shortcut_keys = parse_shortcut(shortcut)
73+
shortcut_listener = pynput.keyboard.Listener(
74+
on_press=on_press, on_release=on_release
75+
)
76+
shortcut_listener.start()
77+
print(f"Shortcut listener started for: {shortcut}")
78+
79+
80+
def stop_shortcut_listener():
81+
global shortcut_listener
82+
if shortcut_listener:
83+
shortcut_listener.stop()
84+
shortcut_listener = None
85+
print("Shortcut listener stopped.")
86+
87+
88+
def lock_keyboard(shortcut="ctrl+q"):
89+
global keyboard_locked
90+
if keyboard_locked:
91+
stop_keyboard()
92+
else:
93+
start_keyboard()
94+
start_shortcut_listener(shortcut)
95+
96+
97+
def lock_mouse(shortcut="ctrl+q"):
98+
global mouse_locked
99+
if mouse_locked:
100+
stop_mouse()
101+
else:
102+
start_mouse()
103+
start_shortcut_listener(shortcut)
104+
105+
106+
VK_MAP = {
107+
48: "0",
108+
49: "1",
109+
50: "2",
110+
51: "3",
111+
52: "4",
112+
53: "5",
113+
54: "6",
114+
55: "7",
115+
56: "8",
116+
57: "9",
117+
}
118+
119+
CONTROL_CHAR_MAP = {
120+
"\x01": "a",
121+
"\x02": "b",
122+
"\x03": "c",
123+
"\x04": "d",
124+
"\x05": "e",
125+
"\x06": "f",
126+
"\x07": "g",
127+
"\x08": "h",
128+
"\x09": "i",
129+
"\x0a": "j",
130+
"\x0b": "k",
131+
"\x0c": "l",
132+
"\x0d": "m",
133+
"\x0e": "n",
134+
"\x0f": "o",
135+
"\x10": "p",
136+
"\x11": "q",
137+
"\x12": "r",
138+
"\x13": "s",
139+
"\x14": "t",
140+
"\x15": "u",
141+
"\x16": "v",
142+
"\x17": "w",
143+
"\x18": "x",
144+
"\x19": "y",
145+
"\x1a": "z",
146+
}
147+
148+
149+
def string_to_key(string):
150+
if string is None:
151+
return None
152+
if isinstance(string, str) and string.startswith("Key."):
153+
special_key_name = string[4:]
154+
try:
155+
return getattr(Key, special_key_name)
156+
except AttributeError:
157+
return None
158+
elif len(string) == 1:
159+
return KeyCode.from_char(string)
160+
else:
161+
return None
162+
163+
164+
def on_press(key):
165+
global pressed_keys
166+
global changed
167+
168+
readable_key = None
169+
170+
if hasattr(key, "char"):
171+
readable_key = key.char
172+
elif isinstance(key, Key):
173+
readable_key = str(key)
174+
175+
if readable_key in CONTROL_CHAR_MAP:
176+
readable_key = CONTROL_CHAR_MAP[readable_key]
177+
178+
key_object = string_to_key(readable_key)
179+
if key_object is not None:
180+
pressed_keys.add(key_object)
181+
182+
print(f"Pressed key: {key}, Readable key: {readable_key}")
183+
184+
if all(
185+
(k in pressed_keys)
186+
or (isinstance(k, str) and k.startswith("\\x") and ord(k) in pressed_keys)
187+
for k in shortcut_keys
188+
):
189+
print("Shortcut pressed, unlocking all...")
190+
stop_keyboard()
191+
stop_mouse()
192+
stop_shortcut_listener()
193+
pressed_keys = set()
194+
changed = True
195+
196+
197+
def on_release(key):
198+
global pressed_keys
199+
if key in pressed_keys:
200+
pressed_keys.remove(key)

format.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import subprocess
2+
import sys
3+
4+
5+
def format():
6+
try:
7+
import black
8+
9+
installed = True
10+
except ImportError:
11+
installed = False
12+
13+
if not installed:
14+
subprocess.check_call([sys.executable, "-m", "pip", "install", "black"])
15+
16+
subprocess.check_call([sys.executable, "-m", "black", "core.py"])
17+
subprocess.check_call([sys.executable, "-m", "black", "main.py"])
18+
subprocess.check_call([sys.executable, "-m", "black", "settings.py"])
19+
20+
21+
if __name__ == "__main__":
22+
format()

keylock.iss

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#define AppName "Keylock"
2+
#define Version "1.0.0"
3+
#define Author "Axorax"
4+
#define URL "https://github.com/Axorax/keylock"
5+
#define ExeName "keylock.exe"
6+
7+
[Setup]
8+
AppId={{4CED7309-FFEB-402D-9132-DFF4929DA1DA}
9+
AppName={#AppName}
10+
AppVersion={#Version}
11+
;AppVerName={#AppName} {#Version}
12+
AppPublisher={#Author}
13+
AppPublisherURL={#URL}
14+
AppSupportURL={#URL}
15+
AppUpdatesURL={#URL}
16+
DefaultDirName={autopf}\{#AppName}
17+
ArchitecturesAllowed=x64compatible
18+
ArchitecturesInstallIn64BitMode=x64compatible
19+
DisableProgramGroupPage=yes
20+
PrivilegesRequired=lowest
21+
OutputBaseFilename=Keylock-setup
22+
SetupIconFile=assets\icon.ico
23+
Compression=lzma
24+
SolidCompression=yes
25+
WizardStyle=modern
26+
27+
[Languages]
28+
Name: "english"; MessagesFile: "compiler:Default.isl"
29+
Name: "armenian"; MessagesFile: "compiler:Languages\Armenian.isl"
30+
Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl"
31+
Name: "bulgarian"; MessagesFile: "compiler:Languages\Bulgarian.isl"
32+
Name: "catalan"; MessagesFile: "compiler:Languages\Catalan.isl"
33+
Name: "corsican"; MessagesFile: "compiler:Languages\Corsican.isl"
34+
Name: "czech"; MessagesFile: "compiler:Languages\Czech.isl"
35+
Name: "danish"; MessagesFile: "compiler:Languages\Danish.isl"
36+
Name: "dutch"; MessagesFile: "compiler:Languages\Dutch.isl"
37+
Name: "finnish"; MessagesFile: "compiler:Languages\Finnish.isl"
38+
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
39+
Name: "german"; MessagesFile: "compiler:Languages\German.isl"
40+
Name: "hebrew"; MessagesFile: "compiler:Languages\Hebrew.isl"
41+
Name: "hungarian"; MessagesFile: "compiler:Languages\Hungarian.isl"
42+
Name: "icelandic"; MessagesFile: "compiler:Languages\Icelandic.isl"
43+
Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl"
44+
Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl"
45+
Name: "korean"; MessagesFile: "compiler:Languages\Korean.isl"
46+
Name: "norwegian"; MessagesFile: "compiler:Languages\Norwegian.isl"
47+
Name: "polish"; MessagesFile: "compiler:Languages\Polish.isl"
48+
Name: "portuguese"; MessagesFile: "compiler:Languages\Portuguese.isl"
49+
Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"
50+
Name: "slovak"; MessagesFile: "compiler:Languages\Slovak.isl"
51+
Name: "slovenian"; MessagesFile: "compiler:Languages\Slovenian.isl"
52+
Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl"
53+
Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl"
54+
Name: "ukrainian"; MessagesFile: "compiler:Languages\Ukrainian.isl"
55+
56+
[Tasks]
57+
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
58+
59+
[Files]
60+
Source: "dist\{#ExeName}"; DestDir: "{app}"; Flags: ignoreversion
61+
62+
[Icons]
63+
Name: "{autoprograms}\{#AppName}"; Filename: "{app}\{#ExeName}"
64+
Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#ExeName}"; Tasks: desktopicon
65+
66+
[Run]
67+
Filename: "{app}\{#ExeName}"; Description: "{cm:LaunchProgram,{#StringChange(AppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent

0 commit comments

Comments
 (0)