Rewrite of XTables to use Xlib. Considerable cleanup in TableWindow.

This commit is contained in:
Eratosthenes 2010-09-07 09:50:29 -04:00
parent cc4f8e2bea
commit 9772129f30
4 changed files with 212 additions and 108 deletions

View File

@ -501,12 +501,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

View File

@ -1,11 +1,13 @@
#!/usr/bin/env python
"""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 - 2009, 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
@ -24,35 +26,36 @@ 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:')
@ -75,11 +78,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.
@ -91,60 +102,142 @@ 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:

View File

@ -31,7 +31,6 @@ import gobject
# fpdb/free poker tools modules
import Configuration
from HandHistoryConverter import getTableTitleRe
# get the correct module for the current os
if os.name == 'posix':
@ -58,24 +57,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()
@ -92,16 +98,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)
gobject.timeout_add(1000, table.check_game, fake)
gobject.timeout_add(100, table.check_table, fake)
print "calling main"
gtk.main()

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python
"""XTables.py
XWindows specific methods for TableWindows Class.
"""XWindows specific methods for TableWindows Class.
"""
# Copyright 2008 - 2010, Ray E. Barker
@ -23,73 +21,67 @@ XWindows specific methods for TableWindows Class.
# Standard Library modules
import re
import os
import subprocess
# pyGTK modules
import gtk
# Other Library modules
import Xlib.display
from Xlib import Xatom
# 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)
icon_atom = disp.get_atom("WM_ICON_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
# if inside.get_wm_name() and re.search(search_string, inside.get_wm_name()):
if inside.get_wm_name() and re.search(search_string, inside.get_wm_icon_name()):
print "inside = ", inside
print "\n".join(dir(inside))
for atom in inside.list_properties(): print atom, disp.get_atom_name(atom), inside.get_full_property(atom, Xatom.STRING).value
if hasattr(inside, "window"): print "window = ", str(inside.window)
if self.check_bad_words(inside.get_wm_name()): continue
self.window = inside
self.parent = outside
done_looping = True
def find_table_parameters(self):
self.number = None
for listing in os.popen('xwininfo -root -tree').readlines():
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)
self.number = int( mo.group(1), 0)
self.title = re.sub('\"', '', mo.group(2))
self.exe = "" # not used?
self.hud = None # specified later
break
if self.window == None or self.parent == None:
print "Window %s not found. Skipping." % search_string
if self.number is None:
return None
my_geo = self.window.get_geometry()
pa_geo = self.parent.get_geometry()
self.window = self.get_window_from_xid(self.number)
self.parent = self.window.query_tree().parent
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
self.number = self.get_xid()
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']
def get_xid(self):
mo = re.match('Xlib\.display\.Window\(([\dxabcdef]+)', str(self.window))
if not mo:
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
else:
return int( mo.group(1), 0)
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
}
@ -97,7 +89,13 @@ 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)