My latest assignment for Commie was to create a plugin for xchat to list running encoding processes in the staff IRC channel. You can write plugins in a few various languages and me knowing a little bit of Python I chose to do it that way.
The problem I was facing was to find all windows of x264, fetch their titles (which included how far the encoding was, time remaining etc), and output each title to the channel when a user would say “.encoding”. Easy enough, I thought, I’ll just fetch all processes and check if “x264” is in the title. I found some methods and code snippets using pywin32 (win32gui, included in pywin32). Unfortunately, xchat only supports Python 2.6 (which is a rather old version of Python) and the windows installer for pywin32 for Python 2.6 is broken and I didn’t manage to compile from source either, so I had to find an alternate way of doing it without any libraries.
Luckily, I found a way of getting all windows titles using ctypes (which is included in Python by default) and the win32 API (thanks to this blog post). It worked really well and from there I managed to easily get the titles I wanted and send them to the IRC channel. Unfortunately, the titles didn’t contain what was encoding, so it would be pretty useless to know that something is 50% done.
Looking around a bit, I managed to find a way to get what was being encoded. The commandline of the process that started the encoding included the directory name of what was encoding. Now I needed to somehow fetch the commandline, but I only had a window and not a process. Having the window I obviously had the window handle. By reading some documentation I found the GetWindowThreadProcessId, which returns a process id from a window handle. Now that I have that, how do I easily get the CommandLine? The easiest way I could figure out was to use WMIC, a commandline interface to WMI (a type of query language to fetch various stuff from the OS, something in the lines of that). From this on it was only some formatting left to do. Below is the full code. Hopefully others can use it to understand how it works, as I had some troubles searching for the right way to do this (all thought it may be a rare thing to do).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# xchat module info __module_name__ = "x264 Encode Status" __module_version__ = "1.0" __module_description__ = "Prints the windowtitle of running x264 proccesses" import ctypes, subprocess, xchat, re from ctypes import byref, c_int # on channel message event handler def on_message(word, word_eol, userdata): # register winapi functions EnumWindows = ctypes.windll.user32.EnumWindows EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)) GetWindowText = ctypes.windll.user32.GetWindowTextW GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW IsWindowVisible = ctypes.windll.user32.IsWindowVisible GetWindowThreadProcessId = ctypes.windll.user32.GetWindowThreadProcessId # this is called for each window def foreach_window(hwnd, lParam): # make sure it's a visible window if IsWindowVisible(hwnd): # get the length of the title, create a buffer then fetch the text to the buffer length = GetWindowTextLength(hwnd) buff = ctypes.create_unicode_buffer(length + 1) GetWindowText(hwnd, buff, length + 1) # if the title contains an illegal character it will throw an error (like the (tm) in Skype(tm)). # doesn't matter in out case tho, we only need the title of the x264 window which contain no such chars try: windowtitle = buff.value; if "x264" in windowtitle: # get the proccessid from the windowhandle processID = c_int() threadID = GetWindowThreadProcessId(hwnd,byref(processID)) # we use WMIC to fetch the commandline of the process. It's a commandline interface for WMI # a sort of query language to fetch various OS stuff. # more info: http://technet.microsoft.com/en-us/library/bb742610.aspx cmd = 'WMIC process where processid='+str(processID.value)+' get Commandline /Format:csv' # execute the command proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) cmdLine = ""; # fetch the command line. The last one is the command line, the first lines are carriage-returns etc # Lazy hack for line in proc.stdout: cmdLine = str(line) # get the file part of the command line file = cmdLine.strip().split(" ")[-1]; # separate the directory from the file split = file.split("\\"); #" # get the episode number (and version if any) rg = re.compile('(\d+(v\d+)?)',re.IGNORECASE|re.DOTALL); m = rg.search(split[0]); if m: split[0] = re.sub(m.group(1), "", split[0]); split[0] += " "+m.group(1); # create the message encodeMessage = split[0]+" part "+split[-1][:split[-1].find(".")]+": "+windowtitle; # say it in the channel xchat.command('say '+encodeMessage) pass; except: # something went wrong, write in xchat console print(__module_name__+": Unexpected error:"+ sys.exc_info()[0]) pass; # check if .encoding is in the message if ".encoding" in word[1]: # finds and returns a count of instances of a process cmd1 = 'WMIC process where Caption="x320.exe" get Commandline /Format:csv' # execute the command proc1 = subprocess.Popen(cmd1, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) cmdLine1 = ""; # fetch the command line. The last one is the command line, the first lines are carriage-returns etc # Lazy hack for line in proc1.stdout: cmdLine1 += str(line) # check if anything is encoding, otherwise tell that nothing is encoding if "No Instance(s) Available" in cmdLine1: xchat.command('say nothing is encoding right now') else: EnumWindows(EnumWindowsProc(foreach_window), 0) # hook the channel message event xchat.hook_print('Channel Message', on_message); # write to xchat console upon load print (__module_name__+" loaded."); |