diff --git a/pyfpdb/TableWindow.py b/pyfpdb/TableWindow.py new file mode 100644 index 00000000..bf62a69c --- /dev/null +++ b/pyfpdb/TableWindow.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python +"""Discover_TableWindow.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 - 2009, 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 +import sys + +# pyGTK modules +import pygtk +import gtk +import gobject + +# FreePokerTools modules +import Configuration +#if os.name == "posix": +# import XTables +#elif os.name == "nt": +# import WinTables + +# Global used for figuring out the current game being played from the title +# The dict key is the fpdb name 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 +# 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") + } + +# A window title might have our table name + one of theses words/ +# phrases. If it has this word in the title, it is not a table. +bad_words = ('History for table:', 'HUD:', 'Chat:') + +# 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 +# is moved, resized, or closed on of these signals is emitted to the +# HUD main window. +gobject.signal_new("client_moved", gtk.Window, + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)) + +gobject.signal_new("client_resized", gtk.Window, + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)) + +gobject.signal_new("client_destroyed", 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. +# 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. + +class Table_Window(object): + def __init__(self, table_name = None, tournament = None, table_number = None): + + if table_name != None: + search_string = table_name + self.name = table_name + self.tournament = None + self.table = None + elif tournament != None and table_number != 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) + search_string = "%s.+Table\s%s" % (tournament, table_number) + else: + return None + + self.find_table_parameters(search_string) + + 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) +# temp = temp + " game = %s\n structure = %s\n max = %s\n" % (self.game, self.structure, self.max) + 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 + + def get_game(self): + title = self.get_window_title() + print title + for game, names in game_names.iteritems(): + for name in names: + if name in title: + return game + return None + + def check_geometry(self): + new_geo = self.get_geometry() + + if new_geo == 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" + + else: return False # window not changed + + def check_bad_words(self, title): + for word in bad_words: + if word in title: return True + return False \ No newline at end of file diff --git a/pyfpdb/Tables_Demo.py b/pyfpdb/Tables_Demo.py new file mode 100644 index 00000000..ffc8d412 --- /dev/null +++ b/pyfpdb/Tables_Demo.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +"""Tables_Demo.py + +Main program module to test/demo the Tables subclasses. +""" +# Copyright 2008 - 2009, 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 sys +import os +import re + +# pyGTK modules +import pygtk +import gtk +import gobject + +# fpdb/free poker tools modules +# get the correct module for the current os +if os.name == 'posix': + import XTables as Tables +elif os.name == 'nt': + import WinTables as Tables + +# Main function used for testing +if __name__=="__main__": +# c = Configuration.Config() + + class fake_hud(object): + def __init__(self, table, dx = 100, dy = 100): + self.table = table + self.dx = dx + self.dy = dy + + self.main_window = gtk.Window() + self.main_window.connect("destroy", self.client_destroyed) + self.label = gtk.Label('Fake Fake Fake Fake\nFake\nFake\nFake') + self.main_window.add(self.label) + self.main_window.set_title("Fake HUD Main Window") + self.main_window.move(table.x + dx, table.y + dy) + self.main_window.show_all() + table.topify(self) + 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) + + 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" + + def client_destroyed(self, *args): # call back for terminating the main eventloop + gtk.main_quit() + + def check_on_table(table, hud): + result = table.check_geometry() + if result != False: + hud.main_window.emit(result, hud) + return True + + print "enter table name to find: ", + table_name = sys.stdin.readline() + if "," in table_name: # tournament + print "tournament" + (tour_no, tab_no) = table_name.split(",", 1) + tour_no = tour_no.rstrip() + tab_no = tab_no.rstrip() + table = Tables.Table(tournament = tour_no, table_number = tab_no) + else: # not a tournament + print "cash game" + table_name = table_name.rstrip() + table = Tables.Table(table_name = table_name) + + print "table =", table + print "game =", table.get_game() + + fake = fake_hud(table) + gobject.timeout_add(100, check_on_table, table, fake) + gtk.main() + diff --git a/pyfpdb/WinTables.py b/pyfpdb/WinTables.py new file mode 100644 index 00000000..1b064eef --- /dev/null +++ b/pyfpdb/WinTables.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +"""WinTables.py + +Routines for detecting and handling poker client windows for MS Windows. +""" +# Copyright 2008 - 2009, 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 re + +# pyGTK modules +import pygtk +import gtk + +# Other Library modules +import win32gui +import win32process +import win32api +import win32con +import win32security + +# FreePokerTools modules +from TableWindow import Table_Window + +# We don't know the border width or title bar height +# so we guess here. We can probably get these from a windows call. +b_width = 3 +tb_height = 29 + +class Table(Table_Window): + + def find_table_parameters(self, search_string): + """Finds poker client window with the given table name.""" + titles = {} + win32gui.EnumWindows(win_enum_handler, titles) + for hwnd in titles: + 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 + self.window = hwnd + break + + if self.window == None: + print "Window %s not found. Skipping." % search_string + return None + + (x, y, width, height) = win32gui.GetWindowRect(hwnd) + print "x = %s y = %s width = %s height = %s" % (x, y, width, height) + self.x = int(x) + b_width + self.y = int(y) + tb_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] + self.site = "" + self.hud = None + self.number = gtk.gdk.window_foreign_new(long(self.window)) + + def get_geometry(self): + + if not win32gui.IsWindow(self.window): # window closed + return None + + try: + (x, y, width, height) = win32gui.GetWindowRect(hwnd) + return {'x' : int(x) + b_width, + 'y' : int(y) + tb_height, + 'width' : int(height) - b_width - tb_height, + 'height' : int(width) - 2*b_width + } + except: + return None + + def get_window_title(self): + return win32gui.GetWindowText(self.window) + + def get_nt_exe(self, hwnd): + """Finds the name of the executable that the given window handle belongs to.""" + + # 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.OpenProcessToken (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 + processid = win32process.GetWindowThreadProcessId(hwnd) + pshandle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, False, processid[1]) + exename = win32process.GetModuleFileNameEx(pshandle, 0) + + # clean up + win32api.CloseHandle(pshandle) + win32api.CloseHandle(hToken) + + return exename +def win_enum_handler(hwnd, titles): + titles[hwnd] = win32gui.GetWindowText(hwnd) + +def topify_window(hud, window): + """Set the specified gtk window to stayontop in MS Windows.""" + + def windowEnumerationHandler(hwnd, resultList): + '''Callback for win32gui.EnumWindows() to generate list of window handles.''' + resultList.append((hwnd, win32gui.GetWindowText(hwnd))) + + unique_name = 'unique name for finding this window' + real_name = window.get_title() + window.set_title(unique_name) + tl_windows = [] + win32gui.EnumWindows(windowEnumerationHandler, tl_windows) + + for w in tl_windows: + if w[1] == unique_name: + hud.main_window.parentgdkhandle = gtk.gdk.window_foreign_new(long(hud.table.number)) + hud.main_window.gdkhandle = gtk.gdk.window_foreign_new(w[0]) + hud.main_window.gdkhandle.set_transient_for(hud.main_window.parentgdkhandle) + + style = win32gui.GetWindowLong(self.table.number, win32con.GWL_EXSTYLE) + style |= win32con.WS_CLIPCHILDREN + win32gui.SetWindowLong(hud.table.number, win32con.GWL_EXSTYLE, style) + break + + window.set_title(real_name) + diff --git a/pyfpdb/XTables.py b/pyfpdb/XTables.py new file mode 100644 index 00000000..1e692209 --- /dev/null +++ b/pyfpdb/XTables.py @@ -0,0 +1,101 @@ +#!/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 - 2009, 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 re + +# pyGTK modules +import pygtk +import gtk + +# Other Library modules +import Xlib +import Xlib.display + +# FreePokerTools 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 + +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 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: + print "Window %s not found. Skipping." % search_string + return None + + 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 + + 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) + self.gdk_handle = gtk.gdk.window_foreign_new(int(self.number)) + + 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, + 'width' : my_geo.width, + 'height' : my_geo.height + } + except: + return None + + def get_window_title(self): + return self.window.get_wm_name() + + 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)