How run a python console script from a hiding console python script? - python

I'm not very experienced in python and I start building an app with Tkinter.
Like it's a window project I open it with pythonw.exe but it call a console script and I can't display the console of the second script if the first is hidden ...
There is the piece of code which call the second script :
from selenium_script import main
self.btn_downloadAnime = tk.Button(self.frm_addAnime, text='Lunch download script with voiranime.com links',
bg=self.colorBTN, font=22, activebackground=self.colorBG, height=2, width=50,
command=main)

Is this what you are looking for:
main.py:
import tkinter as tk
import subprocess
import sys
COMMAND = "start python selenium_script.py"
def start_new_proc():
proc = subprocess.Popen(COMMAND, close_fds=False, shell=True)
root = tk.Tk()
button = tk.Button(root, command=start_new_proc, text="Start new process")
button.pack()
root.mainloop()
selenium_script.py:
user_input = input(">>> ")
print("You typed this: " + str(user_input))
input("Press enter to exit.")
I start a new process using subprocess.Popen. The new process starts with its own shell (because of the "start" in the COMMAND variable)

Yes, I was missing the notion of process ...
The previous solution works and I found one too :
def open_selenium(self):
subprocess.Popen(args=['python', 'selenium2.py'], stdout=sys.stdout)

Related

Python Ttkinter project not working when converted to .exe

i have created a small mining bot for minecraft and for user friendliness (for my friends) i used Tkinter for the app. When i run my app in python it works great, just as expected, but when i turn it into an .exe file (using PyInstaller) it suddenly doesn't work anymore. It just opens a random console and hides the console after a few seconds. To convert my file to .exe i used the following command in the command prompt: "python -m PyInstaller test.py" or "python -m PyInstaller test.py --onefile". Does anybody have any idea whats going on? For anyone interested, here is the code. Any help is appreciated!
import keyboard
import pyautogui
from time import sleep
import tkinter as tk
delay = 5
# program
def INIT():
window = tk.Tk()
window.title("Minecraft Mining Bot")
Stop_Key_Button = tk.StringVar()
Stop_Key_Button.set("i")
Greeting_Label = tk.Label(
text="Hello, user! To start using this program, please make sure to enter your stop key on your keyboard. Goodluck!",
foreground="white",
background="black"
)
Greeting_Label.pack()
Set_Key_Label = tk.Label(
text="Your current stop key is:",
foreground="white",
background="black"
)
Set_Key_Label.pack()
Display_Key = tk.Label(
textvariable=Stop_Key_Button,
foreground="white",
background="black"
)
Display_Key.pack()
Set_Key_Input = tk.Button(
text="Click on this to enter your new key",
foreground="white",
background="black",
command = lambda: SET_KEYBIND(Stop_Key_Button)
)
Set_Key_Input.pack()
Start_Button_Label = tk.Label(
text="To start the bot, press the button below. You then have 5 seconds to open minecraft and it will start! To stop the bot please press your button you put in.",
foreground="white",
background="black"
)
Start_Button_Label.pack()
Start_Button = tk.Button(
text="Click on this to start the bot with a 5 second delay!",
foreground="white",
background="black",
command = lambda: START_ROBOT(delay, Stop_Key_Button)
)
Start_Button.pack()
window.mainloop()
def SET_KEYBIND(Stop_Key_Button):
while (1):
key = keyboard.read_key()
if (key != None):
Stop_Key_Button.set(key)
print(Stop_Key_Button.get())
return
def START_ROBOT(delay, Stop_Key_Button):
sleep(delay)
keyboard.press("ctrl")
keyboard.press("w")
pyautogui.mouseDown()
while(1):
if(keyboard.is_pressed(Stop_Key_Button.get())):
keyboard.release("w")
keyboard.release("ctrl")
pyautogui.mouseUp()
break
sleep(0.1)
if __name__ == "__main__":
INIT()
use this command to convert it to exe with gui support and without console
pyinstaller.exe --onefile --windowed --icon=icon.ico app.py
if you want tkinter default icon, remove --icon=icon.ico
Aparently it was actually my fault, in short: i used the wrong python script (i was working with multiple but i only wanted to convert 1 file). I accidently kept converting a file with only 5 lines of code and not the file i actually wanted to convert. Thanks for the help because else i didint know all those arguments!
So in the end i only used python -m PyInstaller --onefile --windowed --icon=icon.ico main.py

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()

How to print messages in tkinter from subprocess?

everyone! I've run into a problem. Let's say I have a script that is launched using subprocess module from tkinter giu like this (params includes the name of the script):
p = Popen(['python.exe'] + params)
During being executed, my script has some messages that I want to be printed in my gui. While I was using console, I just did it with print function like this:
print(f'Connected to {ppm_file}')
What I now need to do, is to make this message be printed in a text widget in tkinter gui.
I suppose it implys using stdout, but I'm a newbie to this concept and find it a little bit hard to understand.
Thanks for your help!
You can capture the console output of the external script and insert the captured output into a Text widget.
Below is an example:
import tkinter as tk
import subprocess
import threading
root = tk.Tk()
logbox = tk.Text(root, width=80, height=20)
logbox.pack()
def capture_output():
# "python -u" make stdout stream to be unbuffered
p = subprocess.Popen(["python", "-u", "other.py"], stdout=subprocess.PIPE)
while p.poll() is None: # process is still running
logbox.insert("end", p.stdout.readline())
logbox.see("end")
logbox.insert("end", "--- done ---\n")
# use thread so that it won't block the application
tk.Button(root, text="Run", command=lambda: threading.Thread(target=capture_output, daemon=True).start()).pack()
root.mainloop()
In the text widget, you can insert by using following method:
txtbox.delete('1.0', END) # to remove any preceding text(ignore if empty)
txtbox.insert(END, 'Connected to {ppm_file}') # insert the text you want
txtbox.insert(END, '\n') # additional insertion for example a newline character

Is there way to solve authentication like the known programs?

I have a program in Python. I would like when the program starts for it to ask me to enter root password, in a GUI dialog. I run the program then it shows the below error:
couldn't connect to display ": 0.0"
If I delete all lines with pkexec stuff inside upgrade.py then it works perfectly. Also if I run in gnome-terminal with the command sudo python3 /home/user/Python/upgrade.py then it works too. Maybe it's a problem with sudo? Exactly the same happens with GTK for Python.
Python code:
from tkinter import *
#!/usr/bin/python3
import os
import subprocess
import sys
euid = os.geteuid()
if euid != 0:
print ("Script not started as root. Running sudo..")
args = ['pkexec', sys.executable] + sys.argv + [os.environ]
# the next line replaces the currently-running process with the sudo
os.execlpe('pkexec', *args)
print ('Running. Your euid is', euid)
root = Tk()
root.title('Update system')
#root.geometry("290x100")
def button_add1():
callProcess = subprocess.Popen(['ls', '-la'], shell=True)
subprocess.run(['pkexec', 'ls', '-la'], check=True)
def button_add4():
root.destroy()
# Define Buttons
button_1 = Button(root, text="Upgrade system", padx=40, pady=20, command=button_add1)
button_4 = Button(root, text="Quit", padx=40, pady=20, command=button_add4)
# Put the buttons on the screen
button_1.grid(row=0, column=0)
button_4.grid(row=0, column=4)
root.mainloop()
Please I don't want like something this... Simplest method of asking user for password using graphical dialog in Python?
I would like authentication like programs such as Synaptic, Gparted, Bleachbit
I have already asked on Stackoverflow but they said, It is related on system policy configuration and nothing related to Python or tkinter. This question is now migrated back to Stack Overflow!
I tried my code on Debian and Linux Mint.
You mustn't try to run the GUI as root. You should isolate the smallest possible component and only run that as privileged. See https://wiki.archlinux.org/index.php/Running_GUI_applications_as_root and the note in the pkexec manual page which specifically explains that it will not allow you to take over the invoking user's $DISPLAY.
Here's a rough sketch, but it's untested right now.
#!/usr/bin/python3
# ^ this needs to be the absolutely first line of the file
from tkinter import * # FIXME: probably avoid import *
# import os # no longer used
import subprocess
# import sys # no longer used
print ('Running. Your euid is whatever it is.')
root = Tk()
root.title('Update system')
#root.geometry("290x100")
def button_add1():
subprocess.run(['ls', '-la'], check=True)
subprocess.run(['pkexec', 'ls', '-la'], check=True)
def button_add4():
root.destroy()
# Define Buttons
button_1 = Button(root, text="Upgrade system", padx=40, pady=20, command=button_add1)
button_4 = Button(root, text="Quit", padx=40, pady=20, command=button_add4)
# Put the buttons on the screen
button_1.grid(row=0, column=0)
button_4.grid(row=0, column=4)
root.mainloop()
If (as in your code before your edit) you want to run multiple commands behind one pkexec, you can do that with something like
subprocess.run(
['pkexec', 'sh', '-c', 'apt-get update && apt-get upgrade -y'],
check=True)
As an aside, if your example code is representative, you can replace from tkinter import * with from tkinter import Tk to avoid the import * antipattern.

Writing a Python UI for a seperate program with tkinter. The stop button for this program basically freezes the UI and continues with the script

Here is what I coded...
import tkinter as tk
import subprocess
import sys
import time
import os
import tkinter.font as font
from tkinter.ttk import *
app = tk.Tk()
app.geometry("400x400")
app.configure(bg='gray')
photo = tk.PhotoImage(file=r"C:\Users\ex\ex_button_active.png")
myFont = font.Font(family='Helvetica', size=20, weight='normal')
tk.Label(app, text='EX', bg='gray', font=(
'Verdana', 15)).pack(side=tk.TOP, pady=10)
app.iconbitmap(r'C:\Users\ex\ex_icon.ico')
start = time.time()
cmd = sys.executable + " -c 'import time; time.sleep(2)' &"
subprocess.check_call(cmd, shell=True)
assert (time.time() - start) < 1
p = subprocess.Popen(cmd, shell=True)
def ex_activation():
#Python Code
#Python Code...
def ex_stop():
sys.exit(ex_activation) #This area is basically where I have a button to terminate the other script running.
#I have tried sys.exit() and had the same result
ex_activation_button = tk.Button(app,
bg='black',
image=photo,
width=120,
height=120,
command=ex_activation)
ex_stop_button = tk.Button(app,
bg='Gray',
text='ex',
width=12,
command=ex_stop
height=3)
ex_stop_button['font'] = myFont
app.title("Example")
ex_activation_button.pack(side=tk.TOP)
ex_stop_button.pack(side=tk.LEFT)
app.mainloop()
I am looking for a way to get my program to stop the program the other button runs. I realized that this maybe be a "self destruct button" but I don't know how to do this with the script the other button runs. Any help greatly appreciated! I tried killing the code by putting the def ex_activation in the p.kill
This did not work...
If the other python script is made to run forever (has some kind of while True:), you can't run it on the command line as you did, because it will freeze your window while that script is running.
In order to run a python script on background you will need to do it with the subprocess library. (Find out here)
I also found an answer of another question that uses check_ouput() in order to know when the python program has finished. This can also be useful if you want to send a status to the tkinter app: you can print("33% Complete"), for example. You could add this in tkinter's main loop, so you always know if your program is running or not.
And last but not least, to kill that process (using the stop button), you should do it using os, and looking for the subprocess' ID. Here you can also find a good example.
I would try something like this:
cmd = "exec python file.py"
p = subprocess.Popen(cmd, shell=True)
# Continue running tkinter tasks.
tk.update()
tk.update_idletasks() # These both lines should be inside a while True
# Stop secondary program
p.kill()
EDIT
Example code using your question's code. WARNING: I have changed the png file location for testing, commented the app icon, and tested ONLY on Windows.
It's important to remove the mainloop() on the main file and put update...() in order to catch the keyboardInterrupt that (I don't know why) is killing both parent and child process.
I invite you to try it and be as happy as I have been when it was working after half an hour of testing!!
File 1: daemon.py - this file will run forever.
from time import sleep
from sys import exit
while True:
try:
print("hello")
sleep(1)
except KeyboardInterrupt:
print("bye")
exit()
File 2: tkinterapp.py - The name is self-explainatory
import tkinter as tk
import subprocess
import sys
import time
import os
import tkinter.font as font
from tkinter.ttk import *
app = tk.Tk()
app.geometry("400x400")
app.configure(bg='gray')
photo = tk.PhotoImage(file=r"C:\Users\royal\github\RandomSketches\baixa.png")
myFont = font.Font(family='Helvetica', size=20, weight='normal')
tk.Label(app, text='EX', bg='gray', font=(
'Verdana', 15)).pack(side=tk.TOP, pady=10)
# app.iconbitmap(r'C:\Users\ex\ex_icon.ico')
def ex_activation():
global pro
print("running!")
pro = subprocess.Popen("python daemon.py", shell=True)
def ex_stop():
global pro
print("stopping!")
os.kill(pro.pid, 0)
ex_activation_button = tk.Button(app,
bg='black',
image=photo,
width=120,
height=120,
command=ex_activation)
ex_stop_button = tk.Button(app,
bg='Gray',
text='ex',
width=12,
command=ex_stop, # BE CAREFUL You were missing a "," here !!!
height=3)
ex_stop_button['font'] = myFont
app.title("Example")
ex_activation_button.pack(side=tk.TOP)
ex_stop_button.pack(side=tk.LEFT)
# app.mainloop()
while True:
try:
app.update()
app.update_idletasks()
except KeyboardInterrupt:
pass

Categories