I'm writing an appindicator for Grive, a daemon for syncing Google Drive files. Since I have little programming experience, I decided to write a Python script that calls Grive as a subprocess instead of integrating it in its C++ source code.
I've adapted Stefaan Lippens' code for asynchronously reading subprocess pipes to both show a notification and change the indicator's icon when something important happens (e.g. a new file is added, or a network error). Notifications work well; however, the indicator's icon changes only when the whole process has finished, which is useless because I need to change it many times after it finishes.
Here is the code I'm using:
async.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import subprocess
import time
import threading
import Queue
class AsynchronousFileReader(threading.Thread):
'''
Helper class to implement asynchronous reading of a file
in a separate thread. Pushes read lines on a queue to
be consumed in another thread.
'''
def __init__(self, fd, queue):
assert isinstance(queue, Queue.Queue)
assert callable(fd.readline)
threading.Thread.__init__(self)
self._fd = fd
self._queue = queue
def run(self):
'''The body of the tread: read lines and put them on the queue.'''
for line in iter(self._fd.readline, ''):
self._queue.put(line)
def eof(self):
'''Check whether there is no more content to expect.'''
return not self.is_alive() and self._queue.empty()
def run(command, show):
'''
Main function to consume the output of a command.
command = The command to be run
show = Function that will process output
'''
# Launch the command as subprocess.
process = subprocess.Popen(command, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
# Launch the asynchronous readers of the process' stdout and stderr.
stdout_queue = Queue.Queue()
stdout_reader = AsynchronousFileReader(process.stdout, stdout_queue)
stdout_reader.start()
stderr_queue = Queue.Queue()
stderr_reader = AsynchronousFileReader(process.stderr, stderr_queue)
stderr_reader.start()
# Check the queues if we received some output (until there is nothing more to get).
while not stdout_reader.eof() or not stderr_reader.eof():
# Show what we received from standard output.
while not stdout_queue.empty():
line = stdout_queue.get()
show(line)
# Show what we received from standard error.
while not stderr_queue.empty():
line = stderr_queue.get()
show(line)
# Sleep a bit before asking the readers again.
time.sleep(.1)
# Let's be tidy and join the threads we've started.
stdout_reader.join()
stderr_reader.join()
# Close subprocess' file descriptors.
process.stdout.close()
process.stderr.close()
return True
grive.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import subprocess
import time
import async
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import GObject
from gi.repository import Notify
from gi.repository import AppIndicator3 as AppIndicator
GRIVE_FOLDER = "/home/ilhuitemoc/Publike/Google Drive"
def show(line):
line = line.replace("\n", "")
print line
if line.startswith("[gr::expt::MsgTag*] = "):
line = line.replace("[gr::expt::MsgTag*] = ","",1)
n = Notify.Notification.new("Error", line, icon)
n.show()
indicator.set_icon("ubuntuone-client-offline")
if line.startswith("sync "):
line = line.replace("sync ","",1)
line = line.replace('"','<b>',1)
line = line.replace('"','</b>',1)
n = Notify.Notification.new("Sync in progress", line, icon)
n.show()
indicator.set_icon("ubuntuone-client-updating")
if "Finished!" in line:
#n = Notify.Notification.new(line, line, icon)
#n.show()
indicator.set_icon("ubuntuone-client-idle")
def openinfolder(obj):
subprocess.call(["xdg-open",GRIVE_FOLDER])
def openinbrowser(obj):
subprocess.call(["xdg-open","http://drive.google.com/"])
if __name__ == '__main__':
subprocess.call(["killall","pantheon-notify"])
time.sleep(1)
indicator = AppIndicator.Indicator.new('grive', 'ubuntuone-client-offline', AppIndicator.IndicatorCategory.APPLICATION_STATUS)
indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE)
menu = Gtk.Menu()
status = Gtk.MenuItem("Connecting...") #Not finished yet
status.set_sensitive(False)
menu.append(status)
sp = Gtk.SeparatorMenuItem()
menu.append(sp)
mi = Gtk.MenuItem("Open Google Drive folder")
mi.connect('activate',openinfolder)
menu.append(mi)
mi = Gtk.MenuItem("Go to Google Drive webpage")
mi.connect('activate',openinbrowser)
menu.append(mi)
sp = Gtk.SeparatorMenuItem()
menu.append(sp)
mi = Gtk.ImageMenuItem("Quit")
img = Gtk.Image.new_from_stock(Gtk.STOCK_QUIT, Gtk.IconSize.MENU)
mi.set_image(img)
mi.connect('activate',Gtk.main_quit)
menu.append(mi)
menu.show_all()
indicator.set_menu(menu)
Notify.init('grive')
icon = 'google-drive'
#async.run('cd "%s" && grive' % GRIVE_FOLDER, show)
GObject.timeout_add(5*60000, async.run, 'cd "%s" && grive' % GRIVE_FOLDER, show)
#GObject.timeout_add(5000, async.run, "test.sh", show)
Gtk.main()
I think I'm doing something wrong, but it's not obvious to me. Is it right to modify the indicator using a subprocess? Or is any other way I can correctly do this?
Related
I'm pretty new to python and tkinter so I'm struggling with creating a script that reads the terminal output into a label or Gui in tkinter. I've searched around and can't find any tutorials on how to do it, a lot of forums have specific or old code which makes it really difficult to adapt especially for a beginner. The code that I found that looks like it will work best for what I'm trying to accomplish was made by jfs, the only problem is that I keep getting errors which for the life of me I cant figure out.
Here is the code:
import logging
import os
import sys
from subprocess import Popen, PIPE, STDOUT
try:
import tkinter as tk
except ImportError: # Python 3
import tkinter as tk
info = logging.getLogger(__name__).info
# define dummy subprocess to generate some output
cmd = [sys.executable or "python", "-u", "-c", """
import itertools, time
for i in itertools.count():
print(i)
time.sleep(0.5)
"""]
class ShowProcessOutputDemo:
def __init__(self, root):
"""Start subprocess, make GUI widgets."""
self.root = root
# start subprocess
self.proc = Popen(cmd, stdout=PIPE, stderr=STDOUT)
# show subprocess' stdout in GUI
self.root.createfilehandler(
self.proc.stdout, tk.READABLE, self.read_output)
self._var = tk.StringVar() # put subprocess output here
tk.Label(root, textvariable=self._var).pack()
# stop subprocess using a button
tk.Button(root, text="Stop subprocess", command=self.stop).pack()
def read_output(self, pipe, mask):
"""Read subprocess' output, pass it to the GUI."""
data = os.read(pipe.fileno(), 1 << 20)
if not data: # clean up
info("eof")
self.root.deletefilehandler(self.proc.stdout)
self.root.after(5000, self.stop) # stop in 5 seconds
return
info("got: %r", data)
self._var.set(data.strip(b'\n').decode())
def stop(self, stopping=[]):
"""Stop subprocess and quit GUI."""
if stopping:
return # avoid killing subprocess more than once
stopping.append(True)
info('stopping')
self.proc.terminate() # tell the subprocess to exit
# kill subprocess if it hasn't exited after a countdown
def kill_after(countdown):
if self.proc.poll() is None: # subprocess hasn't exited yet
countdown -= 1
if countdown < 0: # do kill
info('killing')
self.proc.kill() # more likely to kill on *nix
else:
self.root.after(1000, kill_after, countdown)
return # continue countdown in a second
self.proc.stdout.close() # close fd
self.proc.wait() # wait for the subprocess' exit
self.root.destroy() # exit GUI
kill_after(countdown=5)
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
root = tk.Tk()
app = ShowProcessOutputDemo(root)
root.protocol("WM_DELETE_WINDOW", app.stop) # exit subprocess if GUI is closed
root.mainloop()
info('exited')
Due to my lack of experience, i don't know how to fix the errors that keep being thrown. Here is a terminal output of what keeps occurring.
Traceback (most recent call last):
File "d:\coding\OtherProjects\testserver\tkinter-read-async-subprocess-output.py", line 83, in <module>
app = ShowProcessOutputDemo(root)
File "d:\coding\OtherProjects\testserver\tkinter-read-async-subprocess-output.py", line 37, in __init__
self.root.createfilehandler(
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.9_3.9.1520.0_x64__qbz5n2kfra8p0\lib\tkinter\__init__.py", line 2354, in __getattr__
return getattr(self.tk, attr)
AttributeError: '_tkinter.tkapp' object has no attribute 'createfilehandler'
Thanks to everyone that took the time to read this I really appreciate it.
Also sorry if i didn't put this in the right forum I'm still trying to understand this website and am dedicated to improve.
Thank You - Connor
Try this:
import logging
import os
import sys
from subprocess import Popen, PIPE, STDOUT
from threading import Thread
try:
import tkinter as tk
except ImportError: # Python 3
import tkinter as tk
info = logging.getLogger(__name__).info
# define dummy subprocess to generate some output
cmd = [sys.executable or "python", "-u", "-c", """
import itertools, time
for i in itertools.count():
print(i)
time.sleep(0.5)
"""]
class ShowProcessOutputDemo:
def __init__(self, root):
"""Start subprocess, make GUI widgets."""
self.root = root
# start subprocess
self.proc = Popen(cmd, stdout=PIPE, stderr=STDOUT)
# stop subprocess using a button
tk.Button(root, text="Stop subprocess", command=self.stop).pack()
self.label = tk.Label(root) # put subprocess output here
self.label.pack()
# Create a buffer for the stdout
self.stdout_data = ""
# Create a new thread that will read stdout and write the data to
# `self.stdout_buffer`
thread = Thread(target=self.read_output, args=(self.proc.stdout, ))
thread.start()
# A tkinter loop that will show `self.stdout_data` on the screen
self.show_stdout()
def read_output(self, pipe):
"""Read subprocess' output and store it in `self.stdout_data`."""
while True:
data = os.read(pipe.fileno(), 1 << 20)
# Windows uses: "\r\n" instead of "\n" for new lines.
data = data.replace(b"\r\n", b"\n")
if data:
info("got: %r", data)
self.stdout_data += data.decode()
else: # clean up
info("eof")
self.root.after(5000, self.stop) # stop in 5 seconds
return None
def show_stdout(self):
"""Read `self.stdout_data` and put the data in the GUI."""
self.label.config(text=self.stdout_data.strip("\n"))
self.root.after(100, self.show_stdout)
def stop(self, stopping=[]):
"""Stop subprocess and quit GUI."""
if stopping:
return # avoid killing subprocess more than once
stopping.append(True)
info("stopping")
self.proc.terminate() # tell the subprocess to exit
# kill subprocess if it hasn't exited after a countdown
def kill_after(countdown):
if self.proc.poll() is None: # subprocess hasn't exited yet
countdown -= 1
if countdown < 0: # do kill
info("killing")
self.proc.kill() # more likely to kill on *nix
else:
self.root.after(1000, kill_after, countdown)
return # continue countdown in a second
self.proc.stdout.close() # close fd
self.proc.wait() # wait for the subprocess' exit
self.root.destroy() # exit GUI
kill_after(countdown=5)
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
root = tk.Tk()
app = ShowProcessOutputDemo(root)
root.protocol("WM_DELETE_WINDOW", app.stop) # exit subprocess if GUI is closed
root.mainloop()
info("exited")
This code starts a new thread that reads from self.proc.stdout and writes the data into self.stdout_data in a while True loop. There is also a tkinter loop that takes the data out of the self.stdout_data and puts it in the Label widget.
I didn't directly set the Label's text from the thread because sometimes tkinter can crash if you call it from a different thread.
Another thing: I removed the StringVar because I can just use: <tkinter.Label>.config(text=<new text>) instead.
I have the problem too, this was my solution.
I post it on [https://github.com/ianfun/notes/blob/main/editor/main.py].
data.py was generate by build.py.
I cannot found grate terminal like vscode in python.
note:use key right to select
you can read idlelib run.py
I have a script that is part of an automated test suite. It runs very slowly on Windows but not on Linux and I have found out why. The process that we are testing ('frank') creates a child process (so a grandchild). The python code won't return until that grandchild process also ends (on Windows - doesn't do this on Linux). The grandchild process will kill itself off after 5 seconds if there is no parent (it hangs around in case another process talks to it)
I've found I can stop the communicate function from hanging in this way if I don't capture stdout. But I need stdout. I read somewhere that the communicate function is waiting for all pipes to be closed. I know that the stdout handle is duplicated for the grandchild but I can't change the code I'm testing.
I've been searching for a solution. I tried some creation flags (still in the code) but that didn't help.
This is the cut down test -
import os
import sys
import threading
import subprocess
def read_from_pipe(process):
last_stdout = process.communicate()[0]
print (last_stdout)
CREATE_NEW_PROCESS_GROUP = 0x00000200
DETACHED_PROCESS = 0x00000008
# start process
command = 'frank my arguments'
cwd = "C:\\dev\\ui_test\\frank_test\\workspace\\report183"
p = subprocess.Popen(command,
stdout=subprocess.PIPE,
cwd=cwd)
# run thread to read from output
t = threading.Thread(target=read_from_pipe, args=[p])
t.start()
t.join(30)
print('finished')
Any ideas?
Thanks.
Peter.
After tips from #eryksun and a lot of Googling, I have this rather complicated lot of code! At one point, I considered cheating and doing os.system and redirecting to a temp file but then I realised that our test code allows for a command timing out. os.system would just block forever if the child process doesn't die.
import os
import sys
import threading
import subprocess
import time
if os.name == 'nt':
import msvcrt
import ctypes
# See https://stackoverflow.com/questions/55160319/python-subprocess-waiting-for-grandchild-on-windows-with-stdout-set for details on Windows code
# Based on https://github.com/it2school/Projects/blob/master/2017/Python/party4kids-2/CarGame/src/pygame/tests/test_utils/async_sub.py
from ctypes.wintypes import DWORD
if sys.version_info >= (3,):
null_byte = '\x00'.encode('ascii')
else:
null_byte = '\x00'
def ReadFile(handle, desired_bytes, ol = None):
c_read = DWORD()
buffer = ctypes.create_string_buffer(desired_bytes+1)
success = ctypes.windll.kernel32.ReadFile(handle, buffer, desired_bytes, ctypes.byref(c_read), ol)
buffer[c_read.value] = null_byte
return ctypes.windll.kernel32.GetLastError(), buffer.value
def PeekNamedPipe(handle):
c_avail = DWORD()
c_message = DWORD()
success = ctypes.windll.kernel32.PeekNamedPipe(handle, None, 0, None, ctypes.byref(c_avail), ctypes.byref(c_message))
return "", c_avail.value, c_message.value
def read_available(handle):
buffer, bytesToRead, result = PeekNamedPipe(handle)
if bytesToRead:
hr, data = ReadFile(handle, bytesToRead, None)
return data
return b''
def read_from_pipe(process):
if os.name == 'posix':
last_stdout = process.communicate()[0]
else:
handle = msvcrt.get_osfhandle(process.stdout.fileno())
last_stdout = b''
while process.poll() is None:
last_stdout += read_available(handle)
time.sleep(0.1)
last_stdout += read_available(handle)
print (last_stdout)
# start process
command = 'frank my arguments'
cwd = "C:\\dev\\ui_test\\frank_test\\workspace\\report183"
p = subprocess.Popen(command,
stdout=subprocess.PIPE,
cwd=cwd)
# run thread to read from output
t = threading.Thread(target=read_from_pipe, args=[p])
t.start()
t.join(30)
print('finished')
Apart from the scripts own console (which does nothing) I want to open two consoles and print the variables con1 and con2 in different consoles, How can I achieve this.
con1 = 'This is Console1'
con2 = 'This is Console2'
I've no idea how to achieve this and spent several hours trying to do so with modules such as subprocess but with no luck. I'm on windows by the way.
Edit:
Would the threading module do the job? or is multiprocessing needed?
Eg:
If you don't want to reconsider your problem and use a GUI such as in #Kevin's answer then you could use subprocess module to start two new consoles concurrently and display two given strings in the opened windows:
#!/usr/bin/env python3
import sys
import time
from subprocess import Popen, PIPE, CREATE_NEW_CONSOLE
messages = 'This is Console1', 'This is Console2'
# open new consoles
processes = [Popen([sys.executable, "-c", """import sys
for line in sys.stdin: # poor man's `cat`
sys.stdout.write(line)
sys.stdout.flush()
"""],
stdin=PIPE, bufsize=1, universal_newlines=True,
# assume the parent script is started from a console itself e.g.,
# this code is _not_ run as a *.pyw file
creationflags=CREATE_NEW_CONSOLE)
for _ in range(len(messages))]
# display messages
for proc, msg in zip(processes, messages):
proc.stdin.write(msg + "\n")
proc.stdin.flush()
time.sleep(10) # keep the windows open for a while
# close windows
for proc in processes:
proc.communicate("bye\n")
Here's a simplified version that doesn't rely on CREATE_NEW_CONSOLE:
#!/usr/bin/env python
"""Show messages in two new console windows simultaneously."""
import sys
import platform
from subprocess import Popen
messages = 'This is Console1', 'This is Console2'
# define a command that starts new terminal
if platform.system() == "Windows":
new_window_command = "cmd.exe /c start".split()
else: #XXX this can be made more portable
new_window_command = "x-terminal-emulator -e".split()
# open new consoles, display messages
echo = [sys.executable, "-c",
"import sys; print(sys.argv[1]); input('Press Enter..')"]
processes = [Popen(new_window_command + echo + [msg]) for msg in messages]
# wait for the windows to be closed
for proc in processes:
proc.wait()
You can get something like two consoles using two Tkinter Text widgets.
from Tkinter import *
import threading
class FakeConsole(Frame):
def __init__(self, root, *args, **kargs):
Frame.__init__(self, root, *args, **kargs)
#white text on black background,
#for extra versimilitude
self.text = Text(self, bg="black", fg="white")
self.text.pack()
#list of things not yet printed
self.printQueue = []
#one thread will be adding to the print queue,
#and another will be iterating through it.
#better make sure one doesn't interfere with the other.
self.printQueueLock = threading.Lock()
self.after(5, self.on_idle)
#check for new messages every five milliseconds
def on_idle(self):
with self.printQueueLock:
for msg in self.printQueue:
self.text.insert(END, msg)
self.text.see(END)
self.printQueue = []
self.after(5, self.on_idle)
#print msg to the console
def show(self, msg, sep="\n"):
with self.printQueueLock:
self.printQueue.append(str(msg) + sep)
#warning! Calling this more than once per program is a bad idea.
#Tkinter throws a fit when two roots each have a mainloop in different threads.
def makeConsoles(amount):
root = Tk()
consoles = [FakeConsole(root) for n in range(amount)]
for c in consoles:
c.pack()
threading.Thread(target=root.mainloop).start()
return consoles
a,b = makeConsoles(2)
a.show("This is Console 1")
b.show("This is Console 2")
a.show("I've got a lovely bunch of cocounts")
a.show("Here they are standing in a row")
b.show("Lorem ipsum dolor sit amet")
b.show("consectetur adipisicing elit")
Result:
I don't know if it suits you, but you can open two Python interpreters using Windows start command:
from subprocess import Popen
p1 = Popen('start c:\python27\python.exe', shell=True)
p2 = Popen('start c:\python27\python.exe', shell=True)
Of course there is problem that now Python runs in interactive mode which is not what u want (you can also pass file as parameter and that file will be executed).
On Linux I would try to make named pipe, pass the name of the file to python.exe and write python commands to that file. 'Maybe' it will work ;)
But I don't have an idea how to create named pipe on Windows. Windows API ... (fill urself).
pymux
pymux gets close to what you want: https://github.com/jonathanslenders/pymux
Unfortunately it is mostly a CLI tool replacement for tmux and does not have a decent programmatic API yet.
But hacking it up to expose that API is likely the most robust option if you are serious about this.
The README says:
Parts of pymux could become a library, so that any prompt_toolkit application can embed a vt100 terminal. (Imagine a terminal emulator embedded in pyvim.)
If you are on windows you can use win32console module to open a second console for your thread or subprocess output. This is the most simple and easiest way that works if you are on windows.
Here is a sample code:
import win32console
import multiprocessing
def subprocess(queue):
win32console.FreeConsole() #Frees subprocess from using main console
win32console.AllocConsole() #Creates new console and all input and output of subprocess goes to this new console
while True:
print(queue.get())
#prints any output produced by main script passed to subprocess using queue
queue = multiprocessing.Queue()
multiprocessing.Process(Target=subprocess, args=[queue]).start()
while True:
print("Hello World")
#and whatever else you want to do in ur main process
You can also do this with threading. You have to use queue module if you want the queue functionality as threading module doesn't have queue
Here is the win32console module documentation
I used jfs' response. Here is my embellishment/theft of jfs response.
This is tailored to run on Win10 and also handles Unicode:
# https://stackoverflow.com/questions/19479504/how-can-i-open-two-consoles-from-a-single-script
import sys, time, os, locale
from subprocess import Popen, PIPE, CREATE_NEW_CONSOLE
class console(Popen) :
NumConsoles = 0
def __init__(self, color=None, title=None):
console.NumConsoles += 1
cmd = "import sys, os, locale"
cmd += "\nos.system(\'color " + color + "\')" if color is not None else ""
title = title if title is not None else "console #" + str(console.NumConsoles)
cmd += "\nos.system(\"title " + title + "\")"
# poor man's `cat`
cmd += """
print(sys.stdout.encoding, locale.getpreferredencoding() )
endcoding = locale.getpreferredencoding()
for line in sys.stdin:
sys.stdout.buffer.write(line.encode(endcoding))
sys.stdout.flush()
"""
cmd = sys.executable, "-c", cmd
# print(cmd, end="", flush=True)
super().__init__(cmd, stdin=PIPE, bufsize=1, universal_newlines=True, creationflags=CREATE_NEW_CONSOLE, encoding='utf-8')
def write(self, msg):
self.stdin.write(msg + "\n" )
if __name__ == "__main__":
myConsole = console(color="c0", title="test error console")
myConsole.write("Thank you jfs. Cool explanation")
NoTitle= console()
NoTitle.write("default color and title! This answer uses Windows 10")
NoTitle.write(u"♥♥♥♥♥♥♥♥")
NoTitle.write("♥")
time.sleep(5)
myConsole.terminate()
NoTitle.write("some more text. Run this at the python console.")
time.sleep(4)
NoTitle.terminate()
time.sleep(5)
Do you know about screen/tmux?
How about tmuxp? For example, you can try to run cat in split panes and use "sendkeys" to send output (but dig the docs, may be there is even easier ways to achieve this).
As a side bonus this will work in the text console or GUI.
Ok, so the situation is this: I am building a macOS GUI App using Python and wx (wxphoenix). The user can use the GUI (say: script1) to launch a file-deletion process (contained in script2). In order to run successfully script2 needs to run with sudo rights.
script2 will itterate over a long list of files and delete them. But I need it to communicate with the GUI contained in script1 after each round so that script1 can update the progressbar.
In it's absolute most basic form my current working setup looks like this:
Script1:
import io
from threading import Thread
import subprocess
import wx
# a whole lot of wx GUI stuff
def get_password():
"""Retrieve user password via a GUI"""
# A wx solution using wx.PasswordEntryDialog()
# Store password in a variable
return variable
class run_script_with_sudo(Thread):
"""Launch a script with administrator privileges"""
def __init__(self, path_to_script, wx_pubsub_sendmessage):
"""Set variables to self"""
self.path = path_to_script
self.sender = wx_pubsub_sendmessage
self.password = get_password()
Thread.__init__(self)
self.start()
def run(self):
"""Run thread"""
prepare_script = subprocess.Popen(["echo", password], stdout=subprocess.PIPE)
prepare_script.wait()
launch_script = subprocess.Popen(['sudo', '-S', '/usr/local/bin/python3.6', '-u', self.path], stdin=prepare_script.stdout, stdout=subprocess.PIPE)
for line in io.TextIOWrapper(launch_script.stdout, encoding="utf-8"):
print("Received line: ", line.rstrip())
# Tell progressbar to add another step:
wx.CallAfter(self.sender, "update", msg="")
Script2:
import time
# This is a test setup, just a very simple loop that produces an output.
for i in range(25):
time.sleep(1)
print(i)
The above setup works in that script1 receives the output of script2 in real-time and acts on it. (So in the given example: after each second script1 adds another step to the progress bar until it reaches 25 steps).
What I want to achieve = not storing the password in a variable and using macOS it's native GUI to retrieve the password.
However when I change:
prepare_script = subprocess.Popen(["echo", password], stdout=subprocess.PIPE)
prepare_script.wait()
launch_script = subprocess.Popen(['sudo', '-S', '/usr/local/bin/python3.6', '-u', self.path], stdin=prepare_script.stdout, stdout=subprocess.PIPE)
for line in io.TextIOWrapper(launch_script.stdout, encoding="utf-8"):
print("Received line: ", line.rstrip())
# Tell progressbar to add another step:
wx.CallAfter(self.sender, "update", msg="")
Into:
command = r"""/usr/bin/osascript -e 'do shell script "/usr/local/bin/python3.6 -u """ + self.path + """ with prompt "Sart Deletion Process " with administrator privileges'"""
command_list = shlex.split(command)
launch_script = subprocess.Popen(command_list, stdout=subprocess.PIPE)
for line in io.TextIOWrapper(launch_script.stdout, encoding="utf-8"):
print("Received line: ", line.rstrip())
# Tell progressbar to add another step:
wx.CallAfter(self.sender, "update", msg="")
It stops working because osascript apparently runs in a non-interactive shell. This means script2 doesn't sent any output until it is fully finished, causing the progress bar in script1 to stall.
My question thus becomes: How can I make sure to use macOS native GUI to ask for the sudo password, thus preventing having to store it in a variable, while still maintaining the possibility to catch the stdout from the privileged script in an interactive / real-time stream.
Hope that makes sense.
Would appreciate any insights!
My question thus becomes: How can I make sure to use macOS native GUI
to ask for the sudo password, thus preventing having to store it in a
variable, while still maintaining the possibility to catch the stdout
from the privileged script in an interactive / real-time stream.
I have found a solution myself, using a named pipe (os.mkfifo()).
That way, you can have 2 python scripts communicate with each other while 1 of them is launched with privileged rights via osascript (meaning: you get a native GUI window that asks for the users sudo password).
Working solution:
mainscript.py
import os
from pathlib import Path
import shlex
import subprocess
import sys
from threading import Thread
import time
class LaunchDeletionProcess(Thread):
def __init__(self):
Thread.__init__(self)
def run(self):
launch_command = r"""/usr/bin/osascript -e 'do shell script "/usr/local/bin/python3.6 -u /path/to/priviliged_script.py" with prompt "Sart Deletion Process " with administrator privileges'"""
split_command = shlex.split(launch_command)
print("Thread 1 started")
testprogram = subprocess.Popen(split_command)
testprogram.wait()
print("Thread1 Finished")
class ReadStatus(Thread):
def __init__(self):
Thread.__init__(self)
def run(self):
while not os.path.exists(os.path.expanduser("~/p1")):
time.sleep(0.1)
print("Thread 2 started")
self.wfPath = os.path.expanduser("~/p1")
rp = open(self.wfPath, 'r')
response = rp.read()
self.try_pipe(response)
def try_pipe(self, response):
rp = open(self.wfPath, 'r')
response = rp.read()
print("Receiving response: ", response)
rp.close()
if response == str(self.nr_of_steps-1):
print("Got to end")
os.remove(os.path.expanduser("~/p1"))
else:
time.sleep(1)
self.try_pipe(response)
if __name__ == "__main__":
thread1 = LaunchDeletionProcess()
thread2 = ReadStatus()
thread1.start()
thread2.start()
priviliged_script.py
import os
import time
import random
wfPath = os.path.expanduser("~/p1")
try:
os.mkfifo(wfPath)
except OSError:
print("error")
pass
result = 10
nr = 0
while nr < result:
random_nr = random.random()
wp = open(wfPath, 'w')
print("writing new number: ", random_nr)
wp.write("Number: " + str(random_nr))
wp.close()
time.sleep(1)
nr += 1
wp = open(wfPath, 'w')
wp.write("end")
wp.close()
What I'm trying to do is to write a script which would open an application only in process list. Meaning it would be "hidden". I don't even know if its possible in python.
If its not possible, I would settle for even a function that would allow for a program to be opened with python in a minimized state maybe something like this:
import subprocess
def startProgram():
subprocess.Hide(subprocess.Popen('C:\test.exe')) # I know this is wrong but you get the idea...
startProgram()
Someone suggested to use win32com.client but the thing is that the program that i want to launch doesn't have a COM server registered under the name.
Any ideas?
It's easy :)
Python Popen Accept STARTUPINFO Structure...
About STARTUPINFO Structure: https://msdn.microsoft.com/en-us/library/windows/desktop/ms686331(v=vs.85).aspx
Run Hidden:
import subprocess
def startProgram():
SW_HIDE = 0
info = subprocess.STARTUPINFO()
info.dwFlags = subprocess.STARTF_USESHOWWINDOW
info.wShowWindow = SW_HIDE
subprocess.Popen(r'C:\test.exe', startupinfo=info)
startProgram()
Run Minimized:
import subprocess
def startProgram():
SW_MINIMIZE = 6
info = subprocess.STARTUPINFO()
info.dwFlags = subprocess.STARTF_USESHOWWINDOW
info.wShowWindow = SW_MINIMIZE
subprocess.Popen(r'C:\test.exe', startupinfo=info)
startProgram()
You should use win32api and hide your window e.g. using win32gui.EnumWindows you can enumerate all top windows and hide your window
Here is a small example, you may do something like this:
import subprocess
import win32gui
import time
proc = subprocess.Popen(["notepad.exe"])
# lets wait a bit to app to start
time.sleep(3)
def enumWindowFunc(hwnd, windowList):
""" win32gui.EnumWindows() callback """
text = win32gui.GetWindowText(hwnd)
className = win32gui.GetClassName(hwnd)
#print hwnd, text, className
if text.find("Notepad") >= 0:
windowList.append((hwnd, text, className))
myWindows = []
# enumerate thru all top windows and get windows which are ours
win32gui.EnumWindows(enumWindowFunc, myWindows)
# now hide my windows, we can actually check process info from GetWindowThreadProcessId
# http://msdn.microsoft.com/en-us/library/ms633522(VS.85).aspx
for hwnd, text, className in myWindows:
win32gui.ShowWindow(hwnd, False)
# as our notepad is now hidden
# you will have to kill notepad in taskmanager to get past next line
proc.wait()
print "finished."
What is the purpose?
if you want a hidden(no window) process working in background, best way would be to write a windows service and start/stop it using usual window service mechanism. Windows service can be easily written in python e.g. here is part of my own service (it will not run without some modifications)
import os
import time
import traceback
import pythoncom
import win32serviceutil
import win32service
import win32event
import servicemanager
import jagteraho
class JagteRahoService (win32serviceutil.ServiceFramework):
_svc_name_ = "JagteRaho"
_svc_display_name_ = "JagteRaho (KeepAlive) Service"
_svc_description_ = "Used for keeping important services e.g. broadband connection up"
def __init__(self,args):
win32serviceutil.ServiceFramework.__init__(self,args)
self.stop = False
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
self.log('stopping')
self.stop = True
def log(self, msg):
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
servicemanager.PYS_SERVICE_STARTED,
(self._svc_name_,msg))
def SvcDoRun(self):
self.log('folder %s'%os.getcwd())
self.ReportServiceStatus(win32service.SERVICE_RUNNING)
self.start()
def shouldStop(self):
return self.stop
def start(self):
try:
configFile = os.path.join(jagteraho.getAppFolder(), "jagteraho.cfg")
jagteraho.start_config(configFile, self.shouldStop)
except Exception,e:
self.log(" stopped due to eror %s [%s]" % (e, traceback.format_exc()))
self.ReportServiceStatus(win32service.SERVICE_STOPPED)
if __name__ == '__main__':
win32serviceutil.HandleCommandLine(AppServerSvc)
and you can install it by
python svc_jagteraho.py--startup auto install
and run it by
python python svc_jagteraho.py start
I will be also be seen in services list e.g. services.msc will show it and you can start/stop it else you can use commandline
sc stop jagteraho
Run Hidden:
from subprocess_maximize import Popen
Popen("notepad.exe",show='hidden', priority=0)
Before the code above, use the following command:
pip install subprocess-maximize
If what is appearing is a terminal, redirect the process's stdout.