-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Seems to be working fine - need a better way to decouple ui.py from main logic in main.py - callback layout feels messy Also want to add a checkbox in future to automatically clip to any fullscreen application As usual, use at your own risk - I made this for personal use for games that haven't been set up properly to prevent cursor drift on multi-monitor setups or when running in a window
- Loading branch information
0 parents
commit 5ed8d9a
Showing
6 changed files
with
553 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
#!/usr/bin/env python | ||
# coding: utf-8 | ||
|
||
import winfunctions as wf | ||
|
||
|
||
class Clip: | ||
def __init__(self, bounds): | ||
self._bounds = bounds | ||
self._old_bounds = None | ||
|
||
def __del__(self): | ||
try: | ||
if self.clipped: | ||
self.release() | ||
except AttributeError: | ||
pass | ||
|
||
@staticmethod | ||
def clearClips(): | ||
wf.ClipCursor(None) | ||
|
||
@property | ||
def bounds(self): | ||
return self._bounds | ||
|
||
def _getClipCursor(self): | ||
bounds = wf.Bounds() | ||
wf.GetClipCursor(bounds) | ||
|
||
return bounds | ||
|
||
@property | ||
def clipped(self): | ||
return (self._getClipCursor() == self._bounds) | ||
|
||
def clip(self): | ||
wf.ClipCursor(self._bounds) | ||
|
||
def release(self): | ||
wf.ClipCursor(None) | ||
|
||
def toggle(self): | ||
if self.clipped: | ||
self.release() | ||
else: | ||
self.clip() |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
#!/usr/bin/env python | ||
# coding: utf-8 | ||
|
||
import sys, os | ||
|
||
from window import Window | ||
from cursor import Clip | ||
from ui import UI | ||
|
||
|
||
class App: | ||
list_refresh_delay = 1 * 1000 | ||
clip_refresh_delay = 1 * 1000 | ||
|
||
def __init__(self): | ||
self.windows = {} | ||
self.selected_window = None | ||
self.current_clip = None | ||
|
||
self.ui = ui = UI('Trapper', self.resourcePath, self.list_refresh_delay) | ||
ui.add_callback('getListOfWindows', self.getListOfWindows) | ||
ui.add_callback('changeSelectedWindow', self.changeSelectedWindow) | ||
ui.add_callback('clipCursor', self.clipCursor) | ||
ui.add_callback('isClipped', self.isClipped) | ||
|
||
def __del__(self): | ||
Clip.clearClips() | ||
|
||
@staticmethod | ||
def resourcePath(relative): | ||
basedir = sys._MEIPASS if getattr(sys, 'frozen', False) else os.path.dirname(__file__) | ||
return os.path.join(basedir, relative) | ||
|
||
def run(self): | ||
self.ui.mainloop() | ||
|
||
def getListOfWindows(self): | ||
self.windows = {str(window): window for window in Window.getAllTaskbarWindows()} | ||
windows = [(name, window == self.selected_window) for name, window in self.windows.items()] | ||
return sorted(windows, key=lambda w: w[0]) | ||
|
||
def changeSelectedWindow(self, selection): | ||
self.selected_window = self.windows.get(selection, None) | ||
|
||
def clipCursor(self, selection): | ||
self.selected_window = self.windows.get(selection, None) | ||
|
||
if self.isClipped(): | ||
if self.current_clip: | ||
self.current_clip.release() | ||
self.current_clip = None | ||
|
||
return False | ||
|
||
elif self.selected_window: | ||
self.current_clip = Clip(self.selected_window.bounds) | ||
self.current_clip.clip() | ||
self.ui.root.after(self.clip_refresh_delay, lambda: self.refreshClip(self.current_clip)) | ||
|
||
return True | ||
|
||
return False | ||
|
||
def refreshClip(self, previous_clip): | ||
if (not self.current_clip) or self.current_clip != previous_clip: | ||
return | ||
|
||
if (not self.selected_window) or (not self.selected_window.exists): | ||
return | ||
|
||
window_bounds = self.selected_window.bounds | ||
if previous_clip.bounds != window_bounds: | ||
# window has moved, so get a new clip for the new coords | ||
previous_clip.release() | ||
self.current_clip = Clip(window_bounds) | ||
|
||
self.current_clip.clip() | ||
self.ui.root.after(self.clip_refresh_delay, lambda: self.refreshClip(self.current_clip)) | ||
|
||
def isClipped(self): | ||
return (self.selected_window and self.selected_window.exists | ||
and self.current_clip and self.current_clip.clipped) | ||
|
||
|
||
if __name__ == '__main__': | ||
app = App() | ||
app.run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
#!/usr/bin/env python | ||
# coding: utf-8 | ||
|
||
from tkinter import * | ||
from tkinter.ttk import * | ||
|
||
|
||
class UI: | ||
width = 550 | ||
height = 350 | ||
minwidth = 120 | ||
minheight = 90 | ||
|
||
def __init__(self, app_name, resource_path_func, list_refresh_delay): | ||
self.list_refresh_delay = list_refresh_delay | ||
|
||
self.windows = {} | ||
self.callbacks = {} | ||
|
||
self.root = root = Tk() | ||
root.title(app_name) | ||
root.geometry('{}x{}'.format(self.width, self.height)) | ||
root.minsize(self.minwidth, self.minheight) | ||
root.iconbitmap(resource_path_func('icon.ico')) | ||
root.columnconfigure(0, weight=1) | ||
|
||
self.buttons = Buttons(root) | ||
|
||
self.list = WindowsList(root) | ||
root.rowconfigure(1, weight=1) | ||
|
||
self.buttons.clip_button.configure(command=self.clip_button_clicked) | ||
self.list.results_listbox.bind('<<ListboxSelect>>', self.selection_changed) | ||
self.root.after(1, self.refreshWindowList) | ||
|
||
@property | ||
def mainloop(self): | ||
return self.root.mainloop | ||
|
||
def add_callback(self, key, callback): | ||
self.callbacks[key] = callback | ||
|
||
def remove_callback(self, key): | ||
del self.callbacks[key] | ||
|
||
def run_callback(self, key, *args): | ||
return self.callbacks[key](*args) | ||
|
||
def clip_button_clicked(self): | ||
listbox = self.list.results_listbox | ||
|
||
selection = listbox.curselection() | ||
if not selection: | ||
return | ||
|
||
index = int(selection[0]) | ||
value = listbox.get(index) | ||
clipping = self.run_callback('clipCursor', value) | ||
|
||
self.refreshIfClipped(clipping) | ||
|
||
def refreshIfClipped(self, is_clipped = None): | ||
if is_clipped is None: | ||
is_clipped = self.run_callback('isClipped') | ||
|
||
button = self.buttons.clip_button | ||
listbox = self.list.results_listbox | ||
label = self.list.results_label | ||
|
||
button_text = 'Unclip' if is_clipped else 'Clip' | ||
button.configure(text=button_text) | ||
|
||
listbox_state = DISABLED if is_clipped else NORMAL | ||
listbox.configure(state=listbox_state) | ||
|
||
if is_clipped: | ||
label.grid() | ||
else: | ||
label.grid_remove() | ||
|
||
def selection_changed(self, event): | ||
listbox = event.widget | ||
label = self.list.results_label | ||
|
||
selection = listbox.curselection() | ||
if not selection: | ||
return | ||
|
||
index = int(selection[0]) | ||
value = listbox.get(index) | ||
|
||
label.config(text=value) | ||
|
||
clipping = self.run_callback('changeSelectedWindow', value) | ||
|
||
def refreshWindowList(self): | ||
listbox = self.list.results_listbox | ||
label = self.list.results_label | ||
disabled = listbox.cget('state') == DISABLED | ||
|
||
if disabled: | ||
listbox.config(state=NORMAL) | ||
|
||
listbox.delete(0, END) | ||
|
||
for title, selected in self.run_callback('getListOfWindows'): | ||
listbox.insert(END, title) | ||
if selected: | ||
label.config(text=title) | ||
index = listbox.size() - 1 | ||
listbox.selection_set(index) | ||
listbox.activate(index) | ||
|
||
if disabled: | ||
listbox.config(state=DISABLED) | ||
|
||
self.refreshIfClipped() | ||
|
||
self.root.after(self.list_refresh_delay, self.refreshWindowList) | ||
|
||
|
||
class Section(Frame): | ||
def __init__(self, master=None): | ||
Frame.__init__(self, master) | ||
self.grid(sticky=N+E+W+S, padx=1, pady=1) | ||
self.createWidgets() | ||
|
||
|
||
class Buttons(Section): | ||
def createWidgets(self): | ||
button = Button(self, text='Clip', style='TButton') | ||
button.grid(sticky=N+E+W+S, padx=5, pady=5) | ||
|
||
self.columnconfigure(0, weight=1) | ||
|
||
self.clip_button = button | ||
|
||
|
||
class WindowsList(Section): | ||
def createWidgets(self): | ||
label = Label(self) | ||
scrollbar = AutoScrollbar(self, orient=VERTICAL) | ||
listbox = Listbox(self, yscrollcommand=scrollbar.set, exportselection=0) | ||
scrollbar.config(command=listbox.yview) | ||
|
||
label.grid(row=0, columnspan=2) | ||
label.grid_remove() | ||
listbox.grid(row=1, column=0, sticky=N+E+W+S) | ||
scrollbar.grid(row=1, column=1, sticky=N+S) | ||
self.columnconfigure(0, weight=1) | ||
self.rowconfigure(1, weight=1) | ||
|
||
self.results_label = label | ||
self.results_listbox = listbox | ||
|
||
|
||
class AutoScrollbar(Scrollbar): | ||
def set(self, low, high): | ||
if float(low) <= 0.0 and float(high) >= 1.0: | ||
self.grid_remove() | ||
else: | ||
self.grid() | ||
|
||
Scrollbar.set(self, low, high) |
Oops, something went wrong.