diff --git a/pyfpdb/HandHistoryConverter.py b/pyfpdb/HandHistoryConverter.py index 8eb2b9b6..a145d4dc 100644 --- a/pyfpdb/HandHistoryConverter.py +++ b/pyfpdb/HandHistoryConverter.py @@ -669,12 +669,23 @@ or None if we fail to get the info """ else: return table_name - + @staticmethod + def getTableNoRe(tournament): + "Returns string to search window title for tournament table no." +# Full Tilt: $30 + $3 Tournament (181398949), Table 1 - 600/1200 Ante 100 - Limit Razz +# PokerStars: WCOOP 2nd Chance 02: $1,050 NLHE - Tournament 307521826 Table 1 - Blinds $30/$60 + return "%s.+Table (\d+)" % (tournament, ) def getTableTitleRe(config, sitename, *args, **kwargs): "Returns string to search in windows titles for current site" return getSiteHhc(config, sitename).getTableTitleRe(*args, **kwargs) +def getTableNoRe(config, sitename, *args, **kwargs): + "Returns string to search window titles for tournament table no." + return getSiteHhc(config, sitename).getTableNoRe(*args, **kwargs) + + + def getSiteHhc(config, sitename): "Returns HHC class for current site" hhcName = config.supported_sites[sitename].converter diff --git a/pyfpdb/TableWindow.py b/pyfpdb/TableWindow.py index 5aed1f04..55e1e549 100644 --- a/pyfpdb/TableWindow.py +++ b/pyfpdb/TableWindow.py @@ -1,12 +1,14 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -"""Discover_TableWindow.py +"""Base class for interacting with poker client windows. -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. +There are currently subclasses for X and Windows. + +The class queries the poker client window for data of interest, such as +size and location. It also controls the signals to alert the HUD when the +client has been resized, destroyed, etc. """ -# Copyright 2008-2010, Ray E. Barker +# Copyright 2008 - 2010, 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 @@ -25,37 +27,38 @@ of Table_Window objects representing the windows found. ######################################################################## # Standard Library modules -import os -import sys +import re # pyGTK modules -import pygtk import gtk import gobject # FreePokerTools modules -import Configuration -#if os.name == "posix": -# import XTables -#elif os.name == "nt": -# import WinTables +from HandHistoryConverter import getTableTitleRe +from HandHistoryConverter import getTableNoRe -# Global used for figuring out the current game being played from the title -# The dict key is the fpdb name for the game +# Global used for figuring out the current game being played from the title. +# The dict key is a tuple of (limit type, category) for the game. # The list is the names for those games used by the supported poker sites -# This is currently only used for HORSE, so it only needs to support those +# This is currently only used for mixed games, so it only needs to support those # games on PokerStars and Full Tilt. -game_names = { #fpdb name Stars Name FTP Name - "holdem" : ("Hold\'em" , ), - "omahahilo" : ("Omaha H/L" , ), - "studhilo" : ("Stud H/L" , ), - "razz" : ("Razz" , ), - "studhi" : ("Stud" , "Stud Hi") +nlpl_game_names = { #fpdb name Stars Name FTP Name (if different) + ("nl", "holdem" ) : ("No Limit Hold\'em" , ), + ("pl", "holdem" ) : ("Pot Limit Hold\'em" , ), + ("pl", "omahahi" ) : ("Pot Limit Omaha" ,"Pot Limit Omaha Hi" ), + } +limit_game_names = { #fpdb name Stars Name FTP Name + ("fl", "holdem" ) : ("Limit Hold\'em" , ), + ("fl", "omahahilo" ) : ("Limit Omaha H/L" , ), + ("fl", "studhilo" ) : ("Limit Stud H/L" , ), + ("fl", "razz" ) : ("Limit Razz" , ), + ("fl", "studhi" ) : ("Limit Stud" , "Stud Hi"), + ("fl", "27_3draw" ) : ("Limit Triple Draw 2-7 Lowball", ) } -# A window title might have our table name + one of theses words/ +# A window title might have our table name + one of these words/ # phrases. If it has this word in the title, it is not a table. -bad_words = ('History for table:', 'HUD:', 'Chat:') +bad_words = ('History for table:', 'HUD:', 'Chat:', 'FPDBHUD') # Here are the custom signals we define for allowing the 'client watcher' # thread to communicate with the gui thread. Any time a poker client is @@ -76,11 +79,19 @@ gobject.signal_new("client_destroyed", gtk.Window, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) +gobject.signal_new("game_changed", gtk.Window, + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)) + +gobject.signal_new("table_changed", gtk.Window, + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)) + # 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. +# from the corresponding hand record in the db. # 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. @@ -92,61 +103,143 @@ gobject.signal_new("client_destroyed", gtk.Window, # 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. +# tournament = Tournament number for a tournament or None for a cash game. +# table = Table number for a tournament. +# gdkhandle = +# window = +# parent = +# game = +# search_string = class Table_Window(object): - def __init__(self, search_string, table_name = None, tournament = None, table_number = None): + def __init__(self, config, site, table_name = None, tournament = None, table_number = None): + self.config = config + self.site = site if tournament is not None and table_number is not None: - print "tournament %s, table %s" % (tournament, table_number) self.tournament = int(tournament) self.table = int(table_number) self.name = "%s - %s" % (self.tournament, self.table) + self.type = "tour" + table_kwargs = dict(tournament = self.tournament, table_number = self.table) + self.tableno_re = getTableNoRe(self.config, self.site, tournament = self.tournament) elif table_name is not None: - # search_string = table_name self.name = table_name + self.type = "cash" self.tournament = None + table_kwargs = dict(table_name = table_name) + else: return None - self.find_table_parameters(search_string) + self.search_string = getTableTitleRe(self.config, self.site, self.type, **table_kwargs) + self.find_table_parameters() def __str__(self): # __str__ method for testing - likely_attrs = ("site", "number", "title", "width", "height", "x", "y", - "tournament", "table", "gdkhandle") + likely_attrs = ("number", "title", "site", "width", "height", "x", "y", + "tournament", "table", "gdkhandle", "window", "parent", + "game", "search_string", "tableno_re") temp = 'TableWindow object\n' for a in likely_attrs: if getattr(self, a, 0): temp += " %s = %s\n" % (a, getattr(self, a)) return temp +#################################################################### +# "get" methods. These query the table and return the info to get. +# They don't change the data in the table and are generally used +# by the "check" methods. Most of the get methods are in the +# subclass because they are specific to X, Windows, etc. def get_game(self): title = self.get_window_title() - print title - for game, names in game_names.iteritems(): + if title is None: + return False + +# check for nl and pl games first, to avoid bad matches + for game, names in nlpl_game_names.iteritems(): for name in names: if name in title: return game - return None + for game, names in limit_game_names.iteritems(): + for name in names: + if name in title: + return game + return False - def check_geometry(self): + def get_table_no(self): + new_title = self.get_window_title() + if new_title is None: + return False + + mo = re.search(self.tableno_re, new_title) + if mo is not None: + return mo[1] + return False + +#################################################################### +# check_table() is meant to be called by the hud periodically to +# determine if the client has been moved or resized. check_table() +# also checks and signals if the client has been closed. + def check_table(self, hud): + result = self.check_size() + if result != False: + hud.main_window.emit(result, hud) + if result == "client_destroyed": + return True + + result = self.check_loc() + if result != False: + hud.main_window.emit(result, hud) + if result == "client_destroyed": + return True + return True + +#################################################################### +# "check" methods. They use the corresponding get method, update the +# table object and return the name of the signal to be emitted or +# False if unchanged. These do not signal for destroyed +# clients to prevent a race condition. + +# These might be called by a Window.timeout, so they must not +# return False, or the timeout will be cancelled. + def check_game(self, hud): + new_game = self.get_game() + if new_game is not None and self.game != new_game: + self.game = new_game + hud.main_window.emit("game_changed", hud) + return "game_changed" + return True + + def check_size(self): new_geo = self.get_geometry() - if new_geo is None: # window destroyed return "client_destroyed" - elif self.x != new_geo['x'] or self.y != new_geo['y']: # window moved - self.x = new_geo['x'] - self.y = new_geo['y'] - return "client_moved" - elif self.width != new_geo['width'] or self.height != new_geo['height']: # window resized self.width = new_geo['width'] self.height = new_geo['height'] return "client_resized" + return False # no change - else: return False # window not changed + def check_loc(self): + new_geo = self.get_geometry() + if new_geo is None: # window destroyed + return "client_destroyed" + if self.x != new_geo['x'] or self.y != new_geo['y']: # window moved + self.x = new_geo['x'] + self.y = new_geo['y'] + return "client_moved" + return False # no change + + def check_table_no(self, hud): + result = self.get_table_no() + if result != False and result != self.table: + self.table = result + hud.main_window.emit("table_changed", hud) + return True + def check_bad_words(self, title): for word in bad_words: if word in title: return True diff --git a/pyfpdb/Tables_Demo.py b/pyfpdb/Tables_Demo.py index f583ae48..bb81e2f1 100755 --- a/pyfpdb/Tables_Demo.py +++ b/pyfpdb/Tables_Demo.py @@ -25,16 +25,13 @@ Main program module to test/demo the Tables subclasses. # Standard Library modules import sys import os -import re # pyGTK modules -import pygtk import gtk import gobject # fpdb/free poker tools modules import Configuration -from HandHistoryConverter import getTableTitleRe import locale lang=locale.getdefaultlocale()[0][0:2] @@ -73,24 +70,31 @@ if __name__=="__main__": self.main_window.move(table.x + dx, table.y + dy) self.main_window.show_all() table.topify(self) + +# These are the currently defined signals. Do this in the HUD. self.main_window.connect("client_moved", self.client_moved) self.main_window.connect("client_resized", self.client_resized) self.main_window.connect("client_destroyed", self.client_destroyed) + self.main_window.connect("game_changed", self.game_changed) + self.main_window.connect("table_changed", self.table_changed) +# And these of the handlers that go with those signals. +# These would live inside the HUD code. def client_moved(self, widget, hud): self.main_window.move(self.table.x + self.dx, self.table.y + self.dy) def client_resized(self, *args): - print "client resized" + print "Client resized" def client_destroyed(self, *args): # call back for terminating the main eventloop + print "Client destroyed." gtk.main_quit() - def check_on_table(table, hud): - result = table.check_geometry() - if result != False: - hud.main_window.emit(result, hud) - return True + def game_changed(self, *args): + print "Game Changed." + + def table_changed(self, *args): + print "Table Changed." print _("enter table name to find: "), table_name = sys.stdin.readline() @@ -107,16 +111,12 @@ if __name__=="__main__": type = "cash" table_kwargs = dict(table_name = table_name) - search_string = getTableTitleRe(config, "Full Tilt Poker", type, **table_kwargs) - table = Tables.Table(search_string, **table_kwargs) - table.gdk_handle = gtk.gdk.window_foreign_new(table.number) - - print "table =", table -# print "game =", table.get_game() + table = Tables.Table(config, "Full Tilt Poker", **table_kwargs) + print table fake = fake_hud(table) - print "fake =", fake -# gobject.timeout_add(100, check_on_table, table, fake) - print _("calling main") + gobject.timeout_add(1000, table.check_game, fake) + gobject.timeout_add(100, table.check_table, fake) + print "calling main" gtk.main() diff --git a/pyfpdb/WinTables.py b/pyfpdb/WinTables.py index adaa82be..ab608ea2 100644 --- a/pyfpdb/WinTables.py +++ b/pyfpdb/WinTables.py @@ -1,10 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -"""WinTables.py - -Routines for detecting and handling poker client windows for MS Windows. +"""Routines for detecting and handling poker client windows for MS Windows. """ -# Copyright 2008-2010, Ray E. Barker +# Copyright 2008 - 2010, 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 @@ -62,24 +60,20 @@ tb_height = 29 class Table(Table_Window): - def find_table_parameters(self, search_string): + def find_table_parameters(self): """Finds poker client window with the given table name.""" titles = {} win32gui.EnumWindows(win_enum_handler, titles) for hwnd in titles: if titles[hwnd] == "": continue - # print "searching ", search_string, " in ", titles[hwnd] - if re.search(search_string, titles[hwnd]): - 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 - if 'FPDBHUD' in titles[hwnd]: continue # can't attach to ourselves! + if re.search(self.search_string, titles[hwnd]): + if self.check_bad_words(titles[hwnd]): continue self.window = hwnd break try: if self.window == None: - log.error(_("Window %s not found. Skipping.") % search_string ) + log.error( "Window %s not found. Skipping." % self.search_string ) return None except AttributeError: log.error(_("self.window doesn't exist? why?")) @@ -92,8 +86,6 @@ class Table(Table_Window): self.width = width - x self.height = height - y log.debug("x = %s y = %s width = %s height = %s" % (self.x, self.y, self.width, self.height)) - #self.height = int(height) - b_width - tb_height - #self.width = int(width) - 2*b_width self.exe = self.get_nt_exe(hwnd) self.title = titles[hwnd] diff --git a/pyfpdb/XTables.py b/pyfpdb/XTables.py index 10587ec0..caf2251a 100644 --- a/pyfpdb/XTables.py +++ b/pyfpdb/XTables.py @@ -1,12 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -"""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. +"""XWindows specific methods for TableWindows Class. """ -# Copyright 2008-2010, Ray E. Barker +# Copyright 2008 - 2010, 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 @@ -27,99 +23,68 @@ of Table_Window objects representing the windows found. # Standard Library modules import re import os +import subprocess # pyGTK modules -import pygtk import gtk # Other Library modules -import Xlib import Xlib.display -# FreePokerTools modules +# FPDB modules from TableWindow import Table_Window # We might as well do this once and make them globals disp = Xlib.display.Display() root = disp.screen().root -name_atom = disp.get_atom("WM_NAME", 1) class Table(Table_Window): - def find_table_parameters(self, search_string): -# self.window = None -# done_looping = False -# for outside in root.query_tree().children: -# for inside in outside.query_tree().children: -# if done_looping: break -# prop = inside.get_property(name_atom, Xlib.Xatom.STRING, 0, 1000) -# print prop -# if prop is None: continue -# if prop.value and re.search(search_string, prop.value): -# if self.check_bad_words(prop.value): continue -## if inside.get_wm_name() and re.search(search_string, inside.get_wm_name()): -## if self.check_bad_words(inside.get_wm_name()): -## print "bad word =", inside.get_wm_name() -## continue -# self.window = inside -# self.parent = outside -# done_looping = True -# break + def find_table_parameters(self): - window_number = None + self.number = None for listing in os.popen('xwininfo -root -tree').readlines(): - if re.search(search_string, listing): -# print listing + if re.search(self.search_string, listing): mo = re.match('\s+([\dxabcdef]+) (.+):\s\(\"([a-zA-Z0-9\-.]+)\".+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing) + title = re.sub('\"', '', mo.group(2)) + if self.check_bad_words(title): continue self.number = int( mo.group(1), 0) - self.width = int( mo.group(4) ) - self.height = int( mo.group(5) ) - self.x = int( mo.group(6) ) - self.y = int( mo.group(7) ) - self.title = re.sub('\"', '', mo.group(2)) - self.exe = "" # not used? - self.hud = None -# done_looping = False -# for outside in root.query_tree().children: -# for inside in outside.query_tree().children: -# if done_looping: break -# if inside.id == window_number: -# self.window = inside -# self.parent = outside -# done_looping = True -# break - - if window_number is None: + self.title = title + self.exe = "" # not used? + self.hud = None # specified later + break + + if self.number is None: return None + + self.window = self.get_window_from_xid(self.number) + self.parent = self.window.query_tree().parent -# my_geo = self.window.get_geometry() -# pa_geo = self.parent.get_geometry() -# -# self.x = pa_geo.x + my_geo.x -# self.y = pa_geo.y + my_geo.y -# self.width = my_geo.width -# self.height = my_geo.height -# self.exe = self.window.get_wm_class()[0] -# self.title = self.window.get_wm_name() -# self.site = "" -# self.hud = None + geo = self.get_geometry() + if geo is None: return None + self.width = geo['width'] + self.height = geo['height'] + self.x = geo['x'] + self.y = geo['y'] -# window_string = str(self.window) - mo = re.match('Xlib\.display\.Window\(([\dxabcdef]+)', window_string) - if not mo: - print "Not matched" - self.gdk_handle = None - else: - self.number = int( mo.group(1), 0) - print "number =", self.number -# self.gdk_handle = gtk.gdk.window_foreign_new(int(self.number)) + self.game = self.get_game() + self.gdk_handle = gtk.gdk.window_foreign_new(self.number) + + def get_window_from_xid(self, id): + for outside in root.query_tree().children: + if outside.id == id: + return outside + for inside in outside.query_tree().children: + if inside.id == id: + return inside + return None def get_geometry(self): try: my_geo = self.window.get_geometry() pa_geo = self.parent.get_geometry() - return {'x' : pa_geo.x + my_geo.x, - 'y' : pa_geo.y + my_geo.y, + return {'x' : my_geo.x + pa_geo.x, + 'y' : my_geo.y + pa_geo.y, 'width' : my_geo.width, 'height' : my_geo.height } @@ -127,8 +92,14 @@ class Table(Table_Window): return None def get_window_title(self): - return self.window.get_wm_name() - + proc = subprocess.Popen("xwininfo -wm -id %d" % self.number, shell = True, stdout = subprocess.PIPE) + s = proc.stdout.read() + mo = re.search('"(.+)"', s) + try: + return mo.group(1) + except AttributeError: + return None + def topify(self, hud): hud.main_window.gdkhandle = gtk.gdk.window_foreign_new(hud.main_window.window.xid) hud.main_window.gdkhandle.set_transient_for(self.gdk_handle)