There are sometimes when you like to read content behind your code editor, specially snippets, documentation of APIs or other important stuff that sometimes requires you to switch between your code editor and that documentation (webpage, pdf, etc.).
And that is why it is sometimes useful to have a translucent code editor, and if you love using SublimeText3 editor, you might know that it doesn’t allow you to set its opacity level, so you might end up hitting Alt-Tab hotkeys or adjusting both windows side by side in order to copy or read a documentation without hassle.
Anyhow, having a little knowledge about Windows APIs gave me the idea to change the application’s alpha opacity level, and since most applications running on Windows let us do it, it is only matter of coding now.
All applications that let us adjust the alpha opacity are set to WS_EX_LAYERED in its EX_STYLE flag, specially used on most applications that have custom skinned windows, but we can still force any applications to have that feature.
And if we spy SublimeText3 with uuSpy or anyother application for monitoring executables you like, you will notice that ST3 is not Layered.
Besides, now we also know that SublimeText has a class name and it is "PX_WINDOW_CLASS” which will help us locate all instances of Sublime Text and let us change its layered status.
Of course with uuSpy we can enable WS_EX_LAYERED but we need to do it from SublimeText itself, and luckily it allows us to write our own plugins with Python 3.3.
How to change the opacity of any application with WinAPI
We need to use the following WinAPI functions:
The most important ones are SetWindowLong and SetLayeredWindowAttributes. The other ones will help you locate SublimeText running instances, and ShellExecute for a hack to bypass SublimeText unknown crashes when using SetWindowLong.
Basically we set any application to WS_EX_LAYERED with SetWindowLong as follows:
First we need to make sure to only set WS_EX_LAYERED flag keeping the other styles untouched and that’s why we need GetWindowLong.
wl = GetWindowLong(LHWindow,GWL_EXSTYLE)
We will copy the EX_STYLEs to wl and then add WS_EX_LAYERED with a logic operation (LHWindow contains the unique SublimeText instance id).
wl = wl or WS_EX_LAYERED
And finally we apply that changes back to our target application with SetWindowLong.
SetWindowLong(LHWindow, GWL_EXSTYLE, wl)
And now we can finally set the application’s opacity level (from 255 to 0) with SetLayeredWindowAttributes.
Where opacity contains a value from 0 to 255, i.e. from totally invisible to opaque.
As I mentioned before, SublimeText, for some unknown reason, crashes when we call SetWindowLong from itself using python or an external DLL.
So we need to use an external executable that will help us to set the Layered mode, and that’s a simple console application that I wrote in assembler in order to make it the smallest possible.
This console application will only take two parameters, the HWND (sublime text instance id) and the current EX_STYLE, then will apply WS_LAYERED to it, that is all.
Of course we only need to call it once per sublime instance, but sometimes we have more than one instance (window) of sublime text, which can also be created at any moment, so that’s why we will call this console application everytime, to make sure new sublime windows can also be translucent.
These are the steps we need to do in order to change SublimeText’s opacity.
- Iterate among all windows and find the ones that have PX_WINDOW_CLASS as its class name (those are instances of SublimeText).
- On each instance found we apply Layered mode and modify the opacity level.
That’s all, easy uh!
So here is the part of our code that searches all windows (even hidden ones) in order to find sublime text windows.
def sublime_opacity(opacity): LHDesktop = GetDesktopWindow() LHWindow = GetWindow(LHDesktop,GW_CHILD) Clase = 'PX_WINDOW_CLASS' while(LHWindow != None): LHParent = GetWindowLong(LHWindow, GWL_HWNDPARENT) clas = create_string_buffer(255) GetClassName(LHWindow,clas,255) classs = clas.value if IsWindowVisible(LHWindow): if (LHParent==0) or (LHParent==LHDesktop): if(classs==b'PX_WINDOW_CLASS'): print('Applying opacity level ',opacity) wl = GetWindowLong(LHWindow,GWL_EXSTYLE) try: parametro = str(LHWindow)+' '+ str(wl) ShellExecute(LHDesktop,"open", exe_file,parametro,None,SW_HIDE) if opacity is not None: SetLayeredWindowAttributes(LHWindow,0,opacity, LWA_ALPHA) break except ValueError: print("Error! ") LHWindow = GetWindow(LHWindow, GW_HWNDNEXT)
I will explain as easy as I can:
First we find the Desktop instance ID and then its first child (any application) and we will walk on every next window with GetWindow(currentWindow, GW_HWNDNEXT).
On each window found we need to know if it is visible and is a parent one (i.e. we won’t bother with mdi or sdi child windows which are dependant of the applications styles mostly). And finally making sure it has PX_WINDOW_CLASS as class name.
Only then we can set its EX_STYLE to WS_EX_LAYERED and set its opacity level.
In the code you’ll notice that it calls ShellExecute instead of SetWindowLong, and that’s the horrible hack (workaround) to apply SetWindowLong without crashing SublimeText3. I don’t know exactly what triggers that behavior, but reading on issues related to some Python versions, it has a bug with SetWindowLong api. However, I’ve tried using a DLL which will call SetWindowLong from our python script, but it crashes SublimeText3 too. And finally, executing an external console application with ShellExecute looks to be the better approach, since it is a different independent application that changes our SublimeText EX_STYLE.
Here is a part of the assembler code that does those changes:
1 msg db "Changing to layered window =%s P1=%s P2=%s",0 2 errmsg db "It is required two parameters!",0 3 ... 4 start: 5 cinvoke __getmainargs,argc,argv,env,0 6 cmp [argc],3 7 jne error 8 mov esi,[argv] 9 10 ;this is the HWND (SublimeText3 handle id) 11 stdcall atoi, dword[esi+4],10 12 mov ebx, eax 13 ;this is the current exstyle 14 stdcall atoi, dword[esi+8],10 15 mov ecx, eax 16 or ecx, WS_EX_LAYERED 17 invoke SetWindowLong, ebx, GWL_EXSTYLE, ecx 18 ;let's print it for debugging purposes 19 cinvoke printf,msg,dword [esi],dword [esi+4],dword [esi+8] 20 21 finish: 22 invoke ExitProcess,1 23 24 error: 25 cinvoke printf,errmsg 26 jmp finish
We will pass the found sublime text window id and its current EX_STYLE as parameters, the application will read it, enable the WS_EX_LAYERED flag and invoke SetWindowLong with the new style.
You can take a look to the entire source code at the github repository https://github.com/vhanla/SublimeTextTrans
About the entire plugin
The plugin is meant to be used only on Windows environments, since it uses a lot the Windows API, and sadly I cannot write it for Linux or MacOS since I don’t have enough knowledge on those platforms, hopefully anyone can improve this plugin and port it to those other platforms.
And about the entire plugin, I won’t bother explaining all the source code, it is easy to understand and you can play with it adjusting it to your needs.
Now we can have our SublimeText editor with transparency and we will be able to see behind it.
As you can see in the above picture, I can read the MSDN documentation about SetLayeredWindowAttributes.
And remember, visit the github repository, there you can get the source code https://github.com/vhanla/SublimeTextTrans
You will also see the link to a package that you can place it in the data\installed-packages directory inside SublimeText, and that is an easy way to install it on your sublime text editor.
This package link , but visiting the repository might lead you to updated versions.
Sublime Text 3 plugin feature with the help of Python is a really great help, as you noticed it you can even call WinAPIs to modify our code editor transparency and customize it to our needs, not only adding extra features, themes, shortcut hotkeys, but also modifying the application’s opacity.
Thanks for reading.