Python Launcher Pops Up When Py-based App Is Running (Mac)

James Greenham cooler9.t at icloud.com
Sat Sep 16 21:20:56 EDT 2023


Hello,

On the face of it, the Python-Mac mailing list is largely inactive so I'm posting here since it looks like this one is livelier.  

I'm running macOS Mojave with Python 2.7. I have an old Dashboard widget (last modified in 2009) that appears to use Python as well. The widget captures the full Safari web-page. It works seamlessly on earlier macOS systems but hangs in 10.14. The widget's core functions constitute code of the script that I'm pasting in this message. The problem is that as soon as the execution reaches the image processing stage it gets stuck which manifests by Python Launcher popping up in Dock and is trying forever until I quit the launcher that force-quits the widget showing the error icon.

The issue seems to originate in the doGrab handler (module or whatever the correct Python term fits the definition) immediately following the print "Processing Image..." line: it's the message the widget displays at getting stalled. I( checked that Python 2.7 is installed in the default location at /usr/bin. I also have the binaries idle3, idle3.10, pip3, pip3.10 in /usr/local/bin, apparently used by another Unix program. 

Is that remedied? The script's environment set to python, i.e. to python 2.7 so that shouldn't be the encumbrance. Am I correct?

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# OR:
# coding=utf-8

__version__ = "0.6"

#
# Based on Paul Hammond's webkit2png script - make screenshots of webpages
# http://www.paulhammond.org/webkit2png <http://www.paulhammond.org/webkit2png>
#
# Modified by Paulo Avila for the Page Capture widget
#
# The original source code (v0.5) belongs to Paul Hammond (see notice below).
# Any and all code added after is Copyright (c) 2009 Paulo Avila.
#
# Modification Log by Paul Avila
#
#  2009.04.21 - Changed almost all the output messages in order to behave properly with my Page Capture widget
#  2009.04.21 - Added function headers
#  2009.04.21 - Properly flush the output buffers so that messages display immediately
#  2009.04.21 - Changed encoding of this file to UTF-8
#  2009.04.25 - Updated how files are named: "<domain> > <file>.png"
#  2009.04.25 - Files are now named with the full URL excluding protocol (http://) and arguments (...?*)
#



# Copyright (c) 2009 Paul Hammond
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
                
import sys
import optparse

try:
  import Foundation
  import WebKit
  import AppKit
  import objc
except ImportError:
  #print "Cannot find pyobjc library files.  Are you sure it is installed?"
  print "Mac OS X 10.5 or higher is required."
  sys.stdout.flush()
  sys.exit() 



class AppDelegate (Foundation.NSObject):
    # what happens when the app starts up
    def applicationDidFinishLaunching_(self, aNotification):
        webview = aNotification.object().windows()[0].contentView()
        webview.frameLoadDelegate().getURL(webview)

class WebkitLoad (Foundation.NSObject, WebKit.protocols.WebFrameLoadDelegate):
    # what happens if something goes wrong while loading
    def webView_didFailLoadWithError_forFrame_(self,webview,error,frame):
        #print " ... something went wrong" 
        print "Unable to load WebKit."
        sys.stdout.flush()
        self.getURL(webview)
    def webView_didFailProvisionalLoadWithError_forFrame_(self,webview,error,frame):
        #print " ... something went wrong" 
        print "Check URL or Internet connection."
        sys.stdout.flush()
        self.getURL(webview)



#---------------------------------------------------
#7 - Generates the filename.

    def makeFilename(self,URL,options):
       # make the filename
       if options.filename:
         filename = options.filename
       elif options.md5:
         try:
                import md5
         except ImportError:
                print "--md5 requires python md5 library"
                sys.stdout.flush()
                AppKit.NSApplication.sharedApplication().terminate_(None)
         filename = md5.new(URL).hexdigest()
       else:
         import re
#         filename = re.sub('\W','',URL); #\W is the same as [^a-zA-Z0-9_]

#--------
         #remove anything after (and including) a question mark
         URL = re.sub("\?.*$", "", URL);
         #arg = URL.find("?");
         #if arg >= 0:
         #    URL = URL[0:arg];

         #remove the protocol string in the beginning (SSL or not)
         URL = re.sub("^https?://", "", URL);



#-- Page Capture # (start)
         import os
         dir = os.path.abspath(os.path.expanduser(options.dir));

         count = 1;
         while os.access(dir + "/Page Capture " + str(count) + ".png", os.F_OK): 
             count += 1;

         filename = "Page Capture " + str(count);
#-- Page Capture # (end)



#-- Full URL (start)
         #replace forward slashes with colons to allow to forward slashes in the filename (OS X's HFS+ allows for this)
      #   filename = re.sub("/", ":", URL);

         #max filename length permitted is 255. (255 - 4 = 251). (4 is the length of the extenion '.png')
      #   if len(filename) > 251:
      #       filename = filename[0:248] + "...";
#-- Full URL (end)



#-- Domain > File (start)
      #   URL = URL.split("/");

         #isolate just the domain
      #   domain = URL[0];
         #domain = re.sub("/.*$", "", URL);
         #domain = URL[0:URL.find("/")];

         #isolate just the file, if it doesn't exist, make it blank
      #   file = URL[len(URL)-1];
      #   if len(URL) == 1:
      #       file = "";

      #   filename = domain + " > " + file;

         #print "d= '" + domain + "'";
         #print "f= '" + file + "'";
#-- Domain > File (end)
#--------

       if options.datestamp:
         import time
         now = time.strftime("%Y%m%d")
#         now = time.strftime("%Y%m%d%H%M%S")
         filename = now + "-" + filename
#       import os
#       dir = os.path.abspath(os.path.expanduser(options.dir))
#       return os.path.join(dir,filename)
       return filename


#---------------------------------------------------
#8 - Saves the captured image(s) to the right place.

    def saveImages(self,bitmapdata,filename,options):
        # save the fullsize png
        if options.fullsize:
			bitmapdata.representationUsingType_properties_(AppKit.NSPNGFileType,None).writeToFile_atomically_(filename + "-full.png",objc.YES)

        if options.thumb or options.clipped:
            # work out how big the thumbnail is
            width = bitmapdata.pixelsWide()
            height = bitmapdata.pixelsHigh()
            thumbWidth = (width * options.scale)
            thumbHeight = (height * options.scale)

            # make the thumbnails in a scratch image
            scratch = AppKit.NSImage.alloc().initWithSize_(
                    Foundation.NSMakeSize(thumbWidth,thumbHeight))
            scratch.lockFocus()
            AppKit.NSGraphicsContext.currentContext().setImageInterpolation_(
                    AppKit.NSImageInterpolationHigh)
            thumbRect = Foundation.NSMakeRect(0.0, 0.0, thumbWidth, thumbHeight)
            clipRect = Foundation.NSMakeRect(0.0, 
                    thumbHeight-options.clipheight, 
                    options.clipwidth, options.clipheight)
            bitmapdata.drawInRect_(thumbRect)
            thumbOutput = AppKit.NSBitmapImageRep.alloc().initWithFocusedViewRect_(thumbRect)
            clipOutput = AppKit.NSBitmapImageRep.alloc().initWithFocusedViewRect_(clipRect)
            scratch.unlockFocus()
           
            # save the thumbnails as pngs 
            if options.thumb:
                thumbOutput.representationUsingType_properties_(
                        AppKit.NSPNGFileType,None
                    ).writeToFile_atomically_(filename + ".png",objc.YES)
#                thumbOutput.representationUsingType_properties_(
#                        AppKit.NSPNGFileType,None
#                    ).writeToFile_atomically_(filename + "-thumb.png",objc.YES)
            if options.clipped:
                clipOutput.representationUsingType_properties_(
                        AppKit.NSPNGFileType,None
                    ).writeToFile_atomically_(filename + "-clipped.png",objc.YES)



#---------------------------------------------------
#1 - Downloads URL contents.

    def getURL(self,webview):
        if self.urls:
            if self.urls[0] == '-':
                url = sys.stdin.readline().rstrip()
                if not url: AppKit.NSApplication.sharedApplication().terminate_(None)
            else:
                url = self.urls.pop(0)
        else:
            AppKit.NSApplication.sharedApplication().terminate_(None)
        #print "Fetching", url, "..."
        print "Fetching URL..."
        sys.stdout.flush()
        self.resetWebview(webview)
        webview.mainFrame().loadRequest_(Foundation.NSURLRequest.requestWithURL_(Foundation.NSURL.URLWithString_(url)))
        if not webview.mainFrame().provisionalDataSource():
            #print " ... not a proper url?"
            print "Invalid URL."
            sys.stdout.flush()
            self.getURL(webview)



#---------------------------------------------------
#2 - Resets any previous history?

    def resetWebview(self,webview):
        rect = Foundation.NSMakeRect(0,0,self.options.initWidth,self.options.initHeight)
        webview.window().setContentSize_((self.options.initWidth,self.options.initHeight))
        webview.setFrame_(rect)



#---------------------------------------------------
#5 - Resizes the window.

    def resizeWebview(self,view):
        view.window().display()
        view.window().setContentSize_(view.bounds().size)
        view.setFrame_(view.bounds())



#---------------------------------------------------
#6 - Turns current webview content into a bitmap.
    def captureView(self,view):
        view.lockFocus()
        bitmapdata = AppKit.NSBitmapImageRep.alloc()
        bitmapdata.initWithFocusedViewRect_(view.bounds())
        view.unlockFocus()
        return bitmapdata



#---------------------------------------------------
#3 - Called for each "piece" of the page.

    # what happens when the page has finished loading
    def webView_didFinishLoadForFrame_(self,webview,frame):
        # don't care about subframes
        if (frame == webview.mainFrame()):
            Foundation.NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_( self.options.delay, self, self.doGrab, webview, False)



#---------------------------------------------------
#4 - Process the downloaded content (calls 5-8)

    def doGrab(self,timer):
            print "Processing image..."
            sys.stdout.flush()
            webview = timer.userInfo()
            view = webview.mainFrame().frameView().documentView()
            
            self.resizeWebview(view)

            URL = webview.mainFrame().dataSource().initialRequest().URL().absoluteString()
            filename = self.makeFilename(URL, self.options) 

            import os
            full_path = os.path.join(os.path.abspath(os.path.expanduser(self.options.dir)), filename);

            bitmapdata = self.captureView(view)  
            self.saveImages(bitmapdata, full_path, self.options)

            #print " ... done"
# ✓
            print "<filename>" + filename + "</filename> saved to your Desktop."
            sys.stdout.flush()
            self.getURL(webview)


def main():

    # parse the command line
    usage = """%prog [options] [http://example.net/ <http://example.net/> ...]

examples:
%prog http://google.com/ <http://google.com/>            # screengrab google
%prog -W 1000 -H 1000 http://google.com/ <http://google.com/> # bigger screengrab of google
%prog -T http://google.com/ <http://google.com/>         # just the thumbnail screengrab
%prog -TF http://google.com/ <http://google.com/>        # just thumbnail and fullsize grab
%prog -o foo http://google.com/ <http://google.com/>     # save images as "foo-thumb.png" etc
%prog -                             # screengrab urls from stdin
%prog -h | less                     # full documentation"""

    cmdparser = optparse.OptionParser(usage,version=("webkit2png "+__version__))
    # TODO: add quiet/verbose options
    cmdparser.add_option("-W", "--width",type="float",default=800.0,
       help="initial (and minimum) width of browser (default: 800)")
    cmdparser.add_option("-H", "--height",type="float",default=600.0,
       help="initial (and minimum) height of browser (default: 600)")
    cmdparser.add_option("--clipwidth",type="float",default=200.0,
       help="width of clipped thumbnail (default: 200)",
       metavar="WIDTH")
    cmdparser.add_option("--clipheight",type="float",default=150.0,
       help="height of clipped thumbnail (default: 150)",
       metavar="HEIGHT")
    cmdparser.add_option("-s", "--scale",type="float",default=0.25,
       help="scale factor for thumbnails (default: 0.25)")
    cmdparser.add_option("-m", "--md5", action="store_true",
       help="use md5 hash for filename (like del.icio.us <http://del.icio.us/>)")
    cmdparser.add_option("-o", "--filename", type="string",default="",
       metavar="NAME", help="save images as NAME-full.png,NAME-thumb.png etc")
    cmdparser.add_option("-F", "--fullsize", action="store_true",
       help="only create fullsize screenshot")
    cmdparser.add_option("-T", "--thumb", action="store_true",
       help="only create thumbnail sreenshot")
    cmdparser.add_option("-C", "--clipped", action="store_true",
       help="only create clipped thumbnail screenshot")
    cmdparser.add_option("-d", "--datestamp", action="store_true",
       help="include date in filename")
    cmdparser.add_option("-D", "--dir",type="string",default="./",
       help="directory to place images into")
    cmdparser.add_option("--delay",type="float",default=0,
       help="delay between page load finishing and screenshot")
    cmdparser.add_option("--noimages", action="store_true",
       help="don't load images")
    cmdparser.add_option("--debug", action="store_true",
       help=optparse.SUPPRESS_HELP)
    (options, args) = cmdparser.parse_args()
    if len(args) == 0:
        cmdparser.print_usage()
        return
    if options.filename:
        if len(args) != 1 or args[0] == "-":
          print "--filename option requires exactly one url"
          sys.stdout.flush()
          return
    if options.scale == 0:
      cmdparser.error("scale cannot be zero")
    # make sure we're outputing something
    if not (options.fullsize or options.thumb or options.clipped):
      options.fullsize = True
      options.thumb = True
      options.clipped = True
    # work out the initial size of the browser window
    #  (this might need to be larger so clipped image is right size)
    options.initWidth = (options.clipwidth / options.scale)
    options.initHeight = (options.clipheight / options.scale)
    if options.width>options.initWidth:
       options.initWidth = options.width
    if options.height>options.initHeight:
       options.initHeight = options.height

    app = AppKit.NSApplication.sharedApplication()
    
    # create an app delegate
    delegate = AppDelegate.alloc().init()
    AppKit.NSApp().setDelegate_(delegate)

    # create a window
    rect = Foundation.NSMakeRect(0,0,100,100)
    win = AppKit.NSWindow.alloc()
    win.initWithContentRect_styleMask_backing_defer_ (rect, 
            AppKit.NSBorderlessWindowMask, 2, 0)
    if options.debug:
      win.orderFrontRegardless()
    # create a webview object
    webview = WebKit.WebView.alloc()
    webview.initWithFrame_(rect)
    # turn off scrolling so the content is actually x wide and not x-15
    webview.mainFrame().frameView().setAllowsScrolling_(objc.NO)

    webview.setPreferencesIdentifier_('webkit2png')
    webview.preferences().setLoadsImagesAutomatically_(not options.noimages)

    # add the webview to the window
    win.setContentView_(webview)
    
    # create a LoadDelegate
    loaddelegate = WebkitLoad.alloc().init()
    loaddelegate.options = options
    loaddelegate.urls = args
    webview.setFrameLoadDelegate_(loaddelegate)
    
    app.run()    

if __name__ == '__main__' : main()


Best,
James



More information about the Python-list mailing list