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: else:
return table_name 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): def getTableTitleRe(config, sitename, *args, **kwargs):
"Returns string to search in windows titles for current site" "Returns string to search in windows titles for current site"
return getSiteHhc(config, sitename).getTableTitleRe(*args, **kwargs) 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): def getSiteHhc(config, sitename):
"Returns HHC class for current site" "Returns HHC class for current site"
hhcName = config.supported_sites[sitename].converter hhcName = config.supported_sites[sitename].converter

View File

@ -1,11 +1,13 @@
#!/usr/bin/env python #!/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 There are currently subclasses for X and Windows.
poker table windows from supported sites. Returns a list
of Table_Window objects representing the windows found. 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 # 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 # 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 # Standard Library modules
import os import re
import sys
# pyGTK modules # pyGTK modules
import pygtk
import gtk import gtk
import gobject import gobject
# FreePokerTools modules # FreePokerTools modules
import Configuration from HandHistoryConverter import getTableTitleRe
#if os.name == "posix": from HandHistoryConverter import getTableNoRe
# import XTables
#elif os.name == "nt":
# import WinTables
# Global used for figuring out the current game being played from the title # Global used for figuring out the current game being played from the title.
# The dict key is the fpdb name for the game # 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 # 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. # games on PokerStars and Full Tilt.
game_names = { #fpdb name Stars Name FTP Name nlpl_game_names = { #fpdb name Stars Name FTP Name (if different)
"holdem" : ("Hold\'em" , ), ("nl", "holdem" ) : ("No Limit Hold\'em" , ),
"omahahilo" : ("Omaha H/L" , ), ("pl", "holdem" ) : ("Pot Limit Hold\'em" , ),
"studhilo" : ("Stud H/L" , ), ("pl", "omahahi" ) : ("Pot Limit Omaha" ,"Pot Limit Omaha Hi" ),
"razz" : ("Razz" , ), }
"studhi" : ("Stud" , "Stud 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. # 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:')
@ -75,11 +78,19 @@ gobject.signal_new("client_destroyed", gtk.Window,
gobject.TYPE_NONE, gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,)) (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: # 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 # tw.name = the table name from the title bar, which must to match the table name
# from the corresponding hand history. # from the corresponding hand record in the db.
# 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 # 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 # format that the system presents it. This is Xid in Xwindows and
# hwnd in Microsoft Windows. # hwnd in Microsoft Windows.
@ -91,61 +102,143 @@ gobject.signal_new("client_destroyed", gtk.Window,
# to the top left of the display screen. This also does not include the # 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 # title bar and window borders. To put it another way, this is the
# screen location of (0, 0) in the working window. # 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): 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: if tournament is not None and table_number is not None:
print "tournament %s, table %s" % (tournament, table_number)
self.tournament = int(tournament) self.tournament = int(tournament)
self.table = int(table_number) self.table = int(table_number)
self.name = "%s - %s" % (self.tournament, self.table) 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: elif table_name is not None:
# search_string = table_name
self.name = table_name self.name = table_name
self.type = "cash"
self.tournament = None self.tournament = None
table_kwargs = dict(table_name = table_name)
else: else:
return None 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): def __str__(self):
# __str__ method for testing # __str__ method for testing
likely_attrs = ("site", "number", "title", "width", "height", "x", "y", likely_attrs = ("number", "title", "site", "width", "height", "x", "y",
"tournament", "table", "gdkhandle") "tournament", "table", "gdkhandle", "window", "parent",
"game", "search_string", "tableno_re")
temp = 'TableWindow object\n' temp = 'TableWindow object\n'
for a in likely_attrs: for a in likely_attrs:
if getattr(self, a, 0): if getattr(self, a, 0):
temp += " %s = %s\n" % (a, getattr(self, a)) temp += " %s = %s\n" % (a, getattr(self, a))
return temp 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): def get_game(self):
title = self.get_window_title() title = self.get_window_title()
print title if title is None:
for game, names in game_names.iteritems(): 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: for name in names:
if name in title: if name in title:
return game 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() new_geo = self.get_geometry()
if new_geo is None: # window destroyed if new_geo is None: # window destroyed
return "client_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 elif self.width != new_geo['width'] or self.height != new_geo['height']: # window resized
self.width = new_geo['width'] self.width = new_geo['width']
self.height = new_geo['height'] self.height = new_geo['height']
return "client_resized" 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): def check_bad_words(self, title):
for word in bad_words: for word in bad_words:
if word in title: return True if word in title: return True

View File

@ -31,7 +31,6 @@ import gobject
# fpdb/free poker tools modules # fpdb/free poker tools modules
import Configuration import Configuration
from HandHistoryConverter import getTableTitleRe
# get the correct module for the current os # get the correct module for the current os
if os.name == 'posix': if os.name == 'posix':
@ -58,24 +57,31 @@ if __name__=="__main__":
self.main_window.move(table.x + dx, table.y + dy) self.main_window.move(table.x + dx, table.y + dy)
self.main_window.show_all() self.main_window.show_all()
table.topify(self) 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_moved", self.client_moved)
self.main_window.connect("client_resized", self.client_resized) self.main_window.connect("client_resized", self.client_resized)
self.main_window.connect("client_destroyed", self.client_destroyed) 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): def client_moved(self, widget, hud):
self.main_window.move(self.table.x + self.dx, self.table.y + self.dy) self.main_window.move(self.table.x + self.dx, self.table.y + self.dy)
def client_resized(self, *args): def client_resized(self, *args):
print "client resized" print "Client resized"
def client_destroyed(self, *args): # call back for terminating the main eventloop def client_destroyed(self, *args): # call back for terminating the main eventloop
print "Client destroyed."
gtk.main_quit() gtk.main_quit()
def check_on_table(table, hud): def game_changed(self, *args):
result = table.check_geometry() print "Game Changed."
if result != False:
hud.main_window.emit(result, hud) def table_changed(self, *args):
return True print "Table Changed."
print "enter table name to find: ", print "enter table name to find: ",
table_name = sys.stdin.readline() table_name = sys.stdin.readline()
@ -92,16 +98,12 @@ if __name__=="__main__":
type = "cash" type = "cash"
table_kwargs = dict(table_name = table_name) table_kwargs = dict(table_name = table_name)
search_string = getTableTitleRe(config, "Full Tilt Poker", type, **table_kwargs) table = Tables.Table(config, "Full Tilt Poker", **table_kwargs)
table = Tables.Table(search_string, **table_kwargs) print table
table.gdk_handle = gtk.gdk.window_foreign_new(table.number)
print "table =", table
# print "game =", table.get_game()
fake = fake_hud(table) fake = fake_hud(table)
print "fake =", fake gobject.timeout_add(1000, table.check_game, fake)
gobject.timeout_add(100, check_on_table, table, fake) gobject.timeout_add(100, table.check_table, fake)
print "calling main" print "calling main"
gtk.main() gtk.main()

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
"""XTables.py """XWindows specific methods for TableWindows Class.
XWindows specific methods for TableWindows Class.
""" """
# Copyright 2008 - 2010, Ray E. Barker # Copyright 2008 - 2010, Ray E. Barker
@ -23,73 +21,67 @@ XWindows specific methods for TableWindows Class.
# Standard Library modules # Standard Library modules
import re import re
import os
import subprocess
# pyGTK modules # pyGTK modules
import gtk import gtk
# Other Library modules # Other Library modules
import Xlib.display import Xlib.display
from Xlib import Xatom
# FreePokerTools modules # FPDB modules
from TableWindow import Table_Window from TableWindow import Table_Window
# We might as well do this once and make them globals # We might as well do this once and make them globals
disp = Xlib.display.Display() disp = Xlib.display.Display()
root = disp.screen().root 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): class Table(Table_Window):
def find_table_parameters(self, search_string): def find_table_parameters(self):
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
break
if self.window == None or self.parent == None: self.number = None
print "Window %s not found. Skipping." % search_string for listing in os.popen('xwininfo -root -tree').readlines():
return None 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)
my_geo = self.window.get_geometry() self.number = int( mo.group(1), 0)
pa_geo = self.parent.get_geometry() self.title = re.sub('\"', '', mo.group(2))
self.exe = "" # not used?
self.x = pa_geo.x + my_geo.x self.hud = None # specified later
self.y = pa_geo.y + my_geo.y break
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()
def get_xid(self): if self.number is None:
mo = re.match('Xlib\.display\.Window\(([\dxabcdef]+)', str(self.window))
if not mo:
return None return None
else:
return int( mo.group(1), 0) self.window = self.get_window_from_xid(self.number)
self.parent = self.window.query_tree().parent
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']
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): def get_geometry(self):
try: try:
my_geo = self.window.get_geometry() my_geo = self.window.get_geometry()
pa_geo = self.parent.get_geometry() pa_geo = self.parent.get_geometry()
return {'x' : pa_geo.x + my_geo.x, return {'x' : my_geo.x + pa_geo.x,
'y' : pa_geo.y + my_geo.y, 'y' : my_geo.y + pa_geo.y,
'width' : my_geo.width, 'width' : my_geo.width,
'height' : my_geo.height 'height' : my_geo.height
} }
@ -97,8 +89,14 @@ class Table(Table_Window):
return None return None
def get_window_title(self): 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): def topify(self, hud):
hud.main_window.gdkhandle = gtk.gdk.window_foreign_new(hud.main_window.window.xid) hud.main_window.gdkhandle = gtk.gdk.window_foreign_new(hud.main_window.window.xid)
hud.main_window.gdkhandle.set_transient_for(self.gdk_handle) hud.main_window.gdkhandle.set_transient_for(self.gdk_handle)