2008-08-19 00:53:25 +02:00
#!/usr/bin/env python
""" Discover_Tables.py
Inspects the currently open windows and finds those of interest to us - - that is
poker table windows from supported sites . Returns a list
of Table_Window objects representing the windows found .
"""
# Copyright 2008, Ray E. Barker
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
########################################################################
# Standard Library modules
import os
2008-09-15 22:31:55 +02:00
import sys
2008-08-19 00:53:25 +02:00
import re
2008-09-15 22:31:55 +02:00
# Win32 modules
if os . name == ' nt ' :
import win32gui
2008-10-24 21:44:53 +02:00
import win32process
import win32api
import win32con
2009-01-15 17:09:30 +01:00
import win32security
2008-09-15 22:31:55 +02:00
2008-08-19 00:53:25 +02:00
# FreePokerTools modules
import Configuration
2009-08-12 19:55:19 +02:00
from fpdb_simple import LOCALE_ENCODING
2008-08-19 00:53:25 +02:00
2008-11-11 15:40:31 +01:00
# Each TableWindow object must have the following attributes correctly populated:
# tw.name = the table name from the title bar, which must to match the table name
# from the corresponding hand history.
# tw.site = the site name, e.g. PokerStars, FullTilt. This must match the site
# name specified in the config file.
# tw.number = This is the system id number for the client table window in the
# format that the system presents it. This is Xid in Xwindows and
# hwnd in Microsoft Windows.
# tw.title = The full title from the window title bar.
# tw.width, tw.height = The width and height of the window in pixels. This is
# the internal width and height, not including the title bar and
# window borders.
# tw.x, tw.y = The x, y (horizontal, vertical) location of the window relative
# to the top left of the display screen. This also does not include the
# title bar and window borders. To put it another way, this is the
# screen location of (0, 0) in the working window.
2008-08-19 00:53:25 +02:00
class Table_Window :
2008-11-11 15:40:31 +01:00
def __init__ ( self , info = { } ) :
2009-01-06 11:18:45 +01:00
if ' number ' in info : self . number = info [ ' number ' ]
if ' exe ' in info : self . exe = info [ ' exe ' ]
if ' width ' in info : self . width = info [ ' width ' ]
if ' height ' in info : self . height = info [ ' height ' ]
if ' x ' in info : self . x = info [ ' x ' ]
if ' y ' in info : self . y = info [ ' y ' ]
if ' site ' in info : self . site = info [ ' site ' ]
if ' title ' in info : self . title = info [ ' title ' ]
if ' name ' in info : self . name = info [ ' name ' ]
2009-09-03 16:35:59 +02:00
self . gdkhandle = None
2008-11-11 15:40:31 +01:00
2008-08-19 00:53:25 +02:00
def __str__ ( self ) :
# __str__ method for testing
temp = ' TableWindow object \n '
temp = temp + " name = %s \n site = %s \n number = %s \n title = %s \n " % ( self . name , self . site , self . number , self . title )
2008-09-26 15:18:47 +02:00
# temp = temp + " game = %s\n structure = %s\n max = %s\n" % (self.game, self.structure, self.max)
2008-08-19 00:53:25 +02:00
temp = temp + " width = %d \n height = %d \n x = %d \n y = %d \n " % ( self . width , self . height , self . x , self . y )
if getattr ( self , ' tournament ' , 0 ) :
temp = temp + " tournament = %d \n table = %d " % ( self . tournament , self . table )
return temp
2008-11-11 15:40:31 +01:00
############################################################################
# Top-level discovery routines--these are the modules interface
2008-08-19 00:53:25 +02:00
def discover ( c ) :
2008-11-11 15:40:31 +01:00
""" Dispatch routine for finding all potential poker client windows. """
2008-09-15 22:31:55 +02:00
if os . name == ' posix ' :
tables = discover_posix ( c )
elif os . name == ' nt ' :
tables = discover_nt ( c )
2008-10-24 21:44:53 +02:00
elif os . name == ' mac ' :
2008-09-15 22:31:55 +02:00
tables = discover_mac ( c )
2008-10-25 11:36:18 +02:00
else :
tables = { }
2008-11-11 15:40:31 +01:00
return tables
2008-09-15 22:31:55 +02:00
2008-10-24 21:44:53 +02:00
def discover_table_by_name ( c , tablename ) :
2008-11-11 15:40:31 +01:00
""" Dispatch routine for finding poker client windows with the given name. """
2008-10-24 21:44:53 +02:00
if os . name == ' posix ' :
2008-11-11 15:40:31 +01:00
info = discover_posix_by_name ( c , tablename )
2008-10-24 21:44:53 +02:00
elif os . name == ' nt ' :
2008-11-11 15:40:31 +01:00
info = discover_nt_by_name ( c , tablename )
2008-10-24 21:44:53 +02:00
elif os . name == ' mac ' :
2008-11-11 15:40:31 +01:00
info = discover_mac_by_name ( c , tablename )
2008-10-25 11:36:18 +02:00
else :
2008-11-11 15:40:31 +01:00
return None
if info == None :
return None
return Table_Window ( info )
2008-10-24 21:44:53 +02:00
2008-11-11 15:40:31 +01:00
def discover_tournament_table ( c , tour_number , tab_number ) :
""" Dispatch routine for finding poker clients with tour and table number. """
if os . name == ' posix ' :
info = discover_posix_tournament ( c , tour_number , tab_number )
elif os . name == ' nt ' :
info = discover_nt_tournament ( c , tour_number , tab_number )
elif os . name == ' mac ' :
info = discover_mac_tournament ( c , tour_number , tab_number )
else :
return None
if info :
return Table_Window ( info )
return None
#############################################################################
# Posix (= XWindows) specific routines
2008-09-15 22:31:55 +02:00
def discover_posix ( c ) :
2008-11-11 15:40:31 +01:00
""" Poker client table window finder for posix/Linux = XWindows. """
2008-08-19 00:53:25 +02:00
tables = { }
for listing in os . popen ( ' xwininfo -root -tree ' ) . readlines ( ) :
2008-09-15 22:31:55 +02:00
# xwininfo -root -tree -id 0xnnnnn gets the info on a single window
2008-11-11 15:40:31 +01:00
for s in c . get_supported_sites ( ) :
params = c . get_site_parameters ( s )
2009-10-27 04:28:27 +01:00
2009-01-08 12:17:56 +01:00
# TODO: We need to make a list of phrases, shared between the WIndows and Unix code!!!!!!
2008-11-11 15:40:31 +01:00
if re . search ( params [ ' table_finder ' ] , listing ) :
2009-01-08 12:17:56 +01:00
if ' Lobby ' in listing : continue
if ' Instant Hand History ' in listing : continue
2009-04-07 05:45:56 +02:00
# if '\"Full Tilt Poker\"' in listing: continue
2009-01-08 12:17:56 +01:00
if ' History for table: ' in listing : continue
2009-01-08 12:25:25 +01:00
if ' has no name ' in listing : continue
2008-11-11 15:40:31 +01:00
info = decode_xwininfo ( c , listing )
if info [ ' site ' ] == None : continue
if info [ ' title ' ] == info [ ' exe ' ] : continue
# this appears to be a poker client, so make a table object for it
tw = Table_Window ( info )
eval ( " %s (tw) " % params [ ' decoder ' ] )
2008-08-19 00:53:25 +02:00
tables [ tw . name ] = tw
return tables
2008-10-24 21:44:53 +02:00
def discover_posix_by_name ( c , tablename ) :
2008-11-11 15:40:31 +01:00
""" Find an XWindows poker client of the given name. """
for listing in os . popen ( ' xwininfo -root -tree ' ) . readlines ( ) :
2009-01-08 12:17:56 +01:00
if tablename in listing :
if ' History for table: ' in listing : continue
2008-11-11 15:40:31 +01:00
info = decode_xwininfo ( c , listing )
if not info [ ' name ' ] == tablename : continue
return info
2008-12-31 19:14:28 +01:00
return None
2008-09-15 22:31:55 +02:00
2008-11-11 15:40:31 +01:00
def discover_posix_tournament ( c , t_number , s_number ) :
""" Finds the X window for a client, given tournament and table nos. """
search_string = " %s .+Table \ s %s " % ( t_number , s_number )
for listing in os . popen ( ' xwininfo -root -tree ' ) . readlines ( ) :
if re . search ( search_string , listing ) :
return decode_xwininfo ( c , listing )
2008-12-31 19:14:28 +01:00
return None
2008-09-15 22:31:55 +02:00
2008-11-11 15:40:31 +01:00
def decode_xwininfo ( c , info_string ) :
""" Gets window parameters from xwinifo string--XWindows. """
info = { }
mo = re . match ( ' \ s+([ \ dxabcdef]+) (.+): \ s \ ( \" ([a-zA-Z.]+) \" .+ ( \ d+)x( \ d+) \ + \ d+ \ + \ d+ \ +( \ d+) \ +( \ d+) ' , info_string )
if not mo :
return None
else :
info [ ' number ' ] = int ( mo . group ( 1 ) , 0 )
info [ ' exe ' ] = mo . group ( 3 )
info [ ' width ' ] = int ( mo . group ( 4 ) )
info [ ' height ' ] = int ( mo . group ( 5 ) )
info [ ' x ' ] = int ( mo . group ( 6 ) )
info [ ' y ' ] = int ( mo . group ( 7 ) )
info [ ' site ' ] = get_site_from_exe ( c , info [ ' exe ' ] )
info [ ' title ' ] = re . sub ( ' \" ' , ' ' , mo . group ( 2 ) )
title_bits = re . split ( ' - ' , info [ ' title ' ] )
info [ ' name ' ] = clean_title ( title_bits [ 0 ] )
return info
2008-09-15 22:31:55 +02:00
2008-11-11 15:40:31 +01:00
##############################################################################
# NT (= Windows) specific routines
2008-09-15 22:31:55 +02:00
def discover_nt ( c ) :
""" Poker client table window finder for Windows. """
#
# I cannot figure out how to get the inside dimensions of the poker table
# windows. So I just assume all borders are 3 thick and all title bars
# are 29 high. No doubt this will be off when used with certain themes.
#
b_width = 3
tb_height = 29
titles = { }
tables = { }
win32gui . EnumWindows ( win_enum_handler , titles )
2009-01-08 12:17:56 +01:00
for hwnd in titles :
if ' Logged In as ' in titles [ hwnd ] and not ' Lobby ' in titles [ hwnd ] :
if ' Full Tilt Poker ' in titles [ hwnd ] :
2008-09-26 15:18:47 +02:00
continue
2008-09-15 22:31:55 +02:00
tw = Table_Window ( )
tw . number = hwnd
( x , y , width , height ) = win32gui . GetWindowRect ( hwnd )
tw . title = titles [ hwnd ]
tw . width = int ( width ) - 2 * b_width
tw . height = int ( height ) - b_width - tb_height
tw . x = int ( x ) + b_width
tw . y = int ( y ) + tb_height
2009-01-08 12:17:56 +01:00
# TODO: Isn't the site being determined by the EXE name it belongs to? is this section of code even useful? cleaning it anyway
if ' Logged In as ' in titles [ hwnd ] :
2008-09-26 15:18:47 +02:00
tw . site = " PokerStars "
2009-01-08 12:17:56 +01:00
elif ' Logged In As ' in titles [ hwnd ] :
2008-09-26 15:18:47 +02:00
tw . site = " Full Tilt "
else :
tw . site = " Unknown "
sys . stderr . write ( " Found unknown table = %s " % tw . title )
2009-03-26 19:57:40 +01:00
if tw . site != " Unknown " :
2008-09-26 15:18:47 +02:00
eval ( " %s (tw) " % c . supported_sites [ tw . site ] . decoder )
else :
tw . name = " Unknown "
2008-10-24 21:44:53 +02:00
tables [ len ( tables ) ] = tw
2008-09-15 22:31:55 +02:00
return tables
2008-10-24 21:44:53 +02:00
def discover_nt_by_name ( c , tablename ) :
2008-11-11 15:40:31 +01:00
""" Finds poker client window with the given table name. """
2008-11-11 20:25:56 +01:00
titles = { }
2008-10-24 21:44:53 +02:00
win32gui . EnumWindows ( win_enum_handler , titles )
2009-08-09 14:24:31 +02:00
2009-01-06 11:18:45 +01:00
for hwnd in titles :
2009-06-26 00:10:37 +02:00
#print "Tables.py: tablename =", tablename, "title =", titles[hwnd]
2009-06-18 00:05:20 +02:00
try :
2009-08-09 14:24:31 +02:00
# maybe it's better to make global titles[hwnd] decoding?
2009-06-18 00:05:20 +02:00
# this can blow up in XP on some windows, eg firefox displaying http://docs.python.org/tutorial/classes.html
2009-08-12 19:55:19 +02:00
if not tablename . lower ( ) in titles [ hwnd ] . decode ( LOCALE_ENCODING ) . lower ( ) : continue
2009-06-18 00:05:20 +02:00
except :
continue
2009-01-08 12:17:56 +01:00
if ' History for table: ' in titles [ hwnd ] : continue # Everleaf Network HH viewer window
if ' HUD: ' in titles [ hwnd ] : continue # FPDB HUD window
if ' Chat: ' in titles [ hwnd ] : continue # Some sites (FTP? PS? Others?) have seperable or seperately constructed chat windows
2009-08-27 11:28:59 +02:00
if ' - Table ' in titles [ hwnd ] : continue # Absolute table Chat window.. sigh. TODO: Can we tell what site we're trying to discover for somehow in here, so i can limit this check just to AP searches?
temp = decode_windows ( c , titles [ hwnd ] , hwnd )
#print "attach to window", temp
2008-11-11 15:40:31 +01:00
return decode_windows ( c , titles [ hwnd ] , hwnd )
2008-12-31 19:14:28 +01:00
return None
2008-10-24 21:44:53 +02:00
2008-11-11 15:40:31 +01:00
def discover_nt_tournament ( c , tour_number , tab_number ) :
""" Finds the Windows window handle for the given tournament/table. """
2008-11-11 20:25:56 +01:00
search_string = " %s .+ %s " % ( tour_number , tab_number )
2008-08-19 00:53:25 +02:00
2008-11-11 15:40:31 +01:00
titles = { }
win32gui . EnumWindows ( win_enum_handler , titles )
2009-01-06 11:18:45 +01:00
for hwnd in titles :
2009-09-12 23:14:55 +02:00
if ' Chat: ' in titles [ hwnd ] : continue # Some sites (FTP? PS? Others?) have seperable or seperately constructed chat windows
if ' History for table: ' in titles [ hwnd ] : continue # Everleaf Network HH viewer window
if ' HUD: ' in titles [ hwnd ] : continue # FPDB HUD window
2008-11-11 15:40:31 +01:00
if re . search ( search_string , titles [ hwnd ] ) :
return decode_windows ( c , titles [ hwnd ] , hwnd )
2008-12-31 19:14:28 +01:00
return None
2008-11-11 15:40:31 +01:00
def get_nt_exe ( hwnd ) :
""" Finds the name of the executable that the given window handle belongs to. """
2009-01-15 17:09:30 +01:00
# Request privileges to enable "debug process", so we can later use PROCESS_VM_READ, retardedly required to GetModuleFileNameEx()
priv_flags = win32security . TOKEN_ADJUST_PRIVILEGES | win32security . TOKEN_QUERY
hToken = win32security . O penProcessToken ( win32api . GetCurrentProcess ( ) , priv_flags )
# enable "debug process"
privilege_id = win32security . LookupPrivilegeValue ( None , win32security . SE_DEBUG_NAME )
old_privs = win32security . AdjustTokenPrivileges ( hToken , 0 , [ ( privilege_id , win32security . SE_PRIVILEGE_ENABLED ) ] )
# Open the process, and query it's filename
2008-11-11 15:40:31 +01:00
processid = win32process . GetWindowThreadProcessId ( hwnd )
pshandle = win32api . OpenProcess ( win32con . PROCESS_QUERY_INFORMATION | win32con . PROCESS_VM_READ , False , processid [ 1 ] )
2008-12-09 07:43:13 +01:00
exename = win32process . GetModuleFileNameEx ( pshandle , 0 )
2009-01-15 17:09:30 +01:00
# clean up
2008-12-09 07:43:13 +01:00
win32api . CloseHandle ( pshandle )
2009-01-15 17:09:30 +01:00
win32api . CloseHandle ( hToken )
2008-12-09 07:43:13 +01:00
return exename
2008-11-11 15:40:31 +01:00
def decode_windows ( c , title , hwnd ) :
""" Gets window parameters from the window title and handle--Windows. """
# I cannot figure out how to get the inside dimensions of the poker table
# windows. So I just assume all borders are 3 thick and all title bars
# are 29 high. No doubt this will be off when used with certain themes.
b_width = 3
tb_height = 29
info = { }
info [ ' number ' ] = hwnd
info [ ' title ' ] = re . sub ( ' \" ' , ' ' , title )
2008-11-11 20:25:56 +01:00
( x , y , width , height ) = win32gui . GetWindowRect ( hwnd )
2008-11-11 15:40:31 +01:00
info [ ' x ' ] = int ( x ) + b_width
info [ ' y ' ] = int ( y ) + tb_height
info [ ' width ' ] = int ( width ) - 2 * b_width
info [ ' height ' ] = int ( height ) - b_width - tb_height
2008-11-11 20:25:56 +01:00
info [ ' exe ' ] = get_nt_exe ( hwnd )
2008-11-11 15:40:31 +01:00
title_bits = re . split ( ' - ' , info [ ' title ' ] )
info [ ' name ' ] = title_bits [ 0 ]
info [ ' site ' ] = get_site_from_exe ( c , info [ ' exe ' ] )
2008-10-24 21:44:53 +02:00
2008-11-11 15:40:31 +01:00
return info
def win_enum_handler ( hwnd , titles ) :
2009-08-06 08:10:49 +02:00
str = win32gui . GetWindowText ( hwnd )
if str != " " :
titles [ hwnd ] = win32gui . GetWindowText ( hwnd )
2008-11-11 15:40:31 +01:00
###################################################################
# Utility routines used by all the discoverers.
def get_site_from_exe ( c , exe ) :
""" Look up the site from config, given the exe. """
for s in c . get_supported_sites ( ) :
params = c . get_site_parameters ( s )
if re . search ( params [ ' table_finder ' ] , exe ) :
return params [ ' site_name ' ]
return None
2009-05-30 18:02:31 +02:00
def everleaf_decode_table ( tw ) :
# 2 - Tournament ID: 573256 - NL Hold'em - 150/300 blinds - Good luck <username>! - [Connection is ...]
pass
2008-08-19 00:53:25 +02:00
def pokerstars_decode_table ( tw ) :
2008-11-11 15:40:31 +01:00
# Extract poker information from the window title. This is not needed for
# fpdb, since all that information is available in the db via new_hand_number.
# This is needed only when using the HUD with a backend less integrated.
2008-08-19 00:53:25 +02:00
title_bits = re . split ( ' - ' , tw . title )
name = title_bits [ 0 ]
mo = re . search ( ' Tournament ( \ d+) Table ( \ d+) ' , name )
if mo :
tw . tournament = int ( mo . group ( 1 ) )
tw . table = int ( mo . group ( 2 ) )
tw . name = name
else :
tw . tournament = None
2008-11-11 15:40:31 +01:00
tw . name = clean_title ( name )
mo = re . search ( ' (Razz|Stud H/L|Stud|Omaha H/L|Omaha|Hold \' em|5-Card Draw|Triple Draw 2-7 Lowball|Badugi) ' , tw . title )
2008-08-19 00:53:25 +02:00
tw . game = mo . group ( 1 ) . lower ( )
tw . game = re . sub ( ' \' ' , ' ' , tw . game )
tw . game = re . sub ( ' h/l ' , ' hi/lo ' , tw . game )
mo = re . search ( ' (No Limit|Pot Limit) ' , tw . title )
if mo :
tw . structure = mo . group ( 1 ) . lower ( )
else :
tw . structure = ' limit '
tw . max = None
if tw . game in ( ' razz ' , ' stud ' , ' stud hi/lo ' ) :
tw . max = 8
elif tw . game in ( ' 5-card draw ' , ' triple draw 2-7 lowball ' ) :
tw . max = 6
elif tw . game == ' holdem ' :
pass
elif tw . game in ( ' omaha ' , ' omaha hi/lo ' ) :
pass
2008-09-26 15:18:47 +02:00
def fulltilt_decode_table ( tw ) :
2008-11-11 15:40:31 +01:00
# Extract poker information from the window title. This is not needed for
# fpdb, since all that information is available in the db via new_hand_number.
# This is needed only when using the HUD with a backend less integrated.
2008-09-26 15:18:47 +02:00
title_bits = re . split ( ' - ' , tw . title )
name = title_bits [ 0 ]
tw . tournament = None
2008-11-11 15:40:31 +01:00
tw . name = clean_title ( name )
def clean_title ( name ) :
""" Clean the little info strings from the table name. """
# these strings could go in a config file
for pattern in [ ' \ (6 max \ ) ' , ' \ (heads up \ ) ' , ' \ (deep \ ) ' ,
2009-10-01 04:49:59 +02:00
' \ (deep hu \ ) ' , ' \ (deep 6 \ ) ' , ' \ (6 max, deep \ ) ' , ' \ (2 \ ) ' ,
2008-11-11 15:40:31 +01:00
' \ (edu \ ) ' , ' \ (edu, 6 max \ ) ' , ' \ (6 \ ) ' ,
2009-07-03 19:23:30 +02:00
' \ (speed \ ) ' , ' special ' , ' newVPP ' ,
2009-02-11 08:40:33 +01:00
' no all-in ' , ' fast ' , ' , ' , ' 50BB min ' , ' 50bb min ' , ' \ s+$ ' ] :
2008-09-26 15:18:47 +02:00
name = re . sub ( pattern , ' ' , name )
2008-11-11 15:40:31 +01:00
name = name . rstrip ( )
return name
###########################################################################
# Mac specific routines....all stubs for now
def discover_mac_tournament ( c , tour_number , tab_number ) :
""" Mac users need help. """
return None
def discover_mac ( c ) :
""" Poker client table window finder for Macintosh. """
tables = { }
return tables
def discover_mac_by_name ( c , tablename ) :
""" Oh, the humanity. """
# again, i have no mac to test this on, sorry -eric
return None
2008-12-13 00:30:18 +01:00
###########################################################################
# Main function used for testing
2008-08-19 00:53:25 +02:00
if __name__ == " __main__ " :
c = Configuration . Config ( )
2008-11-11 15:40:31 +01:00
2009-04-07 05:45:56 +02:00
print discover_table_by_name ( c , " Torino " )
2008-12-08 20:10:45 +01:00
# print discover_tournament_table(c, "118942908", "3")
2008-11-11 15:40:31 +01:00
2009-02-26 17:53:31 +01:00
tables = discover ( c )
for t in tables . keys ( ) :
print tables [ t ]
2008-09-15 22:31:55 +02:00
print " press enter to continue "
sys . stdin . readline ( )