7.5 KiB
Auto adapting signal to day-night scheme
So I wanted to automatically change the day/night theme of my brand new Signal desktop app. The way that it is manually done is through the File -> Preferences...
menu. Now that's not convinient for programatic person like me. Let's investigate!
.config
folder
My first stab is looking into the mostly standard ~/.config
folder in Linux for Signal if one actually exists. It turns out it does. Let's see. It even has a ~/.config/Signal/config.json
file. Unfortunately, that file doesn't contain the settings for the theme, just window positions.
grep -r
ing in the folder for any of android
or theme
just returns the logfile that Signal is writing to.
...
./logs/log.log.1:{..."theme-setting changed to android-dark",...}
...
No luck.
Another stab is at /opt/Signal
directory. Again - no luck. And the app is a binary ELF executable
$ file signal-desktop
signal-desktop: ELF 64-bit LSB executable,...
strace
-ing
My next attempt is to attach to attach to the Signal process via strace
. Find the process by ps aux|grep signal
, attach by strace -p <pid>
and try to change the . Boy, this does a lot of stuff! Let's filter by writing:
strace -e write -p 12345
Still, too much but I saw something hapenning amid the writing to 40
and 71
file descriptors (fd).
#...
write(40, "!", 1) = 1
write(71, "\0", 1) = 1
write(40, "!", 1) = 1
write(1, "{\"name\":\"log\",\"hostname\":\"pi2-ho"..., 147) = 147
write(12, "\1\0\0\0\0\0\0\0", 8) = 8
write(71, "\0", 1) = 1
write(40, "!", 1) = 1
#...
All right, the 1
fd is the log that is being written that I already saw. How about that 12 fd? Let's explore within the proc
fs:
$ cd /proc/1234/fd
$ ls -la
# ...
lrwx------ 1 pi2 pi2 64 Mar 29 23:23 12 -> anon_inode:[eventfd]
# ...
Oh boy, some wierd anonymous inode. So we are definately not dealing with a good old filesystem file but rather some on the fly created fd. Let's explore a bit more...
Signal is an Electron app
Taking a look at the github repo of the project. Searching for dark
and theme
reveals css, sass files. Okay
Looking at the menus there is View -> Toggle Developer Tools
and BAM - a good old Chrome developer tools console pops up! Inspecting the elements, it's pretty clear what is happening:
<body class="android">
<!-- changes to -->
<body class="android-dark">
Brilliant, the change of theme just changes the class on the body
element. Let's see if we have jquery or I have to go to the dark ages of javascript's findElementByTagName
:
> $
< function ( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced' ...
Good, we have. Now I "just" have to find a way to connect to chrome console via a good old terminal in some way...
Chrome remote debugging
Searching the Internets, I find a post in Chromium blog from way back in 2011 that I can run Chrome with --remote-debugging-port
flag which enables some sort of a remote console.
Would the electron app do the same?
signal-desktop --remote-debugging-port=9222
Indeed it does. This console however is not just a simple tcp connection to the javascript console. Running echo "alert('echo'); | netcat localhost 9222
does nothing. More reading to go...
Turns out, Chrome DevTools has its own protocol that among other things exposes websockets. Let's see what clients exist for that
Any pythonic ones? pychrome seems simple enough.
Let's be modern, do python3:
pip3 install pychrome
And I will use my favourite repl, ipython
and follow the Getting started tutorial:
import pychrome
browser = pychrome.Browser(url="http://127.0.0.1:9222")
Awesome, I have a connection. The tutorial goes into opening a tab. I don't want that, I assume I should alread have a tab. Exploring browser
object (via my favourite ipython repl):
In [3]: dir(browser)
Out[3]: [... _private_stuff
'activate_tab',
'close_tab',
'dev_url',
'list_tab',
'new_tab',
'version']
In [4]: browser.list_tab()
Out[4]: [<Tab [38473d8e-ad8d-4325-8cb6-81aaec36c124]>]
In [5]: tab = browser.list_tab()[0]
In [6]: dir(tab)
Out[6]: [... _private_stuff
'call_method',
'debug',
'del_all_listeners',
'event_handlers',
'event_queue',
'get_listener',
'id',
'method_results',
'set_listener',
'start',
'status',
'status_initial',
'status_started',
'status_stopped',
'stop',
'type',
'wait']
The README goes into navigating pages and the examples folder is also not what I am looking for. But they do tell me call_method
is probably what I want, just need to find the proper string and parameters in the protocl. I want to execute a javascript call.
Back to the Chrome DevTools Protocol Viewer. I look through a few of the Domains listed and finally arrive at Runtime.evaluate method.
In [7]: tab.start()
In [8]: tab.call_method('Runtime.evaluate', expression='alert("hi");')
# I GET AN ALERT IN THE BROWSER! Dismiss it.
Out[8]: {'result': {'type': 'undefined'}}
Finally! I have access to my javascript console in the browser! Let's write the jquery change of theme:
tab.call_method('Runtime.evaluate', expression='$("body").removeClass("android").addClass("android-dark");')
That worked!
Wrapping it up
Let's write a propper python script now:
#!/usr/bin/env python3
import sys
import pychrome
sys_argv = sys.argv
if len(sys_argv) < 2:
print("first argument needs to be day|night")
exit(1)
command = sys_argv[1]
if command == "night":
expression = js_dark_to_light = """
$("body").removeClass("android").addClass("android-dark");
"""
elif command == "day":
expression = js_light_to_dark = """
$("body").removeClass("android-dark").addClass("android");
"""
else:
print("first argument needs to be day|night")
exit(1)
browser = pychrome.Browser(url="http://127.0.0.1:9222")
tab = browser.list_tab()[0]
tab.start()
tab.call_method('Runtime.evaluate', expression=expression)
tab.stop()
Running ./change_signal.py day
changes to white theme and ./change_signal.py night
goes to dark theme.
I also want to change the signal-desktop
app to always start with the remote-debug flag enabled, so I do that:
rm /usr/local/bin/signal-desktop
cat > /usr/local/bin/signal-desktop << EOF
#!/bin/bash
/opt/Signal/signal-desktop --remote-debugging-port=9222
EOF
chmod +x /usr/local/bin/signal-desktop
And that's it. Code can be found in my sun repo which also deals with other day-night related dark-light themes including a script to detect whether the sun has set or not:
$ ./calc_sun.py
usage: calc --loc=location_name [--date-format]
calc --lat=latitude --lon=longitude [--tz|--date-format]
returns
current_time sunrise sunset day|night
$ ./calc_sun.py --lat=51.21 --lon=4.40 --tz=Europe/Brussels
23:57 07:23 20:10 night
So now I can chain that:
state=`./current.sh | cut -f4 -d' '`
./change_signal.py $state
Voala! That was fun 🙂😊