IS XLIB BRIGHT



Xlib and Brightness Control On Lenovo X230 Running FreeBSD

Note: I am writing this well after the fact and so some of the information is a little incomplete. So hopefully there are no obvious errors :).
I am currently running freebsd on my laptop an x230 ThinkPad (This has recently change and I am now running openBSD, although I haven’t worked everything out yet.) After the installation process I found that I was not able to change the brightness with any keys or combination of keys after I had completed the installation. More importantly I was not able to change the brightness by issuing any command (to my knowledge.) So I was faced with the problem of figuring out what needed to be done to change the brightness on my laptop. After this problem was solved it would be a simple matter of binding a combination of keys to a simple script or program (through the use of my i3 config file) that would run the command/s.

I managed to solve the aforementioned problem and I was pleased with the results. However as time went by I found more and more that I desperately wanted visual feedback when adjusting the brightness (and also the volume.) So I wondered for a while how I might do this. Not knowing very much about how this sort of thing is done I thought about d menu and the I3 status bar. How did they draw to the screen? Sure there not transparent and don’t “hover” above the cursor, but I would be happy enough if I could get anything half decent working and I figured that the size of the code base of these programs would be small enough that I might be able to make some sense of them. So I looked at the source code for d menu. I had the source code for d menu 4.7 under /usr/ports/x11/dmenu/work/dmenu-4.7 on my machine since I had compiled it from ports instead of installing it using the pkg install command so that I could change its default colours. I found that dmenu.c includes a number of things related to X11, including X11/xlib.h. So I decided to look into xlib and also how to write a simple X11 application. I found a couple of tutorials on creating windows under X11 using xlib and doing other simple things like printing text and shapes in the windows. I played around with these examples for a while and with the help of these tutorials and the xlib manual (https://tronche.com/gui/x/xlib/) I figured out how to do what I wanted, that is to make a program that opened a window that I could display different coloured text and shapes in and that had no borders, an absolute position on the screen and was rendered in front of other windows (including the currently active window).

Changing the Brightness

I did more then just execute the previously mentioned command (although I don’t know whether or not the other things I did where useful, I think they probably where. I did will say I was sketchy on the details :) .) I did this some time ago and thus am somewhat sketchy on the details. So it may not be an entirely accurate list of steps.
I added the following lines to /etc/loader.conf:
  #for brightness
  acpi_ibm_load="YES"
  acpi_call_load="YES"
I believe I also installed a package related to IBM Thinkpads and ACPI that was suggested on a forum as part of the solution for getting brightness control to work. The post on the forum also listed a command that could then be used to change the brightness. The command was something like the following (although not exactly the same. More on that in a second.) “acpi_call -p '\_SB.PCI0.VID.LCD0._BCM' -i $1”. Where $1 would be a number between 10 and 100 in increments of 10. It worked. The brightness level changed. But there was a problem that soon became apparent. Sometimes when issued the command would cause my computer to completely freeze up. Whether this would happen or not was seemingly random. But it would happen with a high probability (it would still be completely unacceptable even if the probability was extremely low.) This was CLEARLY not going to work as a solution. There was an ASCII text file on my system I was sure was related to part of this command and changing the brightness I can't recall why I thought this (maybe it was mentioned on the forum.) This file was very large for a text file. I started grepping through it looking for things related to brightness and changing the command slightly according to what variables I found that I thought were related. Most of them didn’t do anything. Some made my system crash. Eventually I found one that worked and seemingly has no side effects and thus I ended up with the previously listed command (“acpi_call -p '\_SB.PCI0.VID.LCD0._BCM' -i $1”). I have been using this to change the brightness on my computer (indirectly) ever since. I have included the file where I found the string “\_SB.PCI0.VID.LCD0._BCM” in the following link for your pleasure: dumpylist

Programmatic Assistance and Pretty Graphics

Main()
The programs main function accepts either one or two arguments with one of those argument’s being the name of the program. The program attempts to read in a single integer value in the range [BR_RANGE_MIN, BR_RANGE_MAX] from the file specified in brLevelFileName (“/usr/tmp/brLevel”.) If this value is successfully read in and is within range and is also evenly divisible by BR_INTERVAL_GRANULARITY (10) then the variable brLevel is set to the value read in, otherwise it is set to BR_DEFAULT (80). If the brLevel is set to BR_DEFAULT doWork() is called before the program exits. If brLevel is not set to BR_DEFAULT then we check to see if argc is equal to MAX_ARGC (2). If it is then two command line arguments have been given with the second one presumably being one of the two characters ‘+’ or ‘-’. We test to see if it is one of these characters by checking the return value of handle2ndArg() if the return value is true then the second argument is not malformed and we call doWork() before exiting the program. If the second argument is malformed we call printUsage() before exiting the program. handle2ndArg() does more than just check if the second argument of argv is valid, it also adjusts the value of brLevel (which is passed to handle2ndArg() by reference so that it will also be changed in main). It will call adjustBR_Val() which will add its third argument (BR_INTERVAL_GRANULARITY) to its return value (it’s fifth argument brLevel) if it’s fourth argument is the ASCII character ‘+’. If it is the ASCII character ‘-’ it will subtract its third argument from the return value instead. However there is an exception for the two above described functions of the function, the function uses saturating arithmetic. So it will not subtract or add BR_INTERVAL_GRANULARITY if it would cause the return value to go out side of the range specified by its second and first arguments respectively [BR_RANGE_MIN, BR_RANGE_MAX].


/* Main requires 1 or 2 command line argument's (this includes the program name)
   the 2nd argument must be either '+' or '-', if there are 2 arguments. */
int main(const int argc, const char * argv[])
{				// BR_DEFAULT should be divisible by BR_INTERVAL_GRANULARITY!
  constexpr int BR_RANGE_MIN {10}, BR_RANGE_MAX {100}, BR_INTERVAL_GRANULARITY {10}, BR_DEFAULT {80};
  constexpr int MAX_ARGC {2}, MIN_ARGC {1}, ARG_2_INDEX {1}; // ARG_1_INDEX {0}.
  constexpr char brLevelFileName [] = "/usr/tmp/brLevel";
  int brLevel {getIntFromFile(BR_DEFAULT, brLevelFileName)}; // Attempt to get current brightness level.

  if(!checkBR_Val(BR_RANGE_MIN, BR_RANGE_MAX, BR_INTERVAL_GRANULARITY, brLevel))
    {
      std::cerr<<"Brightness level stored in \""<<brLevelFileName<<"\", not evenly divisible by "
	"BR_INTERVAL_GRANULARITY ("<<BR_INTERVAL_GRANULARITY<<") or was not able to open file containing"
	" brightness level. Setting brightness level to dealfult ("<<BR_DEFAULT<<")\n";
      brLevel = BR_DEFAULT;
      doWork(brLevel, brLevelFileName);      
    }
  else
    {
      if(argc == MAX_ARGC)
	{
	  if(handle2ndArg(argv, BR_RANGE_MIN, BR_RANGE_MAX, BR_INTERVAL_GRANULARITY, ARG_2_INDEX, brLevel))
	    {
	      doWork(brLevel, brLevelFileName);
	    }
	  else
	    {			   // Malformed input.
	      printUsage(argv[0]);
	    }
	}
      else
	if(argc == MIN_ARGC)
	  { /* Set brightness to level specified in brLevelFileName or if that value is out of
	       range set level to BR_DEFAULT */
	    doWork(brLevel, brLevelFileName);
	    return 0;
	  }
	else
	  printUsage(argv[0]);
    }

  return (EXIT_SUCESS);
}

DoWork()
DoWork() first calls the system() function (the system() function executes the command given to it in a shell) with the following string as it’s argument “~/.config/brightness/brctl.sh LEVEL” where LEVEL is the string returned by std::to_string(level). The doWork() function was called with the arguments brLevel and brLevelFileName (meaning the value of level is the same as the value of brLevel.) DoWork() then calls the saveIntToFile() function which attempts to save it’s second argument “level” to the file specified by its first argument “file” (brLevelFileName in this case.) The final thing doWork() does is to call the display() function with level as it’s argument. The display function initialises and creates the window to which it will draw by calling the init() function. It then runs through a for loop con.SLEEP_TIMES. In this loop it calls draw(), this is where the work of actually drawing the graphics to the window is done. After this it calls XFlush() to make sure that the graphics are actually actually drawn this time ;). After this it calls the functions:
  std::this_thread::sleep_for( std::chrono::milliseconds(X)).
With the “X” argument in the inner function being the constant con.SLEEP_TIME. The purpose of this for loop is of course to draw some bars to the window to represent the current brightness level, or at least that is the purpose of the draw() and XFlush() function calls in the loop. The actual reason for having the loop is to have the graphics stay on the screen for con.SLEEP_TIMES * con.SLEEP_TIME millie seconds (about three seconds.) This means that if some other window is drawn over our window (such as the one created by dmenu) the window may not “refresh” it’s graphics for up to con.SLEEP_TIME millie seconds. The reason for doing it this way is to avoid using the select system call, which I believe could solve the problem, but which I am also sadly confused by :(. There is using an xlib function call a way to have an xlib program go to sleep and then get a notification when it needs to be redrawn, however I don’t know of a way to have it do this and also wake up after a set amount of time.

I have set up my i3 config file so that when you press “caps+X”, “caps+Y” or “caps+Z” it will perform one of three actions and where X, Y and Z are function keys. The actions that are performed are “exec brctl”, “exec brctl +” and “exec brctl -”, “Exec brctl +” and “exec brctl -” increase or decrease the brightness respectively and show it’s level visually and brctl is the name I have given the executable (which had to be in a path the shell would check, /usr/bin in this case.) The current level is saved and retrieved every time the program is run as described. When the computer is started up the program could be run to set the brightness to it’s last value by running brctl with no extra arguments.


void doWork(const int level, const char file [])
{
  std::stringstream command {};
  command<<"~/.config/brightness/brctl.sh "<<std::to_string(level);
  system(command.str().c_str()); // Change brightness level.
  std::cout<<"level = "<<level<<'\n';
  saveIntToFile(file, level); // Save brightness level.
  display(level);	// Show brightness level.
}

bool handle2ndArg(const char * argv [], const int BR_RANGE_MIN, const int BR_RANGE_MAX,
		  const int BR_INTERVAL_GRANULARITY, const int ARG_2_INDEX, int & brLevel)
{
  const char arg {argv[ARG_2_INDEX][0]}; // If there is only one argument argv[ARG_2_INDEX] should equal '\0' 
      
  if(argv[ARG_2_INDEX][1] == '\0' && (arg == '+' || arg == '-'))
    {			// We have a well formed second argument.
      brLevel = adjustBR_Val(BR_RANGE_MAX, BR_RANGE_MIN, BR_INTERVAL_GRANULARITY, arg, brLevel);
      return true;
    }
  return false;
}

int adjustBR_Val(const int rMax, const int rMin, const int iGran, const char sign, const int a)
{
  switch(sign)
    {
    case '+':
      if(a == rMax)
	break;
      return a + iGran;      
    case '-':
      if(a == rMin)
	break;
      return a - iGran;
    }
  return a;
}

So What Does it Look Like Anyway?

It appears in the top left hand corner of the display and has a gap of two pixels above it so as not to annoy when directing the mouse to the top of a window in i3 to click on it. The word brightness is reversed on purpose.

image of brctl
Links to Relevant Files
Date: 17/01/2019