Skip to content

Commit

Permalink
First working version
Browse files Browse the repository at this point in the history
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
Levtastic committed Nov 28, 2016
0 parents commit 5ed8d9a
Show file tree
Hide file tree
Showing 6 changed files with 553 additions and 0 deletions.
47 changes: 47 additions & 0 deletions cursor.py
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 added icon.ico
Binary file not shown.
87 changes: 87 additions & 0 deletions main.py
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()
164 changes: 164 additions & 0 deletions ui.py
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)
Loading

0 comments on commit 5ed8d9a

Please sign in to comment.