#!/usr/bin/python3
""" gpu-pac  -  A utility program and control compatible GPUs

    Program and Control compatible GPUs with this utility.  By default, the commands to
    be written to a GPU are written to a bash file for the user to inspect and run.  If you
    have confidence, the *--execute_pac* option can be used to execute and then delete the
    saved bash file.  Since the GPU device files are writable only by root, sudo is used to
    execute commands in the bash file, as a result, you will be prompted for credentials in the
    terminal where you executed *gpu-pac*. The *--no_fan* option can be used to eliminate
    fan details from the utility. The *--force_write* option can be used to force all configuration
    parameters to be written to the GPU.  The default behavior is to only write changes.

    Copyright (C) 2019  RicksLab

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""
__author__ = 'RueiKe'
__copyright__ = 'Copyright (C) 2019 RicksLab'
__credits__ = ['Craig Echt - Testing, Debug, Verification, and Documentation']
__license__ = 'GNU General Public License'
__program_name__ = 'gpu-pac'
__maintainer__ = 'RueiKe'
__docformat__ = 'reStructuredText'
# pylint: disable=multiple-statements
# pylint: disable=line-too-long
# pylint: disable=bad-continuation

import argparse
import re
import subprocess
import os
import logging
import sys
import time
from uuid import uuid4

try:
    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk
except ModuleNotFoundError as error:
    print('gi import error: {}'.format(error))
    print('gi is required for {}'.format(__program_name__))
    print('   In a venv, first install vext:  pip install --no-cache-dir vext')
    print('   Then install vext.gi:  pip install --no-cache-dir vext.gi')
    sys.exit(0)

from GPUmodules import __version__, __status__
from GPUmodules import GPUgui
from GPUmodules import GPUmodule as Gpu
from GPUmodules import env

MAX_CHAR = 54
CHAR_WIDTH = 8
set_gtk_prop = GPUgui.GuiProps.set_gtk_prop
LOGGER = logging.getLogger('gpu-utils')
PATTERNS = env.GutConst.PATTERNS


class PACWindow(Gtk.Window):
    """
    PAC Window class.
    """
    def __init__(self, gpu_list, devices):
        Gtk.Window.__init__(self, title=env.GUT_CONST.gui_window_title)
        self.set_border_width(0)
        GPUgui.GuiProps.set_style()

        if env.GUT_CONST.icon_path:
            icon_file = os.path.join(env.GUT_CONST.icon_path, 'gpu-pac.icon.png')
            if os.path.isfile(icon_file):
                self.set_icon_from_file(icon_file)

        grid = Gtk.Grid()
        self.add(grid)

        num_com_gpus = gpu_list.num_gpus()['total']
        max_rows = 0
        row = col = 0
        for gpu in gpu_list.gpus():
            row = 0
            # Card Number in top center of box
            devices[gpu.prm.uuid] = {'card_num':  Gtk.Label(name='white_label')}
            devices[gpu.prm.uuid]['card_num'].set_markup('<big><b>Card {}:  </b>{}</big>'.format(
                gpu.get_params_value(str('card_num')), gpu.get_params_value('model_display')[:40]))
            set_gtk_prop(devices[gpu.prm.uuid]['card_num'], align=(0.5, 0.5), top=1, bottom=1, right=4, left=4)
            lbox = Gtk.Box(spacing=6, name='head_box')
            set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
            lbox.pack_start(devices[gpu.prm.uuid]['card_num'], True, True, 0)
            grid.attach(lbox, col, row, 1, 1)
            row += 1

            # Card Path
            devices[gpu.prm.uuid]['card_path'] = Gtk.Label(name='white_label')
            devices[gpu.prm.uuid]['card_path'].set_markup('<b>Device: </b>{}'.format(gpu.get_params_value('card_path')))
            set_gtk_prop(devices[gpu.prm.uuid]['card_path'], align=(0.0, 0.5), top=1, bottom=1,
                         right=4, left=4, width=MAX_CHAR*CHAR_WIDTH)
            lbox = Gtk.Box(spacing=6, name='dark_box')
            set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
            lbox.pack_start(devices[gpu.prm.uuid]['card_path'], True, True, 0)
            grid.attach(lbox, col, row, 1, 1)
            row += 1

            # Card Power Cap
            power_cap_range = gpu.get_params_value('power_cap_range')
            devices[gpu.prm.uuid]['power_cap'] = Gtk.Label(name='white_label')
            devices[gpu.prm.uuid]['power_cap'].set_markup('<b>Power Cap: </b> Range ({} - {} W)'.format(
                                                          power_cap_range[0], power_cap_range[1]))
            set_gtk_prop(devices[gpu.prm.uuid]['power_cap'], align=(0.0, 0.5), top=1, bottom=1, right=4, left=4)
            lbox = Gtk.Box(spacing=6, name='dark_box')
            set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
            lbox.pack_start(devices[gpu.prm.uuid]['power_cap'], True, True, 0)
            grid.attach(lbox, col, row, 1, 1)
            row += 1

            # Card Power Cap Value and Entry
            devices[gpu.prm.uuid]['power_cap_cur'] = Gtk.Label(name='white_label')
            set_gtk_prop(devices[gpu.prm.uuid]['power_cap_cur'], top=1, bottom=1, right=2, left=2)
            devices[gpu.prm.uuid]['power_cap_ent'] = Gtk.Entry()
            set_gtk_prop(devices[gpu.prm.uuid]['power_cap_ent'], top=1, bottom=1, right=0, left=2, xalign=1,
                         width_chars=5, max_length=5)
            devices[gpu.prm.uuid]['power_cap_ent_unit'] = Gtk.Label(name='white_label')
            devices[gpu.prm.uuid]['power_cap_ent_unit'].set_text('W   (value or \'reset\')')
            set_gtk_prop(devices[gpu.prm.uuid]['power_cap_ent_unit'], top=1, bottom=1, right=0, left=0,
                         align=(0.0, 0.5))
            lbox = Gtk.Box(spacing=2, name='med_box')
            set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
            lbox.pack_start(devices[gpu.prm.uuid]['power_cap_cur'], False, False, 0)
            lbox.pack_start(devices[gpu.prm.uuid]['power_cap_ent'], False, False, 0)
            lbox.pack_start(devices[gpu.prm.uuid]['power_cap_ent_unit'], False, False, 0)
            grid.attach(lbox, col, row, 1, 1)
            row += 1

            if env.GUT_CONST.show_fans:
                # Fan PWM Value
                fan_pwm_range = gpu.get_params_value('fan_pwm_range')
                devices[gpu.prm.uuid]['fan_pwm_range'] = Gtk.Label(name='white_label')
                devices[gpu.prm.uuid]['fan_pwm_range'].set_markup('<b>Fan PWM: </b> Range ({} - {} %)'.format(
                                                                  fan_pwm_range[0], fan_pwm_range[1]))
                set_gtk_prop(devices[gpu.prm.uuid]['fan_pwm_range'], top=1, bottom=1, right=4, left=4, align=(0.0, 0.5))
                lbox = Gtk.Box(spacing=6, name='dark_box')
                set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
                lbox.pack_start(devices[gpu.prm.uuid]['fan_pwm_range'], True, True, 0)
                grid.attach(lbox, col, row, 1, 1)
                row += 1

                # Card Fan PWM Value and Entry
                devices[gpu.prm.uuid]['fan_pwm_cur'] = Gtk.Label(name='white_label')
                set_gtk_prop(devices[gpu.prm.uuid]['fan_pwm_cur'], top=1, bottom=1, right=2, left=2)
                devices[gpu.prm.uuid]['fan_pwm_ent'] = Gtk.Entry()
                set_gtk_prop(devices[gpu.prm.uuid]['fan_pwm_ent'], top=1, bottom=1, right=0, left=2,
                             width_chars=5, max_length=5, xalign=1)
                devices[gpu.prm.uuid]['fan_pwm_ent_unit'] = Gtk.Label(name='white_label')
                devices[gpu.prm.uuid]['fan_pwm_ent_unit'].set_text('%   (value, \'reset\', or \'max\')')
                set_gtk_prop(devices[gpu.prm.uuid]['fan_pwm_ent_unit'], top=1, bottom=1,
                             right=0, left=0, align=(0.0, 0.5))
                lbox = Gtk.Box(spacing=2, name='med_box')
                set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
                lbox.pack_start(devices[gpu.prm.uuid]['fan_pwm_cur'], False, False, 0)
                lbox.pack_start(devices[gpu.prm.uuid]['fan_pwm_ent'], False, False, 0)
                lbox.pack_start(devices[gpu.prm.uuid]['fan_pwm_ent_unit'], False, False, 0)
                grid.attach(lbox, col, row, 1, 1)
                row += 1

            if gpu.get_params_value('gpu_type') in [gpu.GPU_Type.PStatesNE, gpu.GPU_Type.PStates]:
                # Sclk P-States
                devices[gpu.prm.uuid]['sclk_range'] = Gtk.Label(name='white_label')
                devices[gpu.prm.uuid]['sclk_range'].set_markup('<b>Sclk P-States: </b> Ranges {}-{}, {}-{} '.format(
                                                               gpu.get_params_value('sclk_f_range')[0],
                                                               gpu.get_params_value('sclk_f_range')[1],
                                                               gpu.get_params_value('vddc_range')[0],
                                                               gpu.get_params_value('vddc_range')[1]))
                set_gtk_prop(devices[gpu.prm.uuid]['sclk_range'],
                             top=1, bottom=1, right=4, left=4, align=(0.0, 0.5))
                lbox = Gtk.Box(spacing=6, name='dark_box')
                set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
                lbox.pack_start(devices[gpu.prm.uuid]['sclk_range'], False, False, 0)
                grid.attach(lbox, col, row, 1, 1)
                row += 1

                # Sclk P-State Values and Entry
                devices[gpu.prm.uuid]['sclk_pstate'] = {}
                for ps in gpu.sclk_state.keys():
                    devices[gpu.prm.uuid]['sclk_pstate'][ps] = {}

                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_cur_obj'] = Gtk.Label(name='white_label')
                    set_gtk_prop(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_cur_obj'], width_chars=20,
                                 top=1, bottom=1, right=2, left=2, align=(0.0, 0.5))
                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj'] = Gtk.Entry()
                    set_gtk_prop(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj'], width_chars=5, max_length=5,
                                 xalign=1, top=1, bottom=1, right=0, left=0)
                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj_unit'] = Gtk.Label(name='white_label')
                    set_gtk_prop(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj_unit'],
                                 top=1, bottom=1, right=4, left=0, align=(0.0, 0.5))
                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_v_obj'] = Gtk.Entry()
                    set_gtk_prop(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_v_obj'], width_chars=5, max_length=5,
                                 xalign=1, top=1, bottom=1, right=0, left=0)
                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_v_obj_unit'] = Gtk.Label(name='white_label')
                    set_gtk_prop(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_v_obj_unit'],
                                 top=1, bottom=1, right=4, left=0, align=(0.0, 0.5))

                    lbox = Gtk.Box(spacing=6, name='med_box')
                    set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
                    lbox.pack_start(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_cur_obj'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj_unit'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_v_obj'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_v_obj_unit'], False, False, 0)
                    grid.attach(lbox, col, row, 1, 1)
                    row += 1

            elif gpu.get_params_value('gpu_type') == gpu.GPU_Type.CurvePts:
                # Sclk Curve End Points
                devices[gpu.prm.uuid]['sclk_range'] = Gtk.Label(name='white_label')
                devices[gpu.prm.uuid]['sclk_range'].set_markup('<b>Sclk Curve End Points: </b> Ranges {}-{} '.format(
                    gpu.get_params_value('sclk_f_range')[0], gpu.get_params_value('sclk_f_range')[1]))
                set_gtk_prop(devices[gpu.prm.uuid]['sclk_range'], top=1, bottom=1, right=4, left=4, align=(0.0, 0.5))
                lbox = Gtk.Box(spacing=6, name='dark_box')
                set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
                lbox.pack_start(devices[gpu.prm.uuid]['sclk_range'], False, False, 0)
                grid.attach(lbox, col, row, 1, 1)
                row += 1

                # Sclk Curve End Points Values and Entry
                devices[gpu.prm.uuid]['sclk_pstate'] = {}
                for ps, psd in gpu.sclk_state.items():
                    devices[gpu.prm.uuid]['sclk_pstate'][ps] = {}

                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_cur_obj'] = Gtk.Label(name='white_label')
                    set_gtk_prop(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_cur_obj'], width_chars=20,
                                 top=1, bottom=1, right=2, left=2, align=(0.0, 0.5))
                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj'] = Gtk.Entry()
                    set_gtk_prop(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj'], width_chars=5, max_length=5,
                                 xalign=1, top=1, bottom=1, right=0, left=0)
                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj_unit'] = Gtk.Label(name='white_label')
                    set_gtk_prop(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj_unit'],
                                 top=1, bottom=1, right=4, left=0, align=(0.0, 0.5))
                    lbox = Gtk.Box(spacing=6, name='med_box')
                    set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
                    lbox.pack_start(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_cur_obj'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj_unit'], False, False, 0)
                    grid.attach(lbox, col, row, 1, 1)
                    row += 1

            if gpu.get_params_value('gpu_type') in [gpu.GPU_Type.CurvePts, gpu.GPU_Type.PStates]:
                # SCLK P-State Mask
                devices[gpu.prm.uuid]['sclk_pst_mask_cur'] = Gtk.Label(name='white_label')
                set_gtk_prop(devices[gpu.prm.uuid]['sclk_pst_mask_cur'], top=1, bottom=1, right=2, left=2)
                devices[gpu.prm.uuid]['sclk_pst_mask_ent'] = Gtk.Entry()
                set_gtk_prop(devices[gpu.prm.uuid]['sclk_pst_mask_ent'], width_chars=17, max_length=17,
                             xalign=0, top=1, bottom=1, right=0, left=1)
                lbox = Gtk.Box(spacing=2, name='med_box')
                set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
                lbox.pack_start(devices[gpu.prm.uuid]['sclk_pst_mask_cur'], False, False, 0)
                lbox.pack_start(devices[gpu.prm.uuid]['sclk_pst_mask_ent'], False, False, 0)
                grid.attach(lbox, col, row, 1, 1)
                row += 1

            if gpu.get_params_value('gpu_type') == gpu.GPU_Type.PStates:
                # Mclk P-States
                devices[gpu.prm.uuid]['mclk_range'] = Gtk.Label(name='white_label')
                devices[gpu.prm.uuid]['mclk_range'].set_markup('<b>Mclk P-States: </b> Ranges {}-{}, {}-{} '.format(
                                                               gpu.get_params_value('mclk_f_range')[0],
                                                                gpu.get_params_value('mclk_f_range')[1],
                                                                gpu.get_params_value('vddc_range')[0],
                                                                gpu.get_params_value('vddc_range')[1]))
                set_gtk_prop(devices[gpu.prm.uuid]['mclk_range'], top=1, bottom=1, right=4, left=4, align=(0.0, 0.5))
                lbox = Gtk.Box(spacing=6, name='dark_box')
                set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
                lbox.pack_start(devices[gpu.prm.uuid]['mclk_range'], True, True, 0)
                grid.attach(lbox, col, row, 1, 1)
                row += 1

                # Mclk P-State Values and Entry
                devices[gpu.prm.uuid]['mclk_pstate'] = {}
                for ps, psd in gpu.mclk_state.items():
                    devices[gpu.prm.uuid]['mclk_pstate'][ps] = {}

                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_cur_obj'] = Gtk.Label(name='white_label')
                    set_gtk_prop(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_cur_obj'], width_chars=20,
                                 top=1, bottom=1, right=2, left=2, align=(0.0, 0.5))
                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj'] = Gtk.Entry()
                    set_gtk_prop(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj'], width_chars=5, max_length=5,
                                 xalign=1, top=1, bottom=1, right=0, left=0)
                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj_unit'] = Gtk.Label(name='white_label')
                    set_gtk_prop(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj_unit'],
                                 top=1, bottom=1, right=4, left=0, align=(0.0, 0.5))
                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_v_obj'] = Gtk.Entry()
                    set_gtk_prop(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_v_obj'], width_chars=5, max_length=5,
                                 xalign=1, top=1, bottom=1, right=0, left=0)
                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_v_obj_unit'] = Gtk.Label(name='white_label')
                    set_gtk_prop(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_v_obj_unit'],
                                 top=1, bottom=1, right=4, left=0, align=(0.0, 0.5))

                    lbox = Gtk.Box(spacing=6, name='med_box')
                    set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
                    lbox.pack_start(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_cur_obj'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj_unit'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_v_obj'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_v_obj_unit'], False, False, 0)
                    grid.attach(lbox, col, row, 1, 1)
                    row += 1

            elif gpu.get_params_value('gpu_type') == gpu.GPU_Type.CurvePts:
                # Mclk Curve End points
                devices[gpu.prm.uuid]['mclk_range'] = Gtk.Label(name='white_label')
                devices[gpu.prm.uuid]['mclk_range'].set_markup('<b>Mclk Curve End Points: </b> Ranges {}-{} '.format(
                                                               gpu.get_params_value('mclk_f_range')[0],
                                                               gpu.get_params_value('mclk_f_range')[1]))
                set_gtk_prop(devices[gpu.prm.uuid]['mclk_range'], top=1, bottom=1, right=4, left=4, align=(0.0, 0.5))
                lbox = Gtk.Box(spacing=6, name='dark_box')
                set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
                lbox.pack_start(devices[gpu.prm.uuid]['mclk_range'], True, True, 0)
                grid.attach(lbox, col, row, 1, 1)
                row += 1

                # Mclk Curve End Points Values and Entry
                devices[gpu.prm.uuid]['mclk_pstate'] = {}
                for ps, psd in gpu.mclk_state.items():
                    devices[gpu.prm.uuid]['mclk_pstate'][ps] = {}

                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_cur_obj'] = Gtk.Label(name='white_label')
                    set_gtk_prop(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_cur_obj'], width_chars=20,
                                 top=1, bottom=1, right=2, left=2, align=(0.0, 0.5))
                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj'] = Gtk.Entry()
                    set_gtk_prop(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj'], width_chars=5, max_length=5,
                                 xalign=1, top=1, bottom=1, right=0, left=0)
                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj_unit'] = Gtk.Label(name='white_label')
                    set_gtk_prop(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj_unit'],
                                 top=1, bottom=1, right=4, left=0, align=(0.0, 0.5))
                    lbox = Gtk.Box(spacing=6, name='med_box')
                    set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
                    lbox.pack_start(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_cur_obj'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj_unit'], False, False, 0)
                    grid.attach(lbox, col, row, 1, 1)
                    row += 1

            if gpu.get_params_value('gpu_type') in [gpu.GPU_Type.CurvePts, gpu.GPU_Type.PStates]:
                # MCLK P-State Mask
                devices[gpu.prm.uuid]['mclk_pst_mask_cur'] = Gtk.Label(name='white_label')
                set_gtk_prop(devices[gpu.prm.uuid]['mclk_pst_mask_cur'], top=1, bottom=1, right=2, left=2)
                devices[gpu.prm.uuid]['mclk_pst_mask_ent'] = Gtk.Entry()
                set_gtk_prop(devices[gpu.prm.uuid]['mclk_pst_mask_ent'], width_chars=17, max_length=17,
                             xalign=0, top=1, bottom=1, right=0, left=1)
                lbox = Gtk.Box(spacing=2, name='med_box')
                set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
                lbox.pack_start(devices[gpu.prm.uuid]['mclk_pst_mask_cur'], False, False, 0)
                lbox.pack_start(devices[gpu.prm.uuid]['mclk_pst_mask_ent'], False, False, 0)
                grid.attach(lbox, col, row, 1, 1)
                row += 1

            if gpu.get_params_value('gpu_type') == gpu.GPU_Type.CurvePts:
                # VDDC Curve Points
                devices[gpu.prm.uuid]['vddc_curve_range'] = Gtk.Label(name='white_label')
                devices[gpu.prm.uuid]['vddc_curve_range'].set_markup(
                    '<b>VDDC Curve Points: </b> Ranges {}-{}, {}-{} '.format(gpu.vddc_curve_range['0']['SCLK'][0],
                                                                             gpu.vddc_curve_range['0']['SCLK'][1],
                                                                             gpu.vddc_curve_range['0']['VOLT'][0],
                                                                             gpu.vddc_curve_range['0']['VOLT'][1]))
                set_gtk_prop(devices[gpu.prm.uuid]['vddc_curve_range'], top=1, bottom=1,
                             right=4, left=4, align=(0.0, 0.5))
                lbox = Gtk.Box(spacing=6, name='dark_box')
                set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
                lbox.pack_start(devices[gpu.prm.uuid]['vddc_curve_range'], False, False, 0)
                grid.attach(lbox, col, row, 1, 1)
                row += 1

                # VDDC CURVE Points Values and Entry
                devices[gpu.prm.uuid]['vddc_curve_pt'] = {}
                for ps, psd in gpu.vddc_curve.items():
                    devices[gpu.prm.uuid]['vddc_curve_pt'][ps] = {}

                    devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_cur_obj'] = Gtk.Label(name='white_label')
                    set_gtk_prop(devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_cur_obj'], width_chars=20,
                                 top=1, bottom=1, right=2, left=2, align=(0.0, 0.5))
                    devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_f_obj'] = Gtk.Entry()
                    set_gtk_prop(devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_f_obj'], width_chars=5,
                                 max_length=5, xalign=1, top=1, bottom=1, right=0, left=0)
                    devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_f_obj_unit'] = Gtk.Label(name='white_label')
                    set_gtk_prop(devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_f_obj_unit'],
                                 top=1, bottom=1, right=4, left=0, align=(0.0, 0.5))
                    devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_v_obj'] = Gtk.Entry()
                    set_gtk_prop(devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_v_obj'], width_chars=5,
                                 max_length=5, xalign=1, top=1, bottom=1, right=0, left=0)
                    devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_v_obj_unit'] = Gtk.Label(name='white_label')
                    set_gtk_prop(devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_v_obj_unit'],
                                 top=1, bottom=1, right=4, left=0, align=(0.0, 0.5))
                    lbox = Gtk.Box(spacing=6, name='med_box')
                    set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
                    lbox.pack_start(devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_cur_obj'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_f_obj'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_f_obj_unit'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_v_obj'], False, False, 0)
                    lbox.pack_start(devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_v_obj_unit'], False, False, 0)
                    grid.attach(lbox, col, row, 1, 1)
                    row += 1

            # Power Performance Mode Selection
            devices[gpu.prm.uuid]['ppm'] = Gtk.Label(name='white_label')
            devices[gpu.prm.uuid]['ppm'].set_markup('<b>Power Performance Modes:</b>')
            set_gtk_prop(devices[gpu.prm.uuid]['ppm'], top=1, bottom=1, right=4, left=4, align=(0.0, 0.5))

            lbox = Gtk.Box(spacing=6, name='dark_box')
            set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
            lbox.pack_start(devices[gpu.prm.uuid]['ppm'], True, True, 0)
            grid.attach(lbox, col, row, 1, 1)
            row += 1

            devices[gpu.prm.uuid]['ppm_modes'] = Gtk.ListStore(int, str)
            devices[gpu.prm.uuid]['ppm_mode_items'] = {}
            item_num = 0
            for mode_num, mode in gpu.ppm_modes.items():
                if mode_num == 'NUM':
                    continue
                if mode[0] == 'CUSTOM':
                    continue
                devices[gpu.prm.uuid]['ppm_modes'].append([int(mode_num), mode[0]])
                devices[gpu.prm.uuid]['ppm_mode_items'][int(mode_num)] = item_num
                item_num += 1

            lbox = Gtk.Box(spacing=6, name='med_box')
            set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
            devices[gpu.prm.uuid]['ppm_selection'] = Gtk.Label(name='white_label')
            devices[gpu.prm.uuid]['ppm_selection'].set_markup('    PPM Selection:  ')
            set_gtk_prop(devices[gpu.prm.uuid]['ppm_selection'], top=1, bottom=1, right=4, left=4, align=(0.0, 0.5))

            devices[gpu.prm.uuid]['ppm_modes_combo'] = Gtk.ComboBox.new_with_model_and_entry(
                devices[gpu.prm.uuid]['ppm_modes'])
            devices[gpu.prm.uuid]['ppm_modes_combo'].get_child().set_name('ppm_combo')
            devices[gpu.prm.uuid]['ppm_modes_combo'].connect('changed', ppm_select, devices[gpu.prm.uuid])
            devices[gpu.prm.uuid]['ppm_modes_combo'].set_entry_text_column(1)
            lbox.pack_start(devices[gpu.prm.uuid]['ppm_selection'], False, False, 0)
            lbox.pack_start(devices[gpu.prm.uuid]['ppm_modes_combo'], False, False, 0)
            grid.attach(lbox, col, row, 1, 1)
            row += 1

            # Save/Reset Card Buttons
            devices[gpu.prm.uuid]['save_button'] = Gtk.Button(label='')
            for child in devices[gpu.prm.uuid]['save_button'].get_children():
                child.set_label('<big><b>Save</b></big>')
                child.set_use_markup(True)
            devices[gpu.prm.uuid]['save_button'].connect('clicked', self.save_card, gpu_list, devices, gpu.prm.uuid)
            set_gtk_prop(devices[gpu.prm.uuid]['save_button'], width=90)

            devices[gpu.prm.uuid]['reset_button'] = Gtk.Button(label='')
            for child in devices[gpu.prm.uuid]['reset_button'].get_children():
                child.set_label('<big><b>Reset</b></big>')
                child.set_use_markup(True)
            devices[gpu.prm.uuid]['reset_button'].connect('clicked', self.reset_card, gpu_list, devices, gpu.prm.uuid)
            set_gtk_prop(devices[gpu.prm.uuid]['reset_button'], width=90)

            lbox = Gtk.Box(spacing=6)
            lbox.set_name('button_box')
            set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
            lbox.pack_start(devices[gpu.prm.uuid]['save_button'], True, False, 0)
            lbox.pack_start(devices[gpu.prm.uuid]['reset_button'], True, False, 0)
            grid.attach(lbox, col, row, 1, 1)
            row += 1

            # Increment column before going to next Device
            if max_rows < row:
                max_rows = row
            col += 1
        # End of for v in values

        # Setup the Save_ALL and Reset_ALL buttons
        if num_com_gpus > 1:
            # Save/Reset/Update ALL Card Buttons
            devices['all_buttons'] = {}
            devices['all_buttons']['save_all_button'] = Gtk.Button(label='')
            for child in devices['all_buttons']['save_all_button'].get_children():
                child.set_label('<big><b>Save All</b></big>')
                child.set_use_markup(True)
            devices['all_buttons']['save_all_button'].connect('clicked', self.save_all_cards, gpu_list, devices)
            set_gtk_prop(devices['all_buttons']['save_all_button'], width=100)

            devices['all_buttons']['reset_all_button'] = Gtk.Button(label='')
            for child in devices['all_buttons']['reset_all_button'].get_children():
                child.set_label('<big><b>Reset All</b></big>')
                child.set_use_markup(True)
            devices['all_buttons']['reset_all_button'].connect('clicked', self.reset_all_cards, gpu_list, devices)
            set_gtk_prop(devices['all_buttons']['reset_all_button'], width=100)

            devices['all_buttons']['refresh_all_button'] = Gtk.Button(label='')
            for child in devices['all_buttons']['refresh_all_button'].get_children():
                child.set_label('<big><b>Refresh All</b></big>')
                child.set_use_markup(True)
            devices['all_buttons']['refresh_all_button'].connect('clicked', self.refresh_all_cards, gpu_list,
                                                                 devices, True)
            set_gtk_prop(devices['all_buttons']['refresh_all_button'], width=100)

            lbox = Gtk.Box(spacing=6)
            lbox.set_name('button_box')
            set_gtk_prop(lbox, top=1, bottom=1, right=1, left=1)
            lbox.pack_start(devices['all_buttons']['save_all_button'], True, False, 0)
            lbox.pack_start(devices['all_buttons']['reset_all_button'], True, False, 0)
            lbox.pack_start(devices['all_buttons']['refresh_all_button'], True, False, 0)
            grid.attach(lbox, 0, max_rows, col, 1)
            row += 1
            max_rows += 1

        # Initialize message box
        devices['message_label'] = Gtk.Label(name='message_label')
        devices['message_label'].set_line_wrap(True)
        set_gtk_prop(devices['message_label'], width_max=num_com_gpus * MAX_CHAR,
                     align=(0.0, 0.5), width=num_com_gpus * MAX_CHAR * CHAR_WIDTH)
        devices['message_label'].set_line_wrap(True)

        devices['message_box'] = Gtk.Box(spacing=6)
        devices['message_box'].set_name('message_box')
        set_gtk_prop(devices['message_box'], top=1, bottom=1, right=1, left=1)
        devices['message_box'].pack_start(devices['message_label'], True, True, 1)
        grid.attach(devices['message_box'], 0, max_rows, col, 1)
        row += 1

        self.update_message(devices, '', 'gray')
        self.refresh_pac(gpu_list, devices)

    @staticmethod
    def update_message(devices: dict, message: str, color: str = 'gray') -> None:
        """
        Set PAC message using default message if no message specified.

        :param devices:  Dictionary of GUI items and GPU data.
        :param message:
        :param color: Valid color strings: gray, yellow, white, red
        """
        if message == '':
            if env.GUT_CONST.execute_pac:
                message = ('Using the --execute_pac option.  Changes will be written to the GPU without '
                           'confirmation.\nSudo will be used, so you may be prompted for credentials in '
                           'the window where gpu-pac was executed from.')
            else:
                message = ('Using gpu-pac without --execute_pac option.\nYou must manually run bash '
                           'file with sudo to execute changes.')

        if color == 'red':
            GPUgui.GuiProps.set_style(css_str="#message_label { color: %s; }" %
                                              GPUgui.GuiProps.color_name_to_hex('white_off'))
            GPUgui.GuiProps.set_style(css_str="#message_box { background-image: image(%s); }" %
                                              GPUgui.GuiProps.color_name_to_hex('red'))
        elif color == 'yellow':
            GPUgui.GuiProps.set_style(css_str="#message_label { color: %s; }" %
                                              GPUgui.GuiProps.color_name_to_hex('white_off'))
            GPUgui.GuiProps.set_style(css_str="#message_box { background-image: image(%s); }" %
                                              GPUgui.GuiProps.color_name_to_hex('yellow'))
        elif color == 'white':
            GPUgui.GuiProps.set_style(css_str="#message_label { color: %s; }" %
                                              GPUgui.GuiProps.color_name_to_hex('gray95'))
            GPUgui.GuiProps.set_style(css_str="#message_box { background-image: image(%s); }" %
                                              GPUgui.GuiProps.color_name_to_hex('gray20'))
        else:
            GPUgui.GuiProps.set_style(css_str="#message_label { color: %s; }" %
                                              GPUgui.GuiProps.color_name_to_hex('white_off'))
            GPUgui.GuiProps.set_style(css_str="#message_box { background-image: image(%s); }" %
                                              GPUgui.GuiProps.color_name_to_hex('gray50'))
        devices['message_label'].set_text(message)

        while Gtk.events_pending():
            Gtk.main_iteration_do(True)

    def refresh_all_cards(self, _, gpu_list: Gpu.GpuList, devices: dict, reset_message: bool = False) -> None:
        """
        Refresh all cards by calling card level refresh.

        :param _: parent not used
        :param gpu_list:
        :param devices:  Dictionary of GUI items and GPU data.
        :param reset_message:
        """
        self.refresh_pac(gpu_list, devices, reset_message)

    def refresh_pac(self, gpu_list: Gpu.GpuList, devices: dict, refresh_message: bool = False) -> None:
        """
        Update device data from gpuList data.

        :param gpu_list: gpuList of all gpuItems
        :param devices:  Dictionary of GUI items and GPU data.
        :param refresh_message:
        """
        # Read sensor and state data from GPUs
        gpu_list.read_gpu_sensor_set(data_type=Gpu.GpuItem.SensorSet.All)
        # Read pstate and ppm table data
        gpu_list.read_gpu_pstates()
        gpu_list.read_gpu_ppm_table()

        for gpu in gpu_list.gpus():
            devices[gpu.prm.uuid]['power_cap_cur'].set_text('    Current: {:3d}W    Set: '.format(
                                                          gpu.get_params_value('power_cap', num_as_int=True)))
            devices[gpu.prm.uuid]['power_cap_ent'].set_text(str(gpu.get_params_value('power_cap', num_as_int=True)))
            if env.GUT_CONST.show_fans:
                devices[gpu.prm.uuid]['fan_pwm_cur'].set_text('    Current: {:3d}%    Set: '.format(
                                                            gpu.get_params_value('fan_pwm', num_as_int=True)))
                devices[gpu.prm.uuid]['fan_pwm_ent'].set_text(str(gpu.get_params_value('fan_pwm', num_as_int=True)))
                LOGGER.debug('Refresh got current pwm speed: %s', devices[gpu.prm.uuid]['fan_pwm_ent'].get_text())
            # SCLK
            if gpu.get_params_value('gpu_type') in [gpu.GPU_Type.PStates]:
                for ps, psd in gpu.sclk_state.items():
                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_cur_obj'].set_text('    {}:  {}, {}'.format(ps, *psd))
                    item_value = re.sub(PATTERNS['END_IN_ALPHA'], '', str(psd[0]))
                    item_unit = re.sub(PATTERNS['IS_FLOAT'], '', str(psd[0]))
                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj'].set_text(item_value)
                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj_unit'].set_text(item_unit + '     ')
                    item_value = re.sub(PATTERNS['END_IN_ALPHA'], '', str(psd[1]))
                    item_unit = re.sub(PATTERNS['IS_FLOAT'], '', str(psd[1]))
                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_v_obj'].set_text(str(item_value))
                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_v_obj_unit'].set_text(item_unit)
                devices[gpu.prm.uuid]['sclk_pst_mask_cur'].set_text(
                    '    SCLK Default: {}    Set Mask: '.format(gpu.prm.sclk_mask))
                devices[gpu.prm.uuid]['sclk_pst_mask_ent'].set_text(gpu.prm.sclk_mask)
            elif gpu.get_params_value('gpu_type') == gpu.GPU_Type.CurvePts:
                for ps, psd in gpu.sclk_state.items():
                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_cur_obj'].set_text('    {}:  {}'.format(ps, psd[0]))
                    item_value = re.sub(PATTERNS['END_IN_ALPHA'], '', str(psd[0]))
                    item_unit = re.sub(PATTERNS['IS_FLOAT'], '', str(psd[0]))
                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj'].set_text(item_value)
                    devices[gpu.prm.uuid]['sclk_pstate'][ps]['gtk_ent_f_obj_unit'].set_text(item_unit + '     ')
                devices[gpu.prm.uuid]['sclk_pst_mask_cur'].set_text(
                    '    SCLK Default: {}    Set Mask: '.format(gpu.prm.sclk_mask))
                devices[gpu.prm.uuid]['sclk_pst_mask_ent'].set_text(gpu.prm.sclk_mask)
            # MCLK
            if gpu.get_params_value('gpu_type') in [gpu.GPU_Type.PStates]:
                for ps, psd in gpu.mclk_state.items():
                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_cur_obj'].set_text('    {}:  {}, {}'.format(ps, *psd))
                    item_value = re.sub(PATTERNS['END_IN_ALPHA'], '', str(psd[0]))
                    item_unit = re.sub(PATTERNS['IS_FLOAT'], '', str(psd[0]))
                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj'].set_text(item_value)
                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj_unit'].set_text(item_unit + '     ')
                    item_value = re.sub(PATTERNS['END_IN_ALPHA'], '', str(psd[1]))
                    item_unit = re.sub(PATTERNS['IS_FLOAT'], '', str(psd[1]))
                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_v_obj'].set_text(str(item_value))
                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_v_obj_unit'].set_text(item_unit)
                devices[gpu.prm.uuid]['mclk_pst_mask_cur'].set_text(
                    '    MCLK Default: {}    Set Mask: '.format(gpu.prm.mclk_mask))
                devices[gpu.prm.uuid]['mclk_pst_mask_ent'].set_text(gpu.prm.mclk_mask)
            elif gpu.get_params_value('gpu_type') == gpu.GPU_Type.CurvePts:
                for ps, psd in gpu.mclk_state.items():
                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_cur_obj'].set_text('    {}:  {}'.format(ps, psd[0]))
                    item_value = re.sub(PATTERNS['END_IN_ALPHA'], '', str(psd[0]))
                    item_unit = re.sub(PATTERNS['IS_FLOAT'], '', str(psd[0]))
                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj'].set_text(item_value)
                    devices[gpu.prm.uuid]['mclk_pstate'][ps]['gtk_ent_f_obj_unit'].set_text(item_unit + '     ')
                devices[gpu.prm.uuid]['mclk_pst_mask_cur'].set_text(
                    '    MCLK Default: {}    Set Mask: '.format(gpu.prm.mclk_mask))
                devices[gpu.prm.uuid]['mclk_pst_mask_ent'].set_text(gpu.prm.mclk_mask)
            # VDDC CURVE
            if gpu.get_params_value('gpu_type') == gpu.GPU_Type.CurvePts:
                for ps, psd in gpu.vddc_curve.items():
                    devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_cur_obj'].set_text('    {}:  {}, {}'.format(ps, *psd))
                    item_value = re.sub(PATTERNS['END_IN_ALPHA'], '', str(psd[0]))
                    item_unit = re.sub(PATTERNS['IS_FLOAT'], '', str(psd[0]))
                    devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_f_obj'].set_text(item_value)
                    devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_f_obj_unit'].set_text(item_unit + '     ')
                    item_value = re.sub(PATTERNS['END_IN_ALPHA'], '', str(psd[1]))
                    item_unit = re.sub(PATTERNS['IS_FLOAT'], '', str(psd[1]))
                    devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_v_obj'].set_text(str(item_value))
                    devices[gpu.prm.uuid]['vddc_curve_pt'][ps]['gtk_ent_v_obj_unit'].set_text(item_unit)

            # refresh active mode item
            devices[gpu.prm.uuid]['ppm_modes_combo'].set_active(
                devices[gpu.prm.uuid]['ppm_mode_items'][gpu.get_current_ppm_mode()[0]])

        if refresh_message:
            self.update_message(devices, 'Refresh complete.\n', 'gray')
        while Gtk.events_pending():
            Gtk.main_iteration_do(True)

    def save_all_cards(self, parent, gpu_list: Gpu.GpuList, devices: dict) -> None:
        """
        Save modified data for all GPUs.

        :param parent: parent
        :param gpu_list:
        :param devices:  Dictionary of GUI items and GPU data.
        """
        changed = 0
        # Write start message
        if env.GUT_CONST.execute_pac:
            message = ('Using the --execute_pac option.  Changes will be written to the GPU without '
                       'confirmation.\nSudo will be used, so you may be prompted for credentials in '
                       'the window where gpu-pac was executed from.')
        else:
            message = 'Writing PAC command bash file.\n'
        self.update_message(devices, message, 'red')

        # save each card
        for uuid in gpu_list.uuids():
            changed += self.save_card(parent, gpu_list, devices, uuid, refresh=False)

        # Write finish message
        time.sleep(1.0)
        if env.GUT_CONST.execute_pac:
            if changed:
                message = ('Write {} PAC commands to card complete.\n'
                           'Confirm changes with gpu-mon.').format(changed)
            else:
                message = 'No PAC commands to write to card.\nNo changes specified.'
        else:
            if changed:
                message = ('Writing {} PAC commands to bash file complete.\n'
                           'Run bash file with sudo to execute changes.').format(changed)
            else:
                message = 'No PAC commands to write to bash file.\nNo changes specified.'
        self.update_message(devices, message, 'yellow')

        self.refresh_all_cards(parent, gpu_list, devices)

    def save_card(self, _, gpu_list: Gpu.GpuList, devices: dict, uuid: str, refresh: bool = True) -> None:
        """
        Save modified data for specified GPU.

        :param _: parent not used
        :param gpu_list:
        :param devices:  Dictionary of GUI items and GPU data.
        :param uuid: GPU device ID
        :param refresh: Flag to indicate if refresh should be done
        """
        if refresh:
            # Write message
            if env.GUT_CONST.execute_pac:
                message = ('Using the --execute_pac option.  Changes will be written to the GPU '
                           'without confirmation.\nSudo will be used, so you may be prompted for '
                           'credentials in the window where gpu-pac was executed from.')
            else:
                message = 'Writing PAC commands to bash file.\n'
            self.update_message(devices, message, 'red')

        # Specify output batch file name
        out_filename = os.path.join(os.getcwd(), 'pac_writer_{}.sh'.format(uuid4().hex))
        fileptr = open(out_filename, 'x')
        # Output header
        print('#!/bin/sh', file=fileptr)
        print('###########################################################################', file=fileptr)
        print('## rickslab-gpu-pac generated script to modify GPU configuration/settings', file=fileptr)
        print('###########################################################################', file=fileptr)
        print('', file=fileptr)
        print('###########################################################################', file=fileptr)
        print('## WARNING - Do not execute this script without completely', file=fileptr)
        print('## understanding appropriate values to write to your specific GPUs', file=fileptr)
        print('###########################################################################', file=fileptr)
        print('#', file=fileptr)
        print('#    Copyright (C) 2019  RueiKe', file=fileptr)
        print('#', file=fileptr)
        print('#    This program is free software: you can redistribute it and/or modify', file=fileptr)
        print('#    it under the terms of the GNU General Public License as published by', file=fileptr)
        print('#    the Free Software Foundation, either version 3 of the License, or', file=fileptr)
        print('#    (at your option) any later version.', file=fileptr)
        print('#', file=fileptr)
        print('#    This program is distributed in the hope that it will be useful,', file=fileptr)
        print('#    but WITHOUT ANY WARRANTY; without even the implied warranty of', file=fileptr)
        print('#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the', file=fileptr)
        print('#    GNU General Public License for more details.', file=fileptr)
        print('#', file=fileptr)
        print('#    You should have received a copy of the GNU General Public License', file=fileptr)
        print('#    along with this program.  If not, see <https://www.gnu.org/licenses/>.', file=fileptr)
        print('###########################################################################', file=fileptr)

        changed = 0
        gpu = gpu_list[uuid]
        print('# ', file=fileptr)
        print('# Card{}  {}'.format(gpu.prm.card_num, gpu.get_params_value('model')), file=fileptr)
        print('# {}'.format(gpu.prm.card_path), file=fileptr)
        if not env.GUT_CONST.write_delta_only:
            print('# Force Write mode.')
        else:
            print('# Write Delta mode.')
        print('# ', file=fileptr)
        print('set -x', file=fileptr)

        # Check/set power_dpm_force_performance_level
        # Mode of manual required to change ppm or clock masks
        curr_power_dpm_force = gpu.get_params_value('power_dpm_force').lower()
        if curr_power_dpm_force == 'manual' and env.GUT_CONST.write_delta_only:
            print('# Power DPM Force Performance Level: already [{}], skipping.'.format(curr_power_dpm_force),
                  file=fileptr)
        else:
            power_dpm_force_file = os.path.join(gpu.prm.card_path, 'power_dpm_force_performance_level')
            print('# Power DPM Force Performance Level: [{}] change to [manual]'.format(curr_power_dpm_force),
                  file=fileptr)
            print("sudo sh -c \"echo \'manual\' >  {}\"".format(power_dpm_force_file), file=fileptr)

        # Power Cap
        power_cap_file = os.path.join(gpu.prm.hwmon_path, 'power1_cap')
        old_power_cap = gpu.get_params_value('power_cap', num_as_int=True)
        new_power_cap_str = devices[uuid]['power_cap_ent'].get_text()
        if new_power_cap_str.lower() == 'reset':
            changed += 1
            print('# Powercap entry: {}, Resetting to default'.format(new_power_cap_str), file=fileptr)
            print("sudo sh -c \"echo \'0\' >  {}\"".format(power_cap_file), file=fileptr)
        elif re.fullmatch(PATTERNS['DIGITS'], new_power_cap_str):
            new_power_cap = int(new_power_cap_str)
            power_cap_range = gpu.get_params_value('power_cap_range')
            print('# Powercap Old: {}'.format(old_power_cap), end='', file=fileptr)
            print(' New: {}'.format(new_power_cap), end='', file=fileptr)
            print(' Min: {}'.format(power_cap_range[0]), end='', file=fileptr)
            print(' Max: {}\n'.format(power_cap_range[1]), end='', file=fileptr)
            if new_power_cap == old_power_cap and env.GUT_CONST.write_delta_only:
                print('# No Powercap changes, skipped', file=fileptr)
            else:
                if gpu.is_valid_power_cap(new_power_cap):
                    changed += 1
                    print("sudo sh -c \"echo \'{}\' >  {}\"".format((int(1000000 * new_power_cap)), power_cap_file),
                          file=fileptr)
                else:
                    print('# Invalid power_cap parameter values', file=fileptr)
                    print('Invalid power_cap parameter values')
        else:
            print('# Powercap New: {}, invalid input, ignoring'.format(new_power_cap_str), file=fileptr)
            new_power_cap = old_power_cap

        if env.GUT_CONST.show_fans:
            # Fan PWM
            pwm_enable_file = os.path.join(gpu.prm.hwmon_path, 'pwm1_enable')
            pwm_file = os.path.join(gpu.prm.hwmon_path, 'pwm1')
            old_pwm = gpu.get_params_value('fan_pwm', num_as_int=True)
            LOGGER.debug('Current pwm value: %s', old_pwm)
            new_pwm_str = devices[uuid]['fan_pwm_ent'].get_text()
            LOGGER.debug('Original pwm value %s, entered value %s', old_pwm, new_pwm_str)
            if new_pwm_str.lower() == 'reset':
                changed += 1
                print('# PWM entry: {}, Resetting to default mode of dynamic'.format(new_pwm_str), file=fileptr)
                print("sudo sh -c \"echo \'0\' >  {}\"".format(pwm_enable_file), file=fileptr)
                print("sudo sh -c \"echo \'2\' >  {}\"".format(pwm_enable_file), file=fileptr)
            elif new_pwm_str.lower() == 'max':
                changed += 1
                print('# PWM entry: {}, Disabling fan control'.format(new_pwm_str), file=fileptr)
                print("sudo sh -c \"echo \'0\' >  {}\"".format(pwm_enable_file), file=fileptr)
            elif re.fullmatch(PATTERNS['DIGITS'], new_pwm_str):
                new_pwm = int(new_pwm_str)
                print('# Fan PWM Old: {}'.format(old_pwm), end='', file=fileptr)
                print(' New: {}'.format(new_pwm), end='', file=fileptr)
                pwm_range = gpu.get_params_value('fan_pwm_range')
                print(' Min: {}'.format(pwm_range[0]), end='', file=fileptr)
                print(' Max: {}\n'.format(pwm_range[1]), end='', file=fileptr)
                if new_pwm == old_pwm and env.GUT_CONST.write_delta_only:
                    print('# No PWM changes, skipped', file=fileptr)
                elif new_pwm == 0 and old_pwm is None:
                    print('# No PWM changes, None to Zero skipped', file=fileptr)
                elif new_pwm < 20:
                    print('# Specified PWM value below min safe limit of 20%, skipped', file=fileptr)
                    LOGGER.debug('Unsafe PWM value skipped: %s', new_pwm_str)
                else:
                    if gpu.is_valid_fan_pwm(new_pwm):
                        changed += 1
                        new_pwm_value = int(255 * new_pwm / 100)
                        print("sudo sh -c \"echo \'1\' >  {}\"".format(pwm_enable_file), file=fileptr)
                        print("sudo sh -c \"echo \'{}\' >  {}\"".format(new_pwm_value, pwm_file), file=fileptr)
                    else:
                        print('# Invalid pwm parameter values', file=fileptr)
                        print('Invalid pwm parameter values')
            else:
                print('# PWM entry: {}, invalid input, ignoring'.format(new_pwm_str), file=fileptr)
                new_pwm = old_pwm

        device_file = os.path.join(gpu.prm.card_path, 'pp_od_clk_voltage')
        commit_needed = False
        if gpu.get_params_value('gpu_type') == gpu.GPU_Type.PStates:
            # Sclk P-states
            for comp_name, comp_item in devices[uuid]['sclk_pstate'].items():
                if not comp_item['gtk_ent_f_obj'].get_text().isnumeric():
                    print('# Invalid sclk pstate entry: {}'.format(comp_item['gtk_ent_f_obj'].get_text()),
                          file=fileptr)
                    print('# Invalid sclk pstate entry: {}'.format(comp_item['gtk_ent_f_obj'].get_text()))
                    continue
                if not comp_item['gtk_ent_v_obj'].get_text().isnumeric():
                    print('# Invalid sclk pstate entry: {}'.format(comp_item['gtk_ent_v_obj'].get_text()),
                          file=fileptr)
                    print('# Invalid sclk pstate entry: {}'.format(comp_item['gtk_ent_v_obj'].get_text()))
                pstate = [comp_name,
                          int(comp_item['gtk_ent_f_obj'].get_text()),
                          int(comp_item['gtk_ent_v_obj'].get_text())]
                print('#sclk p-state: {} : {} MHz, {} mV'.format(pstate[0], pstate[1], pstate[2]), file=fileptr)
                if gpu.is_valid_sclk_pstate(pstate):
                    if gpu.is_changed_sclk_pstate(pstate) or not env.GUT_CONST.write_delta_only:
                        changed += 1
                        commit_needed = True
                        print("sudo sh -c \"echo \'s {} {} {}\' >  {}\"".format(pstate[0], pstate[1],
                              pstate[2], device_file), file=fileptr)
                    else:
                        print('# Sclk pstate {} unchanged, skipping'.format(comp_name), file=fileptr)
                else:
                    print('# Invalid sclk pstate values', file=fileptr)
                    print('Invalid sclk pstate values')
            # Mclk P-states
            for comp_name, comp_item in devices[uuid]['mclk_pstate'].items():
                if not comp_item['gtk_ent_f_obj'].get_text().isnumeric():
                    print('# Invalid mclk pstate entry: {}'.format(comp_item['gtk_ent_f_obj'].get_text()),
                          file=fileptr)
                    print('# Invalid mclk pstate entry: {}'.format(comp_item['gtk_ent_f_obj'].get_text()))
                    continue
                if not comp_item['gtk_ent_v_obj'].get_text().isnumeric():
                    print('# Invalid mclk pstate entry: {}'.format(comp_item['gtk_ent_v_obj'].get_text()),
                          file=fileptr)
                    print('# Invalid mclk pstate entry: {}'.format(comp_item['gtk_ent_v_obj'].get_text()))
                    continue
                pstate = [comp_name,
                          int(comp_item['gtk_ent_f_obj'].get_text()),
                          int(comp_item['gtk_ent_v_obj'].get_text())]
                print('#mclk p-state: {} : {} MHz, {} mV'.format(pstate[0], pstate[1], pstate[2]), file=fileptr)
                if gpu.is_valid_mclk_pstate(pstate):
                    if gpu.is_changed_mclk_pstate(pstate) or not env.GUT_CONST.write_delta_only:
                        changed += 1
                        commit_needed = True
                        print("sudo sh -c \"echo \'m {} {} {}\' >  {}\"".format(pstate[0], pstate[1],
                              pstate[2], device_file), file=fileptr)
                    else:
                        print('# Mclk pstate {} unchanged, skipping'.format(comp_name), file=fileptr)
                else:
                    print('# Invalid mclk pstate values', file=fileptr)
                    print('Invalid mclk pstate values')
        elif gpu.get_params_value('gpu_type') == gpu.GPU_Type.CurvePts:
            # Sclk Curve End Points
            for comp_name, comp_item in devices[uuid]['sclk_pstate'].items():
                if not comp_item['gtk_ent_f_obj'].get_text().isnumeric():
                    print('# Invalid sclk curve end point entry: {}'.format(comp_item['gtk_ent_f_obj'].get_text()),
                          file=fileptr)
                    print('# Invalid sclk curve end point entry: {}'.format(comp_item['gtk_ent_f_obj'].get_text()))
                    continue
                pstate = [comp_name, int(comp_item['gtk_ent_f_obj'].get_text()), '-']
                print('# Sclk curve end point: {} : {} MHz'.format(pstate[0], pstate[1]), file=fileptr)
                if gpu.is_valid_sclk_pstate(pstate):
                    if gpu.is_changed_sclk_pstate(pstate) or not env.GUT_CONST.write_delta_only:
                        changed += 1
                        commit_needed = True
                        print("sudo sh -c \"echo \'s {} {}\' >  {}\"".format(pstate[0], pstate[1], device_file),
                              file=fileptr)
                    else:
                        print('# Sclk curve point {} unchanged, skipping'.format(comp_name), file=fileptr)
                else:
                    print('# Invalid sclk curve end point values', file=fileptr)
                    print('Invalid sclk curve end point values')
            # Mclk Curve End Points
            for comp_name, comp_item in devices[uuid]['mclk_pstate'].items():
                if not comp_item['gtk_ent_f_obj'].get_text().isnumeric():
                    print('# Invalid mclk curve end point entry: {}'.format(comp_item['gtk_ent_f_obj'].get_text()),
                          file=fileptr)
                    print('# Invalid mclk curve end point entry: {}'.format(comp_item['gtk_ent_f_obj'].get_text()))
                    continue
                pstate = [comp_name, int(comp_item['gtk_ent_f_obj'].get_text()), '-']
                print('# Mclk curve end point: {} : {} MHz'.format(pstate[0], pstate[1]), file=fileptr)
                if gpu.is_valid_mclk_pstate(pstate):
                    if gpu.is_changed_mclk_pstate(pstate) or not env.GUT_CONST.write_delta_only:
                        changed += 1
                        commit_needed = True
                        print("sudo sh -c \"echo \'m {} {}\' >  {}\"".format(pstate[0], pstate[1], device_file),
                              file=fileptr)
                    else:
                        print('# Mclk curve point {} unchanged, skipping'.format(comp_name), file=fileptr)
                else:
                    print('# Invalid mclk curve end point values', file=fileptr)
                    print('Invalid mclk curve end point values')
            # VDDC Curve Points
            for comp_name, comp_item in devices[uuid]['vddc_curve_pt'].items():
                if not comp_item['gtk_ent_f_obj'].get_text().isnumeric():
                    print('# Invalid vddc curve point entry: {}'.format(comp_item['gtk_ent_f_obj'].get_text()),
                          file=fileptr)
                    print('# Invalid vddc curve point entry: {}'.format(comp_item['gtk_ent_f_obj'].get_text()))
                    continue
                if not comp_item['gtk_ent_v_obj'].get_text().isnumeric():
                    print('# Invalid vddc curve point entry: {}'.format(comp_item['gtk_ent_v_obj'].get_text()),
                          file=fileptr)
                    print('# Invalid vddc curve point entry: {}'.format(comp_item['gtk_ent_v_obj'].get_text()))
                    continue
                curve_pts = [comp_name,
                             int(comp_item['gtk_ent_f_obj'].get_text()),
                             int(comp_item['gtk_ent_v_obj'].get_text())]
                print('# Vddc curve point: {} : {} MHz, {} mV'.format(curve_pts[0], curve_pts[1], curve_pts[2]),
                      file=fileptr)
                if gpu.is_valid_vddc_curve_pts(curve_pts):
                    if gpu.is_changed_vddc_curve_pt(curve_pts) or not env.GUT_CONST.write_delta_only:
                        changed += 1
                        commit_needed = True
                        print("sudo sh -c \"echo \'vc {} {} {}\' >  {}\"".format(curve_pts[0], curve_pts[1],
                              curve_pts[2], device_file), file=fileptr)
                    else:
                        print('# Vddc curve point {} unchanged, skipping'.format(comp_name), file=fileptr)
                else:
                    print('# Invalid Vddc curve point values', file=fileptr)
                    print('Invalid Vddc curve point values')

        # PPM
        ppm_mode_file = os.path.join(gpu.prm.card_path, 'pp_power_profile_mode')

        tree_iter = devices[uuid]['ppm_modes_combo'].get_active_iter()
        if tree_iter is not None:
            model = devices[uuid]['ppm_modes_combo'].get_model()
            row_id, name = model[tree_iter][:2]
            selected_mode = devices[uuid]['new_ppm'][0]
            print('# Selected: ID={}, name={}'.format(devices[uuid]['new_ppm'][0], devices[uuid]['new_ppm'][1]),
                  file=fileptr)
            if gpu.get_current_ppm_mode()[0] != devices[uuid]['new_ppm'][0] or not env.GUT_CONST.write_delta_only:
                changed += 1
                print("sudo sh -c \"echo \'{}\' >  {}\"".format(devices[uuid]['new_ppm'][0], ppm_mode_file),
                      file=fileptr)
            else:
                print('# PPM mode {} unchanged, skipping'.format(devices[uuid]['new_ppm'][1]), file=fileptr)

        # Commit changes
        device_file = os.path.join(gpu.prm.card_path, 'pp_od_clk_voltage')
        if commit_needed:
            changed += 1
            print("sudo sh -c \"echo \'c\' >  {}\"".format(device_file), file=fileptr)
        else:
            print('# No clock changes made, commit skipped', file=fileptr)

        if gpu.get_params_value('gpu_type') in [gpu.GPU_Type.PStates, gpu.GPU_Type.CurvePts]:
            # Writes of pstate Masks must come after commit of pstate changes
            # Sclk Mask
            sclk_mask_file = os.path.join(gpu.prm.card_path, 'pp_dpm_sclk')
            old_sclk_mask = gpu.prm.sclk_mask.replace(',', ' ')
            new_sclk_mask = devices[uuid]['sclk_pst_mask_ent'].get_text().replace(' ', '').strip()
            new_sclk_mask = new_sclk_mask.replace(',', ' ').strip()
            print('# Sclk P-State Mask Default: {}'.format(old_sclk_mask), end='', file=fileptr)
            print(' New: {}'.format(new_sclk_mask), file=fileptr)
            if new_sclk_mask == old_sclk_mask and env.GUT_CONST.write_delta_only:
                print('# No changes, skipped', file=fileptr)
            else:
                if gpu.is_valid_pstate_list_str(new_sclk_mask, 'SCLK'):
                    changed += 1
                    if new_sclk_mask == '':
                        # reset
                        print('# Resetting SCLK Mask to default', file=fileptr)
                        print("sudo sh -c \"echo \'{}\' >  {}\"".format(old_sclk_mask, sclk_mask_file), file=fileptr)
                    else:
                        print("sudo sh -c \"echo \'{}\' >  {}\"".format(new_sclk_mask, sclk_mask_file), file=fileptr)
                else:
                    print('# Invalid sclk mask parameter values', file=fileptr)
                    print('Invalid sclk mask parameter values: {}'.format(new_sclk_mask))

            # Mclk Mask
            mclk_mask_file = os.path.join(gpu.prm.card_path, 'pp_dpm_mclk')
            old_mclk_mask = gpu.prm.mclk_mask.replace(',', ' ')
            new_mclk_mask = devices[uuid]['mclk_pst_mask_ent'].get_text().replace(' ', '').strip()
            new_mclk_mask = new_mclk_mask.replace(',', ' ').strip()
            print('# Mclk P-State Mask Default: {}'.format(old_mclk_mask), end='', file=fileptr)
            print(' New: {}'.format(new_mclk_mask), file=fileptr)
            if new_mclk_mask == old_mclk_mask and env.GUT_CONST.write_delta_only:
                print('# No changes, skipped', file=fileptr)
            else:
                if gpu.is_valid_pstate_list_str(new_mclk_mask, 'MCLK'):
                    changed += 1
                    if new_mclk_mask == '':
                        # reset
                        print('# Resetting MCLK Mask to default', file=fileptr)
                        print("sudo sh -c \"echo \'{}\' >  {}\"".format(old_mclk_mask, mclk_mask_file), file=fileptr)
                    else:
                        print("sudo sh -c \"echo \'{}\' >  {}\"".format(new_mclk_mask, mclk_mask_file), file=fileptr)
                else:
                    print('# Invalid mclk mask parameter values', file=fileptr)
                    print('Invalid mclk mask parameter values: {}'.format(new_mclk_mask))

        # Close file and Set permissions and Execute it --execute_pac
        fileptr.close()
        os.chmod(out_filename, 0o744)
        print('Batch file completed: {}'.format(out_filename))
        if env.GUT_CONST.execute_pac:
            # Execute bash file
            print('Writing {} changes to GPU {}'.format(changed, gpu.prm.card_path))
            cmd = subprocess.Popen(out_filename, shell=True)
            cmd.wait()
            print('PAC execution complete.')

            if refresh:
                # dismiss execute_pac message
                time.sleep(0.5)
                if changed:
                    message = ('Write of {} PAC commands to card complete.\n'
                               'Confirm changes with gpu-monitor.').format(changed)
                else:
                    message = 'No PAC commands to write to card.\nNo changes specified.'
                self.update_message(devices, message, 'yellow')

            if refresh:
                self.refresh_pac(gpu_list, devices)
            os.remove(out_filename)
        else:
            if refresh:
                # dismiss execute_pac message
                if changed:
                    message = ('Write of {} PAC commands to bash file complete.\n'
                               'Manually run bash file with sudo to execute changes.').format(changed)
                else:
                    message = 'No PAC commands to write bash file.\nNo changes specified.'
                self.update_message(devices, message, 'yellow')
            print('Execute to write changes to GPU {}'.format(gpu.prm.card_path))
            print('')
        return changed

    def reset_all_cards(self, parent, gpu_list: Gpu.GpuList, devices: dict) -> None:
        """
        Reset data for all GPUs.

        :param parent: parent
        :param gpu_list:
        :param devices:  Dictionary of GUI items and GPU data.
        """
        # Write start message
        if env.GUT_CONST.execute_pac:
            message = ('Using the --execute_pac option  Reset commands will be written to the GPU '
                       'without confirmation.\nSudo will be used, so you may be prompted for '
                       'credentials in the window where gpu-pac was executed from.')
        else:
            message = 'Writing reset commands to bash file.\n'
        self.update_message(devices, message, 'red')

        # reset each card
        for uuid in gpu_list.uuids():
            self.reset_card(parent, gpu_list, devices, uuid, refresh=False)

        # Write finish message
        if env.GUT_CONST.execute_pac:
            message = 'Write reset commands to card complete.\nConfirm changes with gpu-mon.'
        else:
            message = 'Write reset commands to bash file complete.\nRun bash file with sudo to execute changes.'
        self.update_message(devices, message, 'yellow')

        self.refresh_all_cards(parent, gpu_list, devices)

    def reset_card(self, _, gpu_list: Gpu.GpuList, devices: dict, uuid: str, refresh: bool = True) -> None:
        """
        Reset data for specified GPU.

        :param _: parent not used
        :param gpu_list:
        :param devices:  Dictionary of GUI items and GPU data.
        :param uuid: GPU device ID
        :param refresh: Flag to indicate if refresh should be done
        """
        if refresh:
            # Write message
            if env.GUT_CONST.execute_pac:
                message = ('Using the --execute_pac option  Reset commands will be written to the GPU '
                           'without confirmation.\nSudo will be used, so you may be prompted for '
                           'credentials in the window where gpu-pac was executed from.')
            else:
                message = 'Writing reset commands to bash file.\n'
            self.update_message(devices, message, 'red')
        # specify output batch file name
        out_filename = os.path.join(os.getcwd(), 'pac_resetter_{}.sh'.format(uuid4().hex))
        fileptr = open(out_filename, 'x')
        # Output header
        print('#!/bin/sh', file=fileptr)
        print('###########################################################################', file=fileptr)
        print('## rickslab-gpu-pac generated script to modify GPU configuration/settings', file=fileptr)
        print('###########################################################################', file=fileptr)
        print('', file=fileptr)
        print('###########################################################################', file=fileptr)
        print('## WARNING - Do not execute this script without completely', file=fileptr)
        print('## understanding appropriate value to write to your specific GPUs', file=fileptr)
        print('###########################################################################', file=fileptr)
        print('#', file=fileptr)
        print('#    Copyright (C) 2019  RueiKe', file=fileptr)
        print('#', file=fileptr)
        print('#    This program is free software: you can redistribute it and/or modify', file=fileptr)
        print('#    it under the terms of the GNU General Public License as published by', file=fileptr)
        print('#    the Free Software Foundation, either version 3 of the License, or', file=fileptr)
        print('#    (at your option) any later version.', file=fileptr)
        print('#', file=fileptr)
        print('#    This program is distributed in the hope that it will be useful,', file=fileptr)
        print('#    but WITHOUT ANY WARRANTY; without even the implied warranty of', file=fileptr)
        print('#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the', file=fileptr)
        print('#    GNU General Public License for more details.', file=fileptr)
        print('#', file=fileptr)
        print('#    You should have received a copy of the GNU General Public License', file=fileptr)
        print('#    along with this program.  If not, see <https://www.gnu.org/licenses/>.', file=fileptr)
        print('###########################################################################', file=fileptr)

        gpu = gpu_list[uuid]
        print('# ', file=fileptr)
        print('# Card{}  {}'.format(gpu.prm.card_num, gpu.get_params_value('model')), file=fileptr)
        print('# {}'.format(gpu.prm.card_path), file=fileptr)
        print('# ', file=fileptr)
        print('set -x', file=fileptr)

        # Commit changes
        power_cap_file = os.path.join(gpu.prm.hwmon_path, 'power1_cap')
        pwm_enable_file = os.path.join(gpu.prm.hwmon_path, 'pwm1_enable')
        device_file = os.path.join(gpu.prm.card_path, 'pp_od_clk_voltage')
        power_dpm_force_file = os.path.join(gpu.prm.card_path, 'power_dpm_force_performance_level')
        print("sudo sh -c \"echo \'0\' >  {}\"".format(power_cap_file), file=fileptr)
        if env.GUT_CONST.show_fans:
            print("sudo sh -c \"echo \'2\' >  {}\"".format(pwm_enable_file), file=fileptr)
        print("sudo sh -c \"echo \'auto\' >  {}\"".format(power_dpm_force_file), file=fileptr)
        print("sudo sh -c \"echo \'r\' >  {}\"".format(device_file), file=fileptr)
        print("sudo sh -c \"echo \'c\' >  {}\"".format(device_file), file=fileptr)
        # No need to reset clk pstate masks as commit to pp_od_clk_voltage will reset

        # Close file and Set permissions and Execute it --execute_pac
        fileptr.close()
        os.chmod(out_filename, 0o744)
        print('Batch file completed: {}'.format(out_filename))
        if env.GUT_CONST.execute_pac:
            print('Writing changes to GPU {}'.format(gpu.prm.card_path))
            cmd = subprocess.Popen(out_filename, shell=True)
            cmd.wait()
            print('')
            if refresh:
                # Dismiss execute_pac message
                message = 'Write reset commands to card complete.\nConfirm changes with gpu-mon.'
                self.update_message(devices, message, 'yellow')
            self.refresh_pac(gpu_list, devices)
            os.remove(out_filename)
        else:
            print('Execute to write changes to GPU {}.\n'.format(gpu.prm.card_path))
            if refresh:
                # Dismiss execute_pac message
                message = 'Write reset commands to bash file complete.\nRun bash file with sudo to execute changes.'
                self.update_message(devices, message, 'yellow')


def ppm_select(_, device: dict) -> None:
    """
    Update device data for ppm selection and update active selected item in Gui.

    :param _: self
    :param device:  Dictionary of GUI items and GPU data.
    """
    tree_iter = device['ppm_modes_combo'].get_active_iter()
    if tree_iter is not None:
        model = device['ppm_modes_combo'].get_model()
        row_id, name = model[tree_iter][:2]
        device['new_ppm'] = [row_id, name]


def main() -> None:
    """
    Main PAC flow.
    """
    parser = argparse.ArgumentParser()
    parser.add_argument('--about', help='README', action='store_true', default=False)
    parser.add_argument('--execute_pac', help='execute pac bash script without review',
                        action='store_true', default=False)
    parser.add_argument('--no_fan', help='do not include fan setting options', action='store_true', default=False)
    parser.add_argument('--force_write', help='write all parameters, even if unchanged',
                        action='store_true', default=False)
    parser.add_argument('-d', '--debug', help='Debug output', action='store_true', default=False)
    args = parser.parse_args()

    # About me
    if args.about:
        print(__doc__)
        print('Author: ', __author__)
        print('Copyright: ', __copyright__)
        print('Credits: ', *['\n      {}'.format(item) for item in __credits__])
        print('License: ', __license__)
        print('Version: ', __version__)
        print('Maintainer: ', __maintainer__)
        print('Status: ', __status__)
        sys.exit(0)

    env.GUT_CONST.set_args(args)
    LOGGER.debug('########## %s %s', __program_name__, __version__)

    if env.GUT_CONST.check_env() < 0:
        print('Error in environment. Exiting...')
        sys.exit(-1)

    # Get list of GPUs and get basic non-driver details
    gpu_list = Gpu.GpuList()
    gpu_list.set_gpu_list()

    # Check list of GPUs
    num_gpus = gpu_list.num_vendor_gpus()
    print('Detected GPUs: ', end='')
    for i, (type_name, type_value) in enumerate(num_gpus.items()):
        if i:
            print(', {}: {}'.format(type_name, type_value), end='')
        else:
            print('{}: {}'.format(type_name, type_value), end='')
    print('')
    if 'AMD' in num_gpus.keys():
        env.GUT_CONST.read_amd_driver_version()
        print('AMD: {}'.format(gpu_list.wattman_status()))
    if 'NV' in num_gpus.keys():
        print('nvidia smi: [{}]'.format(env.GUT_CONST.cmd_nvidia_smi))

    num_gpus = gpu_list.num_gpus()
    if num_gpus['total'] == 0:
        print('No GPUs detected, exiting...')
        sys.exit(-1)

    # Read data static/dynamic/info/state driver information for GPUs
    gpu_list.read_gpu_sensor_set(data_type=Gpu.GpuItem.SensorSet.All)

    # Check number of readable/writable GPUs again
    num_gpus = gpu_list.num_gpus()
    print('{} total GPUs, {} rw, {} r-only, {} w-only\n'.format(num_gpus['total'], num_gpus['rw'],
                                                                num_gpus['r-only'], num_gpus['w-only']))

    # Check number of compatible GPUs again
    com_gpu_list = gpu_list.list_gpus(compatibility=Gpu.GpuItem.GPU_Comp.Writable)
    writable_gpus = com_gpu_list.num_gpus()['total']
    if not writable_gpus:
        print('None are writable, exiting...')
        sys.exit(-1)
    com_gpu_list.read_gpu_pstates()
    com_gpu_list.read_gpu_ppm_table()

    # Display Gtk style Monitor
    devices = {}
    gmonitor = PACWindow(com_gpu_list, devices)
    gmonitor.connect('delete-event', Gtk.main_quit)
    gmonitor.show_all()

    Gtk.main()


if __name__ == '__main__':
    main()
