SBSettings Toggles

In this tutorial, I will help you getting started with SBSettings Toggles, using Theos. Writing SBSettings Toggles is actually a really easy thing to do. SBSettings Toggles only have a handful of functions you must implement. The most trivial SBSettings Toggles don’t need any complex hooking, so teaching how to write them is not hard either. More complex SBSettings Toggles, like you may expect, need to either have some hooking or modify some system plists. If you have followed my other tutorials, you know I like giving theory first and then go practical by writing something. This case won’t be any different. Although the toggle we will be writing is going to be really, really simple: We will simply write a toggle that changes the display type in the Weather app (That is, when toggled, it will show in Celsius: When not, it will display in Fahrenheit).

Writing SBSettings Toggles? First, Understand the Theory.

If you’re here because you had problems reading BigBoss’ SBSettings Toggles Specifications, the first part of this tutorial is based on his theory. After all, I went to him to learn about SBSettings Toggles myself. Needless to say, I will explain things differently here, so if you had problems with the “Official Guide”, this one may still give you hope.

SBSettings is a modular application. All SBSettings Toggles are separate projects. The settings app looks for the togges in /var/mobile/Library/SBSettings/Toggles, and all SBSettings Toggles are given the name of the folder they are in. Toggles are said to be “ON” by default; They are said to be “OFF” when their folders contain a file called “OFF”. All SBSettings Toggles are actually dylibs that expose some C functions.

While writing SBSettings Toggles is not a hard thing and it has a small amount of required functions you must implement, there’s many things you must watch out for. Among these gotchas there’s things like memory usage and the methods that must be implemented. Let’s call them “the cardinal rules”. There’s 4 cardinal rules you should ALWAYS keep in mind when writing SBSettings Toggles.

The Cardinal Rules for Writing SBSettings Toggles

If you’re planning on taking this seriously, make sure you know these “Cardinal rules for SBSettings Toggles” by heart:

  1. All SBSettings Toggles run on SpringBoard. This has two major important implications: If your toggle leaks memory, the system will leak memory. And second, if your toggle crashes, the whole system will crash. Everytime I see people wanting to write jailbreak stuff, I tell them they must be willing to do it right, and I have emphasised this in my previous tutorials as well. If you can’t, or are not planning on doing it right, please stop reading this now. Otherwise you will release buggy tweaks that will be annoying for SBSettings Toggles users in the long run.
  2. SBSettings Toggles should not store data about their own state. Or do anything else that can take up memory for a long time, even if you’re not leaking it. Doing so will affect system-wide performance. You don’t want to slow down your user’s devices, do you?
  3. All SBSettings Toggles should avoid executing processes on disk with popen() or system() functions. These functions freeze SpringBoard when memory is really low (BigBoss states when memory is around or below 20 MBs).
  4. All SBSettings Toggles run as user mobile. This is important to know, because some functions need root access.

SBSettings Toggles Behind the Scenes: What Are the Functions They Need?

Like I have said before, SBSettings Toggles only need to implement a handful of functions.

Required Functions All SBSettings Toggles implement the following functions:

  1. BOOL isCapable()

    This function is called when SpringBoard loads. The purpose of this function is to tell the system whether it can run the toggle in question or not. If you need to check if the user is running a certain iOS version, device, or anything you can think of, this is the right place to do it. If you were writing a tweak that toggles the device’s flashlight, you would check if the device has a torch in this function. return YES if the toggle can be run; NO if otherwise.

    A quick gotcha with this function: If you don’t implement this required function, it will be assumed to return NO. In other words, make sure you implement it, otherwise your toggle will never run under any platform!

  2. BOOL isEnabled()

    This function is called when SpringBoard loads and when the Refresh button is tapped. Return YES if the toggle is enabled. NO if otherwise.

    Note that this function is to determine whether the tweak was enabled by the user or not; it’s not to determine whether the toggle is ON or OFF.

  3. void setState(BOOL enabled)

    This is where we toggle the actual “switch”. If the toggle is ON, ‘enabled’ will be NO (meaning it wants to turn it off) in the parameter. If the toggle is OFF, ‘enabled’ will be YES.

  4. float getDelayTime()
  5. This is the number of seconds, or partial seconds, that your toggle takes to “do its job” (AKA, how long does setState take to execute). This is the amount of time that the UISpinner will be visible after the user taps your toggle button.

Optional Functions:

  1. BOOL getStateFast()

    The isEnabled function takes a bit (a second or two) to determine whether all the SBSettings Toggles are enabled or not. Naturally, this is not acceptable, specially if the user shows the SBSettings window a lot. As such, you can implement getStateFast() to determine whether the toggle is enabled or not. isEnabled is only called when SpringBoard loads, getStateFast gets called everytime SBSettings is displayed. If you have code that can determine whether the toggle is enabled or not, you may place it here. Many times you can return the result of isEnabled here.

  2. void invokeHoldAction()
  3. This one’s a nifty and cool one. When the user taps and hold on SBSettings Toggles, they can reply to the event by calling this function.

  4. void setContainer(int Container)

    While it is optional, you will need it if you need an extra window for your toggle (SBSettings Toggles such as Fast Notes, Brightness, and processes use this). SBSettings will give you a 0 when talking about the SBSettings window itself and a 1 when talking bout the notification center. You will have to determine how you want to show this small window.

  5. void closeWindow()
  6. If your toggle created an optional window (such as the default Brightness toggle, that gives you a small window to set the brightness to whatever you want), you should implement this method to dismiss that view. This function is called when the SBSettings window is about to close.

  7. BOOL allowInCall()

    This determines whether your toggle can be used while in call or not. This is important, because some things such as a 3G toggle could drop the call if toggled.

SBSettings Toggles Can Call External Scripts and Functions From Within.

Before we go build those SBSettings Toggles I told you about at first, let’s absorb a little bit more of technology. After all, SBSettings Toggles are loved by everyone, and they can do much more than people expect.

This is SBSettings newest feature, and it is very nifty.

Although it is not directly, using this you can do many wonderful things. Instead of doing this directly, you can write an script that can be run by the SBSettings root daemon (sbsettingsd). To do this, you will send a notify_post message to the daemon. The daemon will take care of some details, including the fact that SBSettings Toggles cannot be run as root.

Okay, okay, but what is notify_post? notify_post is actually a BSD function, used for system-wide communication in BSD based systems. SBSettings Toggles that need to use the daemon should put files in the /var/mobile/Library/SBSettings/Commands directory. The filename of each file should be the post message. The nifty thing is that they can be compiled executables or simply scripts. Then, in your toggle, you will notify_post the name of your file; The daemon will receive this notification and run your file.

In this tutorial, we are not going to write SBSettings Toggles that call external scripts or resources. It’s another topic for another time, but, I will write “Part 2” of this tutorial and it will be focused on external scripts and functions. I mention this because it is important you know everything SBSettings Toggles can do, even if you don’t learn that yet (If you can’t wait for this, the BigBoss link I linked to above will take you to a small word on this. It writes a script that resprings SpringBoard – It’s really simple, but it should do the trick).

Getting Our Hands Dirty With SBSettings Toggles

The easiest SBSettings Toggles to write are those that modify plists that reside on your device. That’s the kind of toggle we will write here. You could also write a toggle that hooks code, or anything else that you may imagine!

Let’s get started with actual SBSettings Toggles programming! If you were on BigBoss’ site before, you may have noticed that he gives you a makefile and an initial main.m template. Luckily, there is a template that can be used with Theos so you don’t have to worry about the Makefile or anything. So go ahead and download this SBSettings Toggles NIC Template. Navigate to your Theos installation folder and save the template in templates/iphone. Courtesy of this template goes to WillFour20. If you want something more updated, and what I recommend, is the template created by the Theos man himself: Get SBSettings Toggles NIC Tar from DHowett’s Github.

Let’s create the project with the template now (You should have the hang of this by now: Open terminal, and open nic.pl in Theos’ bin folder to create the wizard).

SBSettings Toggles Wizard

If you’re using the template in DHowett’s repo, your template will look like this:

// Required
extern "C" BOOL isCapable() {
	return YES;
}

// Required
extern "C" BOOL isEnabled() {
	return YES;
}

// Optional
// Faster isEnabled. Remove this if it's not necessary. Keep it if isEnabled() is expensive and you can make it faster here.
extern "C" BOOL getStateFast() {
	return YES;
}

// Required
extern "C" void setState(BOOL enabled) {
	// Set State!
}

// Required
// How long the toggle takes to toggle, in seconds.
extern "C" float getDelayTime() {
	return 0.1f;
}

// vim:ft=objc

Don’t worry if you’re using the other template. Both templates will work fine.

You may have noticed that neither template implement all the optional functions. Don’t worry about it, because we won’t need them here, and if you ever need them for your personal projects, you can simply add them manually.

By the way, an “extra” requirement for SBSettings Toggles is that they must have an icon, although SBSettings will grab them anyway, but it will just show an icon with no glyph. If you want your icon to have glyphs, make them with a solid color (white or black are usually preferred). They are sized 60 x 60 points, and they are called on.png and off.png.

You can use these if you want (I just made them for this project – took a sec):

C-on C-Off

Note on using images: In order to use icons, your toggle must install the on.png and off.png images on each of the default themes that are included with SBSettings. At the time of this writing, the default themes are: Default, Default HD (retina: icons are 120×120 points), HUD, Serious SBSettings HD (retina), Tech Shadows, and Tenuis Matte.

To add your icons, create a “layout” folder in the root of your project. Anything that is placed here will be mirrored in the device’s / directory (thanks rpetrich for kindly giving me this note when I asked him why images won’t show up in my toggles). Every theme needs to have a directory with the name of your toggle and put the on.png and off.png images there. At the time of this writing, SBSettings themes are placed in “/var/mobile/Library/SBSettings/Themes”. So inside the layout folder, create a folder called “DEBIAN” (yes, that’s all caps), and place your control file (which is currently at the root of your project) there. Note that this DEBIAN folder is a “special” folder and it won’t show up in your device when you do make package install. Then, inside layout, make a directory called “var”. Inside it make a directory called “mobile”, and so on, until you have the following structure:

layout
	- DEBIAN
		- control
	- var
		- mobile
			- Library
				- SBSettings
					- Themes
						- Default
							- YOUR_TOGGLE_NAME
								- on.png
								- off.png
						- Default HD
							- YOUR_TOGGLE_NAME
								- on.png
								- off.png
						- HUD
							- YOUR_TOGGLE_NAME
								- on.png
								- off.png
						- Serious SBSettings HD
							- YOUR_TOGGLE_NAME
								- on.png
								- off.png
						- Tech Shadows
							- YOUR_TOGGLE_NAME
								- on.png
								- off.png
						- Tenuis Matte
							- YOUR_TOGGLE_NAME
								- on.png
								- off.png

For example, if you wanted the Default theme to contain your icons in this specific project, you would create a directory inside it and call it Celsius. It’s route will then be /var/mobile/Library/SBSettings/Themes/Default/Celcius, and you would drop your icons there.

You may have also noticed that themes don’t have on.png, on@2x.png, and so on images. They are either retina or they are not, so each theme only needs one ‘set’ of images.

Personally, I’m not a huge fan of this model and I think SBSettings could be really improved to make it both a breeze to expand and to theme. Currently it is a breeze to extend, but things like this make it a pain to theme. Hopefully this will change in the future.

To keep things simple in this project, we are going to leave the return value of isCapable to YES. Though for your specific projects you may or may not need to modify this.

Now we will go ahead and modify isEnabled. We are going to modify the weather app’s setting here. For that, you need to know that the file we will modify is called com.apple.weather.plist, and that it is located in /var/mobile/Library/Preferences. If you have studied prior iOS development before (YOU SHOULD HAVE!), you know how to open plists and to modify them with code. This plist has a property called “Celsius”, and it’s a boolean. If it’s enabled, it will display the weather data in celsius. If not, then it will show them in Fahrenheit.

So we are going to implement isEnabled now. For this toggle, “enabled” would mean the weather app is displaying it’s data in celsius. Implement it as follows:

extern "C" BOOL isEnabled() {
	NSDictionary *weatherSettings = [NSDictionary dictionaryWithContentsOfFile:@"/var/mobile/Library/Preferences/com.apple.weather.plist"];
	BOOL inCelsius = [[weatherSettings objectForKey:@"Celsius"] boolValue];
	return inCelsius;
}

Like I told you, the simplest of SBSettings Toggles just read and modify existing plists, but even then, they are very powerful. Here we read the weather settings plist, then we determined the value of Celsius on it and return that. It’s very easy but it does its job.

But of course, so far this toggle is not useful. If you tap it on SBSettings, you will see it always stays green. That’s because we don’t modify its state anywhere. We are going to implement setState now. Do it like this:

extern "C" void setState(BOOL enabled) {
	NSMutableDictionary *weatherSettings = [NSMutableDictionary dictionaryWithContentsOfFile:@"/var/mobile/Library/Preferences/com.apple.weather.plist"];
	if(enabled == YES)
	{
		//We want to display weather data in celsius.
		[weatherSettings setObject:[NSNumber numberWithBool:YES] forKey:@"Celsius"];
		NSLog(@"Celsius");
	}else
	{
		//We want to display data in fahrenheit.
		[weatherSettings setObject:[NSNumber numberWithBool:NO] forKey:@"Celsius"];
		NSLog(@"Fahrenheit");
	}
	[weatherSettings writeToFile:@"/var/mobile/Library/Preferences/com.apple.weather.plist" atomically:YES];
}

Also modify getStateFast():

extern "C" BOOL getStateFast() {
	return isEnabled();
}

This is not always a good idea. In this case it’s fast because reading files from disk is not so slow. Depending on your tweak, you may want to implement fast state on a different way.

Finally, we should tell the our toggle that the change state operation will take around 0.6 seconds, o let’s implement that:

extern "C" float getDelayTime() {
	return 0.6f;
}

And that’s it! You may now test it. Launch SBSettings and toggle the tweak. Then launch the weather and see it displaying it’s data in a different system! Then, close the weather app (make sure it’s not suspended in the background), and toggle it again. it will display it’s data as you had it before.

Writing SBSettings Toggles is really easy. I hope this head start helps you understand these little guys better. SBSettings Toggles are actually a big topic, and I hope I revisit them with other tutorials in the near future. Particularly, if there’s enough demand, I may teach you how to run external scripts and functions.

As usual, if you have any questions or problems, let me know. The comment section is open for anyone who needs or wants help. I have changed to Disquss so people don’t hesitate to ask now.

You can grab the project from my Github account here.

(This post can also be found on iOS-Blog: Click here to view it there.)

CHANGELOG:


November 24, 2012 at 7:00 PM [America/La_Paz]: First version of this document.
June 1, 2013 at 5:30 PM [America/La_Paz]: Added a note on correctly installing toggle icons and images.

Positive SSL