試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

nvidia,fglrx以外のドライバ向けのX Window Systemの明るさとガンマのGUI調整ツールを作成

X Window Systemでnvidia,fglrx以外のドライバを用いた環境における明るさとガンマの調整について(ページ2/3)」「X Window Systemでnvidia,fglrx以外のドライバを用いた環境における明るさとガンマの調整について(ページ3/3)」で作成した、nvidia,fglrx以外のドライバ向けの明るさとガンマの調整ツールのGUI版を作成した。
左側に色曲線,右側に操作のGUI部品を配置しており、いずれかの値を変更するとその場で画面上に反映され、同時に設定の自動キャンセルのカウントダウン(10秒)が開始される。

カウントダウンが始まると「適用」ボタンが押せるようになり、カウントが0になるまでに「適用」を押さないとカウントダウン終了のタイミングで自動的に変更前の値に戻る。
コードはPython(GUIにはPyGTK)を使用しており、動作には

  • バージョン2.6/2.7系のいずれかのPython
  • PyGTK(PyCairo含む)のバージョン2系
  • PyGObject

が必要。
[任意]ファイル名: xbrightness-rgbgamma-gtk.py ライセンス: MIT

#! /usr/bin/python
#
# X11 brightness / gamma adjustment tool (PyGTK GUI)
# version 20110505
# (C) 2011 kakurasan
# Licensed under MIT
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# THANKS:
#  xbrightness by Asher Blum and Jean-Daniel Pauget

import ctypes
import math
import sys
try:
  import pygtk
  pygtk.require ('2.0')
except:
  pass
try:
  import cairo
  import glib
  import gtk
except:
  sys.exit (1)


def run_aboutdialog (window):
  """
  Run "about" dialog
  """
  dialog = gtk.AboutDialog ()
  dialog.props.authors = ['kakurasan',]
  dialog.props.comments = 'Brightness and gamma adjustment tool'
  dialog.props.copyright = '(C) 2011 kakurasan'
  dialog.props.license = 'MIT'
  dialog.props.program_name = 'xbrightness-rgbgamma-gtk'
  dialog.props.version = '20110429'
  dialog.run ()
  dialog.destroy ()


class GetGammaRampSizeError (Exception):
  pass

class GetGammaRampError (Exception):
  pass

class SetGammaRampError (Exception):
  pass

class Libxxf86vm:
  __lib = ctypes.CDLL ('libXxf86vm.so.1')
  __ramp_size = ctypes.c_int ()
  def __init__ (self):
    # XOpenDisplay(): Display *
    self.__display = self.__lib.XOpenDisplay (None)  # envar "DISPLAY"
    # XDefaultScreen: Display *
    self.__screen = self.__lib.XDefaultScreen (self.__display)
    # XF86VidModeGetGammaRampSize(): Display *, int, int *
    ret = self.__lib.XF86VidModeGetGammaRampSize (self.__display, self.__screen, ctypes.byref (self.__ramp_size))
    if not ret:
      raise GetGammaRampSizeError ()
    Ramp = ctypes.c_ushort * self.__ramp_size.value  # type
    (self.__ramp_red, self.__ramp_green, self.__ramp_blue) = (Ramp (), Ramp (), Ramp ())
    # XF86VidModeGetGammaRamp(): Display *, int, int,
    #                            unsigned short *, unsigned short *, unsigned short *
    ret = self.__lib.XF86VidModeGetGammaRamp (self.__display, self.__screen, self.__ramp_size, self.__ramp_red, self.__ramp_green, self.__ramp_blue)
    if not ret:
      raise GetGammaRampError ()
  def __del__ (self):
    if self.__display:
      # XCloseDisplay(): Display *
      self.__lib.XCloseDisplay (self.__display)
  def set_gamma_and_brightness (self, brightness, gamma_red, gamma_green, gamma_blue):
    for i in range (self.__ramp_size.value):
      p = i / float (self.__ramp_size.value - 1)
      self.__ramp_red[i] = ctypes.c_ushort (int (brightness * pow (p, 1 / gamma_red)))
      self.__ramp_green[i] = ctypes.c_ushort (int (brightness * pow (p, 1 / gamma_green)))
      self.__ramp_blue[i] = ctypes.c_ushort (int (brightness * pow (p, 1 / gamma_blue)))
    # XF86VidModeSetGammaRamp(): Display *, int, int,
    #                            unsigned short *, unsigned short *, unsigned short *
    ret = self.__lib.XF86VidModeSetGammaRamp (self.__display, self.__screen, self.__ramp_size, self.__ramp_red, self.__ramp_green, self.__ramp_blue)
    if not ret:
      raise SetGammaRampError ()
  @property
  def ramp_red (self): return self.__ramp_red
  @property
  def ramp_green (self): return self.__ramp_green
  @property
  def ramp_blue (self): return self.__ramp_blue
  @property
  def ramp_size (self): return self.__ramp_size.value

class GraphDrawingArea (gtk.DrawingArea):
  """
  DrawingArea for gamma curve
  """
  def __init__ (self, lib):
    gtk.DrawingArea.__init__ (self)
    self.connect ('size-allocate', self.__on_self_size_allocate)
    self.connect ('expose-event', self.__on_self_expose_event)
    self.__lib = lib
    self.__ramp_red = lib.ramp_red
    self.__ramp_green = lib.ramp_green
    self.__ramp_blue = lib.ramp_blue
    self.__ramp_size = lib.ramp_size
  def __on_self_size_allocate (self, widget, allocation):
    """
    'size-allocate' handler for this DrawingArea
    """
    self.__width = allocation.width
    self.__height = allocation.height
  def __on_self_expose_event (self, widget, event):
    """
    Draw gamma curve
    """
    x_step = float (self.__width) / float (self.__ramp_size - 1)
    y_step = self.__height / 256.0
    ctx_bg = widget.window.cairo_create ()
    ctx_bg.set_source_rgb (1.0, 1.0, 1.0)
    ctx_bg.rectangle (0, 0, self.__width, self.__height)
    ctx_bg.fill ()
    ctx_r = widget.window.cairo_create ()
    ctx_r.set_source_rgba (0.8, 0.0, 0.0, 0.5)
    ctx_r.new_path ()
    ctx_g = widget.window.cairo_create ()
    ctx_g.set_source_rgba (0.0, 0.8, 0.0, 0.5)
    ctx_g.new_path ()
    ctx_b = widget.window.cairo_create ()
    ctx_b.set_source_rgba (0.0, 0.0, 0.8, 0.5)
    ctx_b.new_path ()
    for i in range (self.__ramp_size):
      ctx_r.line_to (x_step * i, y_step * (65535 - self.__ramp_red[i]) / 256.0)
      ctx_g.line_to (x_step * i, y_step * (65535 - self.__ramp_green[i]) / 256.0)
      ctx_b.line_to (x_step * i, y_step * (65535 - self.__ramp_blue[i]) / 256.0)
    ctx_r.stroke ()
    ctx_g.stroke ()
    ctx_b.stroke ()
  def redraw (self):
    """
    Force redraw
    """
    self.__ramp_red = self.__lib.ramp_red
    self.__ramp_green = self.__lib.ramp_green
    self.__ramp_blue = self.__lib.ramp_blue
    self.emit ('expose-event', None)

class MainWindow (gtk.Window):
  """
  Application main window
  """
  def __init__ (self, *args, **kwargs):
    gtk.Window.__init__ (self, *args, **kwargs)
    self.__lib = Libxxf86vm ()
    self.__accelgroup = gtk.AccelGroup ()
    self.add_accel_group (self.__accelgroup)
    self.__item_quit = gtk.ImageMenuItem (gtk.STOCK_QUIT, self.__accelgroup)
    self.__item_about = gtk.ImageMenuItem (gtk.STOCK_ABOUT, self.__accelgroup)
    self.__menu_file = gtk.Menu ()
    self.__menu_file.add (self.__item_quit)
    self.__menu_help = gtk.Menu ()
    self.__menu_help.add (self.__item_about)
    self.__item_file = gtk.MenuItem ('_File')
    self.__item_file.props.submenu = self.__menu_file
    self.__item_help = gtk.MenuItem ('_Help')
    self.__item_help.props.submenu = self.__menu_help
    self.__menubar = gtk.MenuBar ()
    self.__menubar.append (self.__item_file)
    self.__menubar.append (self.__item_help)
    self.__drawingarea = GraphDrawingArea (self.__lib)
    self.__frame_brightness = gtk.Frame ('Brightness')
    self.__frame_gamma = gtk.Frame ('Gamma correction')
    self.__frame_gamma_red = gtk.Frame ('Red')
    self.__frame_gamma_green = gtk.Frame ('Green')
    self.__frame_gamma_blue = gtk.Frame ('Blue')
    self.__frame_brightness.props.shadow_type = self.__frame_gamma.props.shadow_type = gtk.SHADOW_IN
    self.__hbox_brightness = gtk.HBox ()
    self.__hbox_gamma_red = gtk.HBox ()
    self.__hbox_gamma_green = gtk.HBox ()
    self.__hbox_gamma_blue = gtk.HBox ()
    # calculate current gamma
    (ramp_red, ramp_green, ramp_blue) = self.__lib.ramp_red, self.__lib.ramp_green, self.__lib.ramp_blue
    ramp_size = self.__lib.ramp_size
    self.__brightness = ramp_red[ramp_size - 1]
    self.__gamma_red = float ('{0:.2f}'.format (1.0 / math.log (ramp_red[ramp_size / 2] / float (self.__brightness), (ramp_size / 2) / float (ramp_size - 1))))
    self.__gamma_green = float ('{0:.2f}'.format (1.0 / math.log (ramp_green[ramp_size / 2] / float (self.__brightness), (ramp_size / 2) / float (ramp_size - 1))))
    self.__gamma_blue = float ('{0:.2f}'.format (1.0 / math.log (ramp_blue[ramp_size / 2] / float (self.__brightness), (ramp_size / 2) / float (ramp_size - 1))))
    # values
    self.__adjustment_brightness = gtk.Adjustment (value=self.__brightness, lower=0, upper=65535, step_incr=1, page_incr=100)
    self.__adjustment_gamma_red = gtk.Adjustment (value=self.__gamma_red, lower=0.01, upper=3, step_incr=0.01, page_incr=0.05)
    self.__adjustment_gamma_green = gtk.Adjustment (value=self.__gamma_green, lower=0.01, upper=3, step_incr=0.01, page_incr=0.05)
    self.__adjustment_gamma_blue = gtk.Adjustment (value=self.__gamma_blue, lower=0.01, upper=3, step_incr=0.01, page_incr=0.05)
    self.__hscale_brightness = gtk.HScale (self.__adjustment_brightness)
    self.__hscale_gamma_red = gtk.HScale (self.__adjustment_gamma_red)
    self.__hscale_gamma_green = gtk.HScale (self.__adjustment_gamma_green)
    self.__hscale_gamma_blue = gtk.HScale (self.__adjustment_gamma_blue)
    self.__hscale_brightness.props.draw_value \
     = self.__hscale_gamma_red.props.draw_value \
     = self.__hscale_gamma_green.props.draw_value \
     = self.__hscale_gamma_blue.props.draw_value = False
    self.__spbtn_brightness = gtk.SpinButton (self.__adjustment_brightness)
    self.__spbtn_gamma_red = gtk.SpinButton (self.__adjustment_gamma_red, digits=2)
    self.__spbtn_gamma_green = gtk.SpinButton (self.__adjustment_gamma_green, digits=2)
    self.__spbtn_gamma_blue = gtk.SpinButton (self.__adjustment_gamma_blue, digits=2)
    self.__button_apply = gtk.Button (stock=gtk.STOCK_APPLY)
    self.__button_apply.props.sensitive = False
    self.__statusbar = gtk.Statusbar ()
    self.__ctxid = self.__statusbar.get_context_id ('status')
    self.__hbox_brightness = gtk.HBox ()
    self.__hbox_brightness.pack_start (self.__hscale_brightness)
    self.__hbox_brightness.pack_start (self.__spbtn_brightness, expand=False, fill=False)
    self.__hbox_gamma_red = gtk.HBox ()
    self.__hbox_gamma_red.pack_start (self.__hscale_gamma_red)
    self.__hbox_gamma_red.pack_start (self.__spbtn_gamma_red, expand=False, fill=False)
    self.__hbox_gamma_green = gtk.HBox ()
    self.__hbox_gamma_green.pack_start (self.__hscale_gamma_green)
    self.__hbox_gamma_green.pack_start (self.__spbtn_gamma_green, expand=False, fill=False)
    self.__hbox_gamma_blue = gtk.HBox ()
    self.__hbox_gamma_blue.pack_start (self.__hscale_gamma_blue)
    self.__hbox_gamma_blue.pack_start (self.__spbtn_gamma_blue, expand=False, fill=False)
    self.__frame_gamma_red.add (self.__hbox_gamma_red)
    self.__frame_gamma_green.add (self.__hbox_gamma_green)
    self.__frame_gamma_blue.add (self.__hbox_gamma_blue)
    self.__vbox_gamma = gtk.VBox ()
    self.__vbox_gamma.pack_start (self.__frame_gamma_red)
    self.__vbox_gamma.pack_start (self.__frame_gamma_green)
    self.__vbox_gamma.pack_start (self.__frame_gamma_blue)
    self.__frame_brightness.add (self.__hbox_brightness)
    self.__frame_gamma.add (self.__vbox_gamma)
    self.__vbox_right = gtk.VBox ()
    self.__vbox_right.props.width_request = 200
    self.__vbox_right.pack_start (self.__frame_brightness)
    self.__vbox_right.pack_start (self.__frame_gamma)
    self.__vbox_right.pack_start (self.__button_apply, expand=False, fill=False)
    self.__aspectframe = gtk.AspectFrame (yalign=0)
    self.__aspectframe.add (self.__drawingarea)
    self.__hbox = gtk.HBox ()
    self.__hbox.pack_start (self.__aspectframe)
    self.__hbox.pack_start (self.__vbox_right, expand=False, fill=False)
    self.__vbox = gtk.VBox ()
    self.__vbox.pack_start (self.__menubar, expand=False, fill=False)
    self.__vbox.pack_start (self.__hbox)
    self.__vbox.pack_start (self.__statusbar, expand=False, fill=False)
    # signals
    self.__item_quit.connect ('activate', gtk.main_quit)
    self.__item_about.connect ('activate', run_aboutdialog)
    self.__adjustment_brightness.connect ('value-changed', self.__on_adjustment_value_changed)
    self.__adjustment_gamma_red.connect ('value-changed', self.__on_adjustment_value_changed)
    self.__adjustment_gamma_green.connect ('value-changed', self.__on_adjustment_value_changed)
    self.__adjustment_gamma_blue.connect ('value-changed', self.__on_adjustment_value_changed)
    self.__button_apply.connect ('clicked', self.__on_button_apply_clicked)
    self.connect ('delete_event', gtk.main_quit)
    # window
    self.props.title = 'xbrightness-rgbgamma-gtk'
    self.props.width_request = 450
    self.add (self.__vbox)
  def __on_adjustment_value_changed (self, adjustment):
    """
    'value-changed' handler for brightness/gamma
    """
    self.__lib.set_gamma_and_brightness (self.__adjustment_brightness.props.value,
                                         self.__adjustment_gamma_red.props.value,
                                         self.__adjustment_gamma_green.props.value,
                                         self.__adjustment_gamma_blue.props.value)
    self.__drawingarea.redraw ()
    self.__statusbar.pop (self.__ctxid)
    if adjustment == self.__adjustment_brightness:
      self.__statusbar.push (self.__ctxid, 'Set brightness to {0:d}'.format (int (adjustment.props.value)))
    elif adjustment == self.__adjustment_gamma_red:
      self.__statusbar.push (self.__ctxid, 'Set gamma/red to {0:.2f}'.format (adjustment.props.value))
    elif adjustment == self.__adjustment_gamma_green:
      self.__statusbar.push (self.__ctxid, 'Set gamma/green to {0:.2f}'.format (adjustment.props.value))
    elif adjustment == self.__adjustment_gamma_blue:
      self.__statusbar.push (self.__ctxid, 'Set gamma/blue to {0:.2f}'.format (adjustment.props.value))
    if not self.__button_apply.props.sensitive:
      self.__button_apply.props.sensitive = True
      self.__cnt = 10
      self.__toid = glib.timeout_add_seconds (1, self.__to_restore_countdown)
  def __to_restore_countdown (self):
    """
    Count down "cancel after N sec(s)" counter
    """
    self.__cnt -= 1
    self.__statusbar.pop (self.__ctxid)
    self.__statusbar.push (self.__ctxid, 'Cancel after {0} second(s)'.format (self.__cnt))
    if self.__cnt > 0:
      return True  # continue
    else:
      # restore: lib
      self.__lib.set_gamma_and_brightness (self.__brightness,
                                           self.__gamma_red,
                                           self.__gamma_green,
                                           self.__gamma_blue)
      self.__drawingarea.redraw ()
      # restore: widgets
      self.__adjustment_brightness.props.value = self.__brightness
      self.__adjustment_gamma_red.props.value = self.__gamma_red
      self.__adjustment_gamma_green.props.value = self.__gamma_green
      self.__adjustment_gamma_blue.props.value = self.__gamma_blue
      self.__statusbar.pop (self.__ctxid)
      self.__statusbar.push (self.__ctxid, 'Restored values'.format (self.__cnt))
      self.__button_apply.props.sensitive = False
      return False  # stop
  def __on_button_apply_clicked (self, widget):
    """
    Apply values
    """
    glib.source_remove (self.__toid)
    self.__brightness = self.__adjustment_brightness.props.value
    self.__gamma_red = self.__adjustment_gamma_red.props.value
    self.__gamma_green = self.__adjustment_gamma_green.props.value
    self.__gamma_blue = self.__adjustment_gamma_blue.props.value
    self.__button_apply.props.sensitive = False
    self.__statusbar.pop (self.__ctxid)
    self.__statusbar.push (self.__ctxid, 'Applied values'.format (self.__cnt))


def main ():
  """
  Main
  """
  win = MainWindow ()
  win.show_all ()
  gtk.main ()


if __name__ == '__main__':
  main ()

(2011/5/5)ディスプレイが開けなかった場合の処理を修正
libXxf86vm上の処理が失敗すると例外を発生(送出)するようにしているが、今回はlibXxf86vm上の処理が失敗することは想定しておらず、GUIのコード側では捕捉するようにはしていない。
これをもとに、メッセージの国際化や内部処理の改善をしたものをパッケージとして近い内に公開する予定。

使用したバージョン:

  • Python 2.6.6, 2.7.1
  • PyGTK 2.21.0, 2.22.0