 In the past few months, I've made some videos discussing how you can create your own GTK applications using Haskell. And surprisingly, I've gotten some positive feedback from you guys, the viewers on this. People have asked for more content on this. So today, what I wanted to do is I wanted to continue developing one of the applications that I've done on a previous video. You guys remember this session program, this logout program that I called Bye Bye? I've done two previous videos on this program where the first video we created this program from scratch. We started with a blank canvas and wrote this thing in about 30 minutes. And then I created a second video where we refactored the code and cleaned up the code and tried to make the code a little more concise. If you haven't seen those videos, what you want to do is you want to go to my channel here on YouTube and do a search for Haskell probably. And in the search results, you will see building a GTK app with Haskell in under an hour. That's the first video where we created the Bye Bye program. And then a few weeks later, I made the second video more with Haskell and GTK refactoring our first app where we refactored the code. So if you haven't watched those videos, I would recommend going and watching those videos that way you can follow along and create your own little logout program. Today I wanted to continue along in this journey. And what I wanted to do was I wanted to add some new functionality other than just having these buttons that reboot and shut down and log out. I wanted to add a config file and I wanted a config file that we could have settings. For example, use different CSS style sheets. You know, that way I wouldn't have to rely necessarily on a GTK theme that was set. I would have my own custom color schemes that were set using CSS. This is something I wanted to explore as an option. Still I thought it would be neat if in this settings file that the user can configure what the buttons do. For example, one of the buttons is the screen locking button, right? What should that execute? Well, I was hard coding it to execute S lock, which is Suclassless lock program. And it's very minimal. I like it, but not everybody's going to want to use that particular screen locking program. Wouldn't it be great if they could set their own screen locking program in a config file? So I've been working on implementing some of these changes. So let me switch over to the desktop. Now this is the code for the last video I made on Bye-Bye. So this was exactly how we left it in that video about a month ago. Now I, of course, have been working on some new functionalities. So let me actually show you the new source code for Bye-Bye. And let me zoom in a little bit and I'm going to zoom in actually in both frames here. That way you can compare some of the changes. Now let me go ahead and hide myself here that way you can see. So this is the old source code and this is the new source code, right? So not a lot has changed up top. I did have to import some new libraries and some new functions here that I will use. And I will point those out as we go. But looking at the old source code and the new source code really not much has changed. I did assign some new variables. So this was very important here. You see this variable here? Let current colors equals do and this do block current colors. What I wanted was I wanted a variable that would actually give me the current color scheme as it's set in a config file. So let me show you what I'm thinking there. I'm going to switch to a different desktop. Let me open up a graphical file manager. And if I go into my home directory into .config, there is a directory for Bye-Bye. And in the Bye-Bye directory, there is settings.conf, right? The config file for Bye-Bye. And if I open that, I'll just open it in Neo Vim here and zoom in here. You can see it has two lines, color scheme colon and then whatever color scheme it's set to. And then locker colon and then whatever program should be the screen locker. Right now that's all that's in this config file, right? So let me quit out of that and go back to Emacs here. So this here is getting that line that says color scheme colon and then what the color scheme is set to. Right? That's why I called it current colors. This is the current color scheme. So the way this is done is we need to read the settings file. The settings file, of course, is home plus plus and then .config slash Bye-Bye slash settings.conf, right? And I just assigned that a variable settings. And Haskell, how this works in Haskell is there is something called handles. This handle here that I've set for this value here, what it's going to do is it's going to run this function, open file. What file is it going to open? This file here, settings, which is really this here, right? So open file, the settings file, it's going to open it in read mode. Then on the next line, this variable here, contents. What are we assigning the value of contents to? This function here, H get contents, which is handle get contents. What handle are we getting contents from? We're getting it from handle, which was the first line, right? We're open file, settings, read mode. So you can think of contents here. It's going to be the contents of that file. That's all that is. It's going to have both lines of that two line config file. And then you see the next line, I assign a variable. Let call scheme, color scheme, equal drop 13. I want you to drop the first 13 characters because the very first 13 characters are color scheme, colon space. So drop those. And then what I want you to do is I want you to run the function lines on contents. So the Haskell function lines, it takes whatever input you give it and it breaks it into lines. Because if you don't give it that function, if I just say drop 13 contents, it's going to take that entire config file. It could be 100 lines. It's going to make it all one line. All it's going to do is drop 13 characters. But I want you to respect the lines, keep the line breaks. So we're going to run lines on contents. And then bang, bang, 0. So this is an index position. Bang, bang, 0 means the first line. Because in programming, you always start counting from 0, not 1. So if I want the first line, it's bang, bang, 0. If I wanted the second line, which was the locker line, then I would do bang, bang, 1. But in this case, I wanted the color scheme. Then after that, all I do, I print the color scheme, whatever that value is. It gets printed out to standard output, no big deal there. And then hclose, handle, because we don't want to leave it open, right? Because I want you to read the file. I want these values set. But then after that, I want the handle closed. Because if it continues to keep reading that file, I can never do anything with that file later. For example, if I want to write something new to that file, I can't do it because the file is busy because it's being read. I hope that makes sense. So you do need to close the handle when you're done. And then after that, I sign this value. My colors equals current colors. So basically, I just changed the name of that to simply my colors. And if I scroll down, I did the exact same kind of deal here with instead of let current colors, let current locker. And you can imagine what this is going to do. Once again, we're going to do this handle, right? We're going to open file, the settings file, and read mode. We're going to assign this variable contents. It's going to do a hget contents for handle get contents on that handle. Then we're going to assign this variable I called locker. Let locker equal drop eight. We're going to drop the first eight characters of this line because the first eight characters would be locker colon space. I don't want that. Then once again, we're going to run the lines function on the contents. And this time bang bang one because I want the second line. And then once again, we close the handle. Once again, I assign a variable. This time I call it myLocker. Basically, myLocker is going to equal the currentLocker block here. And these are important because later on in the file, I'm going to be doing things where I need to know exactly what the color scheme is set in that settings file and exactly what the locker command is set to in that settings file. And now anytime I need those, I just use myLocker for the locker command and myColors for the color scheme. So I hope that makes sense. And now that I've explained that before I continue on with some of the code, let me actually get rid of the old config. And now let me do a vertical split. I'm going to go into the second split here. And what I'm going to do is I'm going to go ahead and open the eShell because I want a terminal here. And I'm going to go ahead and run the new buy buy program. So let me write, let me make sure everything is tangled. And then let me get into the eShell. I'm already in the buy buy directory, the project here. So I just need to do a stack run because I am using stack for this here. And this is what the new buy buy will look like. You can see I changed the icons. The icons, I may eventually do something fancier. But for right now what I was thinking about doing is I just wanted this to be terminal color schemes. Like the 10 terminal color schemes that I use with DTOS, I wanted the ability to set those different color schemes to the buy buy program. So right now the settings.conf file, the config file is set for the color scheme to be doom one. So that's why it knows exactly what color icons to set if I actually do a file manager here. Let me switch over to this desktop here. This is the buy buy project. And if I go into image, you can see now in image instead of just having seven icons or whatever I was using before, now I actually have 10 sub-directories. I have doom one, Dracula, Grovebox, Dark, MoniCypro, Nord, Oceanic, Next Pill, Night, Solarize Dark, Solarize Light, Tomorrow Night. And so I have an icon set for each color scheme. It's basically the same icons I just went into GIMP and recolored them to make sure they were set to the appropriate color scheme. So this obviously is doom one. Now if I wanted to change this, I have this settings wheel here. If I click on that, look, we get a new grid here. The old grid goes away, a new grid shows up. And this is where we can change some settings. Right now there's only two settings in the config file, but I can change them, right? So this was the locker command. It was by default S lock, but if I wanted to change it to something else, I don't know, S lock two. It's not a real program, but let's just say that's what I wanted to change it to and I wanted to change the color scheme to Grovebox Dark. And let me hit save. It's not gonna do a hot reload. I'd have to exit the program. So I'm gonna hit the home button to get back to the home screen, then I'll cancel. Now let me up arrow and do stack run. It didn't need to recompile because the program itself really didn't change. The only thing that changed were settings in a config file. So, and you can see now it's using the icon set for Grovebox Dark. And if I go into here, you can see, well, the colors are, or we're hard to read, but it's gonna try to run S lock two as the locker program, which isn't a real program. If I hit that button, it's going to crash, right? Cause it's not, it can't execute that command. It doesn't know what to do with that command. So actually I will put this back to actually be just X lock. So let me save that and cancel. And now when I rerun this, Grovebox Dark is still the color scheme, but if I hit the lock program, it actually runs the suckless S lock program. And let me cancel. What I'm gonna do, let me go ahead and keep the program open and I will talk you through the widgets. Most of them are the same as they were in the previous videos, but I'll point out some of the differences. So the very first thing here, after all the imports and setting some variables, we create a window. We create a GTK window. I've explained what all of this is before, but the window of course is the window, right? All the other widgets go inside the window. The most important widgets that we're gonna put inside the window is gonna be the grid, grid one, grid one. You can think of what you're seeing here as grid one. It's three rows by seven columns. So the first row is up here. That's an empty cell. That's an empty cell. That's an empty cell. This cell has the settings button and then three more empty cells. And then the second row are filled with our icons. And then the third row is filled with labels. And of course we're gonna create those icons and those labels with the next function that we get into here. You can see the next section. I titled it creating the buttons and the images. So I created a function that's actually gonna create each button and the appropriate logo, the appropriate label that actually goes with it. The first thing I do is simply a safety check. So create directory if missing. So this is a Haskell function that will create a directory for you if it doesn't exist on your system. So create directory if missing and then you give it true or false true if you want it to actually create that directory. And I want it to create home.config slash bye bye if it doesn't exist. And then I create this variable file exist and what does it do? It runs the function does file exist. This is a standard Haskell function. Does file exist checks if a file exists on the system. What file do I want you to check? I want you to check if that settings file exists. Remember settings is a variable for.config slash bye bye slash settings.conf and what does file exist does is it returns true or false whether that exists or not. And then the next thing I do is I do this if statement. If not file exists. So if it doesn't exist then write file. What file do I want you to write to? I want you to write to the settings file right.config slash bye bye slash settings.conf and I actually want you to write what are the default settings for that config file if it didn't exist. So this is just a safety, right? If that directory and that config file don't exist just create it and add these default settings to the settings file. And then the next section of code is largely stuff you've seen in the last video. We create a list here that we have a choices list. This is a list of tuples. So these in the first column, the first part of the tuple this is the label, right? Cancel, logout, reboot, shut down. Look at the widget here. Cancel, logout, reboot, shut down, right? And then the next part of each tuple is the command that the corresponding button will execute. So the cancel button it does a widget destroy on win. So it destroys the window widget, meaning it kills the window. The logout tuple here, the action is call command which is a Haskell command, a Haskell function that runs a shell command. What do I want you to run for a shell command? Kill all Xmonad because I'm in Xmonad so that's why I have the logout button. It just kills Xmonad. So that is the choices list here. It's a list of tuples. And then I have this function here which I explained on the previous video. I feed it the choices list and it creates the appropriate button and label based on the choices list. That's what that function there does. And it places them in the proper place on the grid. So it just automatically does this for me. So very cool function. Check out the previous video on how we created that. Now the next section is new because now I have grid two where I'm creating a second grid. Why am I creating a second grid? It's because when I hit the settings icon here we go to this next screen. This is a new grid, right? This is grid two. So what happens is when I hit the settings button it runs a command. It does a widget destroy. It kills grid one basically. And then it shows all grid two. That's what that button does. And then when I hit the home button it does the reverse. It kills grid two. It destroys it and shows all of grid one. And then I create a couple of labels. These labels are part of grid two. Command for locking screen and color scheme here. The GrubBox dark color scheme is kind of messy. Let me actually move to something that's a little easier to read. I'm gonna do Monokai Pro here. I can't solve that. And now get back into the E-shell here. And do a stack run. Yeah, I think that that's a little easier to understand what's going on there. So this row here, these are the two labels that I've assigned here. Then we create a entry. What is an entry? Well, this is this field here. It's going to be for text entry. You're gonna enter something into this field. And you can see I did a set entry and then placeholder text. What is the placeholder text by default? If I hadn't already boogered it up it would display the default locker. So if I go back to the home and do cancel and let me rerun this. It should show S lock here. But I think the color scheme is off. Let me set it to doom one and do a stack run. Now I know what it is. When I put the cursor here and I didn't enter anything and then I hit save because I changed the color scheme. What it did is it overwrote the locker command which was set to S lock. I bet if I go to the settings file now it's an empty line. So I actually need to put this back to S lock. Yeah, I might need to work on that a little bit more because yeah, that could be dangerous as somebody could actually overwrite that line. But you can see the placeholder text should show what the locker line in the config file is already set to. Then I create some empty labels. This is just for spacing. For example, I have an empty label I think above the home icon. Just create some spacing and I have an empty label below the icon also again just for spacing. And then I create a list. I create this color schemes list. This is just a list of strings and I specify the type. I do color schemes, colon, colon and then a list of strings. Because if I don't specify that this is strings depending on what I feed this into the Haskell compiler may think it's text instead of a string. And Haskell's really picky about type. And then moving on, I have color combo box. This is just a name I assigned it but what I'm doing is I'm gonna do a GTK combo box new. So I'm creating a new combo box and a combo box is this drop down here. And then the rest of this I won't explain too much of it. Some of this is getting deep and kind of complicated. But what I'm doing is I'm gonna fill that with the values in this color schemes list. So that's how I got the combo box there. One interesting thing I did here as I did this variable here, let IDX for index. I wanted the index position of the member of the list that was currently set in the config file because by default this color combo box is always just gonna default to the first value in the list will always be doom one. Even if I had it set to, I don't know, solarized light and I open this part of the grid, the grid two, this combo box, by default it's still gonna be set to doom one. I actually wanted this to default to whatever is currently set in the config file. So I needed the index position or what is currently the color scheme set in the config file. So what I'm doing is I'm actually using this Haskell function here, elem indices. So elem is element is, do you actually, are you a member of a list? You can think of elem as asking, is this argument a member of a list? So you remember my colors, my colors is our current color scheme set in the config file. So right now it should be doom one. We're asking is doom one an element of the color schemes list, but not just an element. I want the, is it an element of the list and if it is, give me the index position. So if doom one is currently set, the index position in the list would be zero because it's the first member. If Dracula was the current color scheme, it would be index position one. And then what I do is I do this function here, combo box set active. So I'm saying combo box set active. I want you to set this as the current active option showing in the list. And what option do I want you to show in the list? Well, I want you to show the correct index position from the list. So I hope that makes sense. So if I actually switch this to the Nord color scheme and I save and I quit and I'm running the program. Now I go back to grid two. You can see Nord is the default that's showing in the color combo box because it found the index position for Nord based on where it is in this list and it sets that to active. Again, that's a little tricky what was going on there but hopefully I explained that well enough. And then finally what I've got going on here as far as buttons is what happens when the color combo box changes. So when I pick something new out of the list, what actually happens? So if I switch from Nord to doom one, what happens? And what happens is I assign a variable, I call it call scheme for color scheme and color scheme is going to equal the string color scheme colon space and then plus plus choice and choice is whatever I picked out of this. And of course then it's going to take that and do a write file to settings. Write file is a Haskell function that writes to a file, what file? Well, we're writing to the settings file. What are we writing to the settings file? Well, we're writing call scheme which is that string right there. And then we're also going to plus plus so concatenate. So don't just write call scheme. Also write this here, new lock. New lock is whatever value I have set in this field here. And that should be new line, locker colon space plus plus and then whatever is in field one. So if I did S lock here in this field and then move to something like back to Nord for example, what just happened without even me hitting the button, it already wrote both of these values to the config file. And then the next part is the button and essentially the button does the exact same thing, right? The exact same thing is me changing all of this. You can think of clicking the save button does the same thing, whatever values are set in the field and the color combo box, we do a write file to settings and it writes the color scheme and the locker command. And then of course we attach all the widgets to the appropriate place in the grid. The next section is probably the most interesting part as far as the change because how am I getting these color values to change as far as the background colors and the widget colors and everything. Of course that is all done with CSS. So no longer are we worried about what the GTK theme is set to. It could be a light theme, it could be a dark theme, it doesn't matter because I'm setting my own themes. So let me show you how I achieve this. Let me actually make this a little bigger. So with GTK of course you can have it source a CSS file, right, a cascading style sheet. So what I did is we created this CSS provider new and then I assigned this variable CSS file which of course is going to be the CSS file which is going to be this location here. So I did a t.pack so pack is a command to convert a string to text because these CSS provider commands, they need text rather than strings. So by default some text within some double quotes, the Haskell compiler is going to assume it's a string, I need it to be text. So that's why I prepended this at the beginning of this. And then this is the location to the CSS file. Actually that's the location to the directory, right? So that's bye-bye slash CSS slash and then it's going to be another directory. The other directory is going to be my colors. So if it's currently set to doom one, it will be bye-bye slash CSS slash doom dash one and then plus dot CSS is the name of the file. If the current color scheme was set to Nord, the path would be bye-bye slash CSS slash Nord dot CSS. I hope that makes sense. And if I switch back over here, let me get into the bye-bye project here. If I go into bye-bye CSS and you can see there are 10 different CSS files here. Doom one dot CSS, Dracula dot CSS, grubboxdark dot CSS, et cetera, et cetera. And then finally we need to run CSS provider load from path and then you need to feed it the CSS provider and the config file, the CSS file itself. So CSS provider load from path CSS, which is the provider, that's what I named it. I could have named it anything and then the actual file that it should be using and my case I created a variable for that as well, CSS file. Now, just because you've done this doesn't mean automatically your GTK application is going to source that CSS file and everything is going to be themed exactly the way you assume. No, you actually have to specify each and every widget that uses that CSS file for theming. That's why the next section is very important because I have to do these widget get style context. So what widget is sourcing the CSS file? Well, the first widget get style context, I assign this variable CTX one and that is going to be for when, the window and then the next section CTX two, context two, I could have named it anything, is going to be for entry which the entry of course is this field here, right? And then CTX three, context three is going to be color combo box, so the dropdown menu here and then CTX four is the command button which is the save button here. So now those four widgets should go and look at that CSS file for theming. So now let me show you the CSS file. So let me go into the CSS folder and right now we are using the Nord color scheme so I'll just look at Nord.CSS and you can see the very first CSS values here are set for window comma GTK window. So remember my window was assigned a variable win but you actually have to give it the proper GTK widget name so the proper name for a window in GTK is actually just window lowercase and you can see I set the background color, I set a background image to none just to make sure there's no images and then I set the font family to Roboto, the font size to 14 pixels, so if I actually go home that is actually the labels. So that is actually 14 pixel Roboto font and then the color I set to this very light color, almost white and then of course we've got entry and button as well, if I'm looking at the button you see background is this very light blue which of course is taken from the Nord color scheme and then the foreground color which is the text color of course is almost black so that themed the button where if I didn't theme the button of course it would just be whatever GTK theme I happen to have assigned right now it would just theme all the buttons to look like that and I wanted a little bit more control that's why I did all of this stuff with CSS. So that is a little bit of what I've been working on here with bye bye most of the rest of it pretty self-explanatory, I create some new buttons of course that button and that button, the settings button and the home button which all they do is kill one grid and show the other grid and vice versa but yeah, so that was a little bit of what I've been up to with this and again, such a simple program but there was a lot of tricky stuff once I got into the CSS stuff that was tricky once I got into trying to create a config file that I could both read and write to at the same time I had to learn about those handles and I had to figure out exactly what I wanted to be the actions when I changed things in the color combo box and when I hit the save button what should that do and the more I learn about GTK and work on my own apps the more respect I have for you guys that are GTK developers or just graphical program developers in general because you have so much to think about as far as not just the function of the program but how things look, you have to think about a user experience so I do wanna tip my hat to all of you out there that are graphical program developers. Now before I go I need to think a few special people I need to think the producers of this episode Dustin K. James Matt, Maxim, Mehmet, Michael, Mitchell, Paul, Wes, Why You Bald, Homie, Alan, Armored Dragon, Chuck, Commander, Rangary, Diokai, Dylan, Greg, Marsturm, Erion, Alexander, Paul, Peace, Arch, Infador, Polytech, Realitech, Realitech, Forless, Red Prophet, Steven, Tools, Devler, and Willie. These guys, they're my has tiered patrons over on Patreon without these guys. This quick look at Haskell and GTK it wouldn't have been possible the show is also brought to you by each and every one of these fine ladies and gentlemen all these names you're seeing on the screen these are all my supporters over on Patreon because I don't have any corporate sponsors I'm sponsored by you guys, the community. If you like my work and want to see more videos about Linux free and open source software, Haskell and GTK, subscribe to DistroTube over on Patreon. All right guys, peace. Command line programs are so much easier than graphical programs.