-
Notifications
You must be signed in to change notification settings - Fork 7
/
free_snap_tap.py
376 lines (287 loc) · 14.5 KB
/
free_snap_tap.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
'''
Free-Snap-Tap V1.1.3
last updated: 241015-1041
'''
from threading import Thread
from os import startfile
import sys
from time import sleep
import tkinter as tk
from fst_keyboard import FST_Keyboard
from fst_manager import CONSTANTS
# will not overwrite debug settings in config
CONSTANTS.DEBUG = False
# CONSTANTS.DEBUG = True
CONSTANTS.DEBUG2 = False
# CONSTANTS.DEBUG2 = True
CONSTANTS.DEBUG3 = False
# CONSTANTS.DEBUG3 = True
# debug options on numpad numbers - if you use them do not turn on
CONSTANTS.DEBUG_NUMPAD = False
# CONSTANTS.DEBUG_NUMPAD = True
# Define File name for saving of everything, can be any filetype
# But .txt or .cfg recommended for easier editing
CONSTANTS.FILE_NAME = 'FSTconfig.txt'
# CONSTANTS.FILE_NAME = 'FSTconfig_test.txt'
# Control key combinations (vk_code and/or key_string)
# (1,2 or more keys possible - depends on rollover of your keyboard)
CONSTANTS.EXIT_Combination = ["alt", "end"]
CONSTANTS.TOGGLE_ON_OFF_Combination = ["alt", "delete"]
CONSTANTS.MENU_Combination = ["alt", "page_down"]
class Status_Indicator():
def __init__(self, root, fst_keyboard):
self.root = root
self._fst = fst_keyboard
self.crosshair_enabled = False
self.crosshair = None
self.stop = False
self.root.title("FST Status Indicator")
self.root.overrideredirect(True) # Remove window decorations
# Get the screen width and height
self.screen_width = self.root.winfo_screenwidth()
self.screen_height = self.root.winfo_screenheight()
# Calculate the size and position of the window
user_size = self._fst.arg_manager.STATUS_INDICATOR_SIZE
padding = 20
dpadding = 2* padding
x_size = dpadding + user_size
if x_size < 100:
x_size = 100
y_size = dpadding + user_size
x_position = (self.screen_width) - x_size
y_position = 0
# Set the window geometry placement
self.root.geometry(f'{x_size}x{y_size}+{x_position}+{y_position}')
self.root.attributes("-alpha", 0.5) # Set transparency level
self.root.wm_attributes("-topmost", 1) # Keep the window on top
self.root.wm_attributes("-transparentcolor", "yellow")
# print(f"self._fst.arg_manager.STATUS_INDICATOR_SIZE: {self._fst.arg_manager.STATUS_INDICATOR_SIZE}")
# Create a canvas for the indicator
self.canvas = tk.Canvas(self.root, width=x_size, height=y_size, bg='yellow', highlightthickness=0)
self.canvas.pack()
# Draw the indicator
self.indicator = self.canvas.create_oval(x_size - padding - user_size, padding, x_size - padding, y_size - padding, fill="red")
# Bind mouse events to make the window draggable
self.root.bind("<ButtonPress-1>", self.on_start)
self.root.bind('<Double-1>', self.open_config_file) # left mouse button double click
self.root.bind('<Button-2>', self.open_config_file) # middle mouse button
self.root.bind("<B1-Motion>", self.on_drag)
# Create a right-click context menu
self.context_menu = tk.Menu(self.root, tearoff=0)
self.context_menu.add_command(label="Open config file", command=self.open_config_file)
self.context_menu.add_command(label="Reload from file", command=self.reload_from_file)
self.context_menu.add_separator()
self.context_menu.add_command(label="Toggle Pause", command=self._fst.control_toggle_pause)
self.context_menu.add_command(label="Return to Menu", command=self._fst.control_return_to_menu)
self.context_menu.add_command(label="Exit Program", command=self._fst.control_exit_program)
self.context_menu.add_separator()
self.context_menu.add_command(label="Close Indicator", command=self.close_window)
self.context_menu.add_command(label="Toggle Crosshair", command=self.toggle_crosshair)
self.context_menu.add_command(label="Display internal state", command=self._fst.display_internal_repr_groups)
# Bind right-click to show the context menu
self.canvas.bind("<Button-3>", self.show_context_menu)
def open_config_file(self, event = None):
startfile(self._fst.config_manager.file_name)
def reload_from_file(self):
self._fst.update_args_and_groups(self._fst.focus_manager.FOCUS_APP_NAME)
self._fst.cli_menu.update_group_display()
print(f'\n>>> file reloaded for focus app: {self._fst.focus_manager.FOCUS_APP_NAME}\n')
def on_start(self, event):
# Record the starting position of the mouse
self._drag_data = {"x": event.x_root, "y": event.y_root}
def on_drag(self, event):
# Calculate the new position of the window
dx = event.x_root - self._drag_data["x"]
dy = event.y_root - self._drag_data["y"]
x = self.root.winfo_x() + dx
y = self.root.winfo_y() + dy
# Update the starting position of the mouse
self._drag_data["x"] = event.x_root
self._drag_data["y"] = event.y_root
# Move the window to the new position
self.root.geometry(f"+{x}+{y}")
def show_context_menu(self, event):
self.context_menu.tk_popup(event.x_root, event.y_root)
def run(self):
self.root.mainloop()
def update_indicator(self):
wait_one_round = False
manual = self._fst.arg_manager.MANUAL_PAUSED
win32 = self._fst.arg_manager.WIN32_FILTER_PAUSED
while not self.stop:
if self._fst.arg_manager.STATUS_INDICATOR:
# only update if there is a change
if manual is not self._fst.arg_manager.MANUAL_PAUSED or win32 is not self._fst.arg_manager.WIN32_FILTER_PAUSED:
manual = self._fst.arg_manager.MANUAL_PAUSED
win32 = self._fst.arg_manager.WIN32_FILTER_PAUSED
if self._fst.arg_manager.MANUAL_PAUSED or self._fst.arg_manager.WIN32_FILTER_PAUSED:
status = False
else:
status = True
wait_one_round = True
color = "green" if status else "red"
self.canvas.itemconfig(self.indicator, fill=color)
# activate and deactive crosshair from tk.mainloop
# wait an extra round for the new window to settle itself
if wait_one_round:
wait_one_round = False
else:
if self._fst.arg_manager.CROSSHAIR_ENABLED:
if not self.crosshair_enabled:
self.crosshair_activate()
else:
if self.crosshair_enabled:
self.crosshair_deactivate()
# if mainthread is inactive already than end indicator
if not main_thread.is_alive():
if self.crosshair_enabled:
self.crosshair_deactivate()
self.close_window()
sleep(1)
def toggle_crosshair(self):
if not self._fst.arg_manager.CROSSHAIR_ENABLED:#self.crosshair_enabled:
self._fst.arg_manager.CROSSHAIR_ENABLED = True
self.crosshair_activate()
else:
self._fst.arg_manager.CROSSHAIR_ENABLED = False
self.crosshair_deactivate()
def crosshair_activate(self):
if self.crosshair is not None:
self.crosshair_deactivate()
self.crosshair = Crosshair(tk.Toplevel(), self._fst)
self.crosshair_enabled = True
def crosshair_deactivate(self):
self.crosshair.destroy()
self.crosshair = None
self.crosshair_enabled = False
def end(self):
self.stop = True
def close_window(self):
self.end()
# Properly close the Tkinter window and stop the main loop
self.root.destroy()
class Crosshair():
def __init__(self, root, fst_keyboard):
# Create a new Tkinter window
self.root = root
self._fst = fst_keyboard
# Set title to recognise it in focus window
self.root.title("FST Crosshair")
# Remove window decorations
self.root.overrideredirect(True)
self.root.bind('<Button-1>', self.restart)
# Set the window to be transparent
self.root.attributes('-alpha', 1)
self.built_crosshair()
def built_crosshair(self):
def rgbtohex(r,g,b):
return f'#{r:02x}{g:02x}{b:02x}'
# delta x,y for the midpoint of the crosshair
delta_x = self._fst.arg_manager.CROSSHAIR_DELTA_X - 1 # for me this is the center of the screen
delta_y = self._fst.arg_manager.CROSSHAIR_DELTA_Y - 1
# base size has to be at least double the max of |x| or |y|
# min_canvas_size = 2 * max(abs(delta_x), abs(delta_y)) + 25 # add a bit of buffer (25)
# print(min_canvas_size)
# # adapt canvas size to be big enough for the delta values
# if min_canvas_size < 100:
# self.size = 100
# else:
# # make it a multiplicative of 100
# self.size = (min_canvas_size // 100 + 1) * 100
self.size = 100
# middle point distance from coordinate system of he canvas
mid = self.size // 2
# Get the screen width and height
self.screen_width = self.root.winfo_screenwidth()
self.screen_height = self.root.winfo_screenheight()
# Calculate the position to center the window
self.x_position = (self.screen_width // 2) - mid + delta_x
self.y_position = (self.screen_height // 2) - mid + delta_y
# Set the window geometry to 2x2 pixels centered on the screen
self.root.geometry(f'{self.size}x{self.size}+{self.x_position}+{self.y_position}')
# Create a canvas to draw the crosshair
self.canvas = tk.Canvas(self.root, width=self.size, height=self.size, bg='white', highlightthickness=0)
self.canvas.pack()
# set color to glowing pink - that should be usable in most games :-D
# would be interesting if it would be possible to make it the complementory color of
# the window below
color = rgbtohex(255, 0, 255)
# Draw the crosshair lines
self.canvas.create_line(mid+0, mid+10, mid+0, mid+25, fill=color) # Vertical line
self.canvas.create_line(mid+1, mid+10, mid+1, mid+25, fill=color) # Vertical line
self.canvas.create_line(mid-1, mid+10, mid-1, mid+25, fill="black") # Vertical line
self.canvas.create_line(mid+11, mid+0, mid+26, mid+0, fill=color) # Horizontal line right
self.canvas.create_line(mid+11, mid+1, mid+26, mid+1, fill=color) # Horizontal line right
self.canvas.create_line(mid+11, mid+2, mid+26, mid+2, fill="black") # Horizontal line right
self.canvas.create_line(mid-25, mid+0, mid-10, mid+0, fill=color) # Horizontal line left
self.canvas.create_line(mid-25, mid+1, mid-10, mid+1, fill=color) # tHorizontal line left
self.canvas.create_line(mid-25, mid+2, mid-10, mid+2, fill="black") # Horizontal line left
self.canvas.create_line(mid-1, mid+0, mid-1, mid+2, fill=color) # Dot
self.canvas.create_line(mid+2, mid+0, mid+2, mid+3, fill=color) # Dot
self.canvas.create_line(mid-1, mid+2, mid+2, mid+2, fill=color) # Dot
self.canvas.create_line(mid-1, mid+3, mid+3, mid+3, fill="black") # Dot
self.canvas.create_line(mid-2, mid+0, mid-2, mid+3, fill="black") # Dot
# Set the window to be always on top and transparent again for drawing
self.root.attributes('-topmost', True)
self.root.attributes('-transparentcolor', 'white')
# def run(self):
# # Start the Tkinter main loop
# self.root.mainloop()
def destroy(self, event = None):
self.root.destroy()
def restart(self, event = None):
print("restarting crosshair")
self.canvas.destroy()
self.built_crosshair()
def main():
if CONSTANTS.DEBUG:
print(f"D1: tap_groups_hr: {fst_keyboard.config_manager.tap_groups_hr}")
print(f"D1: tap_groups: {fst_keyboard._tap_groups}")
focus_active = fst_keyboard.focus_manager.init_focus_thread()
while not fst_keyboard.arg_manager.STOPPED:
fst_keyboard.init_listener()
if fst_keyboard.arg_manager.MENU_ENABLED:
fst_keyboard.focus_manager.pause_focus_thread()
fst_keyboard.cli_menu.display_menu()
else:
fst_keyboard.config_manager.display_groups()
if focus_active:
fst_keyboard.focus_manager.restart_focus_thread()
# start keyboard and mouse listener
fst_keyboard.start_listener()
# if no focus app is given in config file, then start default as always active
if not focus_active:
fst_keyboard.update_args_and_groups()
fst_keyboard.cli_menu.update_group_display()
fst_keyboard.arg_manager.WIN32_FILTER_PAUSED = False
print('--- Free Snap Tap started ---')
if focus_active:
fst_keyboard.cli_menu.display_focus_names()
fst_keyboard.focus_manager.start_focus_thread()
# wait for listener to finish on internal stop
fst_keyboard.join_listener()
fst_keyboard.stop_listener()
fst_keyboard.focus_manager.stop_focus_thread()
fst_keyboard.cli_menu.flush_the_input_buffer()
sys.exit(1)
if __name__ == "__main__":
fst_keyboard = FST_Keyboard()
fst_keyboard.set_sys_start_arguments(sys.argv[1:] if len(sys.argv) > 1 else [])
fst_keyboard.update_args_and_groups()
if fst_keyboard.arg_manager.STATUS_INDICATOR:
main_thread = Thread(target=main)
main_thread.start()
# waiting for the rest of the program to finish loading
sleep(0.5)
try:
root = tk.Tk()
indicator = Status_Indicator(root, fst_keyboard)
indicator_thread = Thread(target=indicator.update_indicator)
indicator_thread.daemon = True # Daemonize thread
indicator_thread.start()
indicator.run()
except RuntimeError:
pass
sys.exit(1)
else:
main()