241 lines
7.5 KiB
Markdown
241 lines
7.5 KiB
Markdown
# 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).
|
|
|
|
```bash
|
|
#...
|
|
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:
|
|
|
|
```bash
|
|
$ 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](https://github.com/signalapp/Signal-Desktop). 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:
|
|
|
|
```html
|
|
<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`:
|
|
|
|
```javascript
|
|
> $
|
|
< 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](https://blog.chromium.org/2011/05/remote-debugging-with-chrome-developer.html) 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?
|
|
|
|
```bash
|
|
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](https://chromedevtools.github.io/devtools-protocol/) that among other things exposes websockets. Let's see what clients exist for that
|
|
|
|
[Quite a lot](https://github.com/ChromeDevTools/awesome-chrome-devtools).
|
|
|
|
Any pythonic ones? [pychrome](https://github.com/fate0/pychrome) seems simple enough.
|
|
|
|
Let's be modern, do python3:
|
|
|
|
```bash
|
|
pip3 install pychrome
|
|
```
|
|
|
|
And I will use my favourite repl, `ipython` and follow the Getting started tutorial:
|
|
|
|
|
|
```python
|
|
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](http://ipython.org/)):
|
|
|
|
```python
|
|
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](https://chromedevtools.github.io/devtools-protocol/). I look through a few of the Domains listed and finally arrive at [Runtime.evaluate](https://chromedevtools.github.io/devtools-protocol/tot/Runtime#method-evaluate) method.
|
|
|
|
```python
|
|
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:
|
|
|
|
```python
|
|
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:
|
|
|
|
```python
|
|
#!/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:
|
|
|
|
```bash
|
|
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](https://gitlab.com/pisquared/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:
|
|
|
|
```bash
|
|
$ ./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:
|
|
|
|
```bash
|
|
state=`./current.sh | cut -f4 -d' '`
|
|
./change_signal.py $state
|
|
```
|
|
|
|
Voala! That was fun 🙂😊
|
|
|