I have a touchscreen laptop that folds back enough to become like a tablet. If I put it down on the table, I don't want to be hitting keys accidentally, so I'm working on a script to disable the keyboard when I hit Ctrl-F10 and then re-enable it when I do that again. I'm using xlib from PyPI, and I've gotten this so far:
from Xlib.display import Display
from Xlib.ext import xinput
class Handler:
def __init__(self, display):
self.enabled = True
self.display = display
def handle(self, event):
if event.data['detail'] == 76 and event.data['mods']['base_mods'] == 4:
if self.enabled:
self.display.grab_server()
else:
self.display.ungrab_server()
self.enabled = not self.enabled
try:
display = Display()
handler = Handler(display)
screen = display.screen()
screen.root.xinput_select_events([
(xinput.AllDevices, xinput.KeyPressMask),
])
while True:
event = display.next_event()
handler.handle(event)
finally:
display.close()
It does disable the keyboard on Ctrl-F10, but as soon as I re-enable, all the keys I pressed when it was disabled are activated all at once. Is there a way to clear the queue before re-enabling, or a better way to disable the keyboard?
Try XGrabKeyboard: https://tronche.com/gui/x/xlib/input/XGrabKeyboard.html
(But this requires you to create your own window for grabbing; you can e.g. create a window of size 1x1 at position -10x-10)
I think the values for things like owner_events and keyboard_mode do not matter much. The main effect should be that the input focus goes to your own window. time should be CurrentTime (which is 0) and pointer_mode should be GrabModeAsync, so that you do not interfere with the pointer.
Related
I have tried several different things about the mouse clicks not registering correctly in my Qt mainwindow. When using only my QHD monitor, the program worked just fine (video). However, when using my laptop (zoomed in at 1792 x 1120) as the only display, the mouse clicks seemed to have a varying up-right offset and register more accurately near the bottom left corner of the widget (video). I am suspicious that the screen resolution of the display might cause a problem for vedo.
The mouse event is a vedo plotter event. Changing the "screensize", "size", "pos" attributes of the plotter did not fix the issue.
I looked up some examples provided by vedo, specifically mousehover.py and qt_window1.py. The mousehover example worked fine on my laptop. However, adding a clicking event in qt_window1.py also created the same issue. Therefore, the problem most likely was caused by the qt widget.
def __init__(self,size):
super(MainWindow, self).__init__()
# load the components defined in th xml file
loadUi("viewer_gui.ui", self)
self.screenSize = size
# Connections for all elements in Mainwindow
self.pushButton_inputfile.clicked.connect(self.getFilePath)
self.pushButton_clearSelection.clicked.connect(self.clearScreen)
self.action_selectVertex.toggled.connect(self.actionSelection_state_changed)
self.action_selectActor.toggled.connect(self.actionSelection_state_changed)
# Set up VTK widget
self.vtkWidget = QVTKRenderWindowInteractor()
self.splitter_viewer.addWidget(self.vtkWidget)
# ipy console
self.ipyConsole = QIPythonWidget(customBanner="Welcome to the embedded ipython console\n")
self.splitter_viewer.addWidget(self.ipyConsole)
self.ipyConsole.pushVariables({"foo":43, "print_process_id":print_process_id, "ipy":self.ipyConsole, "self":self})
self.ipyConsole.printText("The variable 'foo' and the method 'print_process_id()' are available.\
Use the 'whos' command for information.\n\nTo push variables run this before starting the UI:\
\n ipyConsole.pushVariables({\"foo\":43,\"print_process_id\":print_process_id})")
# Create renderer and add the vedo objects and callbacks
self.plt = Plotter(qtWidget=self.vtkWidget,bg='DarkSlateBlue',bg2='MidnightBlue',screensize=(1792,1120))
self.id1 = self.plt.addCallback("mouse click", self.onMouseClick)
self.id2 = self.plt.addCallback("key press", self.onKeypress)
self.plt.show() # <--- show the vedo rendering
def onMouseClick(self, event):
if(self.action_selectActor.isChecked()):
self.selectActor(event)
elif(self.action_selectVertex.isChecked()):
self.selectVertex(event)
def selectActor(self,event):
if(not event.actor):
return
printc("You have clicked your mouse button. Event info:\n", event, c='y')
printc("Left button pressed on", [event.picked3d])
# adding a silhouette might cause some lags
# self.plt += event.actor.silhouette().lineWidth(2).c('red')
#an alternative solution
self.actorSelection = event.actor.clone()
self.actorSelection.c('red')
self.plt += self.actorSelection
def selectVertex(self,event):
if(not event.isPoints):
return
# print(arr[event.actor.closestPoint(event.picked3d, returnPointId=True)])
printc("You have clicked your mouse button. Event info:\n", event, c='y')
printc("Left button pressed on 3d: ", [event.picked3d])
printc("Left button pressed on 2d: ", [event.picked2d])
p = pointcloud.Point(pos=(event.picked3d[0],event.picked3d[1],event.picked3d[2]),r=12,c='red',alpha=0.5)
self.vertexSelections.append(p)
self.plt += p
Running the following lines:
app = QApplication(sys.argv)
screen = app.primaryScreen()
print('Screen: %s' % screen.name())
size = screen.size()
print('Size: %d x %d' % (size.width(), size.height()))
outputted:
Screen: Color LCD
Size: 1792 x 1120
This turns out to be an upstream bug in vtk, unrelated to vedo. After downgrading VTK to version 8.1.2 and Python to 3.7, the clicking issue disappeared when running the program on my laptop. Other people also encountered the same problem, and here is an error report on vtk’s website that describes how vtkPropPicker is now returning the wrong world coordinates.
I was having trouble formatting the title of this question, because I wasn't sure I'm going about this the right way, so let me explain.
I want to try and add a right-click context menu to an existing program for which I don't have the source code. wxPython is generally my framework of choice. I figured there was a couple ways of doing this:
1) Create a transparent wx.Frame which is tied to and sits on top of the existing program, intercepting mouse events. If I did this, I wasn't sure if the mouse events could then be passed to the underlying window. I like this option, because it would allow adding more useful information in the overlay.
2) Create a headless program which globally intercepts right-click events, and spawns the context menu at the pointer location when certain conditions are met. Based on the research I've done so far, this didn't seem possible without continuously polling for mouse position.
What am I missing? Is there a more elegant solution for this? Is this even possible using Python?
edit: I have a partial proof-of-concept working which looks like this:
import wx
import win32gui
import win32api
import win32con
class POC_Frame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, id=wx.ID_ANY, title='POC', pos=(0,0), size=wx.Size(500, 500), style=wx.DEFAULT_FRAME_STYLE)
self.ToggleWindowStyle(wx.STAY_ON_TOP)
extendedStyleSettings = win32gui.GetWindowLong(self.GetHandle(), win32con.GWL_EXSTYLE)
win32gui.SetWindowLong(self.GetHandle(), win32con.GWL_EXSTYLE,
extendedStyleSettings | win32con.WS_EX_LAYERED | win32con.WS_EX_TRANSPARENT)
win32gui.SetLayeredWindowAttributes(self.GetHandle(), win32api.RGB(0,0,0), 100, win32con.LWA_ALPHA)
self.Bind(wx.EVT_RIGHT_DOWN, self.onRightDown)
self.Bind(wx.EVT_RIGHT_UP, self.onRightUp)
self.CaptureMouse()
def onRightDown(self, event):
print(event)
def onRightUp(self, event):
print(event)
app = wx.App(False)
MainFrame = POC_Frame(None)
MainFrame.Show()
app.MainLoop()
This seems to work OK, as it passes the right click events to the underlying window, while still recognizing them, but it only does it exactly once. As soon as it loses focus, it stops working and nothing I've tried to return focus to it seems to work.
I've always had better luck hooking global mouse and keyboard events with pyHook rather than wx. Here is a simple example:
import pyHook
import pyHook.cpyHook # ensure its included by cx-freeze
class ClickCatcher:
def __init__(self):
self.hm = None
self._is_running = True
self._is_cleaned_up = False
self._is_quitting = False
self.set_hooks()
# this is only necessary when not using wx
# def send_quit_message(self):
# import ctypes
# win32con_WM_QUIT = 18
# ctypes.windll.user32.PostThreadMessageW(self.pump_message_thread.ident, win32con_WM_QUIT, 0, 0)
def __del__(self):
self.quit()
def quit(self):
if not self._is_running:
return
self._is_quitting = True
self._is_running = False
if self.hm:
# self.hm.UnhookKeyboard()
self.hm.UnhookMouse()
# self.send_quit_message()
self._is_cleaned_up = True
def set_hooks(self):
self._is_running = True
self._is_cleaned_up = False
self.hm = pyHook.HookManager()
self.hm.MouseRightUp = self.on_right_click
# self.hm.HookKeyboard()
self.hm.HookMouse()
def on_right_click(self):
# create your menu here
pass
If you weren't using wx, you'd have to use pythoncom.PumpMessages to push mouse and keyboard events to you program, but App.Mainloop() accomplishes the same thing (if you use PumpMessages and Mainloop together about half of the events won't be push to your program).
Creating a wx.Menu is easy enough. You can find the mouse coordinates using wx.GetMousePosition()
I have a frame with one radio box to toggle full screen. The frame is to go full screen when the user clicks the Maximize button. However, if I use the maximize button, the radio box would then fail to restore the window. If I use the radio box to go full screen, it will be able to restore the window.
import wx
class FSWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.SetSize((800, 600))
self.RadioFullScreen = wx.RadioBox(self, -1, "Display", choices=["Windowed","Full Screen"])
self.RadioFullScreen.Bind(wx.EVT_RADIOBOX, self.FS)
self.Bind(wx.EVT_MAXIMIZE, self.OnMaximize)
self.Sizer = None
self.Show()
def FS(self, Event):
if self.RadioFullScreen.GetSelection():
self.ShowFullScreen(True)
else:
self.ShowFullScreen(False)
def OnMaximize(self, Event):
self.ShowFullScreen(True) # <-- Add self.Restore() or self.Maximize(False) here
self.RadioFullScreen.SetSelection(1)
App = wx.App()
frame =FSWindow(None, -1, "MainWindow")
App.MainLoop()
However, if I add self.Restore() or self.Maximize(False) before the self.ShowFullScreen(True) like I commented on the source code above, the radio buttons will work. Problem is, the window will be restored first before going full screen which is ugly. Any solution for this? Also please explain why this happened, if possible.
Running Python 2.7.9, WxPython 3.0.2 on Window 7 Professional 32-bit
It seems that ShowFullScreen is not setting some flag, so things get out of sync.
If I just use Maximize/Restore things work fine for me, i.e. following changes to your code.
def FS(self, Event):
if self.RadioFullScreen.GetSelection():
self.Maximize()
#self.ShowFullScreen(True, style=wx.FULLSCREEN_ALL)
print('done fs true')
else:
#self.ShowFullScreen(False, style=wx.FULLSCREEN_ALL)
self.Restore()
print('done fs false')
def OnMaximize(self, Event):
Event.Skip()
self.RadioFullScreen.SetSelection(1)
print('done max')
If you don't want the menu bar etc when the screen is maximized then uncomment the ShowFullScreen lines.
You are handling an event "Maximize", most of the time you want default behaviour also to happen, that is why I added Event.Skip to the OnMaximize handler - in this case it doesn't make a difference as it looks like the event is only fired after maximisation is already done.
I am new to python. I am trying to gather links from youtube based on search string. Assumption is the link I want will be the first result on my search results.
Problem I am facing is mouse clicks are not recognised on the gui. It just moves to the x,y cordinates but I cannot see that it is getting clicked. Please help me to understand why this clicks are not recognized.
Below is the code: I created a GUI in wxpython, and I am trying to click on search bar of youtube, then paste the search string, then click on search take the link address from the first result. This below code is sample code. In my real code search string will be passed from excel sheet.
import win32api, win32con
import win32com.client as win32
from win32com.client import Dispatch, Constants
import os
import time
import win32clipboard
import wx
import wx.html2
import sys
x_pad = 0
y_pad = 0
def leftClick():
wx.EVT_LEFT_DOWN
time.sleep(.1)
wx.EVT_LEFT_UP
print "Click." #completely optional. But nice for debugging purposes.
def rightClick():
wx.EVT_RIGHT_DOWN
time.sleep(.1)
wx.EVT_RIGHT_UP
print "Right Click." #completely optional. But nice for debugging purposes.
def mousePos(cord):
win32api.SetCursorPos((x_pad + cord[0], y_pad + cord[1]))
class URL(wx.Frame):
def __init__(self, *args, **kw):
super(URL, self).__init__(*args, **kw)
self.InitUI()
def InitUI(self):
pnl = wx.Panel(self)
sbtn = wx.Button(pnl, label='Start', pos=(850, 560))
cbtn = wx.Button(pnl, label='Close', pos=(850, 610))
stdot = wx.TextCtrl(pnl, pos =(400,580),size=(400,80), style=wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
sys.stdout = stdot
sys.stderr = stdot
sbtn.Bind(wx.EVT_BUTTON, self.OnStart)
cbtn.Bind(wx.EVT_BUTTON, self.OnClose)
brwser = wx.html2.WebView.New(pnl, size = (1280,550), pos = (0,0), url =("http://www.youtube.com/results?search_query=sarabhai+vs+sarabhai+episode+25&sm=3"))
self.SetSize((1280, 720))
self.SetTitle('YouTube URL Grab')
self.SetPosition((0,0))
self.Show(True)
def OnStart(self,e):
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText("Sarabhai vs Sarabhai episode 31") #Sample string for search
win32clipboard.CloseClipboard()
mousePos((252, 53))
leftClick()
leftClick()
leftClick()
rightClick()
time.sleep(5)
mousePos((280, 144))
leftClick()
time.sleep(5)
mousePos((755, 53))
leftClick()
time.sleep(7)
mousePos((467, 140))
rightClick()
time.sleep(5)
mousePos((514, 321))
leftClick()
def OnClose(self, e):
self.Close(True)
def main():
ex = wx.App()
URL(None)
ex.MainLoop()
if __name__ == '__main__':
main()
The names wx.EVT_LEFT_DOWN and wx.EVT_LEFT_UP are actually just numbers for the event identifies that you need to bind to they do not cause the events to happen! Look at the examples in the docs and demos package.
The basic process is:
During frame intitialisation bind handlers to the events you need to handle, these should be of type fn(self, evt) and by convention are named onNameOfEvent e.g. def onLeftBtnDown(self, evt) these contain the events that you need to react to and the evt parameter gives you an event object the members of which depend on the event type. The event binding uses the format self.Bind(wx.EVT_NAME_OF_EVENT, onNameOfEvent) and is normally in the windows __init__ method, (this is what the vast majority of GUI code does, including non wx GUIs).
If, (much more rarely), you need to generate events from your code rather than just reacting to the events you still need to do the above then where you need to create the event by declaring the appropriate type of event object, e.g. wx.CommandEvent, (note that you can create your own event types), populating with the required values and then use 'wx.PostEvent' to add it to the event queue for the window or control that it applies to. Sometime later, when it gets to the top of the queue, your event handler will be called with this event.
I need a command to be executed as long as the left mouse button is being held down.
If you want "something to happen" without any intervening events (ie: without the user moving the mouse or pressing any other buttons) your only choice is to poll. Set a flag when the button is pressed, unset it when released. While polling, check the flag and run your code if its set.
Here's something to illustrate the point:
import Tkinter
class App:
def __init__(self, root):
self.root = root
self.mouse_pressed = False
f = Tkinter.Frame(width=100, height=100, background="bisque")
f.pack(padx=100, pady=100)
f.bind("<ButtonPress-1>", self.OnMouseDown)
f.bind("<ButtonRelease-1>", self.OnMouseUp)
def do_work(self):
x = self.root.winfo_pointerx()
y = self.root.winfo_pointery()
print "button is being pressed... %s/%s" % (x, y)
def OnMouseDown(self, event):
self.mouse_pressed = True
self.poll()
def OnMouseUp(self, event):
self.root.after_cancel(self.after_id)
def poll(self):
if self.mouse_pressed:
self.do_work()
self.after_id = self.root.after(250, self.poll)
root=Tkinter.Tk()
app = App(root)
root.mainloop()
However, polling is generally not necessary in a GUI app. You probably only care about what happens while the mouse is pressed and is moving. In that case, instead of the poll function simply bind do_work to a <B1-Motion> event.
Look at table 7-1 of the docs. There are events that specify motion while the button is pressed, <B1-Motion>, <B2-Motion> etc.
If you're not talking about a press-and-move event, then you can start doing your activity on <Button-1> and stop doing it when you receive <B1-Release>.
Use the mouse move/motion events and check the modifier flags. The mouse buttons will show up there.