How to hide a program with the ability to open it again? - python

I have a console application written in python using pyinstaller. I need the program window to be hidden (if the user specified it), but at the same time it continued to work in the background, and when you click on the icon again, the previously hidden window just showed.
Usually such applications are displayed on the taskbar when you click on the arrow with the text - show hidden icons.
import win32gui
import win32con
def main():
while True:
c_out = input(f"Enter command: ")
if c_out.lower() == 'hide':
window('hide')
def window(mode: str):
the_program_to_hide = win32gui.GetForegroundWindow()
if mode == 'show':
win32gui.ShowWindow(the_program_to_hide, win32con.SW_SHOW)
else:
win32gui.ShowWindow(the_program_to_hide, win32con.SW_HIDE)
main()

Related

Creating a .exe from a python script which runs a separate python script using pyinstaller

Short Version:
I have a series of python scripts that connect together (one .py closes and runs a separate .py). This works completely fine when running it through the terminal in VS Code or cmd line. Once it is in a .exe by pyinstaller, only the first code works and the program closes once it tries to execute a separate .py file.
Details:
All of the separate python files are saved in the same directory. The first one to open, 'Main.py', has a tkinter interface that allows the user to select which .py script they want to run. The code then closes the Main window and opens the selected python script using exec(open('chosen .py').read()). (This is a simplified version of the initial code but I am having the same issues)
import tkinter as tk
from tkinter import ttk
from tkinter.constants import W
from tkinter import messagebox as mb
""" Open a window to select which separate script to run"""
root = tk.Tk()
root.title('Selection Window')
root.geometry('300x200')
frame_1 = tk.LabelFrame(root, text='Choose Program')
frame_1.pack()
# Using this function to update on radio button select
def radio_button_get():
global program_int
choice = radio_ID.get()
if(choice == 1):
program_int = 1
elif(choice == 2):
program_int = 2
# Display confirmation popup
def run_script():
if(program_int == 1):
select = mb.askokcancel("Confirm", "Run choice 1?")
if(select == 1):
root.destroy()
else:
return
if(program_int == 2):
select = mb.askokcancel("Confirm", "No selection")
if(select == 1):
root.destroy()
else:
return
# Create radio buttons to select program
radio_ID = tk.IntVar()
radio_ID.set(2)
program_int = 2 # Set default selection
choice_1 = tk.Radiobutton(frame_1, text='Execute Script 1', variable=radio_ID, value=1, command=radio_button_get)
choice_1.pack()
no_choice = tk.Radiobutton(frame_1, text='No Selection', variable=radio_ID, value=2, command=radio_button_get)
no_choice.pack()
# Button to run the selected code
run_button = ttk.Button(root, text='Run', command=run_script)
run_button.pack()
root.mainloop()
# Execute the other python script
if(program_int == 1):
exec(open('Script1.py').read())
The next code is the 'Script1.py' file which 'Main.py' runs at the end. This is the step which works fine in VS Code and cmd line, but causes the .exe from pyinstaller to close.
import tkinter as tk
from tkinter import ttk
""" Create this programs GUI window"""
root = tk.Tk()
root.title('Script 1')
def run():
root.destroy()
label = ttk.Label(root, text='Close to run')
label.pack()
button = ttk.Button(root, text='Close', command=run)
button.pack()
root.mainloop()
""" Do some code stuff here"""
# When above code is done, want to return to the Main.py window
exec(open('Main.py').read())
Each independent .py file have been successfully turned into .exe files with pyinstaller previously. The cmd line command that I am using to execute pyinstaller is pyinstaller 'Main.py' This successfully creates a Main.exe in the dist folder and also includes a build folder.
I have read through pyinstallers documentation, but have not found anything that I believe would be useful in this case. The nearest issue I could find was importing python scripts as modules in the .spec file options but since the code executes the python script as a separate entity, I don't think this is the fix.
Would the issue be in how the scripts are coded and referencing each other, or with the installation process with pyinstaller? If I missed something in the documentation that would explain this issue, please let me know and I will look there!
Any help is greatly appreciated, thank you
We must avoid using the .exec command. It is hacky but unsafe. Ref: Running a Python script from another
Instead use import :
# Execute the other python script
if(program_int == 1):
import Script1
And here too:
# When above code is done, want to return to the Main.py window
import Main
That's it, now use pyinstaller.
EDIT:
Why .exe file fails to execute another script, and why exec() is the problem:
According to the documentation:
Pyinstaller analyzes your code to discover every other module and
library your script needs in order to execute. Then it collects copies
of all those files – including the active Python interpreter! – and
puts them with your script in a single folder, or optionally in a
single executable file.
So, when pyinstaller is analyzing & creating the .exe file, it only executes the exec() function that time (so no error thrown while pyinstaller runs), pyinstaller does not import it or copies it to your .exe. file, and then after the .exe file is created, upon running it throws error that no such script file exists because it was never compiled into that .exe file.
Thus, using import will actually import the script as module, when pyinstaller is executed, and now your .exe file will give no error.
Instead of importing the scripts as modules, for them to be re-executed again and again, import another script as a function in Main.py
Also, instead of destroying your Main root window (since you won't be able to open it again unless you create a new window), use .withdraw() to hide it and then .deiconify() to show.
First, in Script1.py:
import tkinter as tk
from tkinter import ttk
""" Create this programs GUI window"""
def script1Function(root): #the main root window is recieved as parameter, since this function is not inside the scope of Main.py's root
root2 = tk.Tk() #change the name to root2 to remove any ambiguity
root2.title('Script 1')
def run():
root2.destroy() #destroy this root2 window
root.deiconify() #show the hidden Main root window
label = ttk.Label(root2, text='Close to run')
label.pack()
button = ttk.Button(root2, text='Close', command=run)
button.pack()
root2.mainloop()
Then, in Main.py:
import tkinter as tk
from tkinter import ttk
from tkinter.constants import W
from tkinter import messagebox as mb
from Script1 import script1Function #importing Script1's function
# Execute the other python script
def openScript1():
root.withdraw() #hide this root window
script1Function(root) #pass root window as parameter, so that Script1 can show root again
""" Open a window to select which separate script to run"""
root = tk.Tk()
root.title('Selection Window')
root.geometry('300x200')
frame_1 = tk.LabelFrame(root, text='Choose Program')
frame_1.pack()
# Using this function to update on radio button select
def radio_button_get():
global program_int
choice = radio_ID.get()
if(choice == 1):
program_int = 1
elif(choice == 2):
program_int = 2
# Display confirmation popup
def run_script():
global program_int #you forgot to make it global
if(program_int == 1):
select = mb.askokcancel("Confirm", "Run choice 1?")
if(select == 1):
openScript1()
else:
return
if(program_int == 2):
select = mb.askokcancel("Confirm", "No selection")
if(select == 1):
root.destroy()
else:
return
# Create radio buttons to select program
radio_ID = tk.IntVar()
radio_ID.set(2)
program_int = 2 # Set default selection
choice_1 = tk.Radiobutton(frame_1, text='Execute Script 1', variable=radio_ID, value=1, command=radio_button_get)
choice_1.pack()
no_choice = tk.Radiobutton(frame_1, text='No Selection', variable=radio_ID, value=2, command=radio_button_get)
no_choice.pack()
# Button to run the selected code
run_button = ttk.Button(root, text='Run', command=run_script)
run_button.pack()
root.mainloop()

Why the icon doesn't display in System Tray although the python code is executing without any error?

I have just started learning python and trying to create a system tray icon. This program is executing without any error but isn't displaying any icon.
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
app = QApplication([])
app.setQuitOnLastWindowClosed(False)
# Adding an icon
icon = QIcon("fb.png")
# Adding item on the menu bar
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)
# Creating the options
menu = QMenu()
option1 = QAction("Option1")
option2 = QAction("Option2")
menu.addAction(option1)
menu.addAction(option2)
# To quit the app
quit = QAction("Quit")
quit.triggered.connect(app.quit)
menu.addAction(quit)
# Adding options to the System Tray
tray.setContextMenu(menu)
app.exec_()
This code displays following output in VSCode
[Running] python -u "e:\python\systray\systray.py"
When you handle external files as the icon then you must use absolute paths either explicit or build them, in your case I assume that the .png is next to the script so you should use:
import os
CURRENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
# ...
icon = QIcon(os.path.join(CURRENT_DIRECTORY, "fb.png"))
# ...
Maybe try tray.show() instead of tray.setVisible(True).

How to control desktop app windows with python

I'm trying to make a python bot for zoom.us, but to join a meeting, zoom tells us to download an application and to join the meeting from there. Is there any python module like selenium but to control desktop apps? I found PyAutoIt, but is there anything better?
Didn't understand entirely what you're planning to do, but maybe pyautogui can be helpful too
You have to download the zoom app for desktop and use the 'pyautogui' module to automate the GUI of zoom app. here is a script that I wrote to automate mine. edit the positions of the app according to your machine
import pyautogui
import time
pyautogui.FAILSAFE = False
class ZoomOpener():
def __init__(self, meeting_id, password):
self.meeting_id = meeting_id
self.password = password
def main(self):
# clicks on zoom logo in the task bar and opens it
time.sleep(1)
pyautogui.click(550, 800, duration=0.2)
# clicks on join button
time.sleep(2)
pyautogui.click(x=550, y=317, clicks=2, interval=0.2)
# types the meeting id
pyautogui.typewrite(self.meeting_id,interval=0.06)
# clicks the join button
time.sleep(2)
pyautogui.click(x=690, y=487)
# types the password
time.sleep(5)
pyautogui.click(x=550, y=317, clicks=2)
pyautogui.typewrite(self.password, interval=0.06)
# clicks the join button
time.sleep(1)
pyautogui.click(x=690, y=487, clicks=1)
# final joining
time.sleep(5)
pyautogui.click(x=900, y=590, clicks=2, interval=2)
if __name__ == '__main__':
time.sleep(10)
zoom = ZoomOpener('9656400024', '123456')
zoom.main()

MacOS Pyside2 QSystemTrayIcon, left click/right different functions

I would like to know how to do in python/Pyside2:
Create a QSystemTrayIcon with a custom icon, in which:
If I click left button on it, I do a custom action (just print “left click pressed”). No menu should be shown...
If I click right button on it, a context menu appears with an exit action on it, just to close the program.
On MacOS, maybe not in win nor linux, the menu just opens on mouse press... That's why the need of left and right click differentiations
otherwise both actions will be done with left and right click. See note here: On macOS... since the menu opens on mouse press
I need help just implementing the left and right click differentiations in the following code:
from PySide2 import QtWidgets
import sys
class SystrayLauncher(object):
def __init__(self):
w = QtWidgets.QWidget() #just to get the style(), haven't seen other way
icon = w.style().standardIcon(QtWidgets.QStyle.SP_MessageBoxInformation)
self.tray = QtWidgets.QSystemTrayIcon()
self.tray.setIcon(icon)
self.tray.setVisible(True)
self.tray.activated.connect(self.customAction)
# I JUST WANT TO SEE THE MENU WHEN RIGHT CLICK...
self.trayIconMenu = QtWidgets.QMenu()
self.quitAction = QtWidgets.QAction("&Quit", None, triggered=QtWidgets.QApplication.instance().quit)
self.trayIconMenu.addAction(self.quitAction)
self.tray.setContextMenu(self.trayIconMenu)
# JUST WANNA USE THE ACTION WITH LEFT CLICK
def customAction(self, signal):
print "left click pressed"
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
sl = SystrayLauncher()
sys.exit(app.exec_())
Can anyone help me, please?
you can differentiate the click "reason" and decide what to do. Therefore you will need to add a function like follows:
def right_or_left_click(reason):
if reason == QSystemTrayIcon.ActivationReason.Trigger:
print("Left-click detected")
elif reason == QSystemTrayIcon.ActivationReason.Context:
print("Right-click detected")
elif reason == QSystemTrayIcon.ActivationReason.MiddleClick:
print("Middle-click detected")
else:
print("Unknown reason")
self.tray.activated.connect(right_or_left_click)
Then, you can call the desired function on left-click or middle-click. The right-click is occupied by your context menu :)

Disable keyboard but keep getting events

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.

Categories