Advertisement
JudeAustin

GUIMiner-Blake 2

Mar 18th, 2014
141
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 130.91 KB | None | 0 0
  1. #!/usr/bin/python
  2.  
  3. """GUIMiner - graphical frontend to Blake miners.
  4.  
  5. Currently supports:
  6. - cgminer
  7.  
  8.  
  9. Copyright 2011-2012 Chris MacLeod
  10. Copyright 2012 TacoTime
  11. This program is released under the GNU GPL. See LICENSE.txt for details.
  12. """
  13.  
  14. import os, sys, subprocess, errno, re, threading, logging, time, httplib, urllib, distutils.dir_util
  15. print sys.path
  16. import wx
  17. import json
  18. import collections
  19. import pyopencl
  20.  
  21. from _winreg import (
  22.     CloseKey, OpenKey, QueryValueEx, SetValueEx,
  23.     HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE,
  24.     KEY_ALL_ACCESS, KEY_READ, REG_EXPAND_SZ, REG_SZ
  25. )
  26.  
  27. """
  28. Begin startup processes
  29. """
  30.  
  31. ON_POSIX = 'posix' in sys.builtin_module_names
  32.  
  33. try:
  34.     import win32api, win32con, win32process
  35. except ImportError:
  36.     pass
  37.  
  38. from wx.lib.agw import flatnotebook as fnb
  39. from wx.lib.agw import hyperlink
  40. from wx.lib.newevent import NewEvent
  41.  
  42. __version__ = 'v0.01'
  43.  
  44. STARTUP_PATH = os.getcwd()
  45.  
  46. """
  47. End startup processes
  48. """
  49.  
  50. def get_module_path(): # Redundant with os.getcwd() at opening; not needed?  Tacotime
  51.     """Return the folder containing this script (or its .exe)."""
  52.     module_name = sys.executable if hasattr(sys, 'frozen') else __file__
  53.     abs_path = os.path.abspath(module_name)
  54.     return os.path.dirname(abs_path)
  55.  
  56. USE_MOCK = '--mock' in sys.argv
  57. # Set up localization; requires the app to be created
  58. app = wx.App(False)
  59. #Deprecated wx calls commented out.
  60. #app = wx.PySimpleApp(0)
  61. #wx.InitAllImageHandlers()
  62. _ = wx.GetTranslation
  63.  
  64. LANGUAGES = {
  65.     "Chinese Simplified": wx.LANGUAGE_CHINESE_SIMPLIFIED,
  66.     "Dutch": wx.LANGUAGE_DUTCH,
  67.     "English": wx.LANGUAGE_ENGLISH,
  68.     "Esperanto": wx.LANGUAGE_ESPERANTO,
  69.     "French": wx.LANGUAGE_FRENCH,
  70.     "German": wx.LANGUAGE_GERMAN,
  71.     "Hungarian": wx.LANGUAGE_HUNGARIAN,
  72.     "Italian": wx.LANGUAGE_ITALIAN,
  73.     "Portuguese": wx.LANGUAGE_PORTUGUESE,
  74.     "Russian": wx.LANGUAGE_RUSSIAN,
  75.     "Spanish": wx.LANGUAGE_SPANISH,
  76. }
  77. LANGUAGES_REVERSE = dict((v, k) for (k, v) in LANGUAGES.items())
  78.  
  79. DONATION_ADDRESS = "BcGtTyn5JGWziongstrmzFRcSkxxFFvR28"
  80. locale = None
  81. language = None
  82. def update_language(new_language):
  83.     global locale, language
  84.     language = new_language
  85.     if locale:
  86.         del locale
  87.  
  88.     locale = wx.Locale(language)
  89.     if locale.IsOk():
  90.         locale.AddCatalogLookupPathPrefix(os.path.join(get_module_path(), "locale"))
  91.         locale.AddCatalog("guiminer")
  92.     else:
  93.         locale = None
  94.  
  95. def load_language():
  96.     language_config = os.path.join(get_module_path(), 'default_language.ini')
  97.     language_data = dict()
  98.     if os.path.exists(language_config):
  99.         with open(language_config) as f:
  100.             language_data.update(json.load(f))
  101.     language_str = language_data.get('language', "English")
  102.     update_language(LANGUAGES.get(language_str, wx.LANGUAGE_ENGLISH))
  103.  
  104. def save_language():
  105.     language_config = os.path.join(get_module_path(), 'default_language.ini')
  106.     language_str = LANGUAGES_REVERSE.get(language)
  107.     with open(language_config, 'w') as f:
  108.         json.dump(dict(language=language_str), f)
  109.  
  110. load_language()
  111.  
  112. ABOUT_TEXT = _(
  113. """GUIMiner
  114.  
  115. Version: %(version)s
  116. Blake mod by JudeAustin
  117. GUI by Chris 'Kiv' MacLeod
  118. Original poclbm miner by m0mchil
  119. Original rpcminer by puddinpop
  120.  
  121. Get the source code or file issues at GitHub:
  122.    https://github.com/Kiv/poclbm
  123.  
  124. If you enjoyed this software, support its development
  125. by donating to:
  126.  
  127. %(address)s
  128.  
  129. Even a single Blakecoin is appreciated and helps motivate
  130. further work on this software.
  131. """)
  132.  
  133. # Translatable strings that are used repeatedly
  134. STR_NOT_STARTED = _("Not started")
  135. STR_STARTING = _("Starting")
  136. STR_STOPPED = _("Stopped")
  137. STR_PAUSED = _("Paused")
  138. STR_START_MINING = _("Start")
  139. STR_STOP_MINING = _("Stop")
  140. STR_REFRESH_BALANCE = _("Refresh balance")
  141. STR_CONNECTION_ERROR = _("Connection error")
  142. STR_USERNAME = _("Username:")
  143. STR_PASSWORD = _("Password:")
  144. STR_QUIT = _("Quit this program")
  145. STR_ABOUT = _("Show about dialog")
  146.  
  147. # Alternate backends that we know how to call
  148. SUPPORTED_BACKENDS = [
  149.     "rpcminer-4way.exe",
  150.     "rpcminer-cpu.exe",
  151.     "rpcminer-cuda.exe",
  152.     "rpcminer-opencl.exe",
  153. #    "phoenix.py",
  154. #    "phoenix.exe",
  155.     "bitcoin-miner.exe"
  156. ]
  157.  
  158. USER_AGENT = "guiminer/" + __version__
  159.  
  160. # Time constants
  161. SAMPLE_TIME_SECS = 3600
  162. REFRESH_RATE_MILLIS = 2000
  163.  
  164. # Layout constants
  165. LBL_STYLE = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL
  166. BTN_STYLE = wx.ALIGN_CENTER_HORIZONTAL | wx.ALL
  167.  
  168. # Events sent from the worker threads
  169. (UpdateHashRateEvent, EVT_UPDATE_HASHRATE) = NewEvent()
  170. (UpdateAcceptedEvent, EVT_UPDATE_ACCEPTED) = NewEvent()
  171. (ReaperAttributeUpdate, EVT_REAPER_ATTRIBUTE_UPDATE) = NewEvent()
  172. (UpdateAcceptedReaperEvent, EVT_UPDATE_REAPER_ACCEPTED) = NewEvent()
  173. (UpdateSoloCheckEvent, EVT_UPDATE_SOLOCHECK) = NewEvent()
  174. (UpdateStatusEvent, EVT_UPDATE_STATUS) = NewEvent()
  175.  
  176. # Used in class CgListenerThread(MinerListenerThread) and ReaperListenerThread(MinerListenerThread)?
  177. non_decimal = re.compile(r'[^\d.]+')
  178.  
  179. # Utility functions
  180. def merge_whitespace(s):
  181.     """Combine multiple whitespace characters found in s into one."""
  182.     s = re.sub(r"( +)|\t+", " ", s)
  183.     return s.strip()
  184.  
  185. def get_opencl_devices():
  186.     """Return a list of available OpenCL devices.
  187.  
  188.    Raises ImportError if OpenCL is not found.
  189.    Raises IOError if no OpenCL devices are found.
  190.    """
  191.     device_strings = []
  192.     platforms = pyopencl.get_platforms() #@UndefinedVariable
  193.     for i, platform in enumerate(platforms):
  194.         devices = platform.get_devices()
  195.         for j, device in enumerate(devices):
  196.             device_strings.append('[%d-%d] %s' %
  197.                 (i, j, merge_whitespace(device.name)[:25]))
  198.     if len(device_strings) == 0:
  199.         raise IOError
  200.     return device_strings
  201.  
  202. def get_icon_bundle():
  203.     """Return the Bitcoin program icon bundle."""
  204.     return wx.IconBundleFromFile(os.path.join(get_module_path(), "logo.ico"), wx.BITMAP_TYPE_ICO)
  205.  
  206. def get_taskbar_icon():
  207.     """Return the taskbar icon.
  208.  
  209.    This works around Window's annoying behavior of ignoring the 16x16 image
  210.    and using nearest neighbour downsampling on the 32x32 image instead."""
  211.     ib = get_icon_bundle()
  212.     return ib.GetIcon((16, 16))
  213.  
  214. def mkdir_p(path):
  215.     """If the directory 'path' doesn't exist, create it. Same as mkdir -p."""
  216.     try:
  217.         os.makedirs(path)
  218.     except OSError as exc:
  219.         if exc.errno != errno.EEXIST:
  220.             raise
  221.  
  222. def add_tooltip(widget, text):
  223.     """Add a tooltip to widget with the specified text."""
  224.     tooltip = wx.ToolTip(text)
  225.     widget.SetToolTip(tooltip)
  226.  
  227. def format_khash(rate):
  228.     """Format rate for display. A rate of 0 means just connected."""
  229.     if rate > 10 ** 6:
  230.         return _("%.3f Ghash/s") % (rate / 1000000.)
  231.     if rate > 10 ** 3:
  232.         return _("%.1f Mhash/s") % (rate / 1000.)
  233.     elif rate == 0:
  234.         return _("Connecting...")
  235.     elif rate == -0.0000001:
  236.         return _("Proxy connected")
  237.     else:
  238.         return _("%d khash/s") % rate
  239.  
  240. def format_balance(amount):
  241.     """Format a quantity of Bitcoins in BTC."""
  242.     return "%.3f BTC" % float(amount)
  243.  
  244. def init_logger():
  245.     """Set up and return the logging object and custom formatter."""
  246.     logger = logging.getLogger("poclbm-gui")
  247.     logger.setLevel(logging.DEBUG)
  248.     file_handler = logging.FileHandler(
  249.         os.path.join(get_module_path(), 'guiminer-blake.log'), 'w')
  250.     formatter = logging.Formatter("%(asctime)s: %(message)s",
  251.                                   "%Y-%m-%d %H:%M:%S")
  252.     file_handler.setFormatter(formatter)
  253.     logger.addHandler(file_handler)
  254.     return logger, formatter
  255.  
  256. logger, formatter = init_logger()
  257.  
  258. def http_request(hostname, *args, **kwargs):
  259.     """Do a HTTP request and return the response data."""
  260.     conn_cls = httplib.HTTPSConnection if kwargs.get('use_https') else httplib.HTTPConnection        
  261.     conn = conn_cls(hostname)
  262.     try:        
  263.         logger.debug(_("Requesting balance: %(request)s"), dict(request=args))
  264.         conn.request(*args)
  265.         response = conn.getresponse()
  266.         data = response.read()
  267.         logger.debug(_("Server replied: %(status)s, %(data)s"),
  268.                      dict(status=str(response.status), data=data))
  269.         return response, data
  270.     finally:
  271.         conn.close()
  272.  
  273. def get_process_affinity(pid):
  274.     """Return the affinity mask for the specified process."""
  275.     flags = win32con.PROCESS_QUERY_INFORMATION
  276.     handle = win32api.OpenProcess(flags, 0, pid)
  277.     return win32process.GetProcessAffinityMask(handle)[0]
  278.  
  279. def set_process_affinity(pid, mask):
  280.     """Set the affinity for process to mask."""
  281.     flags = win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_SET_INFORMATION
  282.     handle = win32api.OpenProcess(flags, 0, pid)
  283.     win32process.SetProcessAffinityMask(handle, mask)
  284.  
  285. def find_nth(haystack, needle, n):
  286.     """Return the index of the nth occurrence of needle in haystack."""
  287.     start = haystack.find(needle)
  288.     while start >= 0 and n > 1:
  289.         start = haystack.find(needle, start + len(needle))
  290.         n -= 1
  291.     return start
  292.  
  293. class ConsolePanel(wx.Panel):
  294.     """Panel that displays logging events.
  295.  
  296.    Uses with a StreamHandler to log events to a TextCtrl. Thread-safe.
  297.    """
  298.     def __init__(self, parent, n_max_lines):
  299.         wx.Panel.__init__(self, parent, -1)
  300.         self.parent = parent
  301.         self.n_max_lines = n_max_lines
  302.  
  303.         vbox = wx.BoxSizer(wx.VERTICAL)
  304.         style = wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL
  305.         self.text = wx.TextCtrl(self, -1, "", style=style)
  306.         vbox.Add(self.text, 1, wx.EXPAND)
  307.         self.SetSizer(vbox)
  308.  
  309.         self.handler = logging.StreamHandler(self)
  310.  
  311.         formatter = logging.Formatter("%(asctime)s: %(message)s",
  312.                                       "%Y-%m-%d %H:%M:%S")
  313.         self.handler.setFormatter(formatter)
  314.         logger.addHandler(self.handler)
  315.  
  316.     def on_focus(self):
  317.         """On focus, clear the status bar."""
  318.         self.parent.statusbar.SetStatusText("", 0)
  319.         self.parent.statusbar.SetStatusText("", 1)
  320.  
  321.     def on_close(self):
  322.         """On closing, stop handling logging events."""
  323.         logger.removeHandler(self.handler)
  324.  
  325.     def append_text(self, text):
  326.         self.text.AppendText(text)
  327.         lines_to_cut = self.text.GetNumberOfLines() - self.n_max_lines
  328.         if lines_to_cut > 0:
  329.             contents = self.text.GetValue()
  330.             position = find_nth(contents, '\n', lines_to_cut)
  331.             self.text.ChangeValue(contents[position + 1:])                        
  332.  
  333.     def write(self, text):
  334.         """Forward logging events to our TextCtrl."""
  335.         wx.CallAfter(self.append_text, text)
  336.  
  337.  
  338. class SummaryPanel(wx.Panel):
  339.     """Panel that displays a summary of all miners."""
  340.  
  341.     def __init__(self, parent):
  342.         wx.Panel.__init__(self, parent, -1)
  343.         self.parent = parent
  344.         self.timer = wx.Timer(self)
  345.         self.timer.Start(REFRESH_RATE_MILLIS)
  346.         self.Bind(wx.EVT_TIMER, self.on_timer)
  347.  
  348.         flags = wx.ALIGN_CENTER_HORIZONTAL | wx.ALL
  349.         border = 5
  350.         self.column_headers = [
  351.             (wx.StaticText(self, -1, _("Miner")), 0, flags, border),
  352.             (wx.StaticText(self, -1, _("Speed")), 0, flags, border),
  353.             (wx.StaticText(self, -1, _("Accepted")), 0, flags, border),
  354.             (wx.StaticText(self, -1, _("Stale")), 0, flags, border),
  355.             (wx.StaticText(self, -1, _("Start/Stop")), 0, flags, border),
  356.             (wx.StaticText(self, -1, _("Autostart")), 0, flags, border),
  357.         ]
  358.         font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
  359.         font.SetUnderlined(True)
  360.         for st in self.column_headers:
  361.             st[0].SetFont(font)
  362.  
  363.         self.grid = wx.FlexGridSizer(0, len(self.column_headers), 2, 2)
  364.  
  365.         self.grid.AddMany(self.column_headers)
  366.         self.add_miners_to_grid()
  367.  
  368.         self.grid.AddGrowableCol(0)
  369.         self.grid.AddGrowableCol(1)
  370.         self.grid.AddGrowableCol(2)
  371.         self.grid.AddGrowableCol(3)
  372.         self.SetSizer(self.grid)
  373.  
  374.     def add_miners_to_grid(self):
  375.         """Add a summary row for each miner to the summary grid."""
  376.  
  377.         # Remove any existing widgets except the column headers.
  378.         for i in reversed(range(len(self.column_headers), len(self.grid.GetChildren()))):
  379.             self.grid.Hide(i)
  380.             self.grid.Remove(i)
  381.  
  382.         for p in self.parent.profile_panels:
  383.             p.clear_summary_widgets()
  384.             self.grid.AddMany(p.get_summary_widgets(self))
  385.  
  386.         self.grid.Layout()
  387.  
  388.     def on_close(self):
  389.         self.timer.Stop()
  390.  
  391.     def on_timer(self, event=None):
  392.         """Whenever the timer goes off, fefresh the summary data."""
  393.         if self.parent.nb.GetSelection() != self.parent.nb.GetPageIndex(self):
  394.             return
  395.  
  396.         for p in self.parent.profile_panels:
  397.             p.update_summary()
  398.  
  399.         self.parent.statusbar.SetStatusText("", 0) # TODO: show something
  400.         total_rate = sum(p.last_rate for p in self.parent.profile_panels
  401.                          if p.is_mining)
  402.         if any(p.is_mining for p in self.parent.profile_panels):
  403.             self.parent.statusbar.SetStatusText(format_khash(total_rate), 1)
  404.         else:
  405.             self.parent.statusbar.SetStatusText("", 1)
  406.  
  407.     def on_focus(self):
  408.         """On focus, show the statusbar text."""
  409.         self.on_timer()
  410.  
  411. class GUIMinerTaskBarIcon(wx.TaskBarIcon):
  412.     """Taskbar icon for the GUI.
  413.  
  414.    Shows status messages on hover and opens on click.
  415.    """
  416.     TBMENU_RESTORE = wx.NewId()
  417.     TBMENU_PAUSE = wx.NewId()
  418.     TBMENU_CLOSE = wx.NewId()
  419.     TBMENU_CHANGE = wx.NewId()
  420.     TBMENU_REMOVE = wx.NewId()
  421.  
  422.     def __init__(self, frame):
  423.         wx.TaskBarIcon.__init__(self)
  424.         self.frame = frame
  425.         self.icon = get_taskbar_icon()
  426.         self.timer = wx.Timer(self)
  427.         self.timer.Start(REFRESH_RATE_MILLIS)
  428.         self.is_paused = False
  429.         self.SetIcon(self.icon, "GUIMiner-Blake")
  430.         self.imgidx = 1
  431.         self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.on_taskbar_activate)
  432.         self.Bind(wx.EVT_MENU, self.on_taskbar_activate, id=self.TBMENU_RESTORE)
  433.         self.Bind(wx.EVT_MENU, self.on_taskbar_close, id=self.TBMENU_CLOSE)
  434.         self.Bind(wx.EVT_MENU, self.on_pause, id=self.TBMENU_PAUSE)
  435.         self.Bind(wx.EVT_TIMER, self.on_timer)
  436.  
  437.     def CreatePopupMenu(self):
  438.         """Override from wx.TaskBarIcon. Creates the right-click menu."""
  439.         menu = wx.Menu()
  440.         menu.AppendCheckItem(self.TBMENU_PAUSE, _("Pause all"))
  441.         menu.Check(self.TBMENU_PAUSE, self.is_paused)
  442.         menu.Append(self.TBMENU_RESTORE, _("Restore"))
  443.         menu.Append(self.TBMENU_CLOSE, _("Close"))
  444.         return menu
  445.  
  446.     def on_taskbar_activate(self, evt):
  447.         if self.frame.IsIconized():
  448.             self.frame.Iconize(False)
  449.         if not self.frame.IsShown():
  450.             self.frame.Show(True)
  451.         self.frame.Raise()
  452.  
  453.     def on_taskbar_close(self, evt):
  454.         wx.CallAfter(self.frame.Close, force=True)
  455.  
  456.     def on_timer(self, event):
  457.         """Refresh the taskbar icon's status message."""
  458.         objs = self.frame.profile_panels
  459.         if objs:
  460.             text = '\n'.join(p.get_taskbar_text() for p in objs)
  461.             self.SetIcon(self.icon, text)
  462.  
  463.     def on_pause(self, event):
  464.         """Pause or resume the currently running miners."""
  465.         self.is_paused = event.Checked()
  466.         for miner in self.frame.profile_panels:
  467.             if self.is_paused:
  468.                 miner.pause()
  469.             else:
  470.                 miner.resume()
  471.  
  472. def nonBlockRead(output):
  473.     fd = output.fileno()
  474.     fl = fcntl.fcntl(fd, fcntl.F_GETFL)
  475.     fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
  476.     try:
  477.         return output.read()
  478.     except:
  479.         return ''
  480.                
  481. class MinerListenerThread(threading.Thread):
  482.     LINES = ["""
  483.        (r"Target =|average rate|Sending to server|found hash|connected to|Setting server",
  484.            lambda _: None), # Just ignore lines like these
  485.        (r"accepted|\"result\":\s*true",
  486.            lambda _: UpdateAcceptedEvent(accepted=True)),
  487.        (r"invalid|stale|rejected", lambda _:
  488.            UpdateAcceptedEvent(accepted=False)),    
  489.        (r"(\d+)\s*Kh/s", lambda match:
  490.            UpdateHashRateEvent(rate=float(match.group(1)))),
  491.        (r"(\d+\.\d+)\s*MH/s", lambda match:
  492.            UpdateHashRateEvent(rate=float(match.group(1)) * 1000)),
  493.        (r"(\d+\.\d+)\s*Mhash/s", lambda match:
  494.            UpdateHashRateEvent(rate=float(match.group(1)) * 1000)),
  495.        (r"(\d+)\s*Mhash/s", lambda match:
  496.            UpdateHashRateEvent(rate=int(match.group(1)) * 1000)),
  497.        (r"checking (\d+)", lambda _:
  498.            UpdateSoloCheckEvent()),"""
  499.     ]
  500.  
  501.     def __init__(self, parent, miner):
  502.         threading.Thread.__init__(self)
  503.         self.shutdown_event = threading.Event()
  504.         self.parent = parent
  505.         self.parent_name = parent.name
  506.         self.miner = miner
  507.        
  508.     def run(self):
  509.         logger.info(_('Listener for "%s" started') % self.parent_name)
  510.         while not self.shutdown_event.is_set():
  511.             line = self.miner.stdout.readline().strip()
  512.             # logger.debug("Line: %s", line)
  513.             if not line: continue
  514.            
  515.             for s, event_func in self.LINES: # Use self to allow subclassing
  516.                 match = re.search(s, line, flags=re.I)
  517.                 if match is not None:
  518.                     event = event_func(match)
  519.                     if event is not None:
  520.                         wx.PostEvent(self.parent, event)
  521.                     break    
  522.            
  523.             else:
  524.                 # Possible error or new message, just pipe it through
  525.                 event = UpdateStatusEvent(text=line)
  526.                 logger.info(_('Listener for "%(name)s": %(line)s'),
  527.                             dict(name=self.parent_name, line=line))
  528.                 wx.PostEvent(self.parent, event)
  529.         logger.info(_('Listener for "%s" shutting down'), self.parent_name)
  530.    
  531.  
  532. class PhoenixListenerThread(MinerListenerThread):
  533.     LINES = [
  534.         (r"Result: .* accepted",
  535.             lambda _: UpdateAcceptedEvent(accepted=True)),
  536.         (r"Result: .* rejected", lambda _:
  537.             UpdateAcceptedEvent(accepted=False)),
  538.         (r"(\d+)\.?(\d*) Khash/sec", lambda match:
  539.             UpdateHashRateEvent(rate=float(match.group(1) + '.' + match.group(2)))),
  540.         (r"(\d+)\.?(\d*) Mhash/sec", lambda match:
  541.             UpdateHashRateEvent(rate=float(match.group(1) + '.' + match.group(2)) * 1000)),
  542.         (r"Currently on block",
  543.             lambda _: None), # Just ignore lines like these
  544.     ]
  545.    
  546. class CgListenerThread(MinerListenerThread):
  547.     LINES = [
  548.         (r" Accepted .*",
  549.             lambda _: UpdateAcceptedEvent(accepted=True)),
  550.         #(r"A:*\d+",
  551.         #    lambda _: UpdateAcceptedEvent(accepted=False)),
  552.         (r" Rejected .*",
  553.             lambda _: UpdateAcceptedEvent(accepted=False)),
  554.         #(r"R:*\d+",
  555.         #    lambda _: UpdateAcceptedEvent(accepted=False)),
  556.         #(r"Q:*\d+",
  557.         #    lambda _: UpdateAcceptedEvent(accepted=False)),
  558.         #(r"HW:*\d+",
  559.         #    lambda _: UpdateAcceptedEvent(accepted=False)),
  560.         #(r"\(\d+s\):(\d+)\.?(\d*) .* Kh/s", lambda match:
  561.         #    UpdateHashRateEvent(rate=float(match.group(1) + '.' + match.group(2)) * 1000)),
  562.         (r"\(*avg\):.*Kh", lambda match:
  563.             UpdateHashRateEvent(rate=float(non_decimal.sub('', match.group(0))))),
  564.         (r"\(*avg\):.*Mh", lambda match:
  565.             UpdateHashRateEvent(rate=float(non_decimal.sub('', match.group(0))) * 1000)),
  566.         (r"\(*avg\):.*Gh", lambda match:
  567.             UpdateHashRateEvent(rate=float(non_decimal.sub('', match.group(0))) * 1000000)),
  568.         (r"^GPU\s*\d+",
  569.             lambda _: None), # Just ignore lines like these
  570.     ]
  571.  
  572. # Below is kind of an ugly hack for updating reaper shares, but it works - TacoTime
  573. class ReaperListenerThread(MinerListenerThread):
  574.     LINES = [
  575.         (r"GPU \d+.*", lambda match:
  576.             ReaperAttributeUpdate(clstring=match.group(0)))
  577.     ]
  578.      
  579. class CudaminerListenerThread(MinerListenerThread):
  580.     LINES = [
  581.         (r"(yay!!!)",
  582.             lambda _: UpdateAcceptedEvent(accepted=True)),
  583.         (r"(booooo)",
  584.             lambda _: UpdateAcceptedEvent(accepted=False)),
  585.         (r"\hashes, .*khash", lambda match:
  586.             UpdateHashRateEvent(rate=float(non_decimal.sub('', match.group(0))))),
  587.         #(r"^GPU\s*\d+",
  588.         #    lambda _: None), # Just ignore lines like these
  589.     ]
  590.      
  591. class ProxyListenerThread(MinerListenerThread):
  592.     LINES = [
  593.         (r".* accepted, .*",
  594.             lambda _: UpdateAcceptedEvent(accepted=True)),
  595.         (r".* REJECTED:.*",
  596.             lambda _: UpdateAcceptedEvent(accepted=False)),
  597.         (r".*LISTENING.*", lambda match:
  598.             UpdateHashRateEvent(rate = -0.0000001)),
  599.     ]
  600.  
  601. class MinerTab(wx.Panel):
  602.     """A tab in the GUI representing a miner instance.
  603.  
  604.    Each MinerTab has these responsibilities:
  605.    - Persist its data to and from the config file
  606.    - Launch a backend subprocess and monitor its progress
  607.      by creating a MinerListenerThread.
  608.    - Post updates to the GUI's statusbar & summary panel; the format depends
  609.      whether the backend is working solo or in a pool.
  610.    """
  611.     def __init__(self, parent, id, devices, servers, defaults, gpusettings_data, statusbar, data):
  612.         wx.Panel.__init__(self, parent, id)
  613.         self.parent = parent
  614.         self.servers = servers
  615.         self.defaults = defaults
  616.         self.gpusettings_data = gpusettings_data
  617.         self.statusbar = statusbar
  618.         self.is_mining = False
  619.         self.is_paused = False
  620.         self.is_possible_error = False
  621.         self.miner = None # subprocess.Popen instance when mining
  622.         self.miner_listener = None # MinerListenerThread when mining
  623.         self.solo_blocks_found = 0
  624.         self.accepted_shares = 0 # shares for pool, diff1 hashes for solo
  625.         self.accepted_times = collections.deque()
  626.         self.invalid_shares = 0
  627.         self.invalid_times = collections.deque()
  628.         self.last_rate = 0 # units of khash/s
  629.         self.autostart = False
  630.         self.num_processors = int(os.getenv('NUMBER_OF_PROCESSORS', 1))
  631.         self.affinity_mask = 0
  632.         self.server_lbl = wx.StaticText(self, -1, _("Server:"))
  633.         self.summary_panel = None # SummaryPanel instance if summary open
  634.         self.server = wx.ComboBox(self, -1,
  635.                                   choices=[s['name'] for s in servers],
  636.                                   style=wx.CB_READONLY)
  637.         self.gpusettings_lbl = wx.StaticText(self, -1, _("GPU Defaults:"))
  638.         self.gpusettings = wx.ComboBox(self, -1,
  639.                                   choices=[s['name'] for s in gpusettings_data],
  640.                                   style=wx.CB_READONLY)
  641.         self.website_lbl = wx.StaticText(self, -1, _("Website:"))
  642.         self.website = hyperlink.HyperLinkCtrl(self, -1, "")
  643.         self.external_lbl = wx.StaticText(self, -1, _("Ext. Path:"))
  644.         self.txt_external = wx.TextCtrl(self, -1, "")
  645.         self.host_lbl = wx.StaticText(self, -1, _("Host:"))
  646.         self.txt_host = wx.TextCtrl(self, -1, "")
  647.         self.port_lbl = wx.StaticText(self, -1, _("Port:"))
  648.         self.txt_port = wx.TextCtrl(self, -1, "")
  649.         self.user_lbl = wx.StaticText(self, -1, STR_USERNAME)
  650.         self.txt_username = wx.TextCtrl(self, -1, "")
  651.         self.pass_lbl = wx.StaticText(self, -1, STR_PASSWORD)
  652.         self.txt_pass = wx.TextCtrl(self, -1, "", style=wx.TE_PASSWORD)
  653.         self.device_lbl = wx.StaticText(self, -1, _("Device:"))
  654.         self.device_listbox = wx.ComboBox(self, -1, choices=devices or [_("No OpenCL devices")], style=wx.CB_READONLY)
  655.         self.minercgminer_lbl = wx.StaticText(self, -1, _("Miner: cgminer"))
  656.         self.minerreaper_lbl = wx.StaticText(self, -1, _("Miner: reaper"))
  657.         self.minercudaminer_lbl = wx.StaticText(self, -1, _("Miner: cudaminer"))
  658.         self.proxy_lbl = wx.StaticText(self, -1, _("Stratum proxy"))
  659.         self.thrcon_lbl = wx.StaticText(self, -1, _("Thread concurrency:"))
  660.         self.txt_thrcon = wx.TextCtrl(self, -1, "")
  661.         self.worksize_lbl = wx.StaticText(self, -1, _("Worksize:"))
  662.         self.txt_worksize = wx.TextCtrl(self, -1, "")
  663.         self.vectors_lbl = wx.StaticText(self, -1, _("Vectors:"))
  664.         self.txt_vectors = wx.TextCtrl(self, -1, "")
  665.         self.intensity_lbl = wx.StaticText(self, -1, _("Intensity:"))
  666.         self.txt_intensity = wx.TextCtrl(self, -1, "")
  667.         self.gputhreads_lbl = wx.StaticText(self, -1, _("GPU threads:"))
  668.         self.txt_gputhreads = wx.TextCtrl(self, -1, "")
  669.         self.flags_lbl = wx.StaticText(self, -1, _("Extra flags:"))
  670.         self.txt_flags = wx.TextCtrl(self, -1, "")
  671.         self.extra_info = wx.StaticText(self, -1, "")
  672.         self.affinity_lbl = wx.StaticText(self, -1, _("CPU Affinity:"))
  673.         #self.affinity_chks = [wx.CheckBox(self, label='%d ' % i)
  674.         #                      for i in range(self.num_processors)]
  675.         self.stratum_lbl = wx.StaticText(self, -1, _("Use stratum:"))
  676.         self.txt_stratum = wx.ComboBox(self, -1,
  677.                                   choices=['Yes','No'],
  678.                                   style=wx.CB_READONLY)
  679.         self.interactive_lbl = wx.StaticText(self, -1, _("Interactive:"))
  680.         self.txt_interactive = wx.ComboBox(self, -1,
  681.                                   choices=['Yes','No'],
  682.                                   style=wx.CB_READONLY)
  683.         self.texcache_lbl = wx.StaticText(self, -1, _("Texture cache:"))
  684.         self.txt_texcache = wx.ComboBox(self, -1,
  685.                                   choices=['Disabled','1D','2D'],
  686.                                   style=wx.CB_READONLY)
  687.         self.singlemem_lbl = wx.StaticText(self, -1, _("Multiblock memory:"))
  688.         self.txt_singlemem = wx.ComboBox(self, -1,
  689.                                   choices=['Yes','No'],
  690.                                   style=wx.CB_READONLY)
  691.         self.warpflags_lbl = wx.StaticText(self, -1, _("Warp configuration:"))
  692.         self.txt_warpflags = wx.TextCtrl(self, -1, "")
  693.         self.stratuminfo0_lbl = wx.StaticText(self, -1, _("Connect miners to"))
  694.         self.stratuminfo1_lbl = wx.StaticText(self, -1, _("host: localhost port: 8332"))
  695.         self.balance_lbl = wx.StaticText(self, -1, _("Balance:"))
  696.         self.balance_amt = wx.StaticText(self, -1, "0")
  697.         self.balance_refresh = wx.Button(self, -1, STR_REFRESH_BALANCE)
  698.         self.balance_refresh_timer = wx.Timer()
  699.         self.withdraw = wx.Button(self, -1, _("Withdraw"))
  700.         self.balance_cooldown_seconds = 0
  701.         self.balance_auth_token = ""
  702.  
  703.         self.labels = [self.minercgminer_lbl, self.minerreaper_lbl,
  704.                       self.proxy_lbl, self.server_lbl,
  705.                       self.website_lbl, self.host_lbl,
  706.                       self.port_lbl, self.user_lbl,
  707.                       self.pass_lbl, self.device_lbl,
  708.                       self.thrcon_lbl, self.vectors_lbl,
  709.                       self.intensity_lbl, self.gputhreads_lbl,
  710.                       self.worksize_lbl, self.stratum_lbl,
  711.                       self.stratuminfo0_lbl, self.stratuminfo1_lbl,
  712.                       self.flags_lbl, self.balance_lbl,
  713.                       self.interactive_lbl, self.texcache_lbl,
  714.                       self.singlemem_lbl, self.warpflags_lbl,
  715.                       self.minercudaminer_lbl]
  716.         self.txts = [self.txt_host, self.txt_port,
  717.                      self.txt_username, self.txt_pass,
  718.                      self.txt_thrcon, self.txt_worksize,
  719.                      self.txt_vectors, self.txt_intensity,
  720.                      self.txt_gputhreads, self.txt_stratum,
  721.                      self.txt_flags, self.txt_interactive,
  722.                      self.txt_texcache, self.txt_singlemem,
  723.                      self.txt_warpflags]
  724.         self.all_widgets = [self.server,
  725.                             self.website,
  726.                             self.device_listbox,
  727.                             self.balance_amt,
  728.                             self.balance_refresh,
  729.                             self.withdraw] + self.labels + self.txts
  730.         self.hidden_widgets = [self.extra_info,
  731.                                self.txt_external,
  732.                                self.external_lbl]
  733.  
  734.         self.start = wx.Button(self, -1, STR_START_MINING)
  735.  
  736.         self.device_listbox.SetSelection(0)
  737.         self.server.SetStringSelection(self.defaults.get('default_server'))
  738.  
  739.         self.set_data(data)
  740.  
  741.         for txt in self.txts:
  742.             txt.Bind(wx.EVT_KEY_UP, self.check_if_modified)
  743.         self.device_listbox.Bind(wx.EVT_COMBOBOX, self.check_if_modified)
  744.  
  745.         self.start.Bind(wx.EVT_BUTTON, self.toggle_mining)
  746.         self.server.Bind(wx.EVT_COMBOBOX, self.on_select_server)
  747.         self.gpusettings.Bind(wx.EVT_COMBOBOX, self.on_select_gpusettings)
  748.         self.balance_refresh_timer.Bind(wx.EVT_TIMER, self.on_balance_cooldown_tick)
  749.         self.balance_refresh.Bind(wx.EVT_BUTTON, self.on_balance_refresh)
  750.         self.withdraw.Bind(wx.EVT_BUTTON, self.on_withdraw)
  751.         #for chk in self.affinity_chks:
  752.         #    chk.Bind(wx.EVT_CHECKBOX, self.on_affinity_check)
  753.         self.Bind(EVT_UPDATE_HASHRATE, lambda event: self.update_khash(event.rate))
  754.         self.Bind(EVT_UPDATE_ACCEPTED, lambda event: self.update_shares(event.accepted))
  755.         self.Bind(EVT_REAPER_ATTRIBUTE_UPDATE, lambda event: self.update_attributes_reaper(event.clstring))
  756.         self.Bind(EVT_UPDATE_REAPER_ACCEPTED, lambda event: self.update_shares_reaper(event.quantity, event.accepted))
  757.         self.Bind(EVT_UPDATE_STATUS, lambda event: self.update_status(event.text))
  758.         self.Bind(EVT_UPDATE_SOLOCHECK, lambda event: self.update_solo())
  759.         self.update_statusbar()
  760.         self.clear_summary_widgets()
  761.  
  762.     @property
  763.     def last_update_time(self):
  764.         """Return the local time of the last accepted share."""
  765.         if self.accepted_times:
  766.             return time.localtime(self.accepted_times[-1])
  767.         return None
  768.  
  769.     @property
  770.     def server_config(self):
  771.         hostname = self.txt_host.GetValue()
  772.         return self.get_server_by_field(hostname, 'host')
  773.        
  774.     @property
  775.     def gpusettings_config(self):
  776.         profilename = self.gpusettings_data.GetValue()
  777.         return self.get_gpusettings_by_field(profilename, 'name')
  778.  
  779.     @property
  780.     def is_solo(self):
  781.         """Return True if this miner is configured for solo mining."""
  782.         return self.server.GetStringSelection() == "solo"
  783.  
  784.     @property
  785.     def is_modified(self):
  786.         """Return True if this miner has unsaved changes pending."""
  787.         return self.last_data != self.get_data()
  788.  
  789.     @property
  790.     def external_path(self):
  791.         """Return the path to an external miner, or "" if none is present."""
  792.         return self.txt_external.GetValue()
  793.  
  794.     @property
  795.     def is_external_miner(self):
  796.         """Return True if this miner has an external path configured."""
  797.         return self.txt_external.GetValue() != ""
  798.  
  799.     @property
  800.     def host_with_http_prefix(self):
  801.         """Return the host address, with http:// prepended if needed."""
  802.         host = self.txt_host.GetValue()
  803.         if not host.startswith("http://"):
  804.             host = "http://" + host
  805.         return host
  806.  
  807.     @property
  808.     def host_without_http_prefix(self):
  809.         """Return the host address, with http:// stripped off if needed."""
  810.         host = self.txt_host.GetValue()
  811.         if host.startswith("http://"):
  812.             return host[len('http://'):]
  813.         return host
  814.  
  815.     @property
  816.     def device_index(self):
  817.         """Return the index of the currently selected OpenCL device."""
  818.         s = self.device_listbox.GetStringSelection()
  819.         match = re.search(r'\[(\d+)-(\d+)\]', s)
  820.         try: return int(match.group(2))
  821.         except: return 0
  822.  
  823.     @property
  824.     def platform_index(self):
  825.         """Return the index of the currently selected OpenCL platform."""
  826.         s = self.device_listbox.GetStringSelection()
  827.         match = re.search(r'\[(\d+)-(\d+)\]', s)
  828.         try: return int(match.group(1))
  829.         except: return 0
  830.  
  831.     @property
  832.     def is_device_visible(self):
  833.         """Return True if we are using a backend with device selection."""
  834.         NO_DEVICE_SELECTION = ['rpcminer', 'bitcoin-miner']
  835.         return not any(d in self.external_path for d in NO_DEVICE_SELECTION)
  836.  
  837.     def on_affinity_check(self, event):
  838.         """Set the affinity mask to the selected value."""
  839.         self.affinity_mask = 0
  840.         for i in range(self.num_processors):
  841.             # is_checked = self.affinity_chks[i].GetValue()
  842.             self.affinity_mask += (is_checked << i)
  843.         if self.is_mining:
  844.             try:
  845.                 set_process_affinity(self.miner.pid, self.affinity_mask)
  846.             except:
  847.                 pass # TODO: test on Linux
  848.  
  849.     def pause(self):
  850.         """Pause the miner if we are mining, otherwise do nothing."""
  851.         if self.is_mining:
  852.             self.stop_mining()
  853.             self.is_paused = True
  854.  
  855.     def resume(self):
  856.         """Resume the miner if we are paused, otherwise do nothing."""
  857.         if self.is_paused:
  858.             self.start_mining()
  859.             self.is_paused = False
  860.  
  861.     def get_data(self):
  862.         """Return a dict of our profile data."""
  863.         return dict(name=self.name,
  864.                     hostname=self.txt_host.GetValue(),
  865.                     port=self.txt_port.GetValue(),
  866.                     username=self.txt_username.GetValue(),
  867.                     password=self.txt_pass.GetValue(),
  868.                     device=self.device_listbox.GetSelection(),
  869.                     flags=self.txt_flags.GetValue(),
  870.                     thrcon=self.txt_thrcon.GetValue(),
  871.                     worksize=self.txt_worksize.GetValue(),
  872.                     vectors=self.txt_vectors.GetValue(),
  873.                     intensity=self.txt_intensity.GetValue(),
  874.                     gputhreads=self.txt_gputhreads.GetValue(),
  875.                     stratum=self.txt_stratum.GetValue(),
  876.                     autostart=self.autostart,
  877.                     affinity_mask=self.affinity_mask,
  878.                     balance_auth_token=self.balance_auth_token,
  879.                     interactive=self.txt_interactive.GetValue(),
  880.                     texcache=self.txt_texcache.GetValue(),
  881.                     singlemem=self.txt_singlemem.GetValue(),
  882.                     warpflags=self.txt_warpflags.GetValue(),
  883.                     external_path=self.external_path)
  884.  
  885.     def set_data(self, data):
  886.         """Set our profile data to the information in data. See get_data()."""
  887.         self.last_data = data
  888.         default_server_config = self.get_server_by_field(
  889.                                     self.defaults['default_server'], 'name')
  890.         self.name = (data.get('name') or _('Default'))
  891.  
  892.         # Backwards compatibility: hostname key used to be called server.
  893.         # We only save out hostname now but accept server from old INI files.
  894.         hostname = (data.get('hostname') or _('')) # Hack by tacotime, don't give it any host, the user can enter it
  895.         external_path_ref = (data.get('external_path') or _('CGMINER')) # Default miner is cgminer
  896.        
  897.         self.txt_host.SetValue(hostname)
  898.         self.txt_external.SetValue(external_path_ref)
  899.         self.txt_thrcon.SetValue(data.get('thrcon') or _(''))
  900.         self.txt_worksize.SetValue(data.get('worksize') or _(''))
  901.         self.txt_vectors.SetValue(data.get('vectors') or _(''))
  902.         self.txt_intensity.SetValue(data.get('intensity') or _(''))
  903.         self.txt_gputhreads.SetValue(data.get('gputhreads') or _(''))
  904.         self.txt_stratum.SetValue(data.get('stratum') or _('Yes'))
  905.         self.txt_interactive.SetValue(data.get('interactive') or _('Yes'))
  906.         self.txt_texcache.SetValue(data.get('texcache') or _('Disabled'))
  907.         self.txt_singlemem.SetValue(data.get('singlemem') or _('Yes'))
  908.         self.txt_warpflags.SetValue(data.get('warpflags') or _('auto'))
  909.        
  910.         self.server.SetStringSelection(self.server_config.get('name', "Other"))
  911.  
  912.         self.txt_username.SetValue(
  913.             data.get('username') or
  914.             self.defaults.get('default_username', ''))
  915.  
  916.         self.txt_pass.SetValue(
  917.             data.get('password') or
  918.             self.defaults.get('default_password', ''))
  919.  
  920.         self.txt_port.SetValue(str(
  921.             data.get('port') or
  922.             self.server_config.get('port', 3333)))
  923.  
  924.         self.txt_flags.SetValue(data.get('flags', ''))
  925.         self.autostart = data.get('autostart', False)
  926.         self.affinity_mask = data.get('affinity_mask', 1)
  927.         for i in range(self.num_processors):
  928.             # self.affinity_chks[i].SetValue((self.affinity_mask >> i) & 1)
  929.             pass
  930.  
  931.         # Handle case where they removed devices since last run.
  932.         device_index = data.get('device', None)
  933.         if device_index is not None and device_index < self.device_listbox.GetCount():
  934.             self.device_listbox.SetSelection(device_index)
  935.  
  936.         # self.change_gpusettings(self.gpusettings_config)
  937.         self.change_server(self.server_config)
  938.  
  939.         self.balance_auth_token = data.get('balance_auth_token', '')
  940.  
  941.     def clear_summary_widgets(self):
  942.         """Release all our summary widgets."""
  943.         self.summary_name = None
  944.         self.summary_status = None
  945.         self.summary_shares_accepted = None
  946.         self.summary_shares_stale = None
  947.         self.summary_start = None
  948.         self.summary_autostart = None
  949.  
  950.     def get_start_stop_state(self):
  951.         """Return appropriate text for the start/stop button."""
  952.         return _("Stop") if self.is_mining else _("Start")
  953.  
  954.     def get_start_label(self):
  955.         return STR_STOP_MINING if self.is_mining else STR_START_MINING
  956.  
  957.     def update_summary(self):
  958.         """Update our summary fields if possible."""
  959.         if not self.summary_panel:
  960.             return
  961.  
  962.         self.summary_name.SetLabel(self.name)
  963.         if self.is_paused:
  964.             text = STR_PAUSED
  965.         elif not self.is_mining:
  966.             text = STR_STOPPED
  967.         elif self.is_possible_error:
  968.             text = _("Connection problems")
  969.         elif (self.last_rate == -0.0000001):
  970.             text = _("Proxy connected")
  971.         else:
  972.             text = format_khash(self.last_rate)
  973.            
  974.         self.summary_status.SetLabel(text)
  975.        
  976.         # Original
  977.         # self.summary_shares_accepted.SetLabel("%d (%d)" %
  978.         #     (self.accepted_shares, len(self.accepted_times)))
  979.         # New - Don't care about accepted_times since reaper doesn't have them - TacoTime
  980.         self.summary_shares_accepted.SetLabel("%d" %
  981.             (self.accepted_shares))
  982.  
  983.         # Original
  984.         # if self.is_solo:
  985.         #    self.summary_shares_invalid.SetLabel("-")
  986.         # else:
  987.         #     self.summary_shares_invalid.SetLabel("%d (%d)" %
  988.         #         (self.invalid_shares, len(self.invalid_times)))
  989.         # New - Don't care about invalid_times since reaper doesn't have them - TacoTime
  990.         if self.is_solo:
  991.             self.summary_shares_invalid.SetLabel("-")
  992.         else:
  993.             self.summary_shares_invalid.SetLabel("%d" %
  994.                 (self.invalid_shares))
  995.  
  996.         self.summary_start.SetLabel(self.get_start_stop_state())
  997.         self.summary_autostart.SetValue(self.autostart)
  998.         self.summary_panel.grid.Layout()
  999.  
  1000.     def get_summary_widgets(self, summary_panel):
  1001.         """Return a list of summary widgets suitable for sizer.AddMany."""
  1002.         self.summary_panel = summary_panel
  1003.         self.summary_name = wx.StaticText(summary_panel, -1, self.name)
  1004.         self.summary_name.Bind(wx.EVT_LEFT_UP, self.show_this_panel)
  1005.  
  1006.         self.summary_status = wx.StaticText(summary_panel, -1, STR_STOPPED)
  1007.         self.summary_shares_accepted = wx.StaticText(summary_panel, -1, "0")
  1008.         self.summary_shares_invalid = wx.StaticText(summary_panel, -1, "0")
  1009.         self.summary_start = wx.Button(summary_panel, -1, self.get_start_stop_state(), style=wx.BU_EXACTFIT)
  1010.         self.summary_start.Bind(wx.EVT_BUTTON, self.toggle_mining)
  1011.         self.summary_autostart = wx.CheckBox(summary_panel, -1)
  1012.         self.summary_autostart.Bind(wx.EVT_CHECKBOX, self.toggle_autostart)
  1013.         self.summary_autostart.SetValue(self.autostart)
  1014.         return [
  1015.             (self.summary_name, 0, wx.ALIGN_CENTER_HORIZONTAL),
  1016.             (self.summary_status, 0, wx.ALIGN_CENTER_HORIZONTAL, 0),
  1017.             (self.summary_shares_accepted, 0, wx.ALIGN_CENTER_HORIZONTAL, 0),
  1018.             (self.summary_shares_invalid, 0, wx.ALIGN_CENTER_HORIZONTAL, 0),
  1019.             (self.summary_start, 0, wx.ALIGN_CENTER, 0),
  1020.             (self.summary_autostart, 0, wx.ALIGN_CENTER, 0)
  1021.         ]
  1022.  
  1023.     def show_this_panel(self, event):
  1024.         """Set focus to this panel."""
  1025.         self.parent.SetSelection(self.parent.GetPageIndex(self))
  1026.  
  1027.     def toggle_autostart(self, event):
  1028.         self.autostart = event.IsChecked()
  1029.  
  1030.     def toggle_mining(self, event):
  1031.         """Stop or start the miner."""
  1032.         if self.is_mining:
  1033.             self.stop_mining()
  1034.         else:
  1035.             self.start_mining()
  1036.         self.update_summary()
  1037.  
  1038.     #############################
  1039.     # Begin backend specific code
  1040.     def configure_subprocess_poclbm(self):
  1041.         """Set up the command line for poclbm."""
  1042.         folder = get_module_path()
  1043.         if USE_MOCK:
  1044.             executable = "python mockBitcoinMiner.py"
  1045.         else:
  1046.             if hasattr(sys, 'frozen'):
  1047.                 executable = "poclbm.exe"
  1048.             else:
  1049.                 executable = "python poclbm.py"
  1050.         cmd = "%s %s:%s@%s:%s --device=%d --platform=%d --verbose -r1 %s" % (
  1051.                 executable,
  1052.                 self.txt_username.GetValue(),
  1053.                 self.txt_pass.GetValue(),
  1054.                 self.txt_host.GetValue(),
  1055.                 self.txt_port.GetValue(),
  1056.                 self.device_index,
  1057.                 self.platform_index,
  1058.                 self.txt_flags.GetValue()
  1059.         )
  1060.         return cmd, folder
  1061.  
  1062.     def configure_subprocess_rpcminer(self):
  1063.         """Set up the command line for rpcminer.
  1064.  
  1065.        The hostname must start with http:// for these miners.
  1066.        """
  1067.         cmd = "%s -user=%s -password=%s -url=%s:%s %s" % (
  1068.             self.external_path,
  1069.             self.txt_username.GetValue(),
  1070.             self.txt_pass.GetValue(),
  1071.             self.host_with_http_prefix,
  1072.             self.txt_port.GetValue(),
  1073.             self.txt_flags.GetValue()
  1074.         )
  1075.         return cmd, os.path.dirname(self.external_path)
  1076.  
  1077.     def configure_subprocess_ufasoft(self):
  1078.         """Set up the command line for ufasoft's SSE2 miner.
  1079.  
  1080.        The hostname must start with http:// for these miners.
  1081.        """
  1082.         cmd = "%s -u %s -p %s -o %s:%s %s" % (
  1083.             self.external_path,
  1084.             self.txt_username.GetValue(),
  1085.             self.txt_pass.GetValue(),
  1086.             self.host_with_http_prefix,
  1087.             self.txt_port.GetValue(),
  1088.             self.txt_flags.GetValue())
  1089.         return cmd, os.path.dirname(self.external_path)
  1090.  
  1091.     def configure_subprocess_phoenix(self):
  1092.         """Set up the command line for phoenix miner."""
  1093.         path = self.external_path
  1094.         if path.endswith('.py'):
  1095.             path = "python " + path
  1096.  
  1097.         cmd = "%s -u http://%s:%s@%s:%s PLATFORM=%d DEVICE=%d %s" % (
  1098.             path,
  1099.             self.txt_username.GetValue(),
  1100.             self.txt_pass.GetValue(),
  1101.             self.host_without_http_prefix,
  1102.             self.txt_port.GetValue(),
  1103.             self.platform_index,
  1104.             self.device_index,
  1105.             self.txt_flags.GetValue())
  1106.         return cmd, os.path.dirname(self.external_path)
  1107.  
  1108.     def configure_subprocess_cgminer(self):
  1109.         """Set up the command line for cgminer."""
  1110.  
  1111.         # Set the path for cgminer, should be ./cgminer/cgminer.exe
  1112.         # Not set up for unix, modify this to /cgminer/cgminer for unix
  1113.         os.chdir(STARTUP_PATH)
  1114.         path = '\"' + STARTUP_PATH + "\\cgminer\\cgminer.exe" + '\"'
  1115.         cgdir = STARTUP_PATH + "\\cgminer\\"
  1116.        
  1117.         #if path.endswith('.py'):
  1118.         #    path = "python " + path
  1119.        
  1120.         if self.txt_stratum.GetValue() == "Yes":
  1121.             http_header = "stratum+tcp://"
  1122.         else:
  1123.             http_header = "http://"
  1124.  
  1125.         # Command line arguments for cgminer here:
  1126.         # -u <username>
  1127.         # -p <password>
  1128.         # -o <http://server.ip:port>
  1129.         # --gpu-platform <like it sounds>
  1130.         # -w <worksize>
  1131.         # -v <vectors>
  1132.         # -d <device appear in pyopencl>
  1133.         # -l <log message period in second>
  1134.         # -T <disable curses interface and output to console (stdout)>
  1135.         # -g <GPU threads>
  1136.         cmd = "%s --blake256 -u %s -p %s -o %s%s:%s --gpu-platform %s -d %s -w %s -I %s -g %s -T %s" % (
  1137.             path,
  1138.             self.txt_username.GetValue(),
  1139.             self.txt_pass.GetValue(),
  1140.             http_header,
  1141.             self.host_without_http_prefix,
  1142.             self.txt_port.GetValue(),
  1143.             self.platform_index,
  1144.             self.device_index,
  1145.             self.txt_worksize.GetValue(),
  1146.             self.txt_vectors.GetValue(),
  1147.             self.txt_intensity.GetValue(),
  1148.             self.txt_gputhreads.GetValue(),
  1149.             self.txt_flags.GetValue(),
  1150.             )
  1151.        
  1152.         # Full console command for batch file creation given below; don't add -T in this instance so end user gets full output
  1153.         full_console_cmd = "%s --blake256 -u %s -p %s -o %s%s:%s --gpu-platform %s -d %s -w %s -I %s -g %s -T %s" % (
  1154.             path,
  1155.             self.txt_username.GetValue(),
  1156.             self.txt_pass.GetValue(),
  1157.             http_header,
  1158.             self.host_without_http_prefix,
  1159.             self.txt_port.GetValue(),
  1160.             self.platform_index,
  1161.             self.device_index,
  1162.             self.txt_worksize.GetValue(),
  1163.             self.txt_vectors.GetValue(),
  1164.             self.txt_intensity.GetValue(),
  1165.             self.txt_gputhreads.GetValue(),
  1166.             self.txt_flags.GetValue(),
  1167.             )
  1168.            
  1169.         f = open(cgdir + "mine-" +  self.name + ".bat", 'w')
  1170.         f.write(full_console_cmd)
  1171.         f.close()
  1172.        
  1173.         return cmd, os.path.dirname(path)
  1174.  
  1175.     def write_reaper_configs(self, reaperdir):
  1176.         # reaper.conf
  1177.         f = open(reaperdir + "\\reaper.conf", 'w')
  1178.         f.write("kernel reaper.cl\n")
  1179.         f.write("save_binaries yes\n")
  1180.         f.write("enable_graceful_shutdown no\n")
  1181.         f.write("long_polling yes\n")
  1182.         f.write("platform " + str(self.platform_index) + "\n")
  1183.         f.write("device " + str(self.device_index) + "\n\n")
  1184.         f.write("mine litecoin\n")
  1185.         f.close()
  1186.        
  1187.         # litecoin.conf
  1188.         f = open(reaperdir + "\\litecoin.conf", 'w')
  1189.         f.write("host " + self.host_without_http_prefix + "\n")
  1190.         f.write("port " + self.txt_port.GetValue() + "\n")
  1191.         f.write("user " + self.txt_username.GetValue() + "\n")
  1192.         f.write("pass " + self.txt_pass.GetValue() + "\n\n")
  1193.         f.write("protocol litecoin\n\n")
  1194.         f.write("gpu_thread_concurrency " + self.txt_thrcon.GetValue() + "\n")
  1195.         f.write("worksize " + self.txt_worksize.GetValue() + "\n")
  1196.         f.write("vectors " + self.txt_vectors.GetValue() + "\n")
  1197.         f.write("aggression " + self.txt_intensity.GetValue() + "\n")
  1198.         f.write("threads_per_gpu " + self.txt_gputhreads.GetValue() + "\n")
  1199.         f.write("sharethreads 32\n")
  1200.         f.write("lookup_gap 2\n")
  1201.         f.close()
  1202.        
  1203.     def configure_subprocess_reaper(self):
  1204.         """Set up the command line for reaper."""
  1205.         os.chdir(STARTUP_PATH)
  1206.        
  1207.         if os.path.exists(STARTUP_PATH + "\\reaper"):
  1208.             if os.path.exists(STARTUP_PATH + "\\reaper-" + self.name):
  1209.                 logger.info("Reaper folder for miner already exists, writing config and commencing with mining.")
  1210.                 self.write_reaper_configs(STARTUP_PATH + "\\reaper-" + self.name)
  1211.             else:
  1212.                 logger.info("Reaper folder for miner missing, adding folder, files, and config.")
  1213.                 os.makedirs(STARTUP_PATH + "\\reaper-" + self.name)
  1214.                 distutils.dir_util.copy_tree(STARTUP_PATH + "\\reaper", os.getcwd() + "\\reaper-" + self.name)
  1215.                 self.write_reaper_configs(STARTUP_PATH + "\\reaper-" + self.name)
  1216.         else:
  1217.             logger.info("Reaper folder with binaries is missing; can not mine!  Add reaper to ./reaper/ folder please.")
  1218.  
  1219.         path = STARTUP_PATH + "\\reaper-" + self.name
  1220.  
  1221.         # Have to change working directory, windows pain in the ass for reaper - TacoTime
  1222.         os.chdir(path)
  1223.         cmd =  '\"' + path + "\\reaper.exe" +  '\"' # Change this for unix!!!  - TacoTime
  1224.         return cmd, os.path.dirname(path)      
  1225.        
  1226.     def configure_subprocess_cudaminer(self):
  1227.         os.chdir(STARTUP_PATH)
  1228.         path =  '\"' + STARTUP_PATH + "\\cudaminer\\cudaminer.exe" + '\"' # Change this for unix!!!  - TacoTime
  1229.        
  1230.         os.chdir(STARTUP_PATH)
  1231.         cudaminerpath = STARTUP_PATH + "\\cudaminer\\"
  1232.        
  1233.         flag_interactive = 0 # Flag for cudaminer interactive setting
  1234.         if (self.txt_interactive.GetValue() == "Yes"):
  1235.             flag_interactive = 1
  1236.         else:
  1237.             flag_interactive = 0
  1238.  
  1239.         flag_texcache = 1
  1240.         if (self.txt_texcache.GetValue() == "Disabled"):
  1241.             flag_texcache = 0
  1242.         elif (self.txt_texcache.GetValue() == "1D"):
  1243.             flag_texcache = 1
  1244.         else:
  1245.             flag_texcache = 2
  1246.            
  1247.         flag_singlemem = 1
  1248.         if (self.txt_texcache.GetValue() == "Yes"):
  1249.             flag_singlemem = 0
  1250.         else:
  1251.             flag_singlemem = 1
  1252.            
  1253.         # Command line arguments for cudaminer here:
  1254.         # -o host and port prefixed with http://
  1255.         # -O username:password
  1256.         # -d device number (CUDA platform)
  1257.         # -i interactive (bool)
  1258.         # -l kernel/warp configuration (string len 4 or 5?)
  1259.         # -C Texture cache (0=disabled, 1=1D, 2=2D)
  1260.         # -m Single memory block (bool)
  1261.         cmd = "%s -o http://%s:%s/ -O %s:%s -d %s -i %s -l %s -C %s -m %s" % (
  1262.             path,
  1263.             self.host_without_http_prefix,
  1264.             self.txt_port.GetValue(),
  1265.             self.txt_username.GetValue(),
  1266.             self.txt_pass.GetValue(),
  1267.             self.device_index,
  1268.             flag_interactive,
  1269.             self.txt_warpflags.GetValue(),
  1270.             flag_texcache,
  1271.             flag_singlemem)
  1272.            
  1273.         # Create a batch file in case the user wants to try it out in console, too
  1274.         f = open(cudaminerpath + "mine-" +  self.name + ".bat", 'w')
  1275.         f.write(cmd)
  1276.         f.close()
  1277.        
  1278.         return cmd, os.path.dirname(path)  
  1279.        
  1280.     def configure_subprocess_stratumproxy(self):
  1281.         """Set up the command line for proxy miner."""
  1282.         os.chdir(STARTUP_PATH)
  1283.         path = STARTUP_PATH + "\\stratumproxy\\mining_proxy.exe"
  1284.         if path.endswith('.py'):
  1285.             path = "python " + path
  1286.  
  1287.         # Command line arguments for cgminer here:
  1288.         # -u <username>
  1289.         # -p <password>
  1290.         # -o <http://server.ip:port>
  1291.         # -d <device appear in pyopencl>
  1292.         # -l <log message period in second>
  1293.         # -T <disable curses interface and output to console (stdout)>
  1294.         cmd = "%s -pa scrypt -o %s -p %s %s" % (
  1295.             path,
  1296.             self.host_without_http_prefix,
  1297.             self.txt_port.GetValue(),
  1298.             self.txt_flags.GetValue())
  1299.         return cmd, os.path.dirname(path)
  1300.  
  1301.     # End backend specific code
  1302.     ###########################
  1303.  
  1304.     def start_mining(self):
  1305.         """Launch a miner subprocess and attach a MinerListenerThread."""
  1306.         self.is_paused = False
  1307.  
  1308.         # Avoid showing a console window when frozen
  1309.         try: import win32process
  1310.         except ImportError: flags = 0
  1311.         else: flags = win32process.CREATE_NO_WINDOW
  1312.  
  1313.         # Determine what command line arguments to use
  1314.  
  1315.         listener_cls = MinerListenerThread
  1316.        
  1317.         if not self.is_external_miner:
  1318.             conf_func = self.configure_subprocess_poclbm
  1319.         elif "rpcminer" in self.external_path:
  1320.             conf_func = self.configure_subprocess_rpcminer
  1321.         elif "bitcoin-miner" in self.external_path:
  1322.             conf_func = self.configure_subprocess_ufasoft
  1323.         elif "phoenix" in self.external_path:
  1324.             conf_func = self.configure_subprocess_phoenix
  1325.             listener_cls = PhoenixListenerThread
  1326.         elif "CGMINER" in self.external_path:
  1327.             conf_func = self.configure_subprocess_cgminer
  1328.             listener_cls = CgListenerThread
  1329.         elif "REAPER" in self.external_path:
  1330.             conf_func = self.configure_subprocess_reaper
  1331.             listener_cls = ReaperListenerThread
  1332.         elif "CUDAMINER" in self.external_path:
  1333.             conf_func = self.configure_subprocess_cudaminer
  1334.             listener_cls = CudaminerListenerThread
  1335.         elif "PROXY" in self.external_path:
  1336.             conf_func = self.configure_subprocess_stratumproxy
  1337.             listener_cls = ProxyListenerThread
  1338.         else:
  1339.             raise ValueError # TODO: handle unrecognized miner
  1340.         cmd, cwd = conf_func()
  1341.  
  1342.         # for ufasoft:
  1343.         #  redirect stderr to stdout
  1344.         #  use universal_newlines to catch the \r output on Mhash/s lines
  1345.         try:
  1346.             logger.debug(_('Running command: ') + cmd)
  1347.             # for cgminer:
  1348.             if conf_func == self.configure_subprocess_cgminer:
  1349.                 cgminer_env = os.environ # Create an environment to set below environmental variable in
  1350.                 cgminer_env['GPU_MAX_ALLOC_PERCENT'] = "100" # Set this environmental variable so we can use high thread concurrencies in cgminer
  1351.                 self.miner = subprocess.Popen(cmd,
  1352.                                               env=cgminer_env,
  1353.                                               stdout=subprocess.PIPE,
  1354.                                               stderr=None,
  1355.                                               universal_newlines=True,
  1356.                                               creationflags=0x08000000,
  1357.                                               shell=(sys.platform != 'win32'))
  1358.             else:
  1359.                 self.miner = subprocess.Popen(cmd,
  1360.                                               stdout=subprocess.PIPE,
  1361.                                               stderr=subprocess.STDOUT,
  1362.                                               universal_newlines=True,
  1363.                                               creationflags=0x08000000,
  1364.                                               shell=(sys.platform != 'win32'))
  1365.            
  1366.         except OSError:
  1367.             raise #TODO: the folder or exe could not exist
  1368.         self.miner_listener = listener_cls(self, self.miner)
  1369.         self.miner_listener.daemon = True
  1370.         self.miner_listener.start()
  1371.         self.is_mining = True
  1372.         self.set_status(STR_STARTING, 1)
  1373.         self.start.SetLabel(self.get_start_label())
  1374.  
  1375.         try:
  1376.             set_process_affinity(self.miner.pid, self.affinity_mask)
  1377.         except:
  1378.             pass # TODO: test on Linux
  1379.  
  1380.     def on_close(self):
  1381.         """Prepare to close gracefully."""
  1382.         self.stop_mining()
  1383.         self.balance_refresh_timer.Stop()
  1384.  
  1385.     def stop_mining(self):
  1386.         """Terminate the poclbm process if able and its associated listener."""
  1387.         if self.miner is not None:
  1388.             if self.miner.returncode is None:
  1389.                 # It didn't return yet so it's still running.
  1390.                 try:
  1391.                     self.miner.terminate()
  1392.                 except OSError:
  1393.                     pass # TODO: Guess it wasn't still running?
  1394.             self.miner = None
  1395.         if self.miner_listener is not None:
  1396.             self.miner_listener.shutdown_event.set()
  1397.             self.miner_listener = None
  1398.         self.is_mining = False
  1399.         self.is_paused = False
  1400.         self.set_status(STR_STOPPED, 1)
  1401.         self.start.SetLabel(self.get_start_label())
  1402.  
  1403.     def update_khash(self, rate):
  1404.         """Update our rate according to a report from the listener thread.
  1405.  
  1406.        If we are receiving rate messages then it means poclbm is no longer
  1407.        reporting errors.
  1408.        """
  1409.         self.last_rate = rate
  1410.         self.set_status(format_khash(rate), 1)
  1411.        
  1412.         if self.is_possible_error:
  1413.             self.update_statusbar()
  1414.             self.is_possible_error = False
  1415.            
  1416.     def update_shares_reaper(self, quantity, accepted):
  1417.         if self.is_solo:
  1418.             self.solo_blocks_found = quantity
  1419.         elif accepted:
  1420.             self.accepted_shares = quantity
  1421.         else:
  1422.             self.invalid_shares = quantity
  1423.         self.update_last_time(accepted) # BUG: This doesn't work right, but let's ignore it for now - TacoTime
  1424.         self.update_statusbar()
  1425.        
  1426.     def update_attributes_reaper(self, clstring):
  1427.         sharesA = int(non_decimal.sub('', re.search(r"shares: *\d+\|", clstring).group(0)))
  1428.         sharesR = int(non_decimal.sub('', re.search(r"\|\d+,", clstring).group(0)))
  1429.         hashrate = float(non_decimal.sub('', re.search(r"\~.*kH", clstring).group(0)))
  1430.        
  1431.         if self.is_solo:
  1432.             self.solo_blocks_found = sharesA
  1433.         else:
  1434.             self.accepted_shares = sharesA
  1435.        
  1436.         self.invalid_shares = sharesR
  1437.         self.last_rate = hashrate
  1438.         self.set_status(format_khash(hashrate), 1)
  1439.         if self.is_possible_error:
  1440.             self.update_statusbar()
  1441.             self.is_possible_error = False
  1442.         self.update_statusbar()
  1443.  
  1444.     def update_statusbar(self):
  1445.         """Show the shares or equivalent on the statusbar."""
  1446.         if self.is_solo:
  1447.             text = _("Difficulty 1 hashes: %(nhashes)d %(update_time)s") % \
  1448.                 dict(nhashes=self.accepted_shares,
  1449.                      update_time=self.format_last_update_time())
  1450.             if self.solo_blocks_found > 0:
  1451.                 block_text = _("Blocks: %d, ") % self.solo_blocks_found
  1452.                 text = block_text + text
  1453.         else:
  1454.             text = _("Shares: %d accepted") % self.accepted_shares
  1455.             if self.invalid_shares > 0:
  1456.                 text += _(", %d stale/invalid") % self.invalid_shares
  1457.             text += " %s" % self.format_last_update_time()
  1458.         self.set_status(text, 0)
  1459.  
  1460.     def update_last_time(self, accepted):
  1461.         """Set the last update time to now (in local time)."""
  1462.  
  1463.         now = time.time()
  1464.         if accepted:
  1465.             self.accepted_times.append(now)
  1466.             while now - self.accepted_times[0] > SAMPLE_TIME_SECS:
  1467.                 self.accepted_times.popleft()
  1468.         else:
  1469.             self.invalid_times.append(now)
  1470.             while now - self.invalid_times[0] > SAMPLE_TIME_SECS:
  1471.                 self.invalid_times.popleft()
  1472.  
  1473.     def format_last_update_time(self):
  1474.         """Format last update time for display."""
  1475.         time_fmt = '%I:%M:%S%p'
  1476.         if self.last_update_time is None:
  1477.             return ""
  1478.         return _("- last at %s") % time.strftime(time_fmt, self.last_update_time)
  1479.  
  1480.     def update_shares(self, accepted):
  1481.         """Update our shares with a report from the listener thread."""
  1482.         if self.is_solo and accepted:
  1483.             self.solo_blocks_found += 1
  1484.         elif accepted:
  1485.             self.accepted_shares += 1
  1486.         else:
  1487.             self.invalid_shares += 1
  1488.         self.update_last_time(accepted)
  1489.         self.update_statusbar()
  1490.  
  1491.     def update_status(self, msg):
  1492.         """Update our status with a report from the listener thread.
  1493.  
  1494.        If we receive a message from poclbm we don't know how to interpret,
  1495.        it's probably some kind of error state - in this case the best
  1496.        thing to do is just show it to the user on the status bar.
  1497.        """
  1498.         self.set_status(msg)
  1499.         if self.last_rate == -0.0000001:
  1500.             self.is_possible_error = False
  1501.         else:
  1502.             self.is_possible_error = True
  1503.  
  1504.     def set_status(self, msg, index=0):
  1505.         """Set the current statusbar text, but only if we have focus."""
  1506.         if self.parent.GetSelection() == self.parent.GetPageIndex(self):
  1507.             self.statusbar.SetStatusText(msg, index)
  1508.  
  1509.     def on_focus(self):
  1510.         """When we receive focus, update our status.
  1511.  
  1512.        This ensures that when switching tabs, the statusbar always
  1513.        shows the current tab's status.
  1514.        """
  1515.         self.update_statusbar()
  1516.         if self.is_mining:
  1517.             self.update_khash(self.last_rate)
  1518.         else:
  1519.             self.set_status(STR_STOPPED, 1)
  1520.  
  1521.     def get_taskbar_text(self):
  1522.         """Return text for the hover state of the taskbar."""
  1523.         rate = format_khash(self.last_rate) if self.is_mining else STR_STOPPED
  1524.         return "%s: %s" % (self.name, rate)
  1525.  
  1526.     def update_solo(self):
  1527.         """Update our easy hashes with a report from the listener thread."""
  1528.         self.accepted_shares += 1
  1529.         self.update_last_time(True)
  1530.         self.update_statusbar()
  1531.        
  1532.     def on_select_gpusettings(self, event):
  1533.         """Update our info in response to a new server choice."""
  1534.         new_gpusettings_name = str(self.gpusettings.GetValue())
  1535.         new_gpusettings = self.get_gpusettings_by_field(new_gpusettings_name, 'name')
  1536.         self.change_gpusettings(new_gpusettings)
  1537.  
  1538.     def on_select_server(self, event):
  1539.         """Update our info in response to a new server choice."""
  1540.         print self.server.GetValue()
  1541.         new_server_name = self.server.GetValue()
  1542.         new_server = self.get_server_by_field(new_server_name, 'name')
  1543.         self.change_server(new_server)
  1544.  
  1545.     def get_gpusettings_by_field(self, target_val, field):
  1546.         """Return the first server dict with the specified val, or {}."""
  1547.         for s in self.gpusettings_data:
  1548.             if s.get(field) == target_val:
  1549.                 return s
  1550.         return {}        
  1551.        
  1552.     def get_server_by_field(self, target_val, field):
  1553.         """Return the first server dict with the specified val, or {}."""
  1554.         for s in self.servers:
  1555.             if s.get(field) == target_val:
  1556.                 return s
  1557.         return {}
  1558.  
  1559.     def set_widgets_visible(self, widgets, show=False):
  1560.         """Show or hide each widget in widgets according to the show flag."""
  1561.         for w in widgets:
  1562.             if show:
  1563.                 w.Show()
  1564.             else:
  1565.                 w.Hide()
  1566.  
  1567.     def set_tooltips(self):
  1568.         add_tooltip(self.server, _("Server to connect to. Different servers have different fees and features.\nCheck their websites for full information."))
  1569.         add_tooltip(self.website, _("Website of the currently selected server. Click to visit."))
  1570.         add_tooltip(self.device_listbox, _("Available OpenCL devices on your system."))
  1571.         add_tooltip(self.txt_host, _("Host address, without http:// prefix."))
  1572.         add_tooltip(self.txt_port, _("Server port. This is usually 8332 for getwork or 3333 for stratum."))
  1573.         add_tooltip(self.txt_username, _("The miner's username.\nMay be different than your account username.\nExample: Kiv.GPU"))
  1574.         add_tooltip(self.txt_pass, _("The miner's password.\nMay be different than your account password."))
  1575.         add_tooltip(self.txt_flags, _("Extra flags to pass to the miner."))
  1576.         add_tooltip(self.txt_thrcon, _("Set the memory size for the scrypt kernel to use.\n1 unit = 64 KB"))
  1577.         add_tooltip(self.txt_worksize, _("Set the worksize value.\nDefault: 256"))
  1578.         add_tooltip(self.txt_vectors, _("Set the vectors value.\nDefault: 1"))
  1579.         add_tooltip(self.txt_gputhreads, _("Set the number of default threads to use.\nDefault: 1"))
  1580.         add_tooltip(self.txt_intensity, _("Set the intensity/aggression value.\nHigh intensity: 18-20\nLow intensity: 10-14"))
  1581.         add_tooltip(self.gpusettings, _("Default values for any given AMD video card.\nTry these first if you are new to scrypt mining."))
  1582.         add_tooltip(self.txt_interactive, _("Run in interactive mode so that the desktop remains usable while mining.\nMay slow hash rate."))
  1583.         add_tooltip(self.txt_texcache, _("Enable use of 1D or 2D texture cache for mining."))
  1584.         add_tooltip(self.txt_singlemem, _("Use multiple blocks of memory or a single block of memory for mining."))
  1585.         add_tooltip(self.txt_warpflags, _("String in S##x# or ##x# format that gives the warp configuration.\nExamples: S27x3 or 28x4.\nUse auto for automatic warp configuration tuning."))
  1586.         #for chk in self.affinity_chks:
  1587.         #    add_tooltip(chk, _("CPU cores used for mining.\nUnchecking some cores can reduce high CPU usage in some systems."))
  1588.  
  1589.     def reset_statistics(self):
  1590.         """Reset our share statistics to zero."""
  1591.         self.solo_blocks_found = 0
  1592.         self.accepted_shares = 0
  1593.         self.accepted_times.clear()
  1594.         self.invalid_shares = 0
  1595.         self.invalid_times.clear()
  1596.         self.update_statusbar()
  1597.        
  1598.     def change_gpusettings(self, new_gpusettings):
  1599.         self.reset_statistics()
  1600.         if 'thread_concurrency' in new_gpusettings:
  1601.             self.txt_thrcon.SetValue(str(new_gpusettings['thread_concurrency']))
  1602.         if 'worksize' in new_gpusettings:
  1603.             self.txt_worksize.SetValue(str(new_gpusettings['worksize']))
  1604.         if 'vectors' in new_gpusettings:
  1605.             self.txt_vectors.SetValue(str(new_gpusettings['vectors']))
  1606.         if 'gputhreads' in new_gpusettings:
  1607.             self.txt_gputhreads.SetValue(str(new_gpusettings['gputhreads']))
  1608.         if 'intensity' in new_gpusettings:
  1609.             self.txt_intensity.SetValue(str(new_gpusettings['intensity']))
  1610.  
  1611.     def change_server(self, new_server):
  1612.         """Change the server to new_server, updating fields as needed."""
  1613.         self.reset_statistics()
  1614.  
  1615.         # Set defaults before we do server specific code
  1616.         self.set_tooltips()
  1617.         self.set_widgets_visible(self.all_widgets, True)
  1618.         self.withdraw.Disable()
  1619.  
  1620.         url = new_server.get('url', 'n/a')
  1621.         self.website.SetLabel(url)
  1622.         self.website.SetURL(url)
  1623.  
  1624.         # Invalidate any previous auth token since it won't be valid for the
  1625.         # new server.
  1626.         self.balance_auth_token = ""
  1627.  
  1628.         if 'host' in new_server:
  1629.             self.txt_host.SetValue(new_server['host'])
  1630.         if 'port' in new_server:
  1631.             self.txt_port.SetValue(str(new_server['port']))
  1632.  
  1633.  
  1634.         # Call server specific code.
  1635.         host = new_server.get('host', "").lower()
  1636.         if host == "api2.bitcoin.cz" or host == "mtred.com": self.layout_slush()
  1637.         if "eligius.st" in host: self.layout_eligius()
  1638.         elif host == "bitpenny.dyndns.biz": self.layout_bitpenny()
  1639.         elif host == "pit.deepbit.net": self.layout_deepbit()
  1640.         elif host == "btcmine.com": self.layout_btcmine()
  1641.         elif host == "rr.btcmp.com": self.layout_btcmp()
  1642.         elif "btcguild.com" in host: self.layout_btcguild()
  1643.         elif host == "bitcoin-server.de": self.layout_bitcoinserver
  1644.         elif host == "pit.x8s.de": self.layout_x8s()
  1645.         elif self.external_path == "CUDAMINER": self.layout_cudaminer()
  1646.         else: self.layout_default()
  1647.  
  1648.         self.Layout()
  1649.  
  1650.         self.update_tab_name()
  1651.  
  1652.     def on_balance_cooldown_tick(self, event=None):
  1653.         """Each second, decrement the cooldown for refreshing balance."""
  1654.         self.balance_cooldown_seconds -= 1
  1655.         self.balance_refresh.SetLabel("%d..." % self.balance_cooldown_seconds)
  1656.         if self.balance_cooldown_seconds <= 0:
  1657.             self.balance_refresh_timer.Stop()
  1658.             self.balance_refresh.Enable()
  1659.             self.balance_refresh.SetLabel(STR_REFRESH_BALANCE)
  1660.  
  1661.     def require_auth_token(self):
  1662.         """Prompt the user for an auth token if they don't have one already.
  1663.  
  1664.        Set the result to self.balance_auth_token and return None.
  1665.        """
  1666.         if self.balance_auth_token:
  1667.             return
  1668.         url = self.server_config.get('balance_token_url')
  1669.         dialog = BalanceAuthRequest(self, url)
  1670.         dialog.txt_token.SetFocus()
  1671.         result = dialog.ShowModal()
  1672.         dialog.Destroy()
  1673.         if result == wx.ID_CANCEL:
  1674.             return
  1675.         self.balance_auth_token = dialog.get_value() # TODO: validate token?
  1676.  
  1677.     def is_auth_token_rejected(self, response):
  1678.         """If the server rejected our token, reset auth_token and return True.
  1679.  
  1680.        Otherwise, return False.
  1681.        """
  1682.         if response.status in [401, 403]: # 401 Unauthorized or 403 Forbidden
  1683.             # Token rejected by the server - reset their token so they'll be
  1684.             # prompted again
  1685.             self.balance_auth_token = ""
  1686.             return True
  1687.         return False
  1688.  
  1689.     def request_balance_get(self, balance_auth_token, use_https=False):
  1690.         """Request our balance from the server via HTTP GET and auth token.
  1691.  
  1692.        This method should be run in its own thread.
  1693.        """
  1694.         response, data = http_request(
  1695.             self.server_config['balance_host'],
  1696.             "GET",
  1697.             self.server_config["balance_url"] % balance_auth_token,
  1698.             use_https=use_https
  1699.         )
  1700.         if self.is_auth_token_rejected(response):
  1701.             data = _("Auth token rejected by server.")
  1702.         elif not data:
  1703.             data = STR_CONNECTION_ERROR
  1704.         else:
  1705.             try:
  1706.                 info = json.loads(data)
  1707.                 confirmed = (info.get('confirmed_reward') or
  1708.                              info.get('confirmed') or
  1709.                              info.get('balance') or
  1710.                              info.get('user', {}).get('confirmed_rewards') or
  1711.                              0)
  1712.                 unconfirmed = (info.get('unconfirmed_reward') or
  1713.                                info.get('unconfirmed') or
  1714.                                info.get('user', {}).get('unconfirmed_rewards') or
  1715.                                0)
  1716.                 if self.server_config.get('host') == "pit.deepbit.net":
  1717.                     ipa = info.get('ipa', False)
  1718.                     self.withdraw.Enable(ipa)
  1719.  
  1720.                 if self.server_config.get('host') == "rr.btcmp.com":
  1721.                     ipa = info.get('can_payout', False)
  1722.                     self.withdraw.Enable(ipa)
  1723.  
  1724.                 data = _("%s confirmed") % format_balance(confirmed)
  1725.                 if unconfirmed > 0:
  1726.                     data += _(", %s unconfirmed") % format_balance(unconfirmed)
  1727.             except: # TODO: what exception here?
  1728.                 data = _("Bad response from server.")
  1729.  
  1730.         wx.CallAfter(self.balance_amt.SetLabel, data)
  1731.  
  1732.     def on_withdraw(self, event):
  1733.         self.withdraw.Disable()
  1734.         host = self.server_config.get('host')
  1735.         if host == 'bitpenny.dyndns.biz':
  1736.             self.withdraw_bitpenny()
  1737.         elif host == 'pit.deepbit.net':
  1738.             self.withdraw_deepbit()
  1739.         elif host == 'rr.btcmp.com':
  1740.             self.withdraw_btcmp()
  1741.  
  1742.     def requires_auth_token(self, host):
  1743.         """Return True if the specified host requires an auth token for balance update."""
  1744.         HOSTS_REQUIRING_AUTH_TOKEN = ["api2.bitcoin.cz",
  1745.                                       "btcmine.com",
  1746.                                       "pit.deepbit.net",
  1747.                                       "pit.x8s.de",
  1748.                                       "mtred.com",
  1749.                                       "rr.btcmp.com",
  1750.                                       "bitcoin-server.de"]
  1751.         if host in HOSTS_REQUIRING_AUTH_TOKEN: return True        
  1752.         if "btcguild" in host: return True      
  1753.         return False
  1754.    
  1755.     def requires_https(self, host):
  1756.         """Return True if the specified host requires HTTPs for balance update."""
  1757.         HOSTS = ["mtred.com", "api2.bitcoin.cz"]
  1758.         if host in HOSTS: return True
  1759.         if "btcguild" in host: return True
  1760.         return False
  1761.    
  1762.     def on_balance_refresh(self, event=None):
  1763.         """Refresh the miner's balance from the server."""
  1764.         host = self.server_config.get("host")
  1765.         if self.requires_auth_token(host):
  1766.             self.require_auth_token()
  1767.             if not self.balance_auth_token: # They cancelled the dialog
  1768.                 return
  1769.             try:
  1770.                 self.balance_auth_token.decode('ascii')
  1771.             except UnicodeDecodeError:
  1772.                 return # Invalid characters in auth token
  1773.             self.http_thread = threading.Thread(
  1774.                 target=self.request_balance_get,
  1775.                 args=(self.balance_auth_token,),
  1776.                 kwargs=dict(use_https=self.requires_https(host)))
  1777.             self.http_thread.start()
  1778.         elif host == 'bitpenny.dyndns.biz':
  1779.             self.http_thread = threading.Thread(
  1780.             target=self.request_payout_bitpenny, args=(False,))
  1781.             self.http_thread.start()
  1782.         elif 'eligius.st' in host:
  1783.             self.http_thread = threading.Thread(
  1784.                 target=self.request_balance_eligius
  1785.             )
  1786.             self.http_thread.start()
  1787.  
  1788.         self.balance_refresh.Disable()
  1789.         self.balance_cooldown_seconds = 10
  1790.         self.balance_refresh_timer.Start(1000)
  1791.  
  1792.     #################################
  1793.     # Begin server specific HTTP code
  1794.  
  1795.     def withdraw_btcmp(self):
  1796.         """Launch a thread to withdraw from deepbit."""
  1797.         self.require_auth_token()
  1798.         if not self.balance_auth_token: # User refused to provide token
  1799.             return
  1800.         self.http_thread = threading.Thread(
  1801.                 target=self.request_payout_btcmp,
  1802.                 args=(self.balance_auth_token,))
  1803.         self.http_thread.start()
  1804.  
  1805.     def withdraw_deepbit(self):
  1806.         """Launch a thread to withdraw from deepbit."""
  1807.         self.require_auth_token()
  1808.         if not self.balance_auth_token: # User refused to provide token
  1809.             return
  1810.         self.http_thread = threading.Thread(
  1811.                 target=self.request_payout_deepbit,
  1812.                 args=(self.balance_auth_token,))
  1813.         self.http_thread.start()
  1814.  
  1815.     def withdraw_bitpenny(self):
  1816.         self.http_thread = threading.Thread(
  1817.             target=self.request_payout_bitpenny, args=(True,))
  1818.         self.http_thread.start() # TODO: look at aliasing of this variable
  1819.  
  1820.     def request_payout_btcmp(self, balance_auth_token):
  1821.         """Request payout from btcmp's server via HTTP POST."""        
  1822.         response, data = http_request(
  1823.             self.server_config['balance_host'],
  1824.             "GET",
  1825.             self.server_config["payout_url"] % balance_auth_token,
  1826.             use_https=False
  1827.         )
  1828.        
  1829.         if self.is_auth_token_rejected(response):
  1830.             data = _("Auth token rejected by server.")
  1831.         elif not data:
  1832.             data = STR_CONNECTION_ERROR
  1833.         else:
  1834.             data = _("Withdraw OK")
  1835.         wx.CallAfter(self.on_balance_received, data)
  1836.  
  1837.     def request_payout_deepbit(self, balance_auth_token):
  1838.         """Request payout from deepbit's server via HTTP POST."""
  1839.         post_params = dict(id=1,
  1840.                            method="request_payout")
  1841.         response, data = http_request(
  1842.              self.server_config['balance_host'],
  1843.              "POST",
  1844.              self.server_config['balance_url'] % balance_auth_token,
  1845.              json.dumps(post_params),
  1846.              {"Content-type": "application/json; charset=utf-8",
  1847.               "User-Agent": USER_AGENT}
  1848.         )
  1849.         if self.is_auth_token_rejected(response):
  1850.             data = _("Auth token rejected by server.")
  1851.         elif not data:
  1852.             data = STR_CONNECTION_ERROR
  1853.         else:
  1854.             data = _("Withdraw OK")
  1855.         wx.CallAfter(self.on_balance_received, data)
  1856.  
  1857.     def request_payout_bitpenny(self, withdraw):
  1858.         """Request our balance from BitPenny via HTTP POST.
  1859.  
  1860.        If withdraw is True, also request a withdrawal.
  1861.        """
  1862.         post_params = dict(a=self.txt_username.GetValue(), w=int(withdraw))
  1863.         response, data = http_request(
  1864.              self.server_config['balance_host'],
  1865.              "POST",
  1866.              self.server_config['balance_url'],
  1867.              urllib.urlencode(post_params),
  1868.              {"Content-type": "application/x-www-form-urlencoded"}
  1869.         )
  1870.         if self.is_auth_token_rejected(response):
  1871.             data = _("Auth token rejected by server.")
  1872.         elif not data:
  1873.             data = STR_CONNECTION_ERROR
  1874.         elif withdraw:
  1875.             data = _("Withdraw OK")
  1876.         wx.CallAfter(self.on_balance_received, data)
  1877.  
  1878.     def request_balance_eligius(self):
  1879.         """Request our balance from Eligius
  1880.        """
  1881.         response, data = http_request(
  1882.              self.server_config['balance_host'],
  1883.              "POST",
  1884.              self.server_config['balance_url'] % (self.txt_username.GetValue(),),
  1885.         )
  1886.         if not data:
  1887.             data = STR_CONNECTION_ERROR
  1888.         try:
  1889.             data = json.loads(data)
  1890.             data = data['expected'] / 1e8
  1891.         except BaseException as e:
  1892.             data = str(e)
  1893.         wx.CallAfter(self.on_balance_received, data)
  1894.  
  1895.     def on_balance_received(self, balance):
  1896.         """Set the balance in the GUI."""
  1897.         try:
  1898.             amt = float(balance)
  1899.         except ValueError: # Response was some kind of error
  1900.             self.balance_amt.SetLabel(balance)
  1901.         else:
  1902.             if amt > 0.1:
  1903.                 self.withdraw.Enable()
  1904.             amt_str = format_balance(amt)
  1905.             self.balance_amt.SetLabel(amt_str)
  1906.         self.Layout()
  1907.  
  1908.     # End server specific HTTP code
  1909.     ###############################
  1910.  
  1911.     def set_name(self, name):
  1912.         """Set the label on this miner's tab to name."""
  1913.         self.name = name
  1914.         if self.summary_name:
  1915.             self.summary_name.SetLabel(self.name)
  1916.         self.update_tab_name()
  1917.  
  1918.     def update_tab_name(self):
  1919.         """Update the tab name to reflect modified status."""
  1920.         name = self.name
  1921.         if self.is_modified:
  1922.             name += "*"
  1923.         page = self.parent.GetPageIndex(self)
  1924.         if page != -1:
  1925.             self.parent.SetPageText(page, name)
  1926.  
  1927.     def check_if_modified(self, event):
  1928.         """Update the title of the tab to have an asterisk if we are modified."""
  1929.         self.update_tab_name()
  1930.         event.Skip()
  1931.  
  1932.     def on_saved(self):
  1933.         """Update our last data after a save."""
  1934.         self.last_data = self.get_data()
  1935.         self.update_tab_name()
  1936.  
  1937.     def layout_init(self):
  1938.         """Create the sizers for this frame and set up the external text.
  1939.  
  1940.        Return the lowest row that is available.
  1941.        """
  1942.         self.frame_sizer = wx.BoxSizer(wx.VERTICAL)
  1943.         self.frame_sizer.Add((20, 10), 0, wx.EXPAND, 0) # Controls top window size
  1944.         self.inner_sizer = wx.GridBagSizer(10, 5) # Controls inner window height, width
  1945.         self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
  1946.         row = 0
  1947.         # if self.is_external_miner:
  1948.             # self.inner_sizer.Add(self.external_lbl, (row, 0), flag=LBL_STYLE)
  1949.             # self.inner_sizer.Add(self.txt_external, (row, 1), span=(1, 3), flag=wx.EXPAND)
  1950.             # row += 1
  1951.         return row
  1952.  
  1953.     def layout_server_and_website(self, row):
  1954.         """Lay out the server and website widgets in the specified row."""
  1955.         self.inner_sizer.Add(self.server_lbl, (row, 0), flag=LBL_STYLE)
  1956.         self.inner_sizer.Add(self.server, (row, 1), flag=wx.EXPAND)
  1957.         self.inner_sizer.Add(self.website_lbl, (row, 2), flag=LBL_STYLE)
  1958.         self.inner_sizer.Add(self.website, (row, 3), flag=wx.ALIGN_CENTER_VERTICAL)
  1959.        
  1960.     def layout_minertype(self, row):
  1961.         """Display which miner is being used"""
  1962.         if self.external_path == "CGMINER":
  1963.             self.inner_sizer.Add(self.minercgminer_lbl, (row, 0), flag=LBL_STYLE)
  1964.         elif self.external_path == "REAPER":
  1965.             self.inner_sizer.Add(self.minerreaper_lbl, (row, 0), flag=LBL_STYLE)
  1966.         elif self.external_path == "CUDAMINER":
  1967.             self.inner_sizer.Add(self.minercudaminer_lbl, (row, 0), flag=LBL_STYLE)
  1968.         else:
  1969.             self.inner_sizer.Add(self.proxy_lbl, (row, 0), flag=LBL_STYLE)
  1970.  
  1971.     def layout_host_and_port(self, row):
  1972.         """Lay out the host and port widgets in the specified row."""
  1973.         self.inner_sizer.Add(self.host_lbl, (row, 0), flag=LBL_STYLE)
  1974.         self.inner_sizer.Add(self.txt_host, (row, 1), flag=wx.EXPAND)
  1975.         self.inner_sizer.Add(self.port_lbl, (row, 2), flag=LBL_STYLE)
  1976.         self.inner_sizer.Add(self.txt_port, (row, 3), flag=wx.ALIGN_CENTER_VERTICAL)
  1977.  
  1978.     def layout_user_and_pass(self, row):
  1979.         """
  1980.        Lay out the user and pass widgets in the specified row.
  1981.        Also used to help out users with stratum proxy.
  1982.        """
  1983.         if (self.external_path == "PROXY"):
  1984.             self.inner_sizer.Add(self.stratuminfo0_lbl, (row, 0), flag=wx.EXPAND)
  1985.         else:
  1986.             self.inner_sizer.Add(self.user_lbl, (row, 0), flag=LBL_STYLE)
  1987.         if  (self.external_path == "PROXY"):
  1988.             self.inner_sizer.Add(self.stratuminfo1_lbl, (row, 1), flag=wx.EXPAND)
  1989.         else:
  1990.             self.inner_sizer.Add(self.txt_username, (row, 1), flag=wx.EXPAND)
  1991.         self.inner_sizer.Add(self.pass_lbl, (row, 2), flag=LBL_STYLE)
  1992.         self.inner_sizer.Add(self.txt_pass, (row, 3), flag=wx.EXPAND)    
  1993.    
  1994.     def layout_thrcon_worksize(self, row):
  1995.         """
  1996.        Like it sounds, thread concurrency and worksize boxes.
  1997.        """
  1998.         self.inner_sizer.Add(self.thrcon_lbl, (row, 0), flag=LBL_STYLE)
  1999.         self.inner_sizer.Add(self.txt_thrcon, (row, 1), flag=wx.EXPAND)
  2000.         self.inner_sizer.Add(self.worksize_lbl, (row, 2), flag=LBL_STYLE)
  2001.         self.inner_sizer.Add(self.txt_worksize, (row, 3), flag=wx.EXPAND)
  2002.  
  2003.     def layout_vectors_intensity(self, row):
  2004.         """
  2005.        Like it sounds, vector and intensity boxes.
  2006.        """
  2007.         self.inner_sizer.Add(self.vectors_lbl, (row, 0), flag=LBL_STYLE)
  2008.         self.inner_sizer.Add(self.txt_vectors, (row, 1), flag=wx.EXPAND)
  2009.         self.inner_sizer.Add(self.intensity_lbl, (row, 2), flag=LBL_STYLE)
  2010.         self.inner_sizer.Add(self.txt_intensity, (row, 3), flag=wx.EXPAND)
  2011.        
  2012.     def layout_gputhreads_gpusettings(self, row):
  2013.         """
  2014.        Like it sounds, no. gpu threads and gpu defaults
  2015.        """
  2016.         self.inner_sizer.Add(self.gputhreads_lbl, (row, 0), flag=LBL_STYLE)
  2017.         self.inner_sizer.Add(self.txt_gputhreads, (row, 1), flag=wx.EXPAND)
  2018.         self.inner_sizer.Add(self.gpusettings_lbl, (row, 2), flag=LBL_STYLE)
  2019.         self.inner_sizer.Add(self.gpusettings, (row, 3), flag=wx.EXPAND)
  2020.        
  2021.     def layout_stratum(self, row):
  2022.         """
  2023.        Like it sounds, stratum boxes.
  2024.        """
  2025.         self.inner_sizer.Add(self.stratum_lbl, (row, 0), flag=LBL_STYLE)
  2026.         self.inner_sizer.Add(self.txt_stratum, (row, 1), flag=wx.EXPAND)
  2027.    
  2028.     def layout_device_and_flags(self, row):
  2029.         """Lay out the device and flags widgets in the specified row.
  2030.  
  2031.        Hide the device dropdown if RPCMiner is present since it doesn't use it.
  2032.        """
  2033.         device_visible = self.is_device_visible
  2034.         self.set_widgets_visible([self.device_lbl, self.device_listbox], device_visible)
  2035.         if device_visible:
  2036.             self.inner_sizer.Add(self.device_lbl, (row, 0), flag=LBL_STYLE)
  2037.             self.inner_sizer.Add(self.device_listbox, (row, 1), flag=wx.EXPAND)
  2038.         col = 2 * (device_visible)
  2039.         self.inner_sizer.Add(self.flags_lbl, (row, col), flag=LBL_STYLE)
  2040.         span = (1, 1) if device_visible else (1, 4)
  2041.         self.inner_sizer.Add(self.txt_flags, (row, col + 1), span=span, flag=wx.EXPAND)
  2042.        
  2043.     def layout_affinity(self, row):
  2044.         """Lay out the affinity checkboxes in the specified row."""
  2045.         self.inner_sizer.Add(self.affinity_lbl, (row, 0))
  2046.  
  2047.         affinity_sizer = wx.BoxSizer(wx.HORIZONTAL)
  2048.         # for chk in self.affinity_chks:
  2049.             # affinity_sizer.Add(chk)
  2050.         # self.inner_sizer.Add(affinity_sizer, (row, 1))
  2051.  
  2052.     def layout_balance(self, row):
  2053.         """Lay out the balance widgets in the specified row."""
  2054.         self.inner_sizer.Add(self.balance_lbl, (row, 0), flag=LBL_STYLE)
  2055.         self.inner_sizer.Add(self.balance_amt, (row, 1))
  2056.  
  2057.     def layout_finish(self):
  2058.         """Lay out the buttons and fit the sizer to the window."""
  2059.         self.frame_sizer.Add(self.inner_sizer, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 10)
  2060.         self.frame_sizer.Add(self.button_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL)
  2061.         self.inner_sizer.AddGrowableCol(1)
  2062.         self.inner_sizer.AddGrowableCol(3)
  2063.         for btn in [self.start, self.balance_refresh, self.withdraw]:
  2064.             self.button_sizer.Add(btn, 0, BTN_STYLE, 5)
  2065.  
  2066.         # self.set_widgets_visible([self.external_lbl, self.txt_external],
  2067.         #                         self.is_external_miner)
  2068.         self.SetSizerAndFit(self.frame_sizer)
  2069.  
  2070.     def layout_default(self):
  2071.         """Lay out a default miner with no custom changes."""
  2072.        
  2073.         self.user_lbl.SetLabel(STR_USERNAME)
  2074.         self.set_widgets_visible(self.hidden_widgets, False)
  2075.         self.set_widgets_visible([self.balance_lbl,
  2076.                                   self.balance_amt,
  2077.                                   self.balance_refresh,
  2078.                                   self.withdraw,
  2079.                                   self.server,
  2080.                                   self.website,
  2081.                                   self.server_lbl,
  2082.                                   self.website_lbl,
  2083.                                   self.interactive_lbl,
  2084.                                   self.txt_interactive,
  2085.                                   self.texcache_lbl,
  2086.                                   self.txt_texcache,
  2087.                                   self.singlemem_lbl,
  2088.                                   self.txt_singlemem,
  2089.                                   self.warpflags_lbl,
  2090.                                   self.txt_warpflags,
  2091.                                   self.minercudaminer_lbl], False)
  2092.         row = self.layout_init()
  2093.         # self.layout_server_and_website(row=row)
  2094.        
  2095.         customs = ["other", "solo"]
  2096.         is_custom = self.server.GetStringSelection().lower() in customs
  2097.         if is_custom:
  2098.             # self.layout_host_and_port(row=row + 1)
  2099.             pass
  2100.         # Nope - TT
  2101.         #else:
  2102.         #    self.set_widgets_visible([self.host_lbl, self.txt_host,
  2103.         #                              self.port_lbl, self.txt_port], False)
  2104.        
  2105.         self.set_widgets_visible([self.affinity_lbl], False)
  2106.  
  2107.         self.layout_minertype(row=row)
  2108.         self.layout_host_and_port(row=row + 1)
  2109.         self.layout_user_and_pass(row=row + 2 + int(is_custom))
  2110.         self.layout_device_and_flags(row=row + 3 + int(is_custom))
  2111.         self.layout_thrcon_worksize(row=row + 4 + int(is_custom))
  2112.         self.layout_vectors_intensity(row=row + 5 + int(is_custom))
  2113.         self.layout_gputhreads_gpusettings(row=row + 6 + int(is_custom))
  2114.         self.layout_stratum(row=row + 7 + int(is_custom))
  2115.         # self.layout_affinity(row=row + 7 + int(is_custom))
  2116.        
  2117.         if self.external_path == "CGMINER":
  2118.             self.set_widgets_visible([self.minerreaper_lbl, self.proxy_lbl], False)
  2119.         elif self.external_path == "REAPER":
  2120.             self.set_widgets_visible([self.minercgminer_lbl, self.proxy_lbl, self.stratum_lbl, self.txt_stratum, self.flags_lbl, self.txt_flags], False)
  2121.         else:
  2122.             self.set_widgets_visible([self.minercgminer_lbl, self.minerreaper_lbl], False)
  2123.            
  2124.         if self.external_path == "PROXY":
  2125.             self.set_widgets_visible([self.user_lbl, self.pass_lbl, self.device_lbl, self.thrcon_lbl, self.worksize_lbl, self.vectors_lbl, self.intensity_lbl, self.gputhreads_lbl,  self.gpusettings_lbl,  self.stratum_lbl], False)
  2126.             self.set_widgets_visible([self.txt_username, self.txt_pass, self.device_listbox, self.txt_thrcon, self.txt_worksize, self.txt_vectors, self.txt_intensity, self.txt_gputhreads, self.gpusettings, self.txt_stratum], False)
  2127.         else:
  2128.             self.set_widgets_visible([self.stratuminfo0_lbl, self.stratuminfo1_lbl], False)
  2129.        
  2130.         self.layout_finish()
  2131.  
  2132.     def layout_interactive_texcache(self, row):
  2133.         """
  2134.        Interactive and use texture cache for cudaminer
  2135.        """
  2136.         self.inner_sizer.Add(self.interactive_lbl, (row, 0), flag=LBL_STYLE)
  2137.         self.inner_sizer.Add(self.txt_interactive, (row, 1), flag=wx.EXPAND)
  2138.         self.inner_sizer.Add(self.texcache_lbl, (row, 2), flag=LBL_STYLE)
  2139.         self.inner_sizer.Add(self.txt_texcache, (row, 3), flag=wx.EXPAND)
  2140.        
  2141.     def layout_singlemem_warpflags(self, row):
  2142.         """
  2143.        Warp configuration string and use single memory block for cudaminer
  2144.        """
  2145.         self.inner_sizer.Add(self.singlemem_lbl, (row, 0), flag=LBL_STYLE)
  2146.         self.inner_sizer.Add(self.txt_singlemem, (row, 1), flag=wx.EXPAND)
  2147.         self.inner_sizer.Add(self.warpflags_lbl, (row, 2), flag=LBL_STYLE)
  2148.         self.inner_sizer.Add(self.txt_warpflags, (row, 3), flag=wx.EXPAND)
  2149.        
  2150.     def layout_cudaminer(self):
  2151.         """Lay out a default miner with no custom changes."""
  2152.        
  2153.         self.user_lbl.SetLabel(STR_USERNAME)
  2154.         self.set_widgets_visible(self.hidden_widgets, False)
  2155.         self.set_widgets_visible([self.balance_lbl,
  2156.                                   self.balance_amt,
  2157.                                   self.balance_refresh,
  2158.                                   self.withdraw,
  2159.                                   self.server,
  2160.                                   self.website,
  2161.                                   self.server_lbl,
  2162.                                   self.website_lbl,
  2163.                                   self.txt_thrcon,
  2164.                                   self.thrcon_lbl,
  2165.                                   self.txt_worksize,
  2166.                                   self.worksize_lbl,
  2167.                                   self.txt_vectors,
  2168.                                   self.vectors_lbl,
  2169.                                   self.txt_intensity,
  2170.                                   self.intensity_lbl,
  2171.                                   self.txt_gputhreads,
  2172.                                   self.gputhreads_lbl,
  2173.                                   self.txt_stratum,
  2174.                                   self.stratum_lbl,
  2175.                                   self.gpusettings,
  2176.                                   self.gpusettings_lbl,
  2177.                                   self.stratuminfo0_lbl,
  2178.                                   self.stratuminfo1_lbl,
  2179.                                   self.proxy_lbl,
  2180.                                   self.minercgminer_lbl,
  2181.                                   self.minerreaper_lbl], False)
  2182.                                  
  2183.         row = self.layout_init()
  2184.         # self.layout_server_and_website(row=row)
  2185.        
  2186.         customs = ["other", "solo"]
  2187.         is_custom = self.server.GetStringSelection().lower() in customs
  2188.         if is_custom:
  2189.             # self.layout_host_and_port(row=row + 1)
  2190.             pass
  2191.         # Nope - TT
  2192.         #else:
  2193.         #    self.set_widgets_visible([self.host_lbl, self.txt_host,
  2194.         #                              self.port_lbl, self.txt_port], False)
  2195.        
  2196.         self.set_widgets_visible([self.affinity_lbl], False)
  2197.  
  2198.         self.layout_minertype(row=row)
  2199.         self.layout_host_and_port(row=row + 1)
  2200.         self.layout_user_and_pass(row=row + 2 + int(is_custom))
  2201.         self.layout_device_and_flags(row=row + 3 + int(is_custom))
  2202.         self.layout_interactive_texcache(row=row + 4 + int(is_custom))
  2203.         self.layout_singlemem_warpflags(row=row + 5 + int(is_custom))
  2204.  
  2205.         self.layout_finish()
  2206.  
  2207.     ############################
  2208.     # Begin server specific code
  2209.     def layout_bitpenny(self):
  2210.         """BitPenny doesn't require registration or a password.
  2211.  
  2212.        The username is just their receiving address.
  2213.        """
  2214.         invisible = [self.txt_pass, self.txt_host, self.txt_port,
  2215.                      self.pass_lbl, self.host_lbl, self.port_lbl]
  2216.         self.set_widgets_visible(invisible, False)
  2217.         self.set_widgets_visible([self.extra_info], True)
  2218.  
  2219.         row = self.layout_init()
  2220.         self.layout_server_and_website(row=row)
  2221.         self.inner_sizer.Add(self.user_lbl, (row + 1, 0), flag=LBL_STYLE)
  2222.         self.inner_sizer.Add(self.txt_username, (row + 1, 1), span=(1, 3), flag=wx.EXPAND)
  2223.         self.layout_device_and_flags(row=row + 2)
  2224.         self.layout_affinity(row=row + 3)
  2225.         self.layout_balance(row=row + 4)
  2226.         self.inner_sizer.Add(self.extra_info, (row + 5, 0), span=(1, 4), flag=wx.ALIGN_CENTER_HORIZONTAL)
  2227.         self.layout_finish()
  2228.  
  2229.         self.extra_info.SetLabel(_("No registration is required - just enter an address and press Start."))
  2230.         self.txt_pass.SetValue('poclbm-gui')
  2231.         self.user_lbl.SetLabel(_("Address:"))
  2232.         add_tooltip(self.txt_username,
  2233.             _("Your receiving address for Bitcoins.\nE.g.: 1A94cjRpaPBMV9ZNWFihB5rTFEeihBALgc"))
  2234.  
  2235.     def layout_slush(self):
  2236.         """Slush's pool uses a separate username for each miner."""
  2237.         self.set_widgets_visible([self.host_lbl, self.txt_host,
  2238.                                   self.port_lbl, self.txt_port,
  2239.                                   self.withdraw, self.extra_info], False)
  2240.         row = self.layout_init()
  2241.         self.layout_server_and_website(row=row)
  2242.         self.layout_user_and_pass(row=row + 1)
  2243.         self.layout_device_and_flags(row=row + 2)
  2244.         self.layout_affinity(row=row + 3)
  2245.         self.layout_balance(row=row + 4)
  2246.         self.layout_finish()
  2247.  
  2248.         add_tooltip(self.txt_username,
  2249.             _("Your miner username (not your account username).\nExample: Kiv.GPU"))
  2250.         add_tooltip(self.txt_pass,
  2251.             _("Your miner password (not your account password)."))
  2252.  
  2253.     def layout_eligius(self):
  2254.         """Eligius doesn't require registration or a password.
  2255.  
  2256.        The username is just their receiving address.
  2257.        """
  2258.         invisible = [self.txt_pass, self.txt_host, self.txt_port,
  2259.                      self.withdraw,
  2260.                      self.pass_lbl, self.host_lbl, self.port_lbl]
  2261.         self.set_widgets_visible(invisible, False)
  2262.         self.set_widgets_visible([self.extra_info], True)
  2263.  
  2264.         row = self.layout_init()
  2265.         self.layout_server_and_website(row=row)
  2266.         self.inner_sizer.Add(self.user_lbl, (row + 1, 0), flag=LBL_STYLE)
  2267.         self.inner_sizer.Add(self.txt_username, (row + 1, 1), span=(1, 3), flag=wx.EXPAND)
  2268.         self.layout_device_and_flags(row=row + 2)
  2269.         self.layout_affinity(row=row + 3)
  2270.         self.layout_balance(row=row + 4)
  2271.         self.inner_sizer.Add(self.extra_info, (row + 5, 0), span=(1, 4), flag=wx.ALIGN_CENTER_HORIZONTAL)
  2272.         self.layout_finish()
  2273.  
  2274.         self.extra_info.SetLabel(_("No registration is required - just enter an address and press Start."))
  2275.         self.txt_pass.SetValue('x')
  2276.         self.user_lbl.SetLabel(_("Address:"))
  2277.         add_tooltip(self.txt_username,
  2278.             _("Your receiving address for Bitcoins.\nE.g.: 1JMfKKJqtkDPbRRsFSLjX1Cs2dqmjKiwj8"))
  2279.  
  2280.     def layout_btcguild(self):
  2281.         """BTC Guild has the same layout as slush for now."""
  2282.         self.layout_slush()
  2283.  
  2284.     def layout_bitcoinserver(self):
  2285.         """Bitcoin-Server.de has the same layout as slush for now."""
  2286.         self.layout_slush()
  2287.  
  2288.     def layout_btcmine(self):
  2289.         self.set_widgets_visible([self.host_lbl, self.txt_host,
  2290.                                   self.port_lbl, self.txt_port,
  2291.                                   self.withdraw, self.extra_info], False)
  2292.         row = self.layout_init()
  2293.         self.layout_server_and_website(row=row)
  2294.         self.layout_user_and_pass(row=row + 1)
  2295.         self.layout_device_and_flags(row=row + 2)
  2296.         self.layout_affinity(row=row + 3)
  2297.         self.layout_balance(row=row + 4)
  2298.         self.layout_finish()
  2299.  
  2300.         add_tooltip(self.txt_username,
  2301.             _("Your miner username. \nExample: kiv123@kiv123"))
  2302.         add_tooltip(self.txt_pass,
  2303.             _("Your miner password (not your account password)."))
  2304.  
  2305.     def layout_deepbit(self):
  2306.         """Deepbit uses an email address for a username."""
  2307.         self.set_widgets_visible([self.host_lbl, self.txt_host,
  2308.                                   self.port_lbl, self.txt_port,
  2309.                                   self.extra_info], False)
  2310.         row = self.layout_init()
  2311.         self.layout_server_and_website(row=row)
  2312.         self.layout_user_and_pass(row=row + 1)
  2313.         self.layout_device_and_flags(row=row + 2)
  2314.         self.layout_affinity(row=row + 3)
  2315.         self.layout_balance(row=row + 4)
  2316.         self.layout_finish()
  2317.         add_tooltip(self.txt_username,
  2318.             _("The e-mail address you registered with."))
  2319.         self.user_lbl.SetLabel(_("Email:"))
  2320.  
  2321.     def layout_btcmp(self):
  2322.         """Deepbit uses an email address for a username."""
  2323.         self.set_widgets_visible([self.host_lbl, self.txt_host,
  2324.                                   self.port_lbl, self.txt_port,
  2325.                                   self.extra_info], False)
  2326.         row = self.layout_init()
  2327.         self.layout_server_and_website(row=row)
  2328.         self.layout_user_and_pass(row=row + 1)
  2329.         self.layout_device_and_flags(row=row + 2)
  2330.         self.layout_affinity(row=row + 3)
  2331.         self.layout_balance(row=row + 4)
  2332.         self.layout_finish()
  2333.         add_tooltip(self.txt_username,
  2334.             _("Your worker name. Is something in the form of username.workername"))
  2335.         self.user_lbl.SetLabel(_("Workername:"))
  2336.  
  2337.     def layout_x8s(self):
  2338.         """x8s has the same layout as slush for now."""
  2339.         self.layout_slush()
  2340.     # End server specific code
  2341.     ##########################
  2342.  
  2343.  
  2344. class GUIMiner(wx.Frame):
  2345.     def __init__(self, *args, **kwds):
  2346.         wx.Frame.__init__(self, *args, **kwds)
  2347.         style = fnb.FNB_X_ON_TAB | fnb.FNB_FF2 | fnb.FNB_HIDE_ON_SINGLE_TAB
  2348.         self.nb = fnb.FlatNotebook(self, -1, style=style)
  2349.  
  2350.         # Set up notebook context menu
  2351.         notebook_menu = wx.Menu()
  2352.         ID_RENAME, ID_DUPLICATE = wx.NewId(), wx.NewId()
  2353.         notebook_menu.Append(ID_RENAME, _("&Rename..."), _("Rename this miner"))
  2354.         notebook_menu.Append(ID_DUPLICATE, _("&Duplicate...", _("Duplicate this miner")))
  2355.         self.nb.SetRightClickMenu(notebook_menu)
  2356.         self.Bind(wx.EVT_MENU, self.rename_miner, id=ID_RENAME)
  2357.         self.Bind(wx.EVT_MENU, self.duplicate_miner, id=ID_DUPLICATE)
  2358.  
  2359.         self.console_panel = None
  2360.         self.summary_panel = None
  2361.  
  2362.         # Servers and defaults are required, it's a fatal error not to have
  2363.         # them.
  2364.         server_config_path = os.path.join(get_module_path(), 'servers.ini')
  2365.         with open(server_config_path) as f:
  2366.             data = json.load(f)
  2367.             self.servers = data.get('servers')
  2368.  
  2369.         defaults_config_path = os.path.join(get_module_path(), 'defaults.ini')
  2370.         with open(defaults_config_path) as f:
  2371.             self.defaults = json.load(f)
  2372.            
  2373.         gpusettings_config_path = os.path.join(get_module_path(), 'gpusettings.ini')
  2374.         with open(gpusettings_config_path) as f:
  2375.             data = json.load(f)
  2376.             self.gpusettings_data = data.get('gpusettings')
  2377.  
  2378.         self.parse_config()
  2379.         self.do_show_opencl_warning = self.config_data.get('show_opencl_warning', True)
  2380.         self.console_max_lines = self.config_data.get('console_max_lines', 5000)
  2381.  
  2382.         ID_NEW_EXTERNAL, ID_NEW_PHOENIX, ID_NEW_CGMINER, ID_NEW_REAPER, ID_NEW_CUDAMINER, ID_NEW_PROXY, ID_NEW_UFASOFT = wx.NewId(), wx.NewId(), wx.NewId(), wx.NewId(), wx.NewId(), wx.NewId(), wx.NewId()
  2383.         self.menubar = wx.MenuBar()
  2384.         file_menu = wx.Menu()
  2385.         new_menu = wx.Menu()
  2386.         #new_menu.Append(wx.ID_NEW, _("&New OpenCL miner..."), _("Create a new OpenCL miner (default for ATI cards)"), wx.ITEM_NORMAL)
  2387.         #new_menu.Append(ID_NEW_PHOENIX, _("New Phoenix miner..."), _("Create a new Phoenix miner (for some ATI cards)"), wx.ITEM_NORMAL)
  2388.         new_menu.Append(ID_NEW_CGMINER, _("New CG miner..."), _("Create a new CGMiner"), wx.ITEM_NORMAL)
  2389.         new_menu.Append(ID_NEW_REAPER, _("New reaper miner..."), _("Create a new reaper miner"), wx.ITEM_NORMAL)
  2390.         new_menu.Append(ID_NEW_CUDAMINER, _("New CUDA miner..."), _("Create a new CUDA miner"), wx.ITEM_NORMAL)
  2391.         new_menu.Append(ID_NEW_PROXY, _("New stratum proxy..."), _("Create a new stratum proxy"), wx.ITEM_NORMAL)
  2392.         #new_menu.Append(ID_NEW_UFASOFT, _("New Ufasoft CPU miner..."), _("Create a new Ufasoft miner (for CPUs)"), wx.ITEM_NORMAL)
  2393.         #new_menu.Append(ID_NEW_EXTERNAL, _("New &other miner..."), _("Create a new custom miner (requires external program)"), wx.ITEM_NORMAL)
  2394.         file_menu.AppendMenu(wx.NewId(), _('&New miner'), new_menu)
  2395.         file_menu.Append(wx.ID_SAVE, _("&Save settings"), _("Save your settings"), wx.ITEM_NORMAL)
  2396.         file_menu.Append(wx.ID_OPEN, _("&Load settings"), _("Load stored settings"), wx.ITEM_NORMAL)
  2397.         file_menu.Append(wx.ID_EXIT, _("Quit"), STR_QUIT, wx.ITEM_NORMAL)
  2398.         self.menubar.Append(file_menu, _("&File"))
  2399.  
  2400.         ID_SUMMARY, ID_CONSOLE = wx.NewId(), wx.NewId()
  2401.         view_menu = wx.Menu()
  2402.         view_menu.Append(ID_SUMMARY, _("Show summary"), _("Show summary of all miners"), wx.ITEM_NORMAL)
  2403.         view_menu.Append(ID_CONSOLE, _("Show console"), _("Show console logs"), wx.ITEM_NORMAL)
  2404.         self.menubar.Append(view_menu, _("&View"))
  2405.  
  2406.         ID_SOLO, ID_PATHS, ID_BLOCKCHAIN_PATH, ID_LAUNCH = wx.NewId(), wx.NewId(), wx.NewId(), wx.NewId()
  2407.         solo_menu = wx.Menu()
  2408.         solo_menu.Append(ID_SOLO, _("&Create solo password..."), _("Configure a user/pass for solo mining"), wx.ITEM_NORMAL)
  2409.         solo_menu.Append(ID_PATHS, _("&Set Blake client path..."), _("Set the location of the official Blake client"), wx.ITEM_NORMAL)
  2410.         solo_menu.Append(ID_BLOCKCHAIN_PATH, _("&Set Blake data directory..."), _("Set the location of the Blake data directory containing the blockchain and wallet"), wx.ITEM_NORMAL)
  2411.         solo_menu.Append(ID_LAUNCH, _("&Launch Blake client as server"), _("Launch the official Blake client as a server for solo mining"), wx.ITEM_NORMAL)
  2412.         self.menubar.Append(solo_menu, _("&Solo utilities"))
  2413.  
  2414.         ID_START_MINIMIZED = wx.NewId()
  2415.         self.options_menu = wx.Menu()
  2416.         self.start_minimized_chk = self.options_menu.Append(ID_START_MINIMIZED, _("Start &minimized"), _("Start the GUI minimized to the tray."), wx.ITEM_CHECK)
  2417.         self.options_menu.Check(ID_START_MINIMIZED, self.config_data.get('start_minimized', False))
  2418.         self.menubar.Append(self.options_menu, _("&Options"))
  2419.  
  2420.         ID_CHANGE_LANGUAGE = wx.NewId()
  2421.         lang_menu = wx.Menu()
  2422.         lang_menu.Append(ID_CHANGE_LANGUAGE, _("&Change language..."), "", wx.ITEM_NORMAL)
  2423.         self.menubar.Append(lang_menu, _("Language"))
  2424.  
  2425.         ID_DONATE_SMALL = wx.NewId()
  2426.         donate_menu = wx.Menu()
  2427.         donate_menu.Append(ID_DONATE_SMALL, _("&Donate..."), _("Donate Blakecoins to support GUIMiner-Blake development"))
  2428.         self.menubar.Append(donate_menu, _("&Donate"))
  2429.  
  2430.         help_menu = wx.Menu()
  2431.         help_menu.Append(wx.ID_ABOUT, _("&About..."), STR_ABOUT, wx.ITEM_NORMAL)
  2432.  
  2433.         self.menubar.Append(help_menu, _("&Help"))
  2434.         self.SetMenuBar(self.menubar)
  2435.         self.statusbar = self.CreateStatusBar(2, 0)
  2436.  
  2437.         try:
  2438.             self.bitcoin_executable = os.path.join(os.getenv("PROGRAMFILES"), "Blakecoin", "blakecoin-qt.exe")
  2439.         except:
  2440.             self.bitcoin_executable = "" # TODO: where would Bitcoin probably be on Linux/Mac?
  2441.  
  2442.         try:
  2443.             self.blockchain_directory = os.path.join(os.getenv("APPDATA"), "Blakecoin")
  2444.         except:
  2445.             self.blockchain_directory = ""
  2446.          
  2447.        
  2448.         try:
  2449.             self.tbicon = GUIMinerTaskBarIcon(self)
  2450.         except:
  2451.             logging.error(_("Failed to load taskbar icon; continuing."))
  2452.  
  2453.         self.set_properties()
  2454.  
  2455.         try:
  2456.             self.devices = get_opencl_devices()
  2457.         except:
  2458.             self.devices = []
  2459.             file_menu.Enable(wx.ID_NEW, False)
  2460.             file_menu.SetHelpString(wx.ID_NEW, _("OpenCL not found - can't add a OpenCL miner"))
  2461.  
  2462.             if self.do_show_opencl_warning:
  2463.                 dialog = OpenCLWarningDialog(self)
  2464.                 dialog.ShowModal()
  2465.                 self.do_show_opencl_warning = not dialog.is_box_checked()
  2466.  
  2467.         self.Bind(wx.EVT_MENU, self.name_new_profile, id=wx.ID_NEW)
  2468.         #self.Bind(wx.EVT_MENU, self.new_phoenix_profile, id=ID_NEW_PHOENIX)
  2469.         self.Bind(wx.EVT_MENU, self.new_cgminer_profile, id=ID_NEW_CGMINER)
  2470.         self.Bind(wx.EVT_MENU, self.new_ufasoft_profile, id=ID_NEW_UFASOFT)
  2471.         self.Bind(wx.EVT_MENU, self.new_reaper_profile, id=ID_NEW_REAPER)
  2472.         self.Bind(wx.EVT_MENU, self.new_cudaminer_profile, id=ID_NEW_CUDAMINER)
  2473.         self.Bind(wx.EVT_MENU, self.new_proxy_profile, id=ID_NEW_PROXY)
  2474.         self.Bind(wx.EVT_MENU, self.new_external_profile, id=ID_NEW_EXTERNAL)
  2475.         self.Bind(wx.EVT_MENU, self.save_config, id=wx.ID_SAVE)
  2476.         self.Bind(wx.EVT_MENU, self.load_config, id=wx.ID_OPEN)
  2477.         self.Bind(wx.EVT_MENU, self.on_menu_exit, id=wx.ID_EXIT)
  2478.         self.Bind(wx.EVT_MENU, self.set_official_client_path, id=ID_PATHS)
  2479.         self.Bind(wx.EVT_MENU, self.set_blockchain_directory, id=ID_BLOCKCHAIN_PATH)
  2480.         self.Bind(wx.EVT_MENU, self.show_console, id=ID_CONSOLE)
  2481.         self.Bind(wx.EVT_MENU, self.show_summary, id=ID_SUMMARY)
  2482.         self.Bind(wx.EVT_MENU, self.show_about_dialog, id=wx.ID_ABOUT)
  2483.         self.Bind(wx.EVT_MENU, self.create_solo_password, id=ID_SOLO)
  2484.         self.Bind(wx.EVT_MENU, self.launch_solo_server, id=ID_LAUNCH)
  2485.         self.Bind(wx.EVT_MENU, self.on_change_language, id=ID_CHANGE_LANGUAGE)
  2486.         self.Bind(wx.EVT_MENU, self.on_donate, id=ID_DONATE_SMALL)
  2487.         self.Bind(wx.EVT_CLOSE, self.on_close)        
  2488.         self.Bind(wx.EVT_ICONIZE, self.on_iconize)
  2489.         self.Bind(fnb.EVT_FLATNOTEBOOK_PAGE_CLOSING, self.on_page_closing)
  2490.         self.Bind(fnb.EVT_FLATNOTEBOOK_PAGE_CLOSED, self.on_page_closed)
  2491.         self.Bind(fnb.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.on_page_changed)
  2492.  
  2493.         self.load_config()
  2494.         self.do_layout()
  2495.  
  2496.         if not self.start_minimized_chk.IsChecked():
  2497.             self.Show()
  2498.            
  2499.     def on_iconize(self, event):
  2500.         if event.Iconized() and sys.platform == 'win32':
  2501.             self.Hide() # On minimize, hide from taskbar.
  2502.         else:
  2503.             self.Show()
  2504.  
  2505.     def set_properties(self):
  2506.         self.SetIcons(get_icon_bundle())
  2507.         self.SetTitle(_("GUIMiner-Blake alpha"))
  2508.         self.statusbar.SetStatusWidths([-1, 125])
  2509.         statusbar_fields = ["", STR_NOT_STARTED]
  2510.         for i in range(len(statusbar_fields)):
  2511.             self.statusbar.SetStatusText(statusbar_fields[i], i)
  2512.  
  2513.     def do_layout(self):
  2514.         self.vertical_sizer = wx.BoxSizer(wx.VERTICAL)
  2515.         self.vertical_sizer.Add(self.nb, 1, wx.EXPAND, 20)
  2516.         self.SetSizer(self.vertical_sizer)
  2517.         self.vertical_sizer.SetSizeHints(self)
  2518.         self.SetSizerAndFit(self.vertical_sizer)
  2519.         self.Layout()
  2520.  
  2521.     @property
  2522.     def profile_panels(self):
  2523.         """Return a list of currently available MinerTab."""
  2524.         pages = [self.nb.GetPage(i) for i in range(self.nb.GetPageCount())]
  2525.         return [p for p in pages if
  2526.                 p != self.console_panel and p != self.summary_panel]
  2527.  
  2528.     def add_profile(self, data={}):
  2529.         """Add a new MinerTab to the list of tabs."""
  2530.         panel = MinerTab(self.nb, -1, self.devices, self.servers,
  2531.                              self.defaults, self.gpusettings_data, self.statusbar, data)
  2532.         self.nb.AddPage(panel, panel.name)
  2533.         # The newly created profile should have focus.
  2534.         self.nb.EnsureVisible(self.nb.GetPageCount() - 1)
  2535.  
  2536.         if self.summary_panel is not None:
  2537.             self.summary_panel.add_miners_to_grid() # Show new entry on summary
  2538.         return panel
  2539.  
  2540.     def message(self, *args, **kwargs):
  2541.         """Utility method to show a message dialog and return their choice."""
  2542.         dialog = wx.MessageDialog(self, *args, **kwargs)
  2543.         retval = dialog.ShowModal()
  2544.         dialog.Destroy()
  2545.         return retval
  2546.  
  2547.     def name_new_profile(self, event=None, extra_profile_data={}):
  2548.         """Prompt for the new miner's name."""
  2549.         dialog = wx.TextEntryDialog(self, _("Name this miner:"), _("New miner"))
  2550.         if dialog.ShowModal() == wx.ID_OK:
  2551.             name = dialog.GetValue().strip()
  2552.             if not name: name = _("Untitled")
  2553.             data = extra_profile_data.copy()
  2554.             data['name'] = name
  2555.             self.add_profile(data)
  2556.  
  2557.     def new_external_profile(self, event):
  2558.         """Prompt for an external miner path, then create a miner.
  2559.  
  2560.        On Windows we validate against legal miners; on Linux they can pick
  2561.        whatever they want.
  2562.        """
  2563.         wildcard = _('External miner (*.exe)|*.exe|(*.py)|*.py') if sys.platform == 'win32' else '*.*'
  2564.         dialog = wx.FileDialog(self,
  2565.                                _("Select external miner:"),
  2566.                                defaultDir=os.path.join(get_module_path(), 'miners'),
  2567.                                defaultFile="",
  2568.                                wildcard=wildcard,
  2569.                                style=wx.OPEN)
  2570.         if dialog.ShowModal() != wx.ID_OK:
  2571.             return
  2572.  
  2573.         if sys.platform == 'win32' and dialog.GetFilename() not in SUPPORTED_BACKENDS:
  2574.             self.message(
  2575.                 _("Unsupported external miner %(filename)s. Supported are: %(supported)s") % \
  2576.                   dict(filename=dialog.GetFilename(), supported='\n'.join(SUPPORTED_BACKENDS)),
  2577.                 _("Miner not supported"), wx.OK | wx.ICON_ERROR)
  2578.             return
  2579.         path = os.path.join(dialog.GetDirectory(), dialog.GetFilename())
  2580.         dialog.Destroy()
  2581.         self.name_new_profile(extra_profile_data=dict(external_path="CGMINER"))
  2582.  
  2583.     def new_phoenix_profile(self, event):
  2584.         """Create a new miner using the Phoenix OpenCL miner backend."""
  2585.         path = os.path.join(get_module_path(), 'phoenix.exe')
  2586.         self.name_new_profile(extra_profile_data=dict(external_path="CGMINER"))
  2587.  
  2588.     def new_cgminer_profile(self, event):
  2589.         """Create a new miner using the Cgminer OpenCL miner backend."""
  2590.         path = os.path.join(get_module_path(), 'cgminer.exe')
  2591.         self.name_new_profile(extra_profile_data=dict(external_path="CGMINER"))
  2592.  
  2593.     def new_ufasoft_profile(self, event):
  2594.         """Create a new miner using the Ufasoft CPU miner backend."""
  2595.         # path = os.path.join(get_module_path(), 'miners', 'ufasoft', 'bitcoin-miner.exe')
  2596.         self.name_new_profile(extra_profile_data=dict(external_path="CGMINER"))
  2597.  
  2598.     def new_reaper_profile(self, event):
  2599.         """Create a new miner using the REAPER GPU miner backend."""
  2600.         # path = os.path.join(get_module_path(), 'miners', 'puddinpop', 'rpcminer-cuda.exe')
  2601.         self.name_new_profile(extra_profile_data=dict(external_path="REAPER"))
  2602.        
  2603.     def new_cudaminer_profile(self, event):
  2604.         """Create a new miner using the cudaminer GPU miner backend."""
  2605.         self.name_new_profile(extra_profile_data=dict(external_path="CUDAMINER"))
  2606.        
  2607.     def new_proxy_profile(self, event):
  2608.         """Create a stratum proxy backend."""
  2609.         # path = os.path.join(get_module_path(), 'miners', 'puddinpop', 'rpcminer-cuda.exe')
  2610.         self.name_new_profile(extra_profile_data=dict(external_path="PROXY"))
  2611.  
  2612.     def get_storage_location(self):
  2613.         """Get the folder and filename to store our JSON config."""
  2614.         if sys.platform == 'win32':
  2615.             folder = os.path.join(os.environ['AppData'], 'poclbm-v0.01')
  2616.             config_filename = os.path.join(folder, 'poclbm_blake.ini')
  2617.         else: # Assume linux? TODO test
  2618.             folder = os.environ['HOME']
  2619.             config_filename = os.path.join(folder, '.poclbm-v0.01')
  2620.         return folder, config_filename
  2621.  
  2622.     def on_close(self, event):
  2623.         """Minimize to tray if they click "close" but exit otherwise.
  2624.  
  2625.        On closing, stop any miners that are currently working.
  2626.        """
  2627.         if event.CanVeto():
  2628.             self.Hide()
  2629.             event.Veto()
  2630.         else:
  2631.             if any(p.is_modified for p in self.profile_panels):
  2632.                 dialog = wx.MessageDialog(self, _('Do you want to save changes?'), _('Save'),
  2633.                     wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
  2634.                 retval = dialog.ShowModal()
  2635.                 dialog.Destroy()
  2636.                 if retval == wx.ID_YES:
  2637.                     self.save_config()
  2638.  
  2639.             if self.console_panel is not None:
  2640.                 self.console_panel.on_close()
  2641.             if self.summary_panel is not None:
  2642.                 self.summary_panel.on_close()
  2643.             for p in self.profile_panels:
  2644.                 p.on_close()
  2645.             if self.tbicon is not None:
  2646.                 self.tbicon.RemoveIcon()
  2647.                 self.tbicon.timer.Stop()
  2648.                 self.tbicon.Destroy()
  2649.             event.Skip()
  2650.  
  2651.     def save_config(self, event=None):
  2652.         """Save the current miner profiles to our config file in JSON format."""
  2653.         folder, config_filename = self.get_storage_location()
  2654.         mkdir_p(folder)
  2655.         profile_data = [p.get_data() for p in self.profile_panels]
  2656.         config_data = dict(show_console=self.is_console_visible(),
  2657.                            show_summary=self.is_summary_visible(),
  2658.                            profiles=profile_data,
  2659.                            bitcoin_executable=self.bitcoin_executable,
  2660.                            blockchain_directory=self.blockchain_directory,
  2661.                            show_opencl_warning=self.do_show_opencl_warning,
  2662.                            start_minimized=self.start_minimized_chk.IsChecked(),
  2663.                            console_max_lines=self.console_max_lines,
  2664.                            window_position=list(self.GetRect()))
  2665.         logger.debug(_('Saving: ') + json.dumps(config_data))
  2666.         try:
  2667.             with open(config_filename, 'w') as f:
  2668.                 json.dump(config_data, f, indent=4)
  2669.         except IOError:
  2670.             self.message(
  2671.                 _("Couldn't write save file %s.\nCheck the location is writable.") % config_filename,
  2672.                 _("Save unsuccessful"), wx.OK | wx.ICON_ERROR)
  2673.         else:
  2674.             self.message(_("Profiles saved OK to %s.") % config_filename,
  2675.                       _("Save successful"), wx.OK | wx.ICON_INFORMATION)
  2676.             for p in self.profile_panels:
  2677.                 p.on_saved()
  2678.  
  2679.     def parse_config(self):
  2680.         """Set self.config_data to a dictionary of config values."""
  2681.         self.config_data = {}
  2682.  
  2683.         try:
  2684.             config_filename = self.get_storage_location()[1]
  2685.             if os.path.exists(config_filename):
  2686.                 with open(config_filename) as f:
  2687.                     self.config_data.update(json.load(f))
  2688.                 logger.debug(_('Loaded: %s') % json.dumps(self.config_data))
  2689.         except ValueError:
  2690.             self.message(
  2691.                 _("Your settings saved at:\n %s\nare corrupt or could not be read.\nDeleting this file or saving over it may solve the problem." % config_filename),
  2692.                 _("Error"), wx.ICON_ERROR)
  2693.  
  2694.     def load_config(self, event=None):
  2695.         """Load JSON profile info from the config file."""
  2696.         self.parse_config()
  2697.  
  2698.         config_data = self.config_data
  2699.         executable = config_data.get('bitcoin_executable', None)
  2700.         if executable is not None:
  2701.             self.bitcoin_executable = executable
  2702.            
  2703.         blockchain_directory = config_data.get('blockchain_directory', None)
  2704.         if blockchain_directory is not None:
  2705.             self.blockchain_directory = blockchain_directory
  2706.  
  2707.         # Shut down any existing miners before they get clobbered
  2708.         if(any(p.is_mining for p in self.profile_panels)):
  2709.             result = self.message(
  2710.                 _("Loading profiles will stop any currently running miners. Continue?"),
  2711.                 _("Load profile"), wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION)
  2712.             if result == wx.ID_NO:
  2713.                 return
  2714.         for p in reversed(self.profile_panels):
  2715.             p.on_close()
  2716.             self.nb.DeletePage(self.nb.GetPageIndex(p))
  2717.  
  2718.         # If present, summary should be the leftmost tab on startup.
  2719.         if config_data.get('show_summary', False):
  2720.             self.show_summary()
  2721.  
  2722.         profile_data = config_data.get('profiles', [])
  2723.         for d in profile_data:
  2724.             self.add_profile(d)
  2725.  
  2726.         if not any(profile_data):
  2727.             self.add_profile() # Create a default one using defaults.ini
  2728.  
  2729.         if config_data.get('show_console', False):
  2730.             self.show_console()
  2731.            
  2732.         window_position = config_data.get('window_position')
  2733.         if window_position:
  2734.             self.SetRect(window_position)
  2735.  
  2736.         for p in self.profile_panels:
  2737.             if p.autostart:
  2738.                 p.start_mining()
  2739.  
  2740.     def set_official_client_path(self, event):
  2741.         """Set the path to the official Bitcoin client."""
  2742.         wildcard = "*.exe" if sys.platform == 'win32' else '*.*'
  2743.         dialog = wx.FileDialog(self,
  2744.                                _("Select path to Blakecoin.exe"),
  2745.                                defaultFile="blakecoin-qt.exe",
  2746.                                wildcard=wildcard,
  2747.                                style=wx.OPEN)
  2748.         if dialog.ShowModal() == wx.ID_OK:
  2749.             path = os.path.join(dialog.GetDirectory(), dialog.GetFilename())
  2750.             if os.path.exists(path):
  2751.                 self.bitcoin_executable = path
  2752.         dialog.Destroy()
  2753.        
  2754.     def set_blockchain_directory(self, event):
  2755.         """Set the path to the blockchain data directory."""
  2756.         defaultPath = os.path.join(os.getenv("APPDATA"), "Blakecoin")
  2757.         dialog = wx.DirDialog(self,
  2758.                               _("Select path to blockchain"),
  2759.                               defaultPath=defaultPath,
  2760.                               style=wx.DD_DIR_MUST_EXIST)
  2761.         if dialog.ShowModal() == wx.ID_OK:
  2762.             path = dialog.GetPath()
  2763.             if os.path.exists(path):
  2764.                 self.blockchain_directory = path
  2765.         dialog.Destroy()  
  2766.  
  2767.     def show_about_dialog(self, event):
  2768.         """Show the 'about' dialog."""
  2769.         dialog = AboutGuiminer(self, -1, _('About'))
  2770.         dialog.ShowModal()
  2771.         dialog.Destroy()
  2772.  
  2773.     def on_page_closing(self, event):
  2774.         """Handle a tab closing event.
  2775.  
  2776.        If they are closing a special panel, we have to shut it down.
  2777.        If the tab has a miner running in it, we have to stop the miner
  2778.        before letting the tab be removed.
  2779.        """
  2780.         p = self.nb.GetPage(event.GetSelection())
  2781.  
  2782.         if p == self.console_panel:
  2783.             self.console_panel.on_close()
  2784.             self.console_panel = None
  2785.             event.Skip()
  2786.             return
  2787.         if p == self.summary_panel:
  2788.             self.summary_panel.on_close()
  2789.             self.summary_panel = None
  2790.             event.Skip()
  2791.             return
  2792.  
  2793.         if p.is_mining:
  2794.             result = self.message(
  2795.                 _("Closing this miner will stop it. Continue?"),
  2796.                 _("Close miner"),
  2797.                 wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION)
  2798.             if result == wx.ID_NO:
  2799.                 event.Veto()
  2800.                 return
  2801.         p.on_close()
  2802.         event.Skip() # OK to close the tab now
  2803.  
  2804.     def on_page_closed(self, event):
  2805.         if self.summary_panel is not None:
  2806.             self.summary_panel.add_miners_to_grid() # Remove miner summary
  2807.  
  2808.     def on_page_changed(self, event):
  2809.         """Handle a tab change event.
  2810.  
  2811.        Ensures the status bar shows the status of the tab that has focus.
  2812.        """
  2813.         p = self.nb.GetPage(event.GetSelection())
  2814.         p.on_focus()
  2815.  
  2816.     def launch_solo_server(self, event):
  2817.         """Launch the official bitcoin client in server mode.
  2818.  
  2819.        This allows poclbm to connect to it for mining solo.
  2820.        """
  2821.         if self.blockchain_directory and os.path.exists(self.blockchain_directory):
  2822.             datadir = " -datadir=%s" % self.blockchain_directory
  2823.         else:
  2824.             datadir = ""
  2825.         try:
  2826.             subprocess.Popen(self.bitcoin_executable + " -server" + datadir)
  2827.         except OSError:
  2828.             self.message(
  2829.                 _("Couldn't find Blakecoin at %s. Is your path set correctly?") % self.bitcoin_executable,
  2830.                 _("Launch failed"), wx.ICON_ERROR | wx.OK)
  2831.             return
  2832.         self.message(
  2833.             _("The Blakecoin client will now launch in server mode.\nOnce it connects to the network and downloads the block chain, you can start a miner in 'solo' mode."),
  2834.             _("Launched ok."),
  2835.             wx.OK)
  2836.  
  2837.     def create_solo_password(self, event):
  2838.         """Prompt the user for login credentials to the bitcoin client.
  2839.  
  2840.        These are required to connect to the client over JSON-RPC and are
  2841.        stored in 'bitcoin.conf'.
  2842.        """
  2843.         if sys.platform == 'win32':
  2844.             filename = os.path.join(os.getenv("APPDATA"), "Blakecoin", "blakecoin.conf")
  2845.         else: # Assume Linux for now TODO test
  2846.             filename = os.path.join(os.getenv('HOME'), ".blakecoin")
  2847.         if os.path.exists(filename):
  2848.             result = self.message(
  2849.                 _("%s already exists. Overwrite?") % filename,
  2850.                 _("blakecoin.conf already exists."),
  2851.                 wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION)
  2852.             if result == wx.ID_NO:
  2853.                 return
  2854.  
  2855.         dialog = SoloPasswordRequest(self, _('Enter password'))
  2856.         result = dialog.ShowModal()
  2857.         dialog.Destroy()
  2858.         if result == wx.ID_CANCEL:
  2859.             return
  2860.  
  2861.         with open(filename, "w") as f:
  2862.             f.write('\nrpcuser=%s\nrpcpassword=%s\nrpcallowip=*' % dialog.get_value())
  2863.             f.close()
  2864.  
  2865.         self.message(_("Wrote blakecoin config ok."), _("Success"), wx.OK)
  2866.  
  2867.     def is_console_visible(self):
  2868.         """Return True if the console is visible."""
  2869.         return self.nb.GetPageIndex(self.console_panel) != -1
  2870.  
  2871.     def show_console(self, event=None):
  2872.         """Show the console log in its own tab."""
  2873.         if self.is_console_visible():
  2874.             return # Console already shown
  2875.         self.console_panel = ConsolePanel(self, self.console_max_lines)
  2876.         self.nb.AddPage(self.console_panel, _("Console"))
  2877.         self.nb.EnsureVisible(self.nb.GetPageCount() - 1)
  2878.  
  2879.     def is_summary_visible(self):
  2880.         """Return True if the summary is visible."""
  2881.         return self.nb.GetPageIndex(self.summary_panel) != -1
  2882.  
  2883.     def show_summary(self, event=None):
  2884.         """Show the summary window in its own tab."""
  2885.         if self.is_summary_visible():
  2886.             return
  2887.         self.summary_panel = SummaryPanel(self)
  2888.         self.nb.AddPage(self.summary_panel, _("Summary"))
  2889.         index = self.nb.GetPageIndex(self.summary_panel)
  2890.         self.nb.SetSelection(index)
  2891.  
  2892.     def on_menu_exit(self, event):
  2893.         self.Close(force=True)
  2894.  
  2895.     def rename_miner(self, event):
  2896.         """Change the name of a miner as displayed on the tab."""
  2897.         p = self.nb.GetPage(self.nb.GetSelection())
  2898.         if p not in self.profile_panels:
  2899.             return
  2900.  
  2901.         dialog = wx.TextEntryDialog(self, _("Rename to:"), _("Rename miner"))
  2902.         if dialog.ShowModal() == wx.ID_OK:
  2903.             p.set_name(dialog.GetValue().strip())
  2904.  
  2905.     def duplicate_miner(self, event):
  2906.         """Duplicate the current miner to another miner."""
  2907.         p = self.nb.GetPage(self.nb.GetSelection())
  2908.         if p not in self.profile_panels:
  2909.             return        
  2910.         self.name_new_profile(event=None, extra_profile_data=p.get_data())
  2911.  
  2912.     def on_change_language(self, event):
  2913.         dialog = ChangeLanguageDialog(self, _('Change language'), language)
  2914.         result = dialog.ShowModal()
  2915.         dialog.Destroy()
  2916.         if result == wx.ID_CANCEL:
  2917.             return
  2918.  
  2919.         language_name = dialog.get_value()
  2920.         update_language(LANGUAGES[language_name])
  2921.         save_language()
  2922.  
  2923.     def on_donate(self, event):
  2924.         dialog = DonateDialog(self, -1, _('Donate'))
  2925.         dialog.ShowModal()
  2926.         dialog.Destroy()
  2927.  
  2928. class DonateDialog(wx.Dialog):
  2929.     """About dialog for the app with a donation address."""
  2930.     DONATE_TEXT = "If this software helped you, please consider contributing to its development." \
  2931.                   "\nSend Blakecoin donations to:  %(address)s"
  2932.     def __init__(self, parent, id, title):
  2933.         wx.Dialog.__init__(self, parent, id, title)
  2934.         vbox = wx.BoxSizer(wx.VERTICAL)
  2935.  
  2936.         text = DonateDialog.DONATE_TEXT % dict(address=DONATION_ADDRESS)
  2937.         self.about_text = wx.StaticText(self, -1, text)
  2938.         self.copy_btn = wx.Button(self, -1, _("Copy address to clipboard"))
  2939.         vbox.Add(self.about_text, 0, wx.ALL, 10)
  2940.         vbox.Add(self.copy_btn, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 10)
  2941.         self.SetSizerAndFit(vbox)
  2942.  
  2943.         self.copy_btn.Bind(wx.EVT_BUTTON, self.on_copy)
  2944.  
  2945.     def on_copy(self, event):
  2946.         """Copy the donation address to the clipboard."""
  2947.         if wx.TheClipboard.Open():
  2948.             data = wx.TextDataObject()
  2949.             data.SetText(DONATION_ADDRESS)
  2950.             wx.TheClipboard.SetData(data)
  2951.         wx.TheClipboard.Close()
  2952.        
  2953.  
  2954. class ChangeLanguageDialog(wx.Dialog):
  2955.     """Dialog prompting the user to change languages."""
  2956.     def __init__(self, parent, title, current_language):
  2957.         style = wx.DEFAULT_DIALOG_STYLE
  2958.         vbox = wx.BoxSizer(wx.VERTICAL)
  2959.         wx.Dialog.__init__(self, parent, -1, title, style=style)
  2960.         self.lbl = wx.StaticText(self, -1,
  2961.             _("Choose language (requires restart to take full effect)"))
  2962.         vbox.Add(self.lbl, 0, wx.ALL, 10)
  2963.         self.language_choices = wx.ComboBox(self, -1,
  2964.                                             choices=sorted(LANGUAGES.keys()),
  2965.                                             style=wx.CB_READONLY)
  2966.  
  2967.         self.language_choices.SetStringSelection(LANGUAGES_REVERSE[current_language])
  2968.  
  2969.         vbox.Add(self.language_choices, 0, wx.ALL, 10)
  2970.         buttons = self.CreateButtonSizer(wx.OK | wx.CANCEL)
  2971.         vbox.Add(buttons, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 10)
  2972.         self.SetSizerAndFit(vbox)
  2973.  
  2974.     def get_value(self):
  2975.         return self.language_choices.GetStringSelection()
  2976.  
  2977.  
  2978. class SoloPasswordRequest(wx.Dialog):
  2979.     """Dialog prompting user for login credentials for solo mining."""
  2980.     def __init__(self, parent, title):
  2981.         style = wx.DEFAULT_DIALOG_STYLE
  2982.         vbox = wx.BoxSizer(wx.VERTICAL)
  2983.         wx.Dialog.__init__(self, parent, -1, title, style=style)
  2984.         self.user_lbl = wx.StaticText(self, -1, STR_USERNAME)
  2985.         self.txt_username = wx.TextCtrl(self, -1, "")
  2986.         self.pass_lbl = wx.StaticText(self, -1, STR_PASSWORD)
  2987.         self.txt_pass = wx.TextCtrl(self, -1, "", style=wx.TE_PASSWORD)
  2988.         grid_sizer_1 = wx.FlexGridSizer(2, 2, 5, 5)
  2989.         grid_sizer_1.Add(self.user_lbl, 0, wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 0)
  2990.         grid_sizer_1.Add(self.txt_username, 0, wx.EXPAND, 0)
  2991.         grid_sizer_1.Add(self.pass_lbl, 0, wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 0)
  2992.         grid_sizer_1.Add(self.txt_pass, 0, wx.EXPAND, 0)
  2993.         buttons = self.CreateButtonSizer(wx.OK | wx.CANCEL)
  2994.         vbox.Add(grid_sizer_1, wx.EXPAND | wx.ALL, 10)
  2995.         vbox.Add(buttons)
  2996.         self.SetSizerAndFit(vbox)
  2997.  
  2998.     def get_value(self):
  2999.         """Return the (username, password) supplied by the user."""
  3000.         return self.txt_username.GetValue(), self.txt_pass.GetValue()
  3001.  
  3002.  
  3003. class BalanceAuthRequest(wx.Dialog):
  3004.     """Dialog prompting user for an auth token to refresh their balance."""
  3005.     instructions = \
  3006. _("""Click the link below to log in to the pool and get a special token.
  3007. This token lets you securely check your balance.
  3008. To remember this token for the future, save your miner settings.""")
  3009.     def __init__(self, parent, url):
  3010.         style = wx.DEFAULT_DIALOG_STYLE
  3011.         vbox = wx.BoxSizer(wx.VERTICAL)
  3012.         wx.Dialog.__init__(self, parent, -1, STR_REFRESH_BALANCE, style=style)
  3013.         self.instructions = wx.StaticText(self, -1, BalanceAuthRequest.instructions)
  3014.         self.website = hyperlink.HyperLinkCtrl(self, -1, url)
  3015.         self.txt_token = wx.TextCtrl(self, -1, _("(Paste token here)"))
  3016.         buttons = self.CreateButtonSizer(wx.OK | wx.CANCEL)
  3017.  
  3018.         vbox.AddMany([
  3019.             (self.instructions, 0, wx.ALL, 10),
  3020.             (self.website, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 10),
  3021.             (self.txt_token, 0, wx.EXPAND | wx.ALIGN_CENTER_HORIZONTAL, 10),
  3022.             (buttons, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 10)
  3023.         ])
  3024.         self.SetSizerAndFit(vbox)
  3025.  
  3026.     def get_value(self):
  3027.         """Return the auth token supplied by the user."""
  3028.         return self.txt_token.GetValue()
  3029.  
  3030.  
  3031. class AboutGuiminer(wx.Dialog):
  3032.     """About dialog for the app with a donation address."""
  3033.    
  3034.     def __init__(self, parent, id, title):
  3035.         wx.Dialog.__init__(self, parent, id, title)
  3036.         vbox = wx.BoxSizer(wx.VERTICAL)
  3037.  
  3038.         text = ABOUT_TEXT % dict(version=__version__,
  3039.                                  address=DONATION_ADDRESS)
  3040.         self.about_text = wx.StaticText(self, -1, text)
  3041.         self.copy_btn = wx.Button(self, -1, _("Copy address to clipboard"))
  3042.         vbox.Add(self.about_text)
  3043.         vbox.Add(self.copy_btn, 0, wx.ALIGN_BOTTOM | wx.ALIGN_CENTER_HORIZONTAL, 0)
  3044.         self.SetSizerAndFit(vbox)
  3045.  
  3046.         self.copy_btn.Bind(wx.EVT_BUTTON, self.on_copy)
  3047.  
  3048.     def on_copy(self, event):
  3049.         """Copy the donation address to the clipboard."""
  3050.         if wx.TheClipboard.Open():
  3051.             data = wx.TextDataObject()
  3052.             data.SetText(DONATION_ADDRESS)
  3053.             wx.TheClipboard.SetData(data)
  3054.         wx.TheClipboard.Close()
  3055.  
  3056.  
  3057. class OpenCLWarningDialog(wx.Dialog):
  3058.     """Warning dialog when a user does not have OpenCL installed."""
  3059.     def __init__(self, parent):
  3060.         wx.Dialog.__init__(self, parent, -1, _("No OpenCL devices found."))
  3061.         vbox = wx.BoxSizer(wx.VERTICAL)
  3062.         self.message = wx.StaticText(self, -1,
  3063.  _("""No OpenCL devices were found.
  3064. If you only want to mine using CPU or CUDA, you can ignore this message.
  3065. If you want to mine on ATI graphics cards, you may need to install the ATI Stream
  3066. SDK, or your GPU may not support OpenCL."""))
  3067.         vbox.Add(self.message, 0, wx.ALL, 10)
  3068.  
  3069.         hbox = wx.BoxSizer(wx.HORIZONTAL)
  3070.  
  3071.         self.no_show_chk = wx.CheckBox(self, -1)
  3072.         hbox.Add(self.no_show_chk)
  3073.         self.no_show_txt = wx.StaticText(self, -1, _("Don't show this message again"))
  3074.         hbox.Add((5, 0))
  3075.         hbox.Add(self.no_show_txt)
  3076.         vbox.Add(hbox, 0, wx.ALL, 10)
  3077.         buttons = self.CreateButtonSizer(wx.OK)
  3078.         vbox.Add(buttons, 0, wx.ALIGN_BOTTOM | wx.ALIGN_CENTER_HORIZONTAL, 0)
  3079.         self.SetSizerAndFit(vbox)
  3080.  
  3081.     def is_box_checked(self):
  3082.         return self.no_show_chk.GetValue()
  3083.  
  3084.  
  3085. def run():
  3086.     try:
  3087.         frame_1 = GUIMiner(None, -1, "")
  3088.         app.SetTopWindow(frame_1)
  3089.         app.MainLoop()
  3090.     except:
  3091.         logging.exception("Exception:")
  3092.         raise
  3093.  
  3094.  
  3095. if __name__ == "__main__":
  3096.     run()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement