Execute Shell Commands from a Python GUI App

GUI Execute Commands

Here’s my Python – TKinter App that takes a set of Shell Commands from a .json file and displays them for individual execution when a button is clicked. Write these frequently used commands once and let the GUI program remember their syntax.

Keep 3 files in the same directory:

  • guiExecuteCommands.py The TKInter Program
  • config.json The Configuration Data
  • cmds.json The Initial Command Set

Contents

  • The TKInter Program File – guiExecuteCommands.py
  • The Configuration Data File – config.json
  • The Initial Command Set – cmds.json
  • A Screenshot of the TKInter Program
  • A run log of the program

The TKInter Program File – guiExecuteCommands.py

Here is the guiExecuteCommands.py file.

#!/usr/bin/python
# -*- coding: utf-8 -*-
# guiExecuteCommands.py
import sys, os, time, subprocess, json, atexit

# if not('.' in sys.path): sys.path.append('.')
# from MyCommands import cmds

import Tkinter, tkFileDialog

class Theapp(Tkinter.Tk):
    def __init__(self,parent):

        # joe's init
        with open('config.json') as data_file:
            cfg_data = json.load(data_file)
        self.cfg_data = cfg_data 

        with open(cfg_data[0]['commandsf']) as data_file:
            cmds = json.load(data_file)
        self.cmds = cmds 


        atexit.register(self.on_exit) # http://stackoverflow.com/a/28664350/601770


        # /joe's init




        Tkinter.Tk.__init__(self,parent)
        self.parent = parent
        self.initialize()

    def on_exit(self):
        ''' http://stackoverflow.com/a/28664350/601770  
            save the state that has been changed
        '''

        self.cfg_data[0]['workingd'] = self.cwd.get()


        print "self.cwd.get(): %s"%(self.cwd.get())
        print "self.cfg_data: %s"%(self.cfg_data)

        with open('config.json', 'w') as f:
            json.dump(self.cfg_data, f)

    def execute_cmd(self, rnum):
        ''' C:\1d\PythonPjs\subprocessPjs\executeCommandsPj\executeCommands\executeCommands.py

        '''
        commandstring = self.cmd_entries[rnum].get()
        cwdpath = self.cwd.get()
        outstr = subprocess.check_call(commandstring, cwd=cwdpath, shell=True)  #  Convenience Function
        print outstr


    ## http://stackoverflow.com/a/16777523/601770   
    def bclick(self, rnum):
        def click():            
            self.execute_cmd(rnum)
        return click



    def browse2cwd(self):   
        """
        http://stackoverflow.com/questions/11295917/how-to-select-a-directory-and-store-the-location-using-tkinter-in-python
        http://tkinter.unpythonic.net/wiki/tkFileDialog
            find "def askdirectory(self):"
        """   
        tkFileDiaOpt4Dir = {}
        tkFileDiaOpt4Dir['initialdir'] = self.cwd.get()
        tkFileDiaOpt4Dir['mustexist'] = False
        tkFileDiaOpt4Dir['parent'] = self
        tkFileDiaOpt4Dir['title'] = 'Browse to set CWD.'

        self.cwd.set( tkFileDialog.askdirectory(**tkFileDiaOpt4Dir) )

        print "browse2cwd:  self.cwd.get(): %s"%(self.cwd.get())

    def initialize(self):
        self.cwd = Tkinter.StringVar()
        self.cwd.set(self.cfg_data[0]['workingd'])

        self.grid()  # make this be a grid

        ### add the gui elements in here

        ## browse to working directory
        label = Tkinter.Label(self, text="Working Directory", fg="white",bg="blue")
        label.grid(column=0,row=0,columnspan=3,sticky='W')

        self.cwd_entry = Tkinter.Entry(self, width=80, textvariable=self.cwd)
        self.cwd_entry.grid(column=0,row=1,columnspan=2, sticky='W') # http://effbot.org/tkinterbook/entry.htm

        b = Tkinter.Button(self,text=u"Browse", command=self.browse2cwd)
        b.grid(column=2,row=1) 

        toprowscount = 2


        ## command row elements len == len(MyCommands.cmds)
        self.buttons = []
        self.cmd_entries = []
        for rnum, cmd in enumerate(self.cmds):


            label = Tkinter.Label(self, anchor="w",text=cmd['label'], fg="white",bg="blue")
            label.grid(column=0,row=rnum+toprowscount,sticky='EW')

            self.cmd_entries.append( Tkinter.Entry(self, width=60) )
            self.cmd_entries[-1].grid(column=1,row=rnum+toprowscount,sticky='EW'); self.cmd_entries[-1].insert(0, (cmd['cmd']+' '+ cmd['args']))


            b = Tkinter.Button(self,text=u"Ex%s"%(rnum), command=self.bclick(rnum))
            b.grid(column=2,row=rnum+toprowscount) 

            self.buttons.append(b)


        ## Finish up
        self.grid_columnconfigure(0,weight=1) # make resizable
        self.resizable(True,False)            # make resizable ONLY HORIZONTALLY




if __name__ == "__main__":
    app = Theapp(None)
    app.title('Gui Execute Comands')
    app.mainloop()

The Configuration Data File – config.json

Here is the config.json file.

[{
   "programd": "C:\\1d\\PythonPjs\\GUIpjs\\guiExecuteCommandPj\\guiExecuteCommand", 
   "commandsf": "C:\\1d\\PythonPjs\\GUIpjs\\guiExecuteCommandPj\\guiExecuteCommand\\cmds.json",  
   "configd": "C:\\1d\\PythonPjs\\GUIpjs\\guiExecuteCommandPj\\guiExecuteCommand", 
   "workingd": "C:/1d/CrossPlatformPjs/CordovaPjs/cordovaAngular00Pj/cordovaAngular00"
}]

The Initial Command Set – cmds.json

Here is the (INITIAL) cmds.json file

[
  {"cmd": "cordova", "args": "run android", "label": "android build 4 device"}, 
  {"cmd": "cordova", "args": "emulate android", "label": "android build 4 emulation"}, 
  {"cmd": "dir", "args": "*", "label": "dir of working dir"}
]  

A Screenshot of the TKInter Program

Here is a screenshot of the program.

guiExecuteShot

A run log of the program

Here is a run log.

Note: if the user changed the working directory, on exit the new working directory will be saved in the config.json file.

C:\1bat\guiExecuteCommands>guiExecuteCommands.py
browse2cwd:  self.cwd.get(): C:/1d/CrossPlatformPjs/Building Single Page Web Apps with AngularJS PJ
 Volume in drive C is Windows
 Volume Serial Number is F2FB-4810

 Directory of C:\1d\CrossPlatformPjs\Building Single Page Web Apps with AngularJS PJ

12/23/2015  09:58 AM    <DIR>          .
12/23/2015  09:58 AM    <DIR>          ..
12/23/2015  11:11 AM    <DIR>          zetc
               0 File(s)              0 bytes
               3 Dir(s)  900,352,217,088 bytes free
0
self.cwd.get(): C:/1d/CrossPlatformPjs/Building Single Page Web Apps with AngularJS PJ
self.cfg_data: [{u'programd': u'C:\\1d\\PythonPjs\\GUIpjs\\guiExecuteCommandPj\\guiExecuteCommand', u'commandsf': u'C:\\1d\\PythonPjs\\GUIpjs\\guiExecuteCommandPj\\guiExecuteCommand\\cmds.json', u'configd': u'C:\\1d\\PythonPjs\\GUIpjs\\guiExecuteCommandPj\\guiExecuteCommand', u'workingd': u'C:/1d/CrossPlatformPjs/Building Single Page Web Apps with AngularJS PJ'}]

C:\1bat\guiExecuteCommands>

KVLang Series – 4

KVLang Series – 4

← Previous              Next →

Vertical BoxLayout

Content:
– .kv file
– .py file
– screenshot of output

0004_verticalBoxLayout.kv

Just add the BoxLayout orientation: ‘vertical’ property. This arranges its contents in a vertical manner instead of the default ‘horizontal’.

BoxLayout:
    orientation: 'vertical'
    Label:
        text: 'Input:'
    TextInput:
        text: 'Default Text'
    Button:
        text: 'Press Me'

0004_verticalBoxLayout.py

This is adapted from previous python file. Again we make minor changes to update the .kv filename and give a new window height.

  1. EDIT

    CHANGE: The OLD self.root = Builder.load_file(”) TO the NEW

  2. EDIT

    WAS: Config.set(‘graphics’, ‘height’, ’30’)

    NOW: Config.set(‘graphics’, ‘height’, ’90’)

''' 0004_verticalBoxLayout.py
Used to display 0004_verticalBoxLayout.kv - Again, no new concepts in here.
'''
import kivy
kivy.require('1.8.0') # replace with your current kivy version !

from kivy.app import App
from kivy.lang import Builder
from kivy.config import Config

Config.set('graphics', 'width',  '323')
Config.set('graphics', 'height', '90')

class MyApp(App):

    def build(self):
        self.root = Builder.load_file('0004_verticalBoxLayout.kv')
        return self.root

if __name__ == '__main__':
    MyApp().run()

0004_verticalBoxLayout ScreenShot

Here is what this looks like run on Windows XP. In Pixels, it has:
– width: 323
– height: 200

Alt 0004_verticalBoxLayout.png

KVLang Series – 3

KVLang Series – 3

← Previous              Next →

More Widgets in a BoxLayout

Content:
– .kv file
– .py file
– screenshot of output

0003_moreWidgets.kv

In the .kv file we add some more widgets under a BoxLayout.
If we added the widgets directly to the file, not being underneath our layout, it would be and ERROR because they would be considered root rules and “you can have one root rule, and any number of class or template rules.”
The BoxLayout has a default orientation: ‘horizontal’.

### 0003_moreWidgets.kv

BoxLayout:
    Label:
        text: 'Input:'
    TextInput:
        text: 'Default Text'
    Button:
        text: 'Press Me'

0003_moreWidgets.py

This is adapted from previous python file. We make minor changes to update the .kv filename and give a new window height.

  1. EDIT

    CHANGE: The OLD self.root = Builder.load_file(”) TO the NEW

  2. EDIT

    WAS: Config.set(‘graphics’, ‘height’, ‘200’)

    NOW: Config.set(‘graphics’, ‘height’, ’30’)

''' 0003_moreWidgets.py
Used to display 0003_moreWidgets.kv - No new concepts in here.
'''
import kivy
kivy.require('1.8.0') # replace with your current kivy version !

from kivy.app import App
from kivy.lang import Builder
from kivy.config import Config

Config.set('graphics', 'width',  '323')
Config.set('graphics', 'height', '30')

class MyApp(App):

    def build(self):
        self.root = Builder.load_file('0003_moreWidgets.kv')
        return self.root

if __name__ == '__main__':
    MyApp().run()

0003_moreWidgets ScreenShot

Here is what this looks like run on Windows XP. In Pixels, it has:
– width: 323
– height: 200

Alt 0003_moreWidgets.png

KVLang Series – 2

KVLang Series – 2

← Previous              Next →

Window Size – Hello World Label Widget

Content:
– .kv file
– .py file
– screenshot of output

0002_windowSize.kv

This is PRETTY MUCH the same as before. Unfortunately (from my perspective), we must alter the .py file to change the window size. If anyone knows a way to do this in a .kv file please post it to the comments. Thanks.

Label:
    text: 'A smaller hello, world.'

0002_windowSize.py

As I said earlier, Unfortunately (from my perspective), we must alter the .py file to change the window size. If anyone knows a way to do this in a .kv file please post it to the comments. Thanks.

This is adapted from 0001_helloWorld.py. Here are the changes:

  1. EDIT – Builder.load_file()

    WAS: self.root = Builder.load_file(‘0001_helloWorld.kv’)

    NOW: self.root = Builder.load_file(‘0002_windowSize.kv’)

  2. ADD – Window Sizing Info

    AFTER: from kivy.lang import Builder

    BEFORE: class MyApp(App):

    INSERT:

    from kivy.config import Config

    Config.set(‘graphics’, ‘width’, ‘323’)

    Config.set(‘graphics’, ‘height’, ‘200’)

''' 0002_windowSize.py
Demonstrates Window Sizing - kivy.config - Config.set('graphics',
'''
import kivy
kivy.require('1.8.0') # replace with your current kivy version !

from kivy.app import App
from kivy.lang import Builder
from kivy.config import Config

Config.set('graphics', 'width',  '323')
Config.set('graphics', 'height', '200')

class MyApp(App):

    def build(self):
        self.root = Builder.load_file('0002_windowSize.kv')
        return self.root

if __name__ == '__main__':
    MyApp().run()

0002_windowSize ScreenShot

Here is what this looks like run on Windows XP. In Pixels, it has:
– width: 323
– height: 200

Alt 0002_windowSize.png

KVLang Series – 1

KVLang Series – 1

← Previous              Next →

Hello World Label Widget

Content:
– .kv file
– .py file
– screenshot of output

0001_helloWorld.kv

Here is the .kv == KVLang file for our hello world. It generates a GIGANTIC window containing a Kivy Builtin Label that says, “Hello, world.”

Label:
    text: 'Hello, world.'

0001_helloWorld.py

Here is a SIMPLE and what I believe is the MINIMAL, MOST GENERIC, python file to show our KVLang window. Hopefully this is a starting point template for KVLang projects.

This is adapted from:

''' 0001_helloWorld.py 
A simple, general starting point Python template for KVLang projects.
'''
import kivy
kivy.require('1.8.0') # replace with your current kivy version !

from kivy.app import App
from kivy.lang import Builder

class MyApp(App):

    def build(self):
        self.root = Builder.load_file('0001_helloWorld.kv')
        return self.root

if __name__ == '__main__':
    MyApp().run()

0001_helloWorld ScreenShot

Here is what this looks like run on Windows XP. In Pixels, it has:
– width: 808
– height: 634

Alt 0001_helloWorld.png

KVLang Series – 0

KVLang Series – 0

                      Next →

KVLang Step by Step with Minimum Python

I am trying to see what the KVLang can do WITH MINIMUM PYTHON, to identify its features and understand its power.

I use Step by Step examples following the Version 1.8.0 documentation from

Hopefully, the .py and .kv files are SIMPLE enough to be used as TEMPLATES for further investigation.

Click the Next → link at the top to get going.

Kivy Interactive Sandbox Example

Background

The Kivy Interactive Sandbox (aka kivy.interactive.InteractiveLauncher) seems to be a powerful tool to help Kivy developers. “The InteractiveLauncher provides a user-friendly python shell interface to an App so that it can be prototyped and debugged interactively.”

For example, you can take advantage of IPython’s Tab Completion feature to discover Kivy’s class methods and properties.

I first heard about this when I tried to follow Gabriel Pettier’s YouTube Example of Brian Knapp’s Work, but the font was too small for me.

Then I found the documentation for Brian’s work here.

The following are my notes on getting this running. I did this on Windows XP. This article is divided into 2 sections.

Section-1 Preparation of Paths
Prepare & check that the os.getenv(‘path’) and sys.path for IPython work with the Kivy installation. Step-1 Open a Command Window – Prepare Paths. Step-2 In SAME Command Window – Check Paths.
Section-2 Running the Interactive Development Example
Given the preparation is complete, run the Interactive Development Example. Step-3 In SAME Command Window in IPython – Paste the PART_TO_PASTE. Step-4 In SAME Command Window in IPython – Type in the Interactive Commands one at a time.

Section-1 Preparation & Checking of Paths

I am assuming you have already downloaded & installed Kivy, see this link.

For my Windows XP Kivy-1.8.0-py2.7-win32 installation, I am using the “Send-to method” to Start a Kivy Application.

Here’s what I do to prepare & check that the os.getenv(‘path’) and sys.path for IPython work with my Kivy installation.

Step-1 Open a Command Window – Prepare Paths.

Open a cmd window & say something like the following 2 commands to see the listed output.

>cd "PATH_TO_KIVY_INSTALL\kivy\examples"
>..\..kivy.bat
botstrapping Kivy @ C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32
Setting Environment Variables:
#################################
GST_REGISTRY
C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32gstreamerregistry.bin
---------------
GST_PLUGIN_PATH:
C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32gstreamerlibgstreamer-1.0
---------------
PATH:
C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32;C:Documents and SettingsjoeMy Documents
downloadskivyKivy-1.8.0-py2.7-win32Python27;C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-w
in32tools;C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32Python27Scripts;C:Documents an
d SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32gstreamerbin;C:Documents and SettingsjoeMy Documentsd
ownloadskivyKivy-1.8.0-py2.7-win32MinGWbin;C:Program FilesJavajdk1.7.0_10bin;C:WINDOWSsystem32;C:WINDOWS;C:WINDO
WSSystem32Wbem;C:1bat;C:Python27;C:Python27Scripts;C:Program FilesTortoiseHg;C:Program FilesTortoiseSVNbin;C:Pr
ogram FilesNotepad++
----------------------------------
PYTHONPATH:
C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32kivy;
----------------------------------
##################################
done bootstraping kivy...have fun!n

-----------------------------------------------------------------------
- Running a shell, you can browse kivyexamples and launch apps with: -
- python app.py -
-----------------------------------------------------------------------

Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

Step-2 In SAME Command Window – Check Paths.

Next, IN THE SAME COMMAND WINDOW, start IPython. Check to see that the os.getenv(‘path’) and sys.path are good.

>ipython
Python 2.7.5 (default, May 15 2013, 22:43:36) [MSC v.1500 32 bit (Intel)]
Type "copyright", "credits" or "license" for more information.

IPython 0.13.1 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: pwd
Out[1]: u'C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32kivyexamples'

In [2]: import sys, os

In [3]: sys.path
Out[3]:
['',
 'C:Python27Scripts',
 'C:Python27libsite-packagesdistribute-0.6.28-py2.7.egg',
 'C:Python27libsite-packagespyreadline-2.0_dev1-py2.7-win32.egg',
 'C:Python27libsite-packagesfeedparser-5.1.2-py2.7.egg',
 'C:Python27libsite-packagesmechanize-0.2.5-py2.7.egg',
 'C:Python27libsite-packagespip-1.2.1-py2.7.egg',
 'C:Python27libsite-packagespytidylib-0.2.1-py2.7.egg',
 'C:Python27libsite-packageseyed3-0.7.1-py2.7.egg',
 'C:Python27libsite-packagesrequests_oauthlib-0.4.0-py2.7.egg',
 'C:Python27libsite-packagesrequests-2.0.1-py2.7.egg',
 'C:Python27libsite-packagesoauthlib-0.6.0-py2.7.egg',
 'C:Python27libsite-packageshttplib2-0.8-py2.7.egg',
 'C:Python27libsite-packagesoauth2-1.5.211-py2.7.egg',
 'C:Python27libsite-packagespython_twitter-1.1-py2.7.egg',
 'C:Python27libsite-packagessphinx-1.2.1-py2.7.egg',
 'C:Python27libsite-packagesjinja2-2.7.2-py2.7.egg',
 'C:Python27libsite-packagesdocutils-0.11-py2.7.egg',
 'C:Python27libsite-packagespygments-1.6-py2.7.egg',
 'C:Python27libsite-packagesmarkupsafe-0.18-py2.7.egg',
 'C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32kivy',
 'C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32kivyexamples',
 'C:WINDOWSsystem32python27.zip',
 'C:Python27DLLs',
 'C:Python27lib',
 'C:Python27libplat-win',
 'C:Python27liblib-tk',
 'C:Python27',
 'C:Python27libsite-packages',
 'C:Python27libsite-packageswin32',
 'C:Python27libsite-packageswin32lib',
 'C:Python27libsite-packagesPythonwin',
 'C:Python27libsite-packageswx-2.8-msw-unicode',
 'C:Python27libsite-packagesIPythonextensions']

In [4]: os.getenv('path').split(';')
Out[4]:
['C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32',
 'C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32Python27',
 'C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32tools',
 'C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32Python27Scripts',
 'C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32gstreamerbin',
 'C:Documents and SettingsjoeMy DocumentsdownloadskivyKivy-1.8.0-py2.7-win32MinGWbin',
 'C:Program FilesJavajdk1.7.0_10bin',
 'C:WINDOWSsystem32',
 'C:WINDOWS',
 'C:WINDOWSSystem32Wbem',
 'C:1bat',
 'C:Python27',
 'C:Python27Scripts',
 'C:Program FilesTortoiseHg',
 'C:Program FilesTortoiseSVNbin',
 'C:Program FilesNotepad++']

In [5]:

Section-2 Running the Interactive Development Example

Step-3 In SAME Command Window in IPython – Paste the PART_TO_PASTE.

Next, in the same Command Window, in IPython, %paste the PART_TO_PASTE (shown below) of the Interactive Development example into IPython.

# PART_TO_PASTE
from kivy.interactive import InteractiveLauncher
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse

class MyPaintWidget(Widget):
    def on_touch_down(self, touch):
        with self.canvas:
            Color(1, 1, 0)
            d = 30.
            Ellipse(pos=(touch.x - d/2, touch.y - d/2), size=(d, d))


class TestApp(App):
    def build(self):
        return Widget()


i = InteractiveLauncher(TestApp())
i.run()
# /PART_TO_PASTE

Step-4 In SAME Command Window in IPython – Type in the Interactive Commands one at a time.

Now, one at a time, even though IPython is not showing an input prompt, type in the Interactive Commands from the Interactive Development example into IPython as shown below.

i.       # press 'tab' to list attributes of the app - results in the ipython window

i.root.  # press 'tab' to list attributes of the root widget - results in the ipython window

# App is boring.  Attach a new widget! - results in the App window
i.root.add_widget(MyPaintWidget())
# click in the app and yellow dots will show up 

i.safeIn()
# The application is now blocked.
# Click on the screen several times. - dots won't show up 
#    N.B. in windows, window turns white & says "Test (Not Responding)"

i.safeOut()
# The clicks will show up now
#    N.B. In Windows XP, dots show up & clicks again produce yellow dots on black background 
#        but the Test taskbar button still says "Test (Not Responding)" even though it is.

# Erase artwork and start over
i.root.canvas.clear()
# On Windows SP, clicks don't work until after, in ipython, you say "i.root.add_widget(MyPaintWidget())" again

i.root.add_widget(MyPaintWidget())
# click in the app and yellow dots will show up 

Here’s a short link http://wp.me/p1zI3Z-bv

I hope this helps you.

Love and peace,
Joe