Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I launch a file in its default program, and then close it when the script finishes?

Summary

I have wxPython GUI which allows the user to open files to view. Currently I do this with os.startfile(). However, I've come to learn that this is not the best method, so I'm looking to improve. The main drawback of startfile() is that I have no control over the file once it has been launched. This means that a user could leave a file open so it would be unavailable for another user.

What I'm Looking For

In my GUI, it is possible to have children windows. I keep track of all of these by storing the GUI objects in a list, then when the parent is closed, I just run through the list and close all the children. I would like to do the same with any file the user selects. How can I launch a file and retain a python object such that I can close it on command? Thanks in advance

My Dreams of a Solution

  • Launch the file in such a way that there is a Python object which I can pass between functions
  • Some way to launch a file in its default program and return the PID
  • A way to retrieve the PID with the file name

Progress So Far

Here's the frame I plan on using. The important bits are the run() and end() functions of the FileThread class as this is where the solution will go.

import wx
from wx.lib.scrolledpanel import ScrolledPanel 
import threading
import os

class GUI(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, 'Hey, a GUI!', size=(300,300))
        self.panel = ScrolledPanel(parent=self, id=-1) 
        self.panel.SetupScrolling()  
        self.Bind(wx.EVT_CLOSE, self.OnClose)

        self.openFiles = []

        self.openBtn = wx.Button(self.panel, -1, "Open a File")
        self.pollBtn = wx.Button(self.panel, -1, "Poll")
        self.Bind(wx.EVT_BUTTON, self.OnOpen, self.openBtn)
        self.Bind(wx.EVT_BUTTON, self.OnPoll, self.pollBtn)

        vbox = wx.BoxSizer(wx.VERTICAL)

        vbox.Add((20,20), 1)
        vbox.Add(self.openBtn)
        vbox.Add((20,20), 1)
        vbox.Add(self.pollBtn)
        vbox.Add((20,20), 1)

        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox.Add(vbox, flag=wx.TOP|wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.EXPAND, border = 10)

        self.panel.SetSizer(hbox)
        self.panel.Layout()

    def OnOpen(self, event):
        fileName = "AFileIWantToOpenWithTheFullPath.txt"
        self.openFiles.append(FileThread(fileName))

    def OnPoll(self, event):
        self.openFiles[0].Poll()

    def OnClose(self, event):
        for file in self.openFiles:
            file.end()
            self.openFiles.remove(file)

        self.Destroy()

class FileThread(threading.Thread):
    def __init__(self, file):
        threading.Thread.__init__(self)
        self.file = file
        self.start()

    def run(self):
        doc = subprocess.Popen(["start", " /MAX", "/WAIT", self.file], shell=True)
        return doc

    def Poll(self):
        print "polling"
        print self.doc.poll()
        print self.doc.pid

    def end(self):
        try:
            print "killing file {}".format(self.file)
        except:
            print "file has already been killed"

def main():
    app = wx.PySimpleApp()
    gui = GUI()
    gui.Show()
    app.MainLoop()


if __name__ == "__main__": main()

Some Extra Notes

  • I'm not concerned with portability, this will only be run on a few controlled computers around the office
  • I don't think this matters, but I'm running the pythonw executable through a batch file

Update

I played around a bit with subprocess.Popen(), but ran into the same issue. I can make the Popen object using

doc = subprocess.Popen(["start", "Full\\Path\\to\\File.txt"], shell=True)

but when I poll() the object, it always returns 0. The docs say that A None value indicates that the process hasn’t terminated yet so the 0 means that my process has terminated. Thus, attempting to kill() it does nothing.

I suspect this is because the process completes when the start command finishes and the file is launched. I want something that will keep going even after the file is launched, can this be done with Popen()?

like image 577
wnnmaw Avatar asked Dec 25 '13 23:12

wnnmaw


People also ask

How do I change the default program to open files?

You can right click on the file to select open with and select the appropriate program for that file to open. Further you can go to control panel, click on default programs. Under "choose the programs that Windows uses by default" you can make the necessary changes to check if that helps.

What is a default program in Windows?

A default program is the program that Windows uses when you open a particular type of file, such as a music file, an image, or a webpage. on your computer, you can choose one of them to be the default browser. You may be facing the above issue due to unassigned program or application to open that particular file type on the computer.

How to find the default application for opening PDF files?

You can query the registry to identify the default application to open pdf files and then define FileName on your process's StartInfo accordingly. Follow this question for details on doing that: Finding the default application for opening a particular file type on Windows

How do I change the default program on a Mac?

If you want to change the default program on a Mac, you’ll need a file in the file format you’re looking to open. To start, open the Finder app and find the location of your file.


1 Answers

The problem lies within the fact, that the process being handled by the Popen class in your case is the start shell command process, which terminates just after it runs the application associated with given file type. The solution is to use the /WAIT flag for the start command, so the start process waits for its child to terminate. Then, using for example the psutil module you can achieve what you want with the following sequence:

>>> import psutil
>>> import subprocess
>>> doc = subprocess.Popen(["start", "/WAIT", "file.pdf"], shell=True)
>>> doc.poll()
>>> psutil.Process(doc.pid).get_children()[0].kill()
>>> doc.poll()
0
>>> 

After the third line Adobe Reader appears with the file opened. poll returns None as long as the window is open thanks to the /WAIT flag. After killing start's child Adobe Reader window disappears.

Other probably possible solution would be to find the application associated with given file type in the registry and run it without using start and shell=True.

I've tested this on 32 bit Python 2.7.5 on 64 bit Windows Vista, and 32 bit Python 2.7.2 on 64 bit Windows 7. Below is an example run on the latter. I've made some simple adjustments to your code - marked with a freehand red circles (!).

step1step2step3step4

Also, possibly it is worth to consider this comment from the documentation:

The shell argument (which defaults to False) specifies whether to use the shell as the program to execute. If shell is True, it is recommended to pass args as a string rather than as a sequence.

and run the process as follows:

subprocess.Popen("start /WAIT " + self.file, shell=True)
like image 123
BartoszKP Avatar answered Sep 21 '22 12:09

BartoszKP